mirror of
https://github.com/actions/runner.git
synced 2025-12-11 12:57:05 +00:00
Compare commits
106 Commits
yaananth-p
...
users/tihu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c645de9aee | ||
|
|
0e4f76ec4e | ||
|
|
af18df4621 | ||
|
|
5215d95637 | ||
|
|
e750eb7e38 | ||
|
|
ca1f621077 | ||
|
|
80d0b58f3c | ||
|
|
11ff2be7e9 | ||
|
|
3ce763338d | ||
|
|
a45c0278e6 | ||
|
|
658d36c1bc | ||
|
|
ca3b803237 | ||
|
|
4fa691f73e | ||
|
|
dfcfae49e5 | ||
|
|
1235dc1cea | ||
|
|
cc0d0bed90 | ||
|
|
0fac863568 | ||
|
|
42e7359f5c | ||
|
|
5639175ecb | ||
|
|
7128998d77 | ||
|
|
f37e9f80a6 | ||
|
|
0fa08423d2 | ||
|
|
029106a1dc | ||
|
|
493a2a0bf7 | ||
|
|
43f983486e | ||
|
|
f6053b616c | ||
|
|
4f4608b710 | ||
|
|
28686c40d2 | ||
|
|
ce1679bb6f | ||
|
|
0a7611b0b5 | ||
|
|
b3fee33a92 | ||
|
|
d83ef5549e | ||
|
|
fe6719d120 | ||
|
|
400b2d879c | ||
|
|
c4b6d288d4 | ||
|
|
0699597876 | ||
|
|
a592b14ae3 | ||
|
|
04269f7b1b | ||
|
|
e89d2e84bd | ||
|
|
afe7066e39 | ||
|
|
da79ef4acb | ||
|
|
5afb52b272 | ||
|
|
cf87c55557 | ||
|
|
43fa351980 | ||
|
|
ecfc2cc9e9 | ||
|
|
740fb43731 | ||
|
|
f259e5706f | ||
|
|
5d84918ed5 | ||
|
|
881c521005 | ||
|
|
176e7f5208 | ||
|
|
b6d46c148a | ||
|
|
38e33bb8e3 | ||
|
|
404b3418b7 | ||
|
|
7ffd9af644 | ||
|
|
1b69c279f5 | ||
|
|
567870dbb8 | ||
|
|
72fa2a8a0d | ||
|
|
4359dd605b | ||
|
|
aab936d081 | ||
|
|
777ce5a0dc | ||
|
|
1a62162708 | ||
|
|
9a829995e0 | ||
|
|
c5ce52641c | ||
|
|
e82725b580 | ||
|
|
0464f77de3 | ||
|
|
1fc159e0df | ||
|
|
3615fb6923 | ||
|
|
f61dcad5bb | ||
|
|
62d568674c | ||
|
|
07c00f6a8a | ||
|
|
05b84297b7 | ||
|
|
04679b56a9 | ||
|
|
d2ca24fa43 | ||
|
|
abdaacfa6e | ||
|
|
53fd7161e2 | ||
|
|
ce68f3b167 | ||
|
|
e2c7329292 | ||
|
|
22a9d89772 | ||
|
|
3851acd0cf | ||
|
|
aab4aca8f7 | ||
|
|
5af7b87074 | ||
|
|
110eb3a5de | ||
|
|
bd1341e580 | ||
|
|
85ce33b1d3 | ||
|
|
92ec3d0f29 | ||
|
|
4e95d0d6ad | ||
|
|
5281434f3f | ||
|
|
e9a8bf29df | ||
|
|
a65331e887 | ||
|
|
908a082527 | ||
|
|
10ba74f59b | ||
|
|
33ee76df29 | ||
|
|
592ce1b230 | ||
|
|
fff31e11c5 | ||
|
|
6443fe8c97 | ||
|
|
29c09c5bf8 | ||
|
|
09821e2169 | ||
|
|
7c90b2a929 | ||
|
|
ee34f4842e | ||
|
|
713344016d | ||
|
|
0a6c34669c | ||
|
|
40d6eb3da3 | ||
|
|
34a985f3b9 | ||
|
|
42fe704132 | ||
|
|
a1bcd5996b | ||
|
|
31584f4451 |
8
.editorconfig
Normal file
8
.editorconfig
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# https://editorconfig.org/
|
||||||
|
|
||||||
|
[*]
|
||||||
|
insert_final_newline = true # ensure all files end with a single newline
|
||||||
|
trim_trailing_whitespace = true # attempt to remove trailing whitespace on save
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false # in markdown, "two trailing spaces" is unfortunately meaningful; it means `<br>`
|
||||||
335
.github/workflows/e2etest.yml
vendored
335
.github/workflows/e2etest.yml
vendored
@@ -1,335 +0,0 @@
|
|||||||
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
31
.github/workflows/runner-basic-e2e-test-case.yml
vendored
@@ -1,31 +0,0 @@
|
|||||||
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
|
|
||||||
92
docs/adrs/1144-composite-actions.md
Normal file
92
docs/adrs/1144-composite-actions.md
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
**Date**: 2021-06-10
|
||||||
|
|
||||||
|
**Status**: Accepted
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
We released [composite run steps](https://github.com/actions/runner/pull/554) last year which started our journey of reusing steps across different workflow files. To continue that journey, we want to expand composite run steps into composite actions.
|
||||||
|
|
||||||
|
We want to support the `uses` steps from workflows in composite actions, including:
|
||||||
|
- Container actions
|
||||||
|
- Javascript actions
|
||||||
|
- Other Composite actions (up to a limit of course!)
|
||||||
|
- The pre and post steps these actions can generate
|
||||||
|
|
||||||
|
## Guiding Principles
|
||||||
|
|
||||||
|
- Composite Actions should function as a single step or action, no matter how many steps it is composed of or how many levels of recursion it has
|
||||||
|
- In the future we may add a configurable option to make this no longer the case
|
||||||
|
- A workflow author should not need to understand the inner workings of a composite action in order to use it
|
||||||
|
- Composite actions should leverage inputs to get values they need, they will not have full access to the `context` objects. The secrets context will **not** be available to composite actions, users will need to pass these values in as an input.
|
||||||
|
- Other Actions should **just work** inside a composite action, without any code changes
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
### Composite Recursion Limit
|
||||||
|
|
||||||
|
- We will start with supporting a recursion limit of `10` composite actions deep
|
||||||
|
- We are free to bump this limit in the future, the code will be written to just require updating a variable. If the graph evaluates beyond the recursion limit, the job will fail in the pre-job phase (The `Set up job` step).
|
||||||
|
- A composite actions interface is its inputs and outputs, nothing else is carried over when invoking recursively.
|
||||||
|
|
||||||
|
### Pre/Post Steps in nested Actions
|
||||||
|
|
||||||
|
- We do not plan on adding the ability to configure a customizable pre or post step for composite actions at this time. However, we will execute the pre and post steps of any actions referenced in a composite action.
|
||||||
|
- Composite actions will generate a single pre-step and post-step for the entire composite action, even if there are multiple pre-steps and post-steps in the referenced actions.
|
||||||
|
- These steps will execute following the same ordering rules we have today, first to run has their pre step run first and their post step run last.
|
||||||
|
- For example, if you had a composite action with two pre steps and two posts steps:
|
||||||
|
|
||||||
|
```
|
||||||
|
- uses: action1
|
||||||
|
- uses: composite1
|
||||||
|
- uses: action2
|
||||||
|
```
|
||||||
|
|
||||||
|
The order of execution would be:
|
||||||
|
|
||||||
|
```
|
||||||
|
- prestep-action1
|
||||||
|
- prestep-composite1
|
||||||
|
- prestep-composite1-first-action-referenced
|
||||||
|
- prestep-composite1-second-action-referenced
|
||||||
|
- prestep-action2
|
||||||
|
- the job steps
|
||||||
|
- poststep-action2
|
||||||
|
- poststep-composite1
|
||||||
|
- poststep-composite1-the-second-action-referenced
|
||||||
|
- poststep-composite1-first-action-referenced
|
||||||
|
- poststep-action1
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Set-state
|
||||||
|
|
||||||
|
- While the composite action has an individual combined pre/post action, the `set-state` command will not be shared.
|
||||||
|
- If the `set-state` command is used during a composite step, only the action that originally called `set-state` will have access to the env variable during the post run step.
|
||||||
|
- This prevents multiple actions that set the same state from interfering with the execution of another action's post step.
|
||||||
|
|
||||||
|
### Resolve Action Endpoint changes
|
||||||
|
|
||||||
|
- The resolve actions endpoint will now validate policy to ensure that the given workflow run has access to download that action.
|
||||||
|
- Older GHES/GHAE customers with newer runners will be locked out of composite uses steps until they upgrade their instance.
|
||||||
|
|
||||||
|
### Local actions
|
||||||
|
- Local actions will expand the tree, perform policy checks, and download actions Just in Time when the step is running.
|
||||||
|
- Like current local actions, we will not support presteps. If an action is running local, by the time we know that, the time to run presteps have already passed.
|
||||||
|
|
||||||
|
### If, continue-on-error, timeout-minutes - Not being considered at this time
|
||||||
|
|
||||||
|
- `if`, `continue-on-error`, `timeout-minutes` could be supported in composite run/uses steps. These values were not originally supported in our composite run steps implementation.
|
||||||
|
- Browsing the community forums and runner repo, there hasn't been a lot of noise asking for these features, so we will hold off on them.
|
||||||
|
- These values passed as input into the composite action will **not** be carried over as input into the individual steps the composite action runs.
|
||||||
|
|
||||||
|
### Defaults - Not being considered at this time
|
||||||
|
|
||||||
|
- In actions, we have the idea of [defaults](https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#defaultsrun) , which allow you to specify a shell and working directory in one location, rather then on each step.
|
||||||
|
- However, `shell` is currently required in composite run steps
|
||||||
|
- In regular run steps, it is optional, and defaults to a different value based on the OS.
|
||||||
|
- We want to prioritize the right experience for the consumer, and make the action author continue to explicitly set these values. We can consider improving this experience in the future.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
- Workflows are now more reusable across multiple workflow files
|
||||||
|
- Composite actions implement most of the existing workflow run steps, with room to expand these in the future
|
||||||
|
- Feature flags will control this rollout
|
||||||
@@ -26,6 +26,23 @@ Run as a one-liner. NOTE: replace with yourorg/yourrepo (repo level) or just you
|
|||||||
curl -s https://raw.githubusercontent.com/actions/runner/main/scripts/create-latest-svc.sh | bash -s yourorg/yourrepo
|
curl -s https://raw.githubusercontent.com/actions/runner/main/scripts/create-latest-svc.sh | bash -s yourorg/yourrepo
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You can call the script with additional arguments:
|
||||||
|
```bash
|
||||||
|
# Usage:
|
||||||
|
# export RUNNER_CFG_PAT=<yourPAT>
|
||||||
|
# ./create-latest-svc -s scope -g [ghe_domain] -n [name] -u [user] -l [labels]
|
||||||
|
# -s required scope: repo (:owner/:repo) or org (:organization)
|
||||||
|
# -g optional ghe_hostname: the fully qualified domain name of your GitHub Enterprise Server deployment
|
||||||
|
# -n optional name of the runner, defaults to hostname
|
||||||
|
# -u optional user svc will run as, defaults to current
|
||||||
|
# -l optional list of labels (split by comma) applied on the runner"
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `--` to pass any number of optional named parameters:
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -s https://raw.githubusercontent.com/actions/runner/main/scripts/create-latest-svc.sh | bash -s -- -s myorg/myrepo -n myname -l label1,label2
|
||||||
|
```
|
||||||
### Why can't I use a container?
|
### Why can't I use a container?
|
||||||
|
|
||||||
The runner is installed as a service using `systemd` and `systemctl`. Docker does not support `systemd` for service configuration on a container.
|
The runner is installed as a service using `systemd` and `systemctl`. Docker does not support `systemd` for service configuration on a container.
|
||||||
|
|||||||
@@ -2,17 +2,19 @@
|
|||||||
|
|
||||||
### Common things that can cause the runner to not working properly
|
### 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.
|
- A bug in the runner or the dotnet framework that causes the actions runner to be unable to make Http requests 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.
|
- A Proxy or Firewall may block certain HTTP method, such as blocking 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.
|
- A Proxy or Firewall may 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.
|
- A 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.
|
||||||
|
|
||||||
- Proxy try to modify the HTTPS request (like add or change some http headers) and causes the request become incompatible with the Actions Service (ASP.NetCore), Ex: [Nginx](https://github.com/dotnet/aspnetcore/issues/17081)
|
- The SSL handshake may fail if the client and server do not support the same TLS version, or the same cipher suites.
|
||||||
|
|
||||||
- Firewall rules that block action runner from accessing certain hosts, ex: `*.github.com`, `*.actions.githubusercontent.com`, etc.
|
- A Proxy may try to modify the HTTPS request (like add or change some http headers) and causes the request become incompatible with the Actions Service (ASP.NetCore), Ex: [Nginx](https://github.com/dotnet/aspnetcore/issues/17081)
|
||||||
|
|
||||||
|
- Firewall rules that block action runner from accessing certain hosts, ex: `*.github.com`, `*.actions.githubusercontent.com`, etc
|
||||||
|
|
||||||
|
|
||||||
### Identify and solve these problems
|
### Identify and solve these problems
|
||||||
@@ -30,3 +32,31 @@ Use a 3rd party tool to make the same requests as the runner did would be a good
|
|||||||
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.
|
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
|
Otherwise, contact GitHub customer support or log an issue at https://github.com/actions/runner
|
||||||
|
|
||||||
|
### Troubleshooting: Why can't I configure a runner?
|
||||||
|
|
||||||
|
If you are having trouble connecting, try these steps:
|
||||||
|
|
||||||
|
1. Validate you can reach our endpoints from your web browser. If not, double check your local network connection
|
||||||
|
- For hosted Github:
|
||||||
|
- https://api.github.com/
|
||||||
|
- https://vstoken.actions.githubusercontent.com/_apis/health
|
||||||
|
- https://pipelines.actions.githubusercontent.com/_apis/health
|
||||||
|
- For GHES/GHAE
|
||||||
|
- https://myGHES.com/_services/vstoken/_apis/health
|
||||||
|
- https://myGHES.com/_services/pipelines/_apis/health
|
||||||
|
- https://myGHES.com/api/v3
|
||||||
|
2. Validate you can reach those endpoints in powershell core
|
||||||
|
- The runner runs on .net core, lets validate the local settings for that stack
|
||||||
|
- Open up `pwsh`
|
||||||
|
- Run the command using the urls above `Invoke-WebRequest {url}`
|
||||||
|
3. If not, get a packet trace using a tool like wireshark and start looking at the TLS handshake.
|
||||||
|
- If you see a Client Hello followed by a Server RST:
|
||||||
|
- You may need to configure your TLS settings to use the correct version
|
||||||
|
- You should support TLS version 1.2 or later
|
||||||
|
- You may need to configure your TLS settings to have up to date cipher suites, this may be solved by system updates and patches.
|
||||||
|
- Most notably, on windows server 2012 make sure [the tls cipher suite update](https://support.microsoft.com/en-us/topic/update-adds-new-tls-cipher-suites-and-changes-cipher-suite-priorities-in-windows-8-1-and-windows-server-2012-r2-8e395e43-c8ef-27d8-b60c-0fc57d526d94) is installed
|
||||||
|
- Your firewall, proxy or network configuration may be blocking the connection
|
||||||
|
- You will want to reach out to whoever is in charge of your network with these pcap files to further troubleshoot
|
||||||
|
- If you see a failure later in the handshake:
|
||||||
|
- Try the fix in the [SSLCert Fix](./sslcert.md)
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ To let the runner trusts your CA certificate, you will need to:
|
|||||||
2. Ubuntu: http://manpages.ubuntu.com/manpages/focal/man8/update-ca-certificates.8.html
|
2. Ubuntu: http://manpages.ubuntu.com/manpages/focal/man8/update-ca-certificates.8.html
|
||||||
3. Google search: "trust ca certificate on [linux distribution]"
|
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.
|
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\"`
|
> To verify 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
|
### Trust CA certificate for Git CLI
|
||||||
|
|
||||||
|
|||||||
74
job.yml
Normal file
74
job.yml
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: Role
|
||||||
|
metadata:
|
||||||
|
name: pod-admin
|
||||||
|
namespace: default
|
||||||
|
rules:
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["pods", "pods/log", "pods/attach", "pods/exec"]
|
||||||
|
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: RoleBinding
|
||||||
|
metadata:
|
||||||
|
name: default-pod-admin
|
||||||
|
namespace: default
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: Role
|
||||||
|
name: pod-admin
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: default
|
||||||
|
namespace: default
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
namespace: default
|
||||||
|
name: actions-runners
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
# hostNetwork: true
|
||||||
|
volumes:
|
||||||
|
- name: runner-working
|
||||||
|
emptyDir: {}
|
||||||
|
containers:
|
||||||
|
- name: k8srunner
|
||||||
|
image: huangtingluo/kube-runner:v0
|
||||||
|
imagePullPolicy: Always
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /actions-runner/_work
|
||||||
|
name: runner-working
|
||||||
|
env:
|
||||||
|
- name: GITHUB_PAT
|
||||||
|
value: ghp_
|
||||||
|
- name: RUNNER_CONFIG_URL
|
||||||
|
value: https://github.com/bbq-beets/ting-test
|
||||||
|
- name: K8S_NODE_NAME
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: spec.nodeName
|
||||||
|
- name: K8S_POD_NAME
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: metadata.name
|
||||||
|
- name: K8S_POD_NAMESPACE
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: metadata.namespace
|
||||||
|
- name: K8S_POD_IP
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: status.podIP
|
||||||
|
- name: K8S_POD_SERVICE_ACCOUNT
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: spec.serviceAccountName
|
||||||
|
restartPolicy: Never
|
||||||
|
backoffLimit: 1
|
||||||
|
completions: 1
|
||||||
|
parallelism: 1
|
||||||
@@ -1,18 +1,11 @@
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Use GITHUB_TOKEN for ghcr.io containers if credentials are not provided (#990)
|
|
||||||
|
|
||||||
## Bugs
|
## Bugs
|
||||||
|
|
||||||
- Do not trucate error message from template evaluation (#1038)
|
- Fixed an issue where ephemeral runners did not restart after upgrading (#1396)
|
||||||
- Make FileShare ReadWrite (#1033)
|
|
||||||
- Mask secrets with double-quotes when passed to docker command line (#1002)
|
|
||||||
- Delete script files before replacing during update (#984)
|
|
||||||
|
|
||||||
|
|
||||||
## Misc
|
## Misc
|
||||||
|
|
||||||
|
|
||||||
## 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.
|
||||||
|
|
||||||
|
|||||||
@@ -2,36 +2,68 @@
|
|||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
#
|
|
||||||
# Downloads latest releases (not pre-release) runner
|
|
||||||
# Configures as a service
|
|
||||||
#
|
|
||||||
# Examples:
|
|
||||||
# RUNNER_CFG_PAT=<yourPAT> ./create-latest-svc.sh myuser/myrepo my.ghe.deployment.net
|
|
||||||
# RUNNER_CFG_PAT=<yourPAT> ./create-latest-svc.sh myorg my.ghe.deployment.net
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
# export RUNNER_CFG_PAT=<yourPAT>
|
|
||||||
# ./create-latest-svc scope [ghe_domain] [name] [user] [labels]
|
|
||||||
#
|
|
||||||
# scope required repo (:owner/:repo) or org (:organization)
|
|
||||||
# ghe_domain optional the fully qualified domain name of your GitHub Enterprise Server deployment
|
|
||||||
# name optional defaults to hostname
|
|
||||||
# user optional user svc will run as. defaults to current
|
|
||||||
# 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
|
||||||
|
# Downloads latest runner release (not pre-release)
|
||||||
|
# Configures it as a service more secure
|
||||||
# Should be used on VMs and not containers
|
# Should be used on VMs and not containers
|
||||||
# Works on OSX and Linux
|
# Works on OSX and Linux
|
||||||
# Assumes x64 arch
|
# Assumes x64 arch
|
||||||
#
|
# See EXAMPLES below
|
||||||
|
|
||||||
runner_scope=${1}
|
flags_found=false
|
||||||
ghe_hostname=${2}
|
|
||||||
runner_name=${3:-$(hostname)}
|
while getopts 's:g:n:u:l:' opt; do
|
||||||
svc_user=${4:-$USER}
|
flags_found=true
|
||||||
labels=${5}
|
|
||||||
|
case $opt in
|
||||||
|
s)
|
||||||
|
runner_scope=$OPTARG
|
||||||
|
;;
|
||||||
|
g)
|
||||||
|
ghe_hostname=$OPTARG
|
||||||
|
;;
|
||||||
|
n)
|
||||||
|
runner_name=$OPTARG
|
||||||
|
;;
|
||||||
|
u)
|
||||||
|
svc_user=$OPTARG
|
||||||
|
;;
|
||||||
|
l)
|
||||||
|
labels=$OPTARG
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "
|
||||||
|
Runner Service Installer
|
||||||
|
Examples:
|
||||||
|
RUNNER_CFG_PAT=<yourPAT> ./create-latest-svc.sh myuser/myrepo my.ghe.deployment.net
|
||||||
|
RUNNER_CFG_PAT=<yourPAT> ./create-latest-svc.sh -s myorg -u user_name -l label1,label2
|
||||||
|
Usage:
|
||||||
|
export RUNNER_CFG_PAT=<yourPAT>
|
||||||
|
./create-latest-svc scope [ghe_domain] [name] [user] [labels]
|
||||||
|
-s required scope: repo (:owner/:repo) or org (:organization)
|
||||||
|
-g optional ghe_hostname: the fully qualified domain name of your GitHub Enterprise Server deployment
|
||||||
|
-n optional name of the runner, defaults to hostname
|
||||||
|
-u optional user svc will run as, defaults to current
|
||||||
|
-l optional list of labels (split by comma) applied on the runner"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
shift "$((OPTIND - 1))"
|
||||||
|
|
||||||
|
if ! "$flags_found"; then
|
||||||
|
runner_scope=${1}
|
||||||
|
ghe_hostname=${2}
|
||||||
|
runner_name=${3:-$(hostname)}
|
||||||
|
svc_user=${4:-$USER}
|
||||||
|
labels=${5}
|
||||||
|
fi
|
||||||
|
|
||||||
|
# apply defaults
|
||||||
|
runner_name=${runner_name:-$(hostname)}
|
||||||
|
svc_user=${svc_user:-$USER}
|
||||||
|
|
||||||
echo "Configuring runner @ ${runner_scope}"
|
echo "Configuring runner @ ${runner_scope}"
|
||||||
sudo echo
|
sudo echo
|
||||||
@@ -142,7 +174,7 @@ echo
|
|||||||
echo "Configuring as a service ..."
|
echo "Configuring as a service ..."
|
||||||
prefix=""
|
prefix=""
|
||||||
if [ "${runner_plat}" == "linux" ]; then
|
if [ "${runner_plat}" == "linux" ]; then
|
||||||
prefix="sudo "
|
prefix="sudo "
|
||||||
fi
|
fi
|
||||||
|
|
||||||
${prefix}./svc.sh install ${svc_user}
|
${prefix}./svc.sh install ${svc_user}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ fi
|
|||||||
# Ensure offline
|
# Ensure offline
|
||||||
#--------------------------------------
|
#--------------------------------------
|
||||||
runner_status=$(curl -s -X GET ${base_api_url}/${runner_scope}/actions/runners?per_page=100 -H "accept: application/vnd.github.everest-preview+json" -H "authorization: token ${RUNNER_CFG_PAT}" \
|
runner_status=$(curl -s -X GET ${base_api_url}/${runner_scope}/actions/runners?per_page=100 -H "accept: application/vnd.github.everest-preview+json" -H "authorization: token ${RUNNER_CFG_PAT}" \
|
||||||
| jq -M -j ".runners | .[] | [select(.name == \"${runner_name}\")] | .[0].status")
|
| jq -M -j ".runners | .[] | select(.name == \"${runner_name}\") | .status")
|
||||||
|
|
||||||
if [ -z "${runner_status}" ]; then
|
if [ -z "${runner_status}" ]; then
|
||||||
fatal "Could not find runner with name ${runner_name}"
|
fatal "Could not find runner with name ${runner_name}"
|
||||||
@@ -67,7 +67,7 @@ fi
|
|||||||
# Get id of runner to remove
|
# Get id of runner to remove
|
||||||
#--------------------------------------
|
#--------------------------------------
|
||||||
runner_id=$(curl -s -X GET ${base_api_url}/${runner_scope}/actions/runners?per_page=100 -H "accept: application/vnd.github.everest-preview+json" -H "authorization: token ${RUNNER_CFG_PAT}" \
|
runner_id=$(curl -s -X GET ${base_api_url}/${runner_scope}/actions/runners?per_page=100 -H "accept: application/vnd.github.everest-preview+json" -H "authorization: token ${RUNNER_CFG_PAT}" \
|
||||||
| jq -M -j ".runners | .[] | [select(.name == \"${runner_name}\")] | .[0].id")
|
| jq -M -j ".runners | .[] | select(.name == \"${runner_name}\") | .id")
|
||||||
|
|
||||||
if [ -z "${runner_id}" ]; then
|
if [ -z "${runner_id}" ]; then
|
||||||
fatal "Could not find runner with name ${runner_name}"
|
fatal "Could not find runner with name ${runner_name}"
|
||||||
|
|||||||
78
src/Dockerfile
Normal file
78
src/Dockerfile
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
FROM mcr.microsoft.com/dotnet/sdk:3.1 AS Build
|
||||||
|
|
||||||
|
# ENV RUNNER_CONFIG_URL=""
|
||||||
|
# ENV GITHUB_PAT=""
|
||||||
|
# ENV RUNNER_NAME=""
|
||||||
|
# ENV RUNNER_GROUP=""
|
||||||
|
# ENV RUNNER_LABELS=""
|
||||||
|
# ENV GITHUB_RUNNER_SCOPE=""
|
||||||
|
# ENV GITHUB_SERVER_URL=""
|
||||||
|
# ENV GITHUB_API_URL=""
|
||||||
|
# ENV K8S_HOST_IP=""
|
||||||
|
|
||||||
|
RUN apt-get update --fix-missing \
|
||||||
|
&& apt-get install -y --no-install-recommends \
|
||||||
|
curl \
|
||||||
|
# jq \
|
||||||
|
# git \
|
||||||
|
apt-utils \
|
||||||
|
apt-transport-https \
|
||||||
|
unzip \
|
||||||
|
net-tools\
|
||||||
|
gnupg2\
|
||||||
|
&& apt-get clean \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Install kubectl
|
||||||
|
# RUN curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - && \
|
||||||
|
# echo "deb https://apt.kubernetes.io/ kubernetes-xenial main" | tee -a /etc/apt/sources.list.d/kubernetes.list && \
|
||||||
|
# apt-get update && apt-get -y install --no-install-recommends kubectl
|
||||||
|
|
||||||
|
# Install docker
|
||||||
|
# RUN curl -fsSL https://get.docker.com -o get-docker.sh
|
||||||
|
# RUN sh get-docker.sh
|
||||||
|
|
||||||
|
# Allow runner to run as root
|
||||||
|
# ENV RUNNER_ALLOW_RUNASROOT=1
|
||||||
|
|
||||||
|
# Directory for runner to operate in
|
||||||
|
RUN mkdir /actions-runner
|
||||||
|
RUN mkdir /actions-runner/src
|
||||||
|
WORKDIR /actions-runner/src
|
||||||
|
|
||||||
|
COPY ./ /actions-runner/src
|
||||||
|
|
||||||
|
RUN /actions-runner/src/dev.sh l
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/core/runtime-deps:3.1
|
||||||
|
|
||||||
|
ENV RUNNER_CONFIG_URL=""
|
||||||
|
ENV GITHUB_PAT=""
|
||||||
|
|
||||||
|
RUN apt-get update --fix-missing \
|
||||||
|
&& apt-get install -y --no-install-recommends \
|
||||||
|
curl \
|
||||||
|
# jq \
|
||||||
|
# git \
|
||||||
|
# apt-utils \
|
||||||
|
# apt-transport-https \
|
||||||
|
# unzip \
|
||||||
|
# net-tools\
|
||||||
|
gnupg2\
|
||||||
|
&& apt-get clean \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Install kubectl
|
||||||
|
RUN curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - && \
|
||||||
|
echo "deb https://apt.kubernetes.io/ kubernetes-xenial main" | tee -a /etc/apt/sources.list.d/kubernetes.list && \
|
||||||
|
apt-get update && apt-get -y install --no-install-recommends kubectl
|
||||||
|
|
||||||
|
|
||||||
|
# Allow runner to run as root
|
||||||
|
ENV RUNNER_ALLOW_RUNASROOT=1
|
||||||
|
|
||||||
|
# Directory for runner to operate in
|
||||||
|
RUN mkdir /actions-runner
|
||||||
|
WORKDIR /actions-runner
|
||||||
|
COPY --from=Build /actions-runner/_layout /actions-runner
|
||||||
|
ENTRYPOINT ["./entrypoint.sh"]
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
dist/
|
||||||
|
lib/
|
||||||
|
node_modules/
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
{
|
||||||
|
"plugins": ["jest", "@typescript-eslint"],
|
||||||
|
"extends": ["plugin:github/es6"],
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 9,
|
||||||
|
"sourceType": "module",
|
||||||
|
"project": "./tsconfig.json"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"eslint-comments/no-use": "off",
|
||||||
|
"import/no-namespace": "off",
|
||||||
|
"no-console": "off",
|
||||||
|
"no-unused-vars": "off",
|
||||||
|
"@typescript-eslint/no-unused-vars": "error",
|
||||||
|
"@typescript-eslint/explicit-member-accessibility": ["error", {"accessibility": "no-public"}],
|
||||||
|
"@typescript-eslint/no-require-imports": "error",
|
||||||
|
"@typescript-eslint/array-type": "error",
|
||||||
|
"@typescript-eslint/await-thenable": "error",
|
||||||
|
"@typescript-eslint/ban-ts-ignore": "error",
|
||||||
|
"camelcase": "off",
|
||||||
|
"@typescript-eslint/camelcase": "error",
|
||||||
|
"@typescript-eslint/class-name-casing": "error",
|
||||||
|
"@typescript-eslint/explicit-function-return-type": ["error", {"allowExpressions": true}],
|
||||||
|
"@typescript-eslint/func-call-spacing": ["error", "never"],
|
||||||
|
"@typescript-eslint/generic-type-naming": ["error", "^[A-Z][A-Za-z]*$"],
|
||||||
|
"@typescript-eslint/no-array-constructor": "error",
|
||||||
|
"@typescript-eslint/no-empty-interface": "error",
|
||||||
|
"@typescript-eslint/no-explicit-any": "error",
|
||||||
|
"@typescript-eslint/no-extraneous-class": "error",
|
||||||
|
"@typescript-eslint/no-for-in-array": "error",
|
||||||
|
"@typescript-eslint/no-inferrable-types": "error",
|
||||||
|
"@typescript-eslint/no-misused-new": "error",
|
||||||
|
"@typescript-eslint/no-namespace": "error",
|
||||||
|
"@typescript-eslint/no-non-null-assertion": "warn",
|
||||||
|
"@typescript-eslint/no-object-literal-type-assertion": "error",
|
||||||
|
"@typescript-eslint/no-unnecessary-qualifier": "error",
|
||||||
|
"@typescript-eslint/no-unnecessary-type-assertion": "error",
|
||||||
|
"@typescript-eslint/no-useless-constructor": "error",
|
||||||
|
"@typescript-eslint/no-var-requires": "error",
|
||||||
|
"@typescript-eslint/prefer-for-of": "warn",
|
||||||
|
"@typescript-eslint/prefer-function-type": "warn",
|
||||||
|
"@typescript-eslint/prefer-includes": "error",
|
||||||
|
"@typescript-eslint/prefer-interface": "error",
|
||||||
|
"@typescript-eslint/prefer-string-starts-ends-with": "error",
|
||||||
|
"@typescript-eslint/promise-function-async": "error",
|
||||||
|
"@typescript-eslint/require-array-sort-compare": "error",
|
||||||
|
"@typescript-eslint/restrict-plus-operands": "error",
|
||||||
|
"semi": "off",
|
||||||
|
"@typescript-eslint/semi": ["error", "never"],
|
||||||
|
"@typescript-eslint/type-annotation-spacing": "error",
|
||||||
|
"@typescript-eslint/unbound-method": "error"
|
||||||
|
},
|
||||||
|
"env": {
|
||||||
|
"node": true,
|
||||||
|
"es6": true,
|
||||||
|
"jest/globals": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
dist/
|
||||||
|
lib/
|
||||||
|
node_modules/
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"printWidth": 80,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"useTabs": false,
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"bracketSpacing": false,
|
||||||
|
"arrowParens": "avoid",
|
||||||
|
"parser": "typescript"
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
To update kubeInnerHandler under `Misc/layoutbin` run `npm install && npm run all`
|
||||||
6034
src/Misc/containerEngineHandlers/kubeInnerHandler/package-lock.json
generated
Normal file
6034
src/Misc/containerEngineHandlers/kubeInnerHandler/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"name": "kubeInnerHandler",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "GitHub Actions",
|
||||||
|
"main": "lib/kubeInnerHandler.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc",
|
||||||
|
"format": "prettier --write **/*.ts",
|
||||||
|
"format-check": "prettier --check **/*.ts",
|
||||||
|
"lint": "eslint src/**/*.ts",
|
||||||
|
"pack": "ncc build -o ../../layoutbin/kubeInnerHandler",
|
||||||
|
"all": "npm run build && npm run format && npm run lint && npm run pack"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/actions/runner.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"actions"
|
||||||
|
],
|
||||||
|
"author": "GitHub Actions",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@actions/exec": "^1.1.0",
|
||||||
|
"@actions/core": "^1.6.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^12.7.12",
|
||||||
|
"@typescript-eslint/parser": "^2.8.0",
|
||||||
|
"@zeit/ncc": "^0.20.5",
|
||||||
|
"eslint": "^6.8.0",
|
||||||
|
"eslint-plugin-github": "^2.0.0",
|
||||||
|
"prettier": "^1.19.1",
|
||||||
|
"typescript": "^3.6.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import * as exec from '@actions/exec'
|
||||||
|
import * as core from '@actions/core'
|
||||||
|
import * as events from 'events'
|
||||||
|
import * as readline from 'readline'
|
||||||
|
|
||||||
|
async function run(): Promise<void> {
|
||||||
|
let input = ''
|
||||||
|
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
input: process.stdin
|
||||||
|
})
|
||||||
|
|
||||||
|
rl.on('line', line => {
|
||||||
|
core.debug(`Line from STDIN: ${line}`)
|
||||||
|
input = line
|
||||||
|
})
|
||||||
|
|
||||||
|
await events.once(rl, 'close')
|
||||||
|
|
||||||
|
core.debug(input)
|
||||||
|
|
||||||
|
const execInput = JSON.parse(input)
|
||||||
|
core.debug(JSON.stringify(execInput))
|
||||||
|
|
||||||
|
// podman exec -i --workdir /__w/canary/canary
|
||||||
|
// -e GITHUB_JOB -e GITHUB_REF -e GITHUB_SHA -e GITHUB_REPOSITORY
|
||||||
|
// -e GITHUB_REPOSITORY_OWNER -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER
|
||||||
|
// -e GITHUB_RETENTION_DAYS -e GITHUB_RUN_ATTEMPT -e GITHUB_ACTOR
|
||||||
|
// -e GITHUB_WORKFLOW -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GITHUB_EVENT_NAME
|
||||||
|
// -e GITHUB_SERVER_URL -e GITHUB_API_URL -e GITHUB_GRAPHQL_URL
|
||||||
|
// -e GITHUB_WORKSPACE -e GITHUB_ACTION -e GITHUB_EVENT_PATH -e GITHUB_ACTION_REPOSITORY
|
||||||
|
// -e GITHUB_ACTION_REF -e GITHUB_PATH -e GITHUB_ENV -e RUNNER_DEBUG
|
||||||
|
// -e RUNNER_OS -e RUNNER_NAME -e RUNNER_TOOL_CACHE
|
||||||
|
// -e RUNNER_TEMP -e RUNNER_WORKSPACE
|
||||||
|
// eccdf520697a035599d6e8c8dc801f004fdd3797cdce88f590aba3669a88d9bc sh -e /__w/_temp/d3b30383-719c-4e76-a16f-8f85443352be.sh
|
||||||
|
|
||||||
|
const execArgs = []
|
||||||
|
const args = (<string>execInput.arguments).split(' ')
|
||||||
|
core.debug(JSON.stringify(args))
|
||||||
|
execArgs.push(...args)
|
||||||
|
|
||||||
|
core.debug(JSON.stringify(execArgs))
|
||||||
|
|
||||||
|
await exec.exec(execInput.fileName, execArgs, {
|
||||||
|
env: execInput.environmentVariables
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
run()
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
|
||||||
|
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
|
||||||
|
"outDir": "./lib", /* Redirect output structure to the directory. */
|
||||||
|
"rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||||
|
"strict": true, /* Enable all strict type-checking options. */
|
||||||
|
"noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||||
|
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||||
|
},
|
||||||
|
"exclude": ["node_modules", "**/*.test.ts"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
dist/
|
||||||
|
lib/
|
||||||
|
node_modules/
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
{
|
||||||
|
"plugins": ["jest", "@typescript-eslint"],
|
||||||
|
"extends": ["plugin:github/es6"],
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 9,
|
||||||
|
"sourceType": "module",
|
||||||
|
"project": "./tsconfig.json"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"eslint-comments/no-use": "off",
|
||||||
|
"import/no-namespace": "off",
|
||||||
|
"no-console": "off",
|
||||||
|
"no-unused-vars": "off",
|
||||||
|
"@typescript-eslint/no-unused-vars": "error",
|
||||||
|
"@typescript-eslint/explicit-member-accessibility": ["error", {"accessibility": "no-public"}],
|
||||||
|
"@typescript-eslint/no-require-imports": "error",
|
||||||
|
"@typescript-eslint/array-type": "error",
|
||||||
|
"@typescript-eslint/await-thenable": "error",
|
||||||
|
"@typescript-eslint/ban-ts-ignore": "error",
|
||||||
|
"camelcase": "off",
|
||||||
|
"@typescript-eslint/camelcase": "error",
|
||||||
|
"@typescript-eslint/class-name-casing": "error",
|
||||||
|
"@typescript-eslint/explicit-function-return-type": ["error", {"allowExpressions": true}],
|
||||||
|
"@typescript-eslint/func-call-spacing": ["error", "never"],
|
||||||
|
"@typescript-eslint/generic-type-naming": ["error", "^[A-Z][A-Za-z]*$"],
|
||||||
|
"@typescript-eslint/no-array-constructor": "error",
|
||||||
|
"@typescript-eslint/no-empty-interface": "error",
|
||||||
|
"@typescript-eslint/no-explicit-any": "error",
|
||||||
|
"@typescript-eslint/no-extraneous-class": "error",
|
||||||
|
"@typescript-eslint/no-for-in-array": "error",
|
||||||
|
"@typescript-eslint/no-inferrable-types": "error",
|
||||||
|
"@typescript-eslint/no-misused-new": "error",
|
||||||
|
"@typescript-eslint/no-namespace": "error",
|
||||||
|
"@typescript-eslint/no-non-null-assertion": "warn",
|
||||||
|
"@typescript-eslint/no-object-literal-type-assertion": "error",
|
||||||
|
"@typescript-eslint/no-unnecessary-qualifier": "error",
|
||||||
|
"@typescript-eslint/no-unnecessary-type-assertion": "error",
|
||||||
|
"@typescript-eslint/no-useless-constructor": "error",
|
||||||
|
"@typescript-eslint/no-var-requires": "error",
|
||||||
|
"@typescript-eslint/prefer-for-of": "warn",
|
||||||
|
"@typescript-eslint/prefer-function-type": "warn",
|
||||||
|
"@typescript-eslint/prefer-includes": "error",
|
||||||
|
"@typescript-eslint/prefer-interface": "error",
|
||||||
|
"@typescript-eslint/prefer-string-starts-ends-with": "error",
|
||||||
|
"@typescript-eslint/promise-function-async": "error",
|
||||||
|
"@typescript-eslint/require-array-sort-compare": "error",
|
||||||
|
"@typescript-eslint/restrict-plus-operands": "error",
|
||||||
|
"semi": "off",
|
||||||
|
"@typescript-eslint/semi": ["error", "never"],
|
||||||
|
"@typescript-eslint/type-annotation-spacing": "error",
|
||||||
|
"@typescript-eslint/unbound-method": "error"
|
||||||
|
},
|
||||||
|
"env": {
|
||||||
|
"node": true,
|
||||||
|
"es6": true,
|
||||||
|
"jest/globals": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
dist/
|
||||||
|
lib/
|
||||||
|
node_modules/
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"printWidth": 80,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"useTabs": false,
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"bracketSpacing": false,
|
||||||
|
"arrowParens": "avoid",
|
||||||
|
"parser": "typescript"
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
To update kubectlHandler under `Misc/layoutbin` run `npm install && npm run all`
|
||||||
6034
src/Misc/containerEngineHandlers/kubectlHandler/package-lock.json
generated
Normal file
6034
src/Misc/containerEngineHandlers/kubectlHandler/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
36
src/Misc/containerEngineHandlers/kubectlHandler/package.json
Normal file
36
src/Misc/containerEngineHandlers/kubectlHandler/package.json
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"name": "kubectlHandler",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "GitHub Actions",
|
||||||
|
"main": "lib/kubectlHandler.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc",
|
||||||
|
"format": "prettier --write **/*.ts",
|
||||||
|
"format-check": "prettier --check **/*.ts",
|
||||||
|
"lint": "eslint src/**/*.ts",
|
||||||
|
"pack": "ncc build -o ../../layoutbin/kubectlHandler",
|
||||||
|
"all": "npm run build && npm run format && npm run lint && npm run pack"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/actions/runner.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"actions"
|
||||||
|
],
|
||||||
|
"author": "GitHub Actions",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@actions/exec": "^1.1.0",
|
||||||
|
"@actions/core": "^1.6.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^12.7.12",
|
||||||
|
"@typescript-eslint/parser": "^2.8.0",
|
||||||
|
"@zeit/ncc": "^0.20.5",
|
||||||
|
"eslint": "^6.8.0",
|
||||||
|
"eslint-plugin-github": "^2.0.0",
|
||||||
|
"prettier": "^1.19.1",
|
||||||
|
"typescript": "^3.6.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
import * as exec from '@actions/exec'
|
||||||
|
import * as core from '@actions/core'
|
||||||
|
import * as events from 'events'
|
||||||
|
import * as readline from 'readline'
|
||||||
|
|
||||||
|
async function run(): Promise<void> {
|
||||||
|
let input = ''
|
||||||
|
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
input: process.stdin
|
||||||
|
})
|
||||||
|
|
||||||
|
rl.on('line', line => {
|
||||||
|
core.debug(`Line from STDIN: ${line}`)
|
||||||
|
input = line
|
||||||
|
})
|
||||||
|
|
||||||
|
await events.once(rl, 'close')
|
||||||
|
|
||||||
|
core.debug(input)
|
||||||
|
|
||||||
|
const inputJson = JSON.parse(input)
|
||||||
|
core.debug(JSON.stringify(inputJson))
|
||||||
|
|
||||||
|
const command = inputJson.command
|
||||||
|
if (command === 'Create') {
|
||||||
|
const creationInput = inputJson.creationInput
|
||||||
|
core.debug(JSON.stringify(creationInput))
|
||||||
|
const containers = creationInput.containers
|
||||||
|
const jobContainer = containers[0]
|
||||||
|
|
||||||
|
// const networkName = 'actions_podman_network'
|
||||||
|
// // podman network create {network} -> track and return `network` for ${{job.container.network}}
|
||||||
|
// await exec.exec('podman', ['network', 'create', networkName])
|
||||||
|
|
||||||
|
const containerImage = `${jobContainer.containerImage}`
|
||||||
|
// podman pull docker.io/library/{image}
|
||||||
|
// await exec.exec('podman', ['pull', containerImage])
|
||||||
|
|
||||||
|
// kubectl run e088c842be1f46b394212618408aaba0_node1016jessie_6196c9
|
||||||
|
// --image=node:10.16-jessie
|
||||||
|
// -- tail -f /dev/null
|
||||||
|
const runArgs = ['run', 'job-container']
|
||||||
|
// runArgs.push(`--workdir=${jobContainer.containerWorkDirectory}`)
|
||||||
|
// runArgs.push(`--network=${networkName}`)
|
||||||
|
|
||||||
|
// for (const mountVolume of jobContainer.mountVolumes) {
|
||||||
|
// runArgs.push(
|
||||||
|
// `-v=${mountVolume.sourceVolumePath}:${mountVolume.targetVolumePath}`
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
runArgs.push(`--image=${containerImage}`)
|
||||||
|
runArgs.push(`--`)
|
||||||
|
runArgs.push(`tail`)
|
||||||
|
runArgs.push(`-f`)
|
||||||
|
runArgs.push(`/dev/null`)
|
||||||
|
|
||||||
|
core.debug(JSON.stringify(runArgs))
|
||||||
|
|
||||||
|
// const containerId = await exec.getExecOutput('podman', [
|
||||||
|
// 'create',
|
||||||
|
// // `--workdir ${jobContainer.containerWorkDirectory}`,
|
||||||
|
// `--network=${networkName}`,
|
||||||
|
// // `-v=/Users/ting/Desktop/runner/_layout/_work:/__w`,
|
||||||
|
// `--entrypoint=${jobContainer.containerEntryPoint}`,
|
||||||
|
// `${containerImage}`,
|
||||||
|
// `${jobContainer.containerEntryPointArgs}`
|
||||||
|
// ])
|
||||||
|
|
||||||
|
await exec.exec('kubectl', runArgs)
|
||||||
|
|
||||||
|
// get PATH inside the container
|
||||||
|
|
||||||
|
const waitArgs = ['wait', '--for=condition=Ready', 'pod/job-container']
|
||||||
|
await exec.exec('kubectl', waitArgs)
|
||||||
|
|
||||||
|
// output containerId for ${{job.container.id}}
|
||||||
|
|
||||||
|
// copy over node.js
|
||||||
|
const cpNodeArgs = [
|
||||||
|
'cp',
|
||||||
|
'/actions-runner/externals/node12/bin',
|
||||||
|
'job-container:/__runner_util/'
|
||||||
|
]
|
||||||
|
await exec.exec('kubectl', cpNodeArgs)
|
||||||
|
|
||||||
|
// copy over innerhandler
|
||||||
|
const cpKubeInnerArgs = [
|
||||||
|
'cp',
|
||||||
|
'/actions-runner/bin/kubeInnerHandler',
|
||||||
|
'job-container:/__runner_util/kubeInnerHandler'
|
||||||
|
]
|
||||||
|
await exec.exec('kubectl', cpKubeInnerArgs)
|
||||||
|
|
||||||
|
// copy over _work
|
||||||
|
const cpWorkArgs = ['cp', '/actions-runner/_work', 'job-container:/__w/']
|
||||||
|
await exec.exec('kubectl', cpWorkArgs)
|
||||||
|
|
||||||
|
const creationOutput = {
|
||||||
|
JobContainerId: 'job-container',
|
||||||
|
Network: 'job-container'
|
||||||
|
}
|
||||||
|
|
||||||
|
const output = JSON.stringify({CreationOutput: creationOutput})
|
||||||
|
core.debug(output)
|
||||||
|
|
||||||
|
process.stderr.write(
|
||||||
|
`___CONTAINER_ENGINE_HANDLER_OUTPUT___${output}___CONTAINER_ENGINE_HANDLER_OUTPUT___`
|
||||||
|
)
|
||||||
|
} else if (command === 'Remove') {
|
||||||
|
const removeInput = inputJson.removeInput
|
||||||
|
core.debug(JSON.stringify(removeInput))
|
||||||
|
// const jobContainerId = removeInput.jobContainerId
|
||||||
|
|
||||||
|
// await exec.exec('kubectl', ['delete', 'pod', jobContainerId, '--force'])
|
||||||
|
// await exec.exec('podman', ['network', 'rm', '-f', network])
|
||||||
|
} else if (command === 'Exec') {
|
||||||
|
const execInput = inputJson.execInput
|
||||||
|
core.debug(JSON.stringify(execInput))
|
||||||
|
|
||||||
|
// podman exec -i --workdir /__w/canary/canary
|
||||||
|
// -e GITHUB_JOB -e GITHUB_REF -e GITHUB_SHA -e GITHUB_REPOSITORY
|
||||||
|
// -e GITHUB_REPOSITORY_OWNER -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER
|
||||||
|
// -e GITHUB_RETENTION_DAYS -e GITHUB_RUN_ATTEMPT -e GITHUB_ACTOR
|
||||||
|
// -e GITHUB_WORKFLOW -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GITHUB_EVENT_NAME
|
||||||
|
// -e GITHUB_SERVER_URL -e GITHUB_API_URL -e GITHUB_GRAPHQL_URL
|
||||||
|
// -e GITHUB_WORKSPACE -e GITHUB_ACTION -e GITHUB_EVENT_PATH -e GITHUB_ACTION_REPOSITORY
|
||||||
|
// -e GITHUB_ACTION_REF -e GITHUB_PATH -e GITHUB_ENV -e RUNNER_DEBUG
|
||||||
|
// -e RUNNER_OS -e RUNNER_NAME -e RUNNER_TOOL_CACHE
|
||||||
|
// -e RUNNER_TEMP -e RUNNER_WORKSPACE
|
||||||
|
// eccdf520697a035599d6e8c8dc801f004fdd3797cdce88f590aba3669a88d9bc sh -e /__w/_temp/d3b30383-719c-4e76-a16f-8f85443352be.sh
|
||||||
|
|
||||||
|
const cpTempArgs = [
|
||||||
|
'cp',
|
||||||
|
'/actions-runner/_work/_temp',
|
||||||
|
'job-container:/__w/'
|
||||||
|
]
|
||||||
|
await exec.exec('kubectl', cpTempArgs)
|
||||||
|
|
||||||
|
const execArgs = ['exec']
|
||||||
|
execArgs.push(execInput.jobContainer.containerId)
|
||||||
|
execArgs.push('-i')
|
||||||
|
execArgs.push('-t')
|
||||||
|
execArgs.push('--')
|
||||||
|
execArgs.push('/__runner_util/node')
|
||||||
|
execArgs.push('/__runner_util/kubeInnerHandler')
|
||||||
|
|
||||||
|
core.debug(JSON.stringify(execArgs))
|
||||||
|
|
||||||
|
await exec.exec('kubectl', execArgs, {
|
||||||
|
input: Buffer.from(JSON.stringify(execInput))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run()
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
|
||||||
|
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
|
||||||
|
"outDir": "./lib", /* Redirect output structure to the directory. */
|
||||||
|
"rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||||
|
"strict": true, /* Enable all strict type-checking options. */
|
||||||
|
"noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||||
|
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||||
|
},
|
||||||
|
"exclude": ["node_modules", "**/*.test.ts"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
dist/
|
||||||
|
lib/
|
||||||
|
node_modules/
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
{
|
||||||
|
"plugins": ["jest", "@typescript-eslint"],
|
||||||
|
"extends": ["plugin:github/es6"],
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 9,
|
||||||
|
"sourceType": "module",
|
||||||
|
"project": "./tsconfig.json"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"eslint-comments/no-use": "off",
|
||||||
|
"import/no-namespace": "off",
|
||||||
|
"no-console": "off",
|
||||||
|
"no-unused-vars": "off",
|
||||||
|
"@typescript-eslint/no-unused-vars": "error",
|
||||||
|
"@typescript-eslint/explicit-member-accessibility": ["error", {"accessibility": "no-public"}],
|
||||||
|
"@typescript-eslint/no-require-imports": "error",
|
||||||
|
"@typescript-eslint/array-type": "error",
|
||||||
|
"@typescript-eslint/await-thenable": "error",
|
||||||
|
"@typescript-eslint/ban-ts-ignore": "error",
|
||||||
|
"camelcase": "off",
|
||||||
|
"@typescript-eslint/camelcase": "error",
|
||||||
|
"@typescript-eslint/class-name-casing": "error",
|
||||||
|
"@typescript-eslint/explicit-function-return-type": ["error", {"allowExpressions": true}],
|
||||||
|
"@typescript-eslint/func-call-spacing": ["error", "never"],
|
||||||
|
"@typescript-eslint/generic-type-naming": ["error", "^[A-Z][A-Za-z]*$"],
|
||||||
|
"@typescript-eslint/no-array-constructor": "error",
|
||||||
|
"@typescript-eslint/no-empty-interface": "error",
|
||||||
|
"@typescript-eslint/no-explicit-any": "error",
|
||||||
|
"@typescript-eslint/no-extraneous-class": "error",
|
||||||
|
"@typescript-eslint/no-for-in-array": "error",
|
||||||
|
"@typescript-eslint/no-inferrable-types": "error",
|
||||||
|
"@typescript-eslint/no-misused-new": "error",
|
||||||
|
"@typescript-eslint/no-namespace": "error",
|
||||||
|
"@typescript-eslint/no-non-null-assertion": "warn",
|
||||||
|
"@typescript-eslint/no-object-literal-type-assertion": "error",
|
||||||
|
"@typescript-eslint/no-unnecessary-qualifier": "error",
|
||||||
|
"@typescript-eslint/no-unnecessary-type-assertion": "error",
|
||||||
|
"@typescript-eslint/no-useless-constructor": "error",
|
||||||
|
"@typescript-eslint/no-var-requires": "error",
|
||||||
|
"@typescript-eslint/prefer-for-of": "warn",
|
||||||
|
"@typescript-eslint/prefer-function-type": "warn",
|
||||||
|
"@typescript-eslint/prefer-includes": "error",
|
||||||
|
"@typescript-eslint/prefer-interface": "error",
|
||||||
|
"@typescript-eslint/prefer-string-starts-ends-with": "error",
|
||||||
|
"@typescript-eslint/promise-function-async": "error",
|
||||||
|
"@typescript-eslint/require-array-sort-compare": "error",
|
||||||
|
"@typescript-eslint/restrict-plus-operands": "error",
|
||||||
|
"semi": "off",
|
||||||
|
"@typescript-eslint/semi": ["error", "never"],
|
||||||
|
"@typescript-eslint/type-annotation-spacing": "error",
|
||||||
|
"@typescript-eslint/unbound-method": "error"
|
||||||
|
},
|
||||||
|
"env": {
|
||||||
|
"node": true,
|
||||||
|
"es6": true,
|
||||||
|
"jest/globals": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
dist/
|
||||||
|
lib/
|
||||||
|
node_modules/
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"printWidth": 80,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"useTabs": false,
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"bracketSpacing": false,
|
||||||
|
"arrowParens": "avoid",
|
||||||
|
"parser": "typescript"
|
||||||
|
}
|
||||||
1
src/Misc/containerEngineHandlers/podmanHandler/README.md
Normal file
1
src/Misc/containerEngineHandlers/podmanHandler/README.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
To update podmanHandler under `Misc/layoutbin` run `npm install && npm run all`
|
||||||
6034
src/Misc/containerEngineHandlers/podmanHandler/package-lock.json
generated
Normal file
6034
src/Misc/containerEngineHandlers/podmanHandler/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
36
src/Misc/containerEngineHandlers/podmanHandler/package.json
Normal file
36
src/Misc/containerEngineHandlers/podmanHandler/package.json
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"name": "podmanHandler",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "GitHub Actions",
|
||||||
|
"main": "lib/podmanHandler.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc",
|
||||||
|
"format": "prettier --write **/*.ts",
|
||||||
|
"format-check": "prettier --check **/*.ts",
|
||||||
|
"lint": "eslint src/**/*.ts",
|
||||||
|
"pack": "ncc build -o ../../layoutbin/podmanHandler",
|
||||||
|
"all": "npm run build && npm run format && npm run lint && npm run pack"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/actions/runner.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"actions"
|
||||||
|
],
|
||||||
|
"author": "GitHub Actions",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@actions/exec": "^1.1.0",
|
||||||
|
"@actions/core": "^1.6.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^12.7.12",
|
||||||
|
"@typescript-eslint/parser": "^2.8.0",
|
||||||
|
"@zeit/ncc": "^0.20.5",
|
||||||
|
"eslint": "^6.8.0",
|
||||||
|
"eslint-plugin-github": "^2.0.0",
|
||||||
|
"prettier": "^1.19.1",
|
||||||
|
"typescript": "^3.6.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
import * as exec from '@actions/exec'
|
||||||
|
import * as core from '@actions/core'
|
||||||
|
import * as events from 'events'
|
||||||
|
import * as readline from 'readline'
|
||||||
|
|
||||||
|
async function run(): Promise<void> {
|
||||||
|
let input = ''
|
||||||
|
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
input: process.stdin
|
||||||
|
})
|
||||||
|
|
||||||
|
rl.on('line', line => {
|
||||||
|
core.debug(`Line from STDIN: ${line}`)
|
||||||
|
input = line
|
||||||
|
})
|
||||||
|
|
||||||
|
await events.once(rl, 'close')
|
||||||
|
|
||||||
|
core.debug(input)
|
||||||
|
|
||||||
|
const inputJson = JSON.parse(input)
|
||||||
|
core.debug(JSON.stringify(inputJson))
|
||||||
|
|
||||||
|
const command = inputJson.command
|
||||||
|
if (command === 'Create') {
|
||||||
|
const creationInput = inputJson.creationInput
|
||||||
|
core.debug(JSON.stringify(creationInput))
|
||||||
|
const containers = creationInput.containers
|
||||||
|
const jobContainer = containers[0]
|
||||||
|
|
||||||
|
const networkName = 'actions_podman_network'
|
||||||
|
// podman network create {network} -> track and return `network` for ${{job.container.network}}
|
||||||
|
await exec.exec('podman', ['network', 'create', networkName])
|
||||||
|
|
||||||
|
const containerImage = `docker.io/library/${jobContainer.containerImage}`
|
||||||
|
// podman pull docker.io/library/{image}
|
||||||
|
await exec.exec('podman', ['pull', containerImage])
|
||||||
|
|
||||||
|
// podman create --name e088c842be1f46b394212618408aaba0_node1016jessie_6196c9
|
||||||
|
// --label fa4e14
|
||||||
|
// --workdir /__w/canary/canary
|
||||||
|
// --network github_network_f98a6e1e96e74d919d814c165641cba3
|
||||||
|
// -e "HOME=/github/home" -e GITHUB_ACTIONS=true -e CI=true
|
||||||
|
// -v "/var/run/docker.sock":"/var/run/docker.sock"
|
||||||
|
// -v "/home/runner/work":"/__w"
|
||||||
|
// -v "/home/runner/runners/2.283.2/externals":"/__e":ro
|
||||||
|
// -v "/home/runner/work/_temp":"/__w/_temp"
|
||||||
|
// -v "/home/runner/work/_actions":"/__w/_actions"
|
||||||
|
// -v "/opt/hostedtoolcache":"/__t"
|
||||||
|
// -v "/home/runner/work/_temp/_github_home":"/github/home"
|
||||||
|
// -v "/home/runner/work/_temp/_github_workflow":"/github/workflow"
|
||||||
|
// --entrypoint "tail" node:10.16-jessie "-f" "/dev/null"
|
||||||
|
const creatArgs = ['create']
|
||||||
|
creatArgs.push(`--workdir=${jobContainer.containerWorkDirectory}`)
|
||||||
|
creatArgs.push(`--network=${networkName}`)
|
||||||
|
|
||||||
|
for (const mountVolume of jobContainer.mountVolumes) {
|
||||||
|
creatArgs.push(
|
||||||
|
`-v=${mountVolume.sourceVolumePath}:${mountVolume.targetVolumePath}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
creatArgs.push(`--entrypoint=tail`)
|
||||||
|
creatArgs.push(containerImage)
|
||||||
|
creatArgs.push(`-f`)
|
||||||
|
creatArgs.push(`/dev/null`)
|
||||||
|
|
||||||
|
core.debug(JSON.stringify(creatArgs))
|
||||||
|
|
||||||
|
// const containerId = await exec.getExecOutput('podman', [
|
||||||
|
// 'create',
|
||||||
|
// // `--workdir ${jobContainer.containerWorkDirectory}`,
|
||||||
|
// `--network=${networkName}`,
|
||||||
|
// // `-v=/Users/ting/Desktop/runner/_layout/_work:/__w`,
|
||||||
|
// `--entrypoint=${jobContainer.containerEntryPoint}`,
|
||||||
|
// `${containerImage}`,
|
||||||
|
// `${jobContainer.containerEntryPointArgs}`
|
||||||
|
// ])
|
||||||
|
|
||||||
|
const containerId = await exec.getExecOutput('podman', creatArgs)
|
||||||
|
|
||||||
|
core.debug(JSON.stringify(containerId))
|
||||||
|
|
||||||
|
// podman start {containerId}
|
||||||
|
await exec.exec('podman', ['start', containerId.stdout.trim()])
|
||||||
|
|
||||||
|
// get PATH inside the container
|
||||||
|
|
||||||
|
// output containerId for ${{job.container.id}}
|
||||||
|
|
||||||
|
const creationOutput = {
|
||||||
|
JobContainerId: containerId.stdout.trim(),
|
||||||
|
Network: networkName
|
||||||
|
}
|
||||||
|
|
||||||
|
const output = JSON.stringify({CreationOutput: creationOutput})
|
||||||
|
core.debug(output)
|
||||||
|
|
||||||
|
process.stderr.write(
|
||||||
|
`___CONTAINER_ENGINE_HANDLER_OUTPUT___${output}___CONTAINER_ENGINE_HANDLER_OUTPUT___`
|
||||||
|
)
|
||||||
|
} else if (command === 'Remove') {
|
||||||
|
const removeInput = inputJson.removeInput
|
||||||
|
core.debug(JSON.stringify(removeInput))
|
||||||
|
const jobContainerId = removeInput.jobContainerId
|
||||||
|
const network = removeInput.network
|
||||||
|
|
||||||
|
await exec.exec('podman', ['rm', '-f', jobContainerId])
|
||||||
|
await exec.exec('podman', ['network', 'rm', '-f', network])
|
||||||
|
} else if (command === 'Exec') {
|
||||||
|
const execInput = inputJson.execInput
|
||||||
|
core.debug(JSON.stringify(execInput))
|
||||||
|
|
||||||
|
// podman exec -i --workdir /__w/canary/canary
|
||||||
|
// -e GITHUB_JOB -e GITHUB_REF -e GITHUB_SHA -e GITHUB_REPOSITORY
|
||||||
|
// -e GITHUB_REPOSITORY_OWNER -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER
|
||||||
|
// -e GITHUB_RETENTION_DAYS -e GITHUB_RUN_ATTEMPT -e GITHUB_ACTOR
|
||||||
|
// -e GITHUB_WORKFLOW -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GITHUB_EVENT_NAME
|
||||||
|
// -e GITHUB_SERVER_URL -e GITHUB_API_URL -e GITHUB_GRAPHQL_URL
|
||||||
|
// -e GITHUB_WORKSPACE -e GITHUB_ACTION -e GITHUB_EVENT_PATH -e GITHUB_ACTION_REPOSITORY
|
||||||
|
// -e GITHUB_ACTION_REF -e GITHUB_PATH -e GITHUB_ENV -e RUNNER_DEBUG
|
||||||
|
// -e RUNNER_OS -e RUNNER_NAME -e RUNNER_TOOL_CACHE
|
||||||
|
// -e RUNNER_TEMP -e RUNNER_WORKSPACE
|
||||||
|
// eccdf520697a035599d6e8c8dc801f004fdd3797cdce88f590aba3669a88d9bc sh -e /__w/_temp/d3b30383-719c-4e76-a16f-8f85443352be.sh
|
||||||
|
|
||||||
|
const execArgs = ['exec']
|
||||||
|
execArgs.push('-i')
|
||||||
|
execArgs.push(`--workdir=${execInput.workingDirectory}`)
|
||||||
|
for (const envKey of execInput.environmentKeys) {
|
||||||
|
execArgs.push(`-e=${envKey}`)
|
||||||
|
}
|
||||||
|
execArgs.push(execInput.jobContainer.containerId)
|
||||||
|
execArgs.push(execInput.fileName)
|
||||||
|
|
||||||
|
const args = (<string>execInput.arguments).split(' ')
|
||||||
|
core.debug(JSON.stringify(args))
|
||||||
|
|
||||||
|
execArgs.push(...args)
|
||||||
|
|
||||||
|
core.debug(JSON.stringify(execArgs))
|
||||||
|
|
||||||
|
await exec.exec('podman', execArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
await exec.exec('podman', ['network', 'ls'])
|
||||||
|
await exec.exec('podman', ['ps', '-a'])
|
||||||
|
}
|
||||||
|
|
||||||
|
run()
|
||||||
12
src/Misc/containerEngineHandlers/podmanHandler/tsconfig.json
Normal file
12
src/Misc/containerEngineHandlers/podmanHandler/tsconfig.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
|
||||||
|
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
|
||||||
|
"outDir": "./lib", /* Redirect output structure to the directory. */
|
||||||
|
"rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||||
|
"strict": true, /* Enable all strict type-checking options. */
|
||||||
|
"noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||||
|
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||||
|
},
|
||||||
|
"exclude": ["node_modules", "**/*.test.ts"]
|
||||||
|
}
|
||||||
24
src/Misc/expressionFunc/hashFiles/package-lock.json
generated
24
src/Misc/expressionFunc/hashFiles/package-lock.json
generated
@@ -1291,9 +1291,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"glob-parent": {
|
"glob-parent": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||||
"integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==",
|
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-glob": "^4.0.1"
|
"is-glob": "^4.0.1"
|
||||||
@@ -1374,9 +1374,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"hosted-git-info": {
|
"hosted-git-info": {
|
||||||
"version": "2.8.8",
|
"version": "2.8.9",
|
||||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz",
|
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
|
||||||
"integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==",
|
"integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"iconv-lite": {
|
"iconv-lite": {
|
||||||
@@ -1683,9 +1683,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lodash": {
|
"lodash": {
|
||||||
"version": "4.17.19",
|
"version": "4.17.21",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==",
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"lodash.unescape": {
|
"lodash.unescape": {
|
||||||
@@ -1947,9 +1947,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"path-parse": {
|
"path-parse": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||||
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
|
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"path-type": {
|
"path-type": {
|
||||||
|
|||||||
@@ -82,7 +82,8 @@ var gracefulShutdown = function (code) {
|
|||||||
console.log('Sending SIGINT to runner listener to stop');
|
console.log('Sending SIGINT to runner listener to stop');
|
||||||
listener.kill('SIGINT');
|
listener.kill('SIGINT');
|
||||||
|
|
||||||
// TODO wait for 30 seconds and send a SIGKILL
|
console.log('Sending SIGKILL to runner listener');
|
||||||
|
setTimeout(() => listener.kill('SIGKILL'), 30000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,5 +25,7 @@
|
|||||||
</dict>
|
</dict>
|
||||||
<key>ProcessType</key>
|
<key>ProcessType</key>
|
||||||
<string>Interactive</string>
|
<string>Interactive</string>
|
||||||
|
<key>SessionCreate</key>
|
||||||
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -94,7 +94,6 @@ then
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# libssl version prefer: libssl1.1 -> libssl1.0.2 -> libssl1.0.0
|
|
||||||
apt_get_with_fallbacks libssl1.1$ libssl1.0.2$ libssl1.0.0$
|
apt_get_with_fallbacks libssl1.1$ libssl1.0.2$ libssl1.0.0$
|
||||||
if [ $? -ne 0 ]
|
if [ $? -ne 0 ]
|
||||||
then
|
then
|
||||||
@@ -103,8 +102,7 @@ then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# libicu version prefer: libicu66 -> libicu63 -> libicu60 -> libicu57 -> libicu55 -> libicu52
|
apt_get_with_fallbacks libicu72 libicu71 libicu70 libicu69 libicu68 libicu67 libicu66 libicu65 libicu63 libicu60 libicu57 libicu55 libicu52
|
||||||
apt_get_with_fallbacks libicu66 libicu63 libicu60 libicu57 libicu55 libicu52
|
|
||||||
if [ $? -ne 0 ]
|
if [ $? -ne 0 ]
|
||||||
then
|
then
|
||||||
echo "'$apt_get' failed with exit code '$?'"
|
echo "'$apt_get' failed with exit code '$?'"
|
||||||
|
|||||||
3031
src/Misc/layoutbin/kubeInnerHandler/index.js
Normal file
3031
src/Misc/layoutbin/kubeInnerHandler/index.js
Normal file
File diff suppressed because it is too large
Load Diff
3119
src/Misc/layoutbin/kubectlHandler/index.js
Normal file
3119
src/Misc/layoutbin/kubectlHandler/index.js
Normal file
File diff suppressed because it is too large
Load Diff
49
src/Misc/layoutbin/podman-handler.js
Normal file
49
src/Misc/layoutbin/podman-handler.js
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
// Job container creation
|
||||||
|
|
||||||
|
// podman network create {network} -> track and return `network` for ${{job.container.network}}
|
||||||
|
|
||||||
|
// podman pull docker.io/library/{image}
|
||||||
|
|
||||||
|
// podman create --name e088c842be1f46b394212618408aaba0_node1016jessie_6196c9
|
||||||
|
// --label fa4e14
|
||||||
|
// --workdir /__w/canary/canary
|
||||||
|
// --network github_network_f98a6e1e96e74d919d814c165641cba3
|
||||||
|
// -e "HOME=/github/home" -e GITHUB_ACTIONS=true -e CI=true
|
||||||
|
// -v "/var/run/docker.sock":"/var/run/docker.sock"
|
||||||
|
// -v "/home/runner/work":"/__w"
|
||||||
|
// -v "/home/runner/runners/2.283.2/externals":"/__e":ro
|
||||||
|
// -v "/home/runner/work/_temp":"/__w/_temp"
|
||||||
|
// -v "/home/runner/work/_actions":"/__w/_actions"
|
||||||
|
// -v "/opt/hostedtoolcache":"/__t"
|
||||||
|
// -v "/home/runner/work/_temp/_github_home":"/github/home"
|
||||||
|
// -v "/home/runner/work/_temp/_github_workflow":"/github/workflow"
|
||||||
|
// --entrypoint "tail" node:10.16-jessie "-f" "/dev/null"
|
||||||
|
|
||||||
|
// podman start {containerId}
|
||||||
|
|
||||||
|
// get PATH inside the container
|
||||||
|
|
||||||
|
// output containerId for ${{job.container.id}}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Job container stop
|
||||||
|
|
||||||
|
// podman rm --force {containerId}
|
||||||
|
|
||||||
|
// podman network rm {network}
|
||||||
|
|
||||||
|
|
||||||
|
// Run step
|
||||||
|
|
||||||
|
// podman exec -i --workdir /__w/canary/canary
|
||||||
|
// -e GITHUB_JOB -e GITHUB_REF -e GITHUB_SHA -e GITHUB_REPOSITORY
|
||||||
|
// -e GITHUB_REPOSITORY_OWNER -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER
|
||||||
|
// -e GITHUB_RETENTION_DAYS -e GITHUB_RUN_ATTEMPT -e GITHUB_ACTOR
|
||||||
|
// -e GITHUB_WORKFLOW -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GITHUB_EVENT_NAME
|
||||||
|
// -e GITHUB_SERVER_URL -e GITHUB_API_URL -e GITHUB_GRAPHQL_URL
|
||||||
|
// -e GITHUB_WORKSPACE -e GITHUB_ACTION -e GITHUB_EVENT_PATH -e GITHUB_ACTION_REPOSITORY
|
||||||
|
// -e GITHUB_ACTION_REF -e GITHUB_PATH -e GITHUB_ENV -e RUNNER_DEBUG
|
||||||
|
// -e RUNNER_OS -e RUNNER_NAME -e RUNNER_TOOL_CACHE
|
||||||
|
// -e RUNNER_TEMP -e RUNNER_WORKSPACE
|
||||||
|
// eccdf520697a035599d6e8c8dc801f004fdd3797cdce88f590aba3669a88d9bc sh -e /__w/_temp/d3b30383-719c-4e76-a16f-8f85443352be.sh
|
||||||
3110
src/Misc/layoutbin/podmanHandler/index.js
Normal file
3110
src/Misc/layoutbin/podmanHandler/index.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -106,18 +106,30 @@ function stop()
|
|||||||
|
|
||||||
function uninstall()
|
function uninstall()
|
||||||
{
|
{
|
||||||
stop
|
if service_exists; then
|
||||||
systemctl disable ${SVC_NAME} || failed "failed to disable ${SVC_NAME}"
|
stop
|
||||||
rm "${UNIT_PATH}" || failed "failed to delete ${UNIT_PATH}"
|
systemctl disable ${SVC_NAME} || failed "failed to disable ${SVC_NAME}"
|
||||||
|
rm "${UNIT_PATH}" || failed "failed to delete ${UNIT_PATH}"
|
||||||
|
else
|
||||||
|
echo "Service ${SVC_NAME} is not installed"
|
||||||
|
fi
|
||||||
if [ -f "${CONFIG_PATH}" ]; then
|
if [ -f "${CONFIG_PATH}" ]; then
|
||||||
rm "${CONFIG_PATH}" || failed "failed to delete ${CONFIG_PATH}"
|
rm "${CONFIG_PATH}" || failed "failed to delete ${CONFIG_PATH}"
|
||||||
fi
|
fi
|
||||||
systemctl daemon-reload || failed "failed to reload daemons"
|
systemctl daemon-reload || failed "failed to reload daemons"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function service_exists() {
|
||||||
|
if [ -f "${UNIT_PATH}" ]; then
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
function status()
|
function status()
|
||||||
{
|
{
|
||||||
if [ -f "${UNIT_PATH}" ]; then
|
if service_exists; then
|
||||||
echo
|
echo
|
||||||
echo "${UNIT_PATH}"
|
echo "${UNIT_PATH}"
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ downloadrunnerversion=_DOWNLOAD_RUNNER_VERSION_
|
|||||||
logfile="_UPDATE_LOG_"
|
logfile="_UPDATE_LOG_"
|
||||||
restartinteractiverunner=_RESTART_INTERACTIVE_RUNNER_
|
restartinteractiverunner=_RESTART_INTERACTIVE_RUNNER_
|
||||||
|
|
||||||
|
telemetryfile="$rootfolder/_diag/.telemetry"
|
||||||
|
|
||||||
# log user who run the script
|
# log user who run the script
|
||||||
date "+[%F %T-%4N] --------whoami--------" >> "$logfile" 2>&1
|
date "+[%F %T-%4N] --------whoami--------" >> "$logfile" 2>&1
|
||||||
whoami >> "$logfile" 2>&1
|
whoami >> "$logfile" 2>&1
|
||||||
@@ -118,6 +120,104 @@ then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# fix upgrade issue with macOS when running as a service
|
||||||
|
attemptedtargetedfix=0
|
||||||
|
currentplatform=$(uname | awk '{print tolower($0)}')
|
||||||
|
if [[ "$currentplatform" == 'darwin' && restartinteractiverunner -eq 0 ]]; then
|
||||||
|
# We needed a fix for https://github.com/actions/runner/issues/743
|
||||||
|
# We will recreate the ./externals/node12/bin/node of the past runner version that launched the runnerlistener service
|
||||||
|
# Otherwise mac gatekeeper kills the processes we spawn on creation as we are running a process with no backing file
|
||||||
|
|
||||||
|
# We need the pid for the nodejs loop, get that here, its the parent of the runner C# pid
|
||||||
|
# assumption here is only one process is invoking rootfolder/runsvc.sh
|
||||||
|
procgroup=$(ps x -o pgid,command | grep "$rootfolder/runsvc.sh" | grep -v grep | awk '{print $1}')
|
||||||
|
if [[ $? -eq 0 && -n "$procgroup" ]]
|
||||||
|
then
|
||||||
|
# inspect the open file handles to find the node process
|
||||||
|
# we can't actually inspect the process using ps because it uses relative paths and doesn't follow symlinks
|
||||||
|
path=$(lsof -a -g "$procgroup" -F n | grep node12/bin/node | grep externals | tail -1 | cut -c2-)
|
||||||
|
if [[ $? -eq 0 && -n "$path" ]]
|
||||||
|
then
|
||||||
|
# trim the last 5 characters of the path '/node'
|
||||||
|
trimmedpath=$(dirname "$path")
|
||||||
|
if [[ $? -eq 0 && -n "$trimmedpath" ]]
|
||||||
|
then
|
||||||
|
attemptedtargetedfix=1
|
||||||
|
# Create the path if it does not exist
|
||||||
|
if [[ ! -e "$path" ]]
|
||||||
|
then
|
||||||
|
date "+[%F %T-%4N] Creating fallback node at path $path" >> "$logfile" 2>&1
|
||||||
|
mkdir -p "$trimmedpath"
|
||||||
|
cp "$rootfolder/externals/node12/bin/node" "$path"
|
||||||
|
else
|
||||||
|
date "+[%F %T-%4N] Path for fallback node exists, skipping creating $path" >> "$logfile" 2>&1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
date "+[%F %T-%4N] DarwinRunnerUpgrade: Failed to trim runner path. TrimmedPath: $trimmedpath, path: $path, pgid: $procgroup, root: $rootfolder" >> "$logfile" 2>&1
|
||||||
|
date "+[%F %T-%4N] DarwinRunnerUpgrade: Failed to trim runner path. TrimmedPath: $trimmedpath, path: $path, pgid: $procgroup, root: $rootfolder" >> "$telemetryfile" 2>&1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
date "+[%F %T-%4N] DarwinRunnerUpgrade: Failed to find runner path. Path: $path, pgid: $procgroup, root: $rootfolder" >> "$logfile" 2>&1
|
||||||
|
date "+[%F %T-%4N] DarwinRunnerUpgrade: Failed to find runner path. Path: $path, pgid: $procgroup, root: $rootfolder" >> "$telemetryfile" 2>&1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
date "+[%F %T-%4N] DarwinRunnerUpgrade: Failed to find runner pgid. pgid: $procgroup, root: $rootfolder" >> "$logfile" 2>&1
|
||||||
|
date "+[%F %T-%4N] DarwinRunnerUpgrade: Failed to find runner pgid. pgid: $procgroup, root: $rootfolder" >> "$telemetryfile" 2>&1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $attemptedtargetedfix -eq 0 ]
|
||||||
|
then
|
||||||
|
|
||||||
|
date "+[%F %T-%4N] DarwinRunnerUpgrade: Defaulting to old macOS service fix" >> "$logfile" 2>&1
|
||||||
|
date "+[%F %T-%4N] DarwinRunnerUpgrade: Defaulting to old macOS service fix" >> "$telemetryfile" 2>&1
|
||||||
|
if [[ ! -e "$rootfolder/externals.2.280.3/node12/bin/node" ]]
|
||||||
|
then
|
||||||
|
mkdir -p "$rootfolder/externals.2.280.3/node12/bin"
|
||||||
|
cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.280.3/node12/bin/node"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -e "$rootfolder/externals.2.280.2/node12/bin/node" ]]
|
||||||
|
then
|
||||||
|
mkdir -p "$rootfolder/externals.2.280.2/node12/bin"
|
||||||
|
cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.280.2/node12/bin/node"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -e "$rootfolder/externals.2.280.1/node12/bin/node" ]]
|
||||||
|
then
|
||||||
|
mkdir -p "$rootfolder/externals.2.280.1/node12/bin"
|
||||||
|
cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.280.1/node12/bin/node"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# GHES 3.2
|
||||||
|
if [[ ! -e "$rootfolder/externals.2.279.0/node12/bin/node" ]]
|
||||||
|
then
|
||||||
|
mkdir -p "$rootfolder/externals.2.279.0/node12/bin"
|
||||||
|
cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.279.0/node12/bin/node"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# GHES 3.1.2 or later
|
||||||
|
if [[ ! -e "$rootfolder/externals.2.278.0/node12/bin/node" ]]
|
||||||
|
then
|
||||||
|
mkdir -p "$rootfolder/externals.2.278.0/node12/bin"
|
||||||
|
cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.278.0/node12/bin/node"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# GHES 3.1.0
|
||||||
|
if [[ ! -e "$rootfolder/externals.2.276.1/node12/bin/node" ]]
|
||||||
|
then
|
||||||
|
mkdir -p "$rootfolder/externals.2.276.1/node12/bin"
|
||||||
|
cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.276.1/node12/bin/node"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# GHES 3.0
|
||||||
|
if [[ ! -e "$rootfolder/externals.2.273.5/node12/bin/node" ]]
|
||||||
|
then
|
||||||
|
mkdir -p "$rootfolder/externals.2.273.5/node12/bin"
|
||||||
|
cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.273.5/node12/bin/node"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
date "+[%F %T-%4N] Update succeed" >> "$logfile"
|
date "+[%F %T-%4N] Update succeed" >> "$logfile"
|
||||||
|
|
||||||
# rename the update log file with %logfile%.succeed/.failed/succeedneedrestart
|
# rename the update log file with %logfile%.succeed/.failed/succeedneedrestart
|
||||||
|
|||||||
68
src/Misc/layoutroot/entrypoint.sh
Executable file
68
src/Misc/layoutroot/entrypoint.sh
Executable file
@@ -0,0 +1,68 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
function fatal() {
|
||||||
|
echo "error: $1" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
[ -n "${GITHUB_PAT:-""}" ] || fatal "GITHUB_PAT variable must be set"
|
||||||
|
[ -n "${RUNNER_CONFIG_URL:-""}" ] || fatal "RUNNER_CONFIG_URL variable must be set"
|
||||||
|
# [ -n "${RUNNER_NAME:-""}" ] || fatal "RUNNER_NAME variable must be set"
|
||||||
|
|
||||||
|
# if [ -n "${RUNNER_NAME}" ]; then
|
||||||
|
# # Use container id to gen unique runner name if name not provide
|
||||||
|
# CONTAINER_ID=$(cat /proc/self/cgroup | head -n 1 | tr '/' '\n' | tail -1 | cut -c1-12)
|
||||||
|
# RUNNER_NAME="actions-runner-${CONTAINER_ID}"
|
||||||
|
# fi
|
||||||
|
|
||||||
|
# if the scope has a slash, it's a repo runner
|
||||||
|
# orgs_or_repos="orgs"
|
||||||
|
# if [[ "$GITHUB_RUNNER_SCOPE" == *\/* ]]; then
|
||||||
|
# orgs_or_repos="repos"
|
||||||
|
# fi
|
||||||
|
|
||||||
|
# RUNNER_REG_URL="${GITHUB_SERVER_URL:=https://github.com}/${GITHUB_RUNNER_SCOPE}"
|
||||||
|
|
||||||
|
# echo "Runner Name : ${RUNNER_NAME}"
|
||||||
|
echo "Registration URL : ${RUNNER_CONFIG_URL}"
|
||||||
|
# echo "GitHub API URL : ${GITHUB_API_URL:=https://api.github.com}"
|
||||||
|
# echo "Runner Labels : ${RUNNER_LABELS:=""}"
|
||||||
|
|
||||||
|
# TODO: if api url is not default, validate it ends in /api/v3
|
||||||
|
|
||||||
|
# RUNNER_LABELS_ARG=""
|
||||||
|
# if [ -n "${RUNNER_LABELS}" ]; then
|
||||||
|
# RUNNER_LABELS_ARG="--labels ${RUNNER_LABELS}"
|
||||||
|
# fi
|
||||||
|
|
||||||
|
# RUNNER_GROUP_ARG=""
|
||||||
|
# if [ -n "${RUNNER_GROUP}" ]; then
|
||||||
|
# RUNNER_GROUP_ARG="--runnergroup ${RUNNER_GROUP}"
|
||||||
|
# fi
|
||||||
|
|
||||||
|
# if [ -n "${K8S_HOST_IP}" ]; then
|
||||||
|
# export http_proxy=http://$K8S_HOST_IP:9090
|
||||||
|
# fi
|
||||||
|
|
||||||
|
# curl -v -s -X POST ${GITHUB_API_URL}/${orgs_or_repos}/${GITHUB_RUNNER_SCOPE}/actions/runners/registration-token -H "authorization: token $GITHUB_PAT" -H "accept: application/vnd.github.everest-preview+json"
|
||||||
|
|
||||||
|
# Generate registration token
|
||||||
|
# RUNNER_REG_TOKEN=$(curl -s -X POST ${GITHUB_API_URL}/${orgs_or_repos}/${GITHUB_RUNNER_SCOPE}/actions/runners/registration-token -H "authorization: token $GITHUB_PAT" -H "accept: application/vnd.github.everest-preview+json" | jq -r '.token')
|
||||||
|
|
||||||
|
# Create the runner and configure it
|
||||||
|
./config.sh --unattended --url $RUNNER_CONFIG_URL --pat $GITHUB_PAT --replace --ephemeral
|
||||||
|
|
||||||
|
# while (! docker version ); do
|
||||||
|
# # Docker takes a few seconds to initialize
|
||||||
|
# echo "Waiting for Docker to launch..."
|
||||||
|
# sleep 1
|
||||||
|
# done
|
||||||
|
|
||||||
|
# unset env
|
||||||
|
unset RUNNER_CONFIG_URL
|
||||||
|
unset GITHUB_PAT
|
||||||
|
|
||||||
|
# Run it
|
||||||
|
./run.sh
|
||||||
@@ -43,6 +43,21 @@ else
|
|||||||
else
|
else
|
||||||
sleep 5
|
sleep 5
|
||||||
fi
|
fi
|
||||||
|
elif [[ $returnCode == 4 ]]; then
|
||||||
|
if [ ! -x "$(command -v sleep)" ]; then
|
||||||
|
if [ ! -x "$(command -v ping)" ]; then
|
||||||
|
COUNT="0"
|
||||||
|
while [[ $COUNT != 5000 ]]; do
|
||||||
|
echo "SLEEP" > /dev/null
|
||||||
|
COUNT=$[$COUNT+1]
|
||||||
|
done
|
||||||
|
else
|
||||||
|
ping -c 5 127.0.0.1 > /dev/null
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
sleep 5
|
||||||
|
fi
|
||||||
|
"$DIR"/bin/Runner.Listener run $*
|
||||||
else
|
else
|
||||||
exit $returnCode
|
exit $returnCode
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ namespace GitHub.Runner.Common
|
|||||||
[DataMember(EmitDefaultValue = false)]
|
[DataMember(EmitDefaultValue = false)]
|
||||||
public string PoolName { get; set; }
|
public string PoolName { get; set; }
|
||||||
|
|
||||||
|
[DataMember(EmitDefaultValue = false)]
|
||||||
|
public bool Ephemeral { get; set; }
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
[DataMember(EmitDefaultValue = false)]
|
||||||
public string ServerUrl { get; set; }
|
public string ServerUrl { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ namespace GitHub.Runner.Common
|
|||||||
Certificates,
|
Certificates,
|
||||||
Options,
|
Options,
|
||||||
SetupInfo,
|
SetupInfo,
|
||||||
|
Telemetry
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Constants
|
public static class Constants
|
||||||
@@ -41,6 +42,8 @@ namespace GitHub.Runner.Common
|
|||||||
public static string PluginTracePrefix = "##[plugin.trace]";
|
public static string PluginTracePrefix = "##[plugin.trace]";
|
||||||
public static readonly int RunnerDownloadRetryMaxAttempts = 3;
|
public static readonly int RunnerDownloadRetryMaxAttempts = 3;
|
||||||
|
|
||||||
|
public static readonly int CompositeActionsMaxDepth = 9;
|
||||||
|
|
||||||
// This enum is embedded within the Constants class to make it easier to reference and avoid
|
// This enum is embedded within the Constants class to make it easier to reference and avoid
|
||||||
// ambiguous type reference with System.Runtime.InteropServices.OSPlatform and System.Runtime.InteropServices.Architecture
|
// ambiguous type reference with System.Runtime.InteropServices.OSPlatform and System.Runtime.InteropServices.Architecture
|
||||||
public enum OSPlatform
|
public enum OSPlatform
|
||||||
@@ -123,9 +126,10 @@ namespace GitHub.Runner.Common
|
|||||||
{
|
{
|
||||||
public static readonly string Check = "check";
|
public static readonly string Check = "check";
|
||||||
public static readonly string Commit = "commit";
|
public static readonly string Commit = "commit";
|
||||||
|
public static readonly string Ephemeral = "ephemeral";
|
||||||
public static readonly string Help = "help";
|
public static readonly string Help = "help";
|
||||||
public static readonly string Replace = "replace";
|
public static readonly string Replace = "replace";
|
||||||
public static readonly string Once = "once";
|
public static readonly string Once = "once"; // Keep this around since customers still relies on it
|
||||||
public static readonly string RunAsService = "runasservice";
|
public static readonly string RunAsService = "runasservice";
|
||||||
public static readonly string Unattended = "unattended";
|
public static readonly string Unattended = "unattended";
|
||||||
public static readonly string Version = "version";
|
public static readonly string Version = "version";
|
||||||
@@ -151,6 +155,7 @@ namespace GitHub.Runner.Common
|
|||||||
public static readonly string LowDiskSpace = "LOW_DISK_SPACE";
|
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 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/";
|
||||||
|
public static readonly string UnsupportedStopCommandTokenDisabled = "You cannot use a endToken that is an empty string, the string 'pause-logging', or another workflow command. For more information see: https://docs.github.com/en/actions/learn-github-actions/workflow-commands-for-github-actions#example-stopping-and-starting-workflow-commands or opt into insecure command execution by setting the `ACTIONS_ALLOW_UNSECURE_STOPCOMMAND_TOKENS` environment variable to `true`.";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class RunnerEvent
|
public static class RunnerEvent
|
||||||
@@ -210,6 +215,7 @@ namespace GitHub.Runner.Common
|
|||||||
// Keep alphabetical
|
// Keep alphabetical
|
||||||
//
|
//
|
||||||
public static readonly string AllowUnsupportedCommands = "ACTIONS_ALLOW_UNSECURE_COMMANDS";
|
public static readonly string AllowUnsupportedCommands = "ACTIONS_ALLOW_UNSECURE_COMMANDS";
|
||||||
|
public static readonly string AllowUnsupportedStopCommandTokens = "ACTIONS_ALLOW_UNSECURE_STOPCOMMAND_TOKENS";
|
||||||
public static readonly string RunnerDebug = "ACTIONS_RUNNER_DEBUG";
|
public static readonly string RunnerDebug = "ACTIONS_RUNNER_DEBUG";
|
||||||
public static readonly string StepDebug = "ACTIONS_STEP_DEBUG";
|
public static readonly string StepDebug = "ACTIONS_STEP_DEBUG";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ namespace GitHub.Runner.Common
|
|||||||
Add<T>(extensions, "GitHub.Runner.Worker.RemoveMatcherCommandExtension, Runner.Worker");
|
Add<T>(extensions, "GitHub.Runner.Worker.RemoveMatcherCommandExtension, Runner.Worker");
|
||||||
Add<T>(extensions, "GitHub.Runner.Worker.WarningCommandExtension, Runner.Worker");
|
Add<T>(extensions, "GitHub.Runner.Worker.WarningCommandExtension, Runner.Worker");
|
||||||
Add<T>(extensions, "GitHub.Runner.Worker.ErrorCommandExtension, Runner.Worker");
|
Add<T>(extensions, "GitHub.Runner.Worker.ErrorCommandExtension, Runner.Worker");
|
||||||
|
Add<T>(extensions, "GitHub.Runner.Worker.NoticeCommandExtension, Runner.Worker");
|
||||||
Add<T>(extensions, "GitHub.Runner.Worker.DebugCommandExtension, Runner.Worker");
|
Add<T>(extensions, "GitHub.Runner.Worker.DebugCommandExtension, Runner.Worker");
|
||||||
Add<T>(extensions, "GitHub.Runner.Worker.GroupCommandExtension, Runner.Worker");
|
Add<T>(extensions, "GitHub.Runner.Worker.GroupCommandExtension, Runner.Worker");
|
||||||
Add<T>(extensions, "GitHub.Runner.Worker.EndGroupCommandExtension, Runner.Worker");
|
Add<T>(extensions, "GitHub.Runner.Worker.EndGroupCommandExtension, Runner.Worker");
|
||||||
|
|||||||
@@ -90,6 +90,8 @@ namespace GitHub.Runner.Common
|
|||||||
this.SecretMasker.AddValueEncoder(ValueEncoders.UriDataEscape);
|
this.SecretMasker.AddValueEncoder(ValueEncoders.UriDataEscape);
|
||||||
this.SecretMasker.AddValueEncoder(ValueEncoders.XmlDataEscape);
|
this.SecretMasker.AddValueEncoder(ValueEncoders.XmlDataEscape);
|
||||||
this.SecretMasker.AddValueEncoder(ValueEncoders.TrimDoubleQuotes);
|
this.SecretMasker.AddValueEncoder(ValueEncoders.TrimDoubleQuotes);
|
||||||
|
this.SecretMasker.AddValueEncoder(ValueEncoders.PowerShellPreAmpersandEscape);
|
||||||
|
this.SecretMasker.AddValueEncoder(ValueEncoders.PowerShellPostAmpersandEscape);
|
||||||
|
|
||||||
// Create the trace manager.
|
// Create the trace manager.
|
||||||
if (string.IsNullOrEmpty(logFile))
|
if (string.IsNullOrEmpty(logFile))
|
||||||
@@ -341,6 +343,12 @@ namespace GitHub.Runner.Common
|
|||||||
".setup_info");
|
".setup_info");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case WellKnownConfigFile.Telemetry:
|
||||||
|
path = Path.Combine(
|
||||||
|
GetDirectory(WellKnownDirectory.Diag),
|
||||||
|
".telemetry");
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new NotSupportedException($"Unexpected well known config file: '{configFile}'");
|
throw new NotSupportedException($"Unexpected well known config file: '{configFile}'");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,11 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Net.Http;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using GitHub.Runner.Sdk;
|
||||||
|
using GitHub.Services.Common;
|
||||||
using GitHub.Services.WebApi;
|
using GitHub.Services.WebApi;
|
||||||
|
|
||||||
namespace GitHub.Runner.Common
|
namespace GitHub.Runner.Common
|
||||||
@@ -36,6 +39,9 @@ namespace GitHub.Runner.Common
|
|||||||
{
|
{
|
||||||
_connection = jobConnection;
|
_connection = jobConnection;
|
||||||
int attemptCount = 5;
|
int attemptCount = 5;
|
||||||
|
var configurationStore = HostContext.GetService<IConfigurationStore>();
|
||||||
|
var runnerSettings = configurationStore.GetSettings();
|
||||||
|
|
||||||
while (!_connection.HasAuthenticated && attemptCount-- > 0)
|
while (!_connection.HasAuthenticated && attemptCount-- > 0)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -45,8 +51,13 @@ namespace GitHub.Runner.Common
|
|||||||
}
|
}
|
||||||
catch (Exception ex) when (attemptCount > 0)
|
catch (Exception ex) when (attemptCount > 0)
|
||||||
{
|
{
|
||||||
Trace.Info($"Catch exception during connect. {attemptCount} attemp left.");
|
Trace.Info($"Catch exception during connect. {attemptCount} attempts left.");
|
||||||
Trace.Error(ex);
|
Trace.Error(ex);
|
||||||
|
|
||||||
|
if (runnerSettings.IsHostedServer)
|
||||||
|
{
|
||||||
|
await CheckNetworkEndpointsAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await Task.Delay(100);
|
await Task.Delay(100);
|
||||||
@@ -56,6 +67,52 @@ namespace GitHub.Runner.Common
|
|||||||
_hasConnection = true;
|
_hasConnection = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task CheckNetworkEndpointsAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Trace.Info("Requesting Actions Service health endpoint status");
|
||||||
|
using (var httpClientHandler = HostContext.CreateHttpClientHandler())
|
||||||
|
using (var actionsClient = new HttpClient(httpClientHandler))
|
||||||
|
{
|
||||||
|
var baseUri = new Uri(_connection.Uri.GetLeftPart(UriPartial.Authority));
|
||||||
|
|
||||||
|
actionsClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents);
|
||||||
|
|
||||||
|
// Call the _apis/health endpoint
|
||||||
|
var response = await actionsClient.GetAsync(new Uri(baseUri, "_apis/health"));
|
||||||
|
Trace.Info($"Actions health status code: {response.StatusCode}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Log error, but continue as this call is best-effort
|
||||||
|
Trace.Info($"Actions Service health endpoint failed due to {ex.GetType().Name}");
|
||||||
|
Trace.Error(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Trace.Info("Requesting Github API endpoint status");
|
||||||
|
// This is a dotcom public API... just call it directly
|
||||||
|
using (var httpClientHandler = HostContext.CreateHttpClientHandler())
|
||||||
|
using (var gitHubClient = new HttpClient(httpClientHandler))
|
||||||
|
{
|
||||||
|
gitHubClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents);
|
||||||
|
|
||||||
|
// Call the api.github.com endpoint
|
||||||
|
var response = await gitHubClient.GetAsync("https://api.github.com");
|
||||||
|
Trace.Info($"api.github.com status code: {response.StatusCode}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Log error, but continue as this call is best-effort
|
||||||
|
Trace.Info($"Github API endpoint failed due to {ex.GetType().Name}");
|
||||||
|
Trace.Error(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void CheckConnection()
|
private void CheckConnection()
|
||||||
{
|
{
|
||||||
if (!_hasConnection)
|
if (!_hasConnection)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ namespace GitHub.Runner.Common
|
|||||||
[ServiceLocator(Default = typeof(JobServerQueue))]
|
[ServiceLocator(Default = typeof(JobServerQueue))]
|
||||||
public interface IJobServerQueue : IRunnerService, IThrottlingReporter
|
public interface IJobServerQueue : IRunnerService, IThrottlingReporter
|
||||||
{
|
{
|
||||||
|
TaskCompletionSource<int> JobRecordUpdated { get; }
|
||||||
event EventHandler<ThrottlingEventArgs> JobServerQueueThrottling;
|
event EventHandler<ThrottlingEventArgs> JobServerQueueThrottling;
|
||||||
Task ShutdownAsync();
|
Task ShutdownAsync();
|
||||||
void Start(Pipelines.AgentJobRequestMessage jobRequest);
|
void Start(Pipelines.AgentJobRequestMessage jobRequest);
|
||||||
@@ -62,8 +63,11 @@ namespace GitHub.Runner.Common
|
|||||||
private IJobServer _jobServer;
|
private IJobServer _jobServer;
|
||||||
private Task[] _allDequeueTasks;
|
private Task[] _allDequeueTasks;
|
||||||
private readonly TaskCompletionSource<int> _jobCompletionSource = new TaskCompletionSource<int>();
|
private readonly TaskCompletionSource<int> _jobCompletionSource = new TaskCompletionSource<int>();
|
||||||
|
private readonly TaskCompletionSource<int> _jobRecordUpdated = new TaskCompletionSource<int>();
|
||||||
private bool _queueInProcess = false;
|
private bool _queueInProcess = false;
|
||||||
|
|
||||||
|
public TaskCompletionSource<int> JobRecordUpdated => _jobRecordUpdated;
|
||||||
|
|
||||||
public event EventHandler<ThrottlingEventArgs> JobServerQueueThrottling;
|
public event EventHandler<ThrottlingEventArgs> JobServerQueueThrottling;
|
||||||
|
|
||||||
// Web console dequeue will start with process queue every 250ms for the first 60*4 times (~60 seconds).
|
// Web console dequeue will start with process queue every 250ms for the first 60*4 times (~60 seconds).
|
||||||
@@ -455,6 +459,14 @@ namespace GitHub.Runner.Common
|
|||||||
{
|
{
|
||||||
Trace.Verbose("Cleanup buffered timeline record for timeline: {0}.", update.TimelineId);
|
Trace.Verbose("Cleanup buffered timeline record for timeline: {0}.", update.TimelineId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!_jobRecordUpdated.Task.IsCompleted &&
|
||||||
|
update.PendingRecords.Any(x => x.Id == _jobTimelineRecordId && x.State != null))
|
||||||
|
{
|
||||||
|
// We have changed the state of the job
|
||||||
|
Trace.Info("Job timeline record has been updated for the first time.");
|
||||||
|
_jobRecordUpdated.TrySetResult(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -544,6 +556,11 @@ namespace GitHub.Runner.Common
|
|||||||
timelineRecord.WarningCount = rec.WarningCount;
|
timelineRecord.WarningCount = rec.WarningCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (rec.NoticeCount != null && rec.NoticeCount > 0)
|
||||||
|
{
|
||||||
|
timelineRecord.NoticeCount = rec.NoticeCount;
|
||||||
|
}
|
||||||
|
|
||||||
if (rec.Issues.Count > 0)
|
if (rec.Issues.Count > 0)
|
||||||
{
|
{
|
||||||
timelineRecord.Issues.Clear();
|
timelineRecord.Issues.Clear();
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using System.IO;
|
|||||||
using System.IO.Pipes;
|
using System.IO.Pipes;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using GitHub.Runner.Sdk;
|
||||||
|
|
||||||
namespace GitHub.Runner.Common
|
namespace GitHub.Runner.Common
|
||||||
{
|
{
|
||||||
@@ -68,6 +69,7 @@ namespace GitHub.Runner.Common
|
|||||||
|
|
||||||
public async Task SendAsync(MessageType messageType, string body, CancellationToken cancellationToken)
|
public async Task SendAsync(MessageType messageType, string body, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
Trace.Info($"Sending message of length {body.Length}, with hash '{IOUtil.GetSha256Hash(body)}'");
|
||||||
await _writeStream.WriteInt32Async((int)messageType, cancellationToken);
|
await _writeStream.WriteInt32Async((int)messageType, cancellationToken);
|
||||||
await _writeStream.WriteStringAsync(body, cancellationToken);
|
await _writeStream.WriteStringAsync(body, cancellationToken);
|
||||||
}
|
}
|
||||||
@@ -77,6 +79,7 @@ namespace GitHub.Runner.Common
|
|||||||
WorkerMessage result = new WorkerMessage(MessageType.NotInitialized, string.Empty);
|
WorkerMessage result = new WorkerMessage(MessageType.NotInitialized, string.Empty);
|
||||||
result.MessageType = (MessageType)await _readStream.ReadInt32Async(cancellationToken);
|
result.MessageType = (MessageType)await _readStream.ReadInt32Async(cancellationToken);
|
||||||
result.Body = await _readStream.ReadStringAsync(cancellationToken);
|
result.Body = await _readStream.ReadStringAsync(cancellationToken);
|
||||||
|
Trace.Info($"Receiving message of length {result.Body.Length}, with hash '{IOUtil.GetSha256Hash(result.Body)}'");
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,8 +29,10 @@ namespace GitHub.Runner.Common
|
|||||||
// Configuration
|
// Configuration
|
||||||
Task<TaskAgent> AddAgentAsync(Int32 agentPoolId, TaskAgent agent);
|
Task<TaskAgent> AddAgentAsync(Int32 agentPoolId, TaskAgent agent);
|
||||||
Task DeleteAgentAsync(int agentPoolId, int agentId);
|
Task DeleteAgentAsync(int agentPoolId, int agentId);
|
||||||
|
Task DeleteAgentAsync(int agentId);
|
||||||
Task<List<TaskAgentPool>> GetAgentPoolsAsync(string agentPoolName = null, TaskAgentPoolType poolType = TaskAgentPoolType.Automation);
|
Task<List<TaskAgentPool>> GetAgentPoolsAsync(string agentPoolName = null, TaskAgentPoolType poolType = TaskAgentPoolType.Automation);
|
||||||
Task<List<TaskAgent>> GetAgentsAsync(int agentPoolId, string agentName = null);
|
Task<List<TaskAgent>> GetAgentsAsync(int agentPoolId, string agentName = null);
|
||||||
|
Task<List<TaskAgent>> GetAgentsAsync(string agentName);
|
||||||
Task<TaskAgent> ReplaceAgentAsync(int agentPoolId, TaskAgent agent);
|
Task<TaskAgent> ReplaceAgentAsync(int agentPoolId, TaskAgent agent);
|
||||||
|
|
||||||
// messagequeue
|
// messagequeue
|
||||||
@@ -252,6 +254,11 @@ namespace GitHub.Runner.Common
|
|||||||
return _genericTaskAgentClient.GetAgentsAsync(agentPoolId, agentName, false);
|
return _genericTaskAgentClient.GetAgentsAsync(agentPoolId, agentName, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task<List<TaskAgent>> GetAgentsAsync(string agentName)
|
||||||
|
{
|
||||||
|
return GetAgentsAsync(0, agentName); // search in all all agentPools
|
||||||
|
}
|
||||||
|
|
||||||
public Task<TaskAgent> ReplaceAgentAsync(int agentPoolId, TaskAgent agent)
|
public Task<TaskAgent> ReplaceAgentAsync(int agentPoolId, TaskAgent agent)
|
||||||
{
|
{
|
||||||
CheckConnection(RunnerConnectionType.Generic);
|
CheckConnection(RunnerConnectionType.Generic);
|
||||||
@@ -264,6 +271,11 @@ namespace GitHub.Runner.Common
|
|||||||
return _genericTaskAgentClient.DeleteAgentAsync(agentPoolId, agentId);
|
return _genericTaskAgentClient.DeleteAgentAsync(agentPoolId, agentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task DeleteAgentAsync(int agentId)
|
||||||
|
{
|
||||||
|
return DeleteAgentAsync(0, agentId); // agentPool is ignored server side
|
||||||
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------
|
//-----------------------------------------------------------------
|
||||||
// MessageQueue
|
// MessageQueue
|
||||||
//-----------------------------------------------------------------
|
//-----------------------------------------------------------------
|
||||||
|
|||||||
@@ -164,9 +164,8 @@ namespace GitHub.Runner.Common
|
|||||||
if (!Silent)
|
if (!Silent)
|
||||||
{
|
{
|
||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
Console.ForegroundColor = ConsoleColor.White;
|
|
||||||
Console.WriteLine($"# {message}");
|
|
||||||
Console.ResetColor();
|
Console.ResetColor();
|
||||||
|
Console.WriteLine($"# {message}");
|
||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -177,9 +176,8 @@ namespace GitHub.Runner.Common
|
|||||||
{
|
{
|
||||||
Console.ForegroundColor = ConsoleColor.Green;
|
Console.ForegroundColor = ConsoleColor.Green;
|
||||||
Console.Write("√ ");
|
Console.Write("√ ");
|
||||||
Console.ForegroundColor = ConsoleColor.White;
|
|
||||||
Console.WriteLine(message);
|
|
||||||
Console.ResetColor();
|
Console.ResetColor();
|
||||||
|
Console.WriteLine(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,10 +29,11 @@ namespace GitHub.Runner.Listener
|
|||||||
{
|
{
|
||||||
Constants.Runner.CommandLine.Flags.Check,
|
Constants.Runner.CommandLine.Flags.Check,
|
||||||
Constants.Runner.CommandLine.Flags.Commit,
|
Constants.Runner.CommandLine.Flags.Commit,
|
||||||
|
Constants.Runner.CommandLine.Flags.Ephemeral,
|
||||||
Constants.Runner.CommandLine.Flags.Help,
|
Constants.Runner.CommandLine.Flags.Help,
|
||||||
|
Constants.Runner.CommandLine.Flags.Once,
|
||||||
Constants.Runner.CommandLine.Flags.Replace,
|
Constants.Runner.CommandLine.Flags.Replace,
|
||||||
Constants.Runner.CommandLine.Flags.RunAsService,
|
Constants.Runner.CommandLine.Flags.RunAsService,
|
||||||
Constants.Runner.CommandLine.Flags.Once,
|
|
||||||
Constants.Runner.CommandLine.Flags.Unattended,
|
Constants.Runner.CommandLine.Flags.Unattended,
|
||||||
Constants.Runner.CommandLine.Flags.Version
|
Constants.Runner.CommandLine.Flags.Version
|
||||||
};
|
};
|
||||||
@@ -66,7 +67,9 @@ namespace GitHub.Runner.Listener
|
|||||||
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);
|
||||||
public bool Version => TestFlag(Constants.Runner.CommandLine.Flags.Version);
|
public bool Version => TestFlag(Constants.Runner.CommandLine.Flags.Version);
|
||||||
|
public bool Ephemeral => TestFlag(Constants.Runner.CommandLine.Flags.Ephemeral);
|
||||||
|
|
||||||
|
// Keep this around since customers still relies on it
|
||||||
public bool RunOnce => TestFlag(Constants.Runner.CommandLine.Flags.Once);
|
public bool RunOnce => TestFlag(Constants.Runner.CommandLine.Flags.Once);
|
||||||
|
|
||||||
// Constructor.
|
// Constructor.
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
bool IsConfigured();
|
bool IsConfigured();
|
||||||
Task ConfigureAsync(CommandSettings command);
|
Task ConfigureAsync(CommandSettings command);
|
||||||
Task UnconfigureAsync(CommandSettings command);
|
Task UnconfigureAsync(CommandSettings command);
|
||||||
|
void DeleteLocalRunnerConfig();
|
||||||
RunnerSettings LoadSettings();
|
RunnerSettings LoadSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,18 +66,18 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
public async Task ConfigureAsync(CommandSettings command)
|
public async Task ConfigureAsync(CommandSettings command)
|
||||||
{
|
{
|
||||||
_term.WriteLine();
|
_term.WriteLine();
|
||||||
_term.WriteLine("--------------------------------------------------------------------------------", ConsoleColor.White);
|
_term.WriteLine("--------------------------------------------------------------------------------");
|
||||||
_term.WriteLine("| ____ _ _ _ _ _ _ _ _ |", ConsoleColor.White);
|
_term.WriteLine("| ____ _ _ _ _ _ _ _ _ |");
|
||||||
_term.WriteLine("| / ___(_) |_| | | |_ _| |__ / \\ ___| |_(_) ___ _ __ ___ |", ConsoleColor.White);
|
_term.WriteLine("| / ___(_) |_| | | |_ _| |__ / \\ ___| |_(_) ___ _ __ ___ |");
|
||||||
_term.WriteLine("| | | _| | __| |_| | | | | '_ \\ / _ \\ / __| __| |/ _ \\| '_ \\/ __| |", ConsoleColor.White);
|
_term.WriteLine("| | | _| | __| |_| | | | | '_ \\ / _ \\ / __| __| |/ _ \\| '_ \\/ __| |");
|
||||||
_term.WriteLine("| | |_| | | |_| _ | |_| | |_) | / ___ \\ (__| |_| | (_) | | | \\__ \\ |", ConsoleColor.White);
|
_term.WriteLine("| | |_| | | |_| _ | |_| | |_) | / ___ \\ (__| |_| | (_) | | | \\__ \\ |");
|
||||||
_term.WriteLine("| \\____|_|\\__|_| |_|\\__,_|_.__/ /_/ \\_\\___|\\__|_|\\___/|_| |_|___/ |", ConsoleColor.White);
|
_term.WriteLine("| \\____|_|\\__|_| |_|\\__,_|_.__/ /_/ \\_\\___|\\__|_|\\___/|_| |_|___/ |");
|
||||||
_term.WriteLine("| |", ConsoleColor.White);
|
_term.WriteLine("| |");
|
||||||
_term.Write("| ", ConsoleColor.White);
|
_term.Write("| ");
|
||||||
_term.Write("Self-hosted runner registration", ConsoleColor.Cyan);
|
_term.Write("Self-hosted runner registration", ConsoleColor.Cyan);
|
||||||
_term.WriteLine(" |", ConsoleColor.White);
|
_term.WriteLine(" |");
|
||||||
_term.WriteLine("| |", ConsoleColor.White);
|
_term.WriteLine("| |");
|
||||||
_term.WriteLine("--------------------------------------------------------------------------------", ConsoleColor.White);
|
_term.WriteLine("--------------------------------------------------------------------------------");
|
||||||
|
|
||||||
Trace.Info(nameof(ConfigureAsync));
|
Trace.Info(nameof(ConfigureAsync));
|
||||||
if (IsConfigured())
|
if (IsConfigured())
|
||||||
@@ -117,6 +118,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)
|
||||||
|
// Hosted usually means github.com or localhost, while OnPremises means GHES or GHAE
|
||||||
runnerSettings.IsHostedServer = runnerSettings.GitHubUrl == null || UrlUtil.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
|
||||||
@@ -165,7 +167,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
List<TaskAgentPool> agentPools = await _runnerServer.GetAgentPoolsAsync();
|
List<TaskAgentPool> agentPools = await _runnerServer.GetAgentPoolsAsync();
|
||||||
TaskAgentPool defaultPool = agentPools?.Where(x => x.IsInternal).FirstOrDefault();
|
TaskAgentPool defaultPool = agentPools?.Where(x => x.IsInternal).FirstOrDefault();
|
||||||
|
|
||||||
if (agentPools?.Where(x => !x.IsHosted).Count() > 1)
|
if (agentPools?.Where(x => !x.IsHosted).Count() > 0)
|
||||||
{
|
{
|
||||||
poolName = command.GetRunnerGroupName(defaultPool?.Name);
|
poolName = command.GetRunnerGroupName(defaultPool?.Name);
|
||||||
_term.WriteLine();
|
_term.WriteLine();
|
||||||
@@ -186,7 +188,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Trace.Info("Found a self-hosted runner group with id {1} and name {2}", agentPool.Id, agentPool.Name);
|
Trace.Info($"Found a self-hosted runner group with id {agentPool.Id} and name {agentPool.Name}");
|
||||||
runnerSettings.PoolId = agentPool.Id;
|
runnerSettings.PoolId = agentPool.Id;
|
||||||
runnerSettings.PoolName = agentPool.Name;
|
runnerSettings.PoolName = agentPool.Name;
|
||||||
}
|
}
|
||||||
@@ -194,6 +196,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
TaskAgent agent;
|
TaskAgent agent;
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
|
runnerSettings.Ephemeral = command.Ephemeral;
|
||||||
runnerSettings.AgentName = command.GetRunnerName();
|
runnerSettings.AgentName = command.GetRunnerName();
|
||||||
|
|
||||||
_term.WriteLine();
|
_term.WriteLine();
|
||||||
@@ -210,7 +213,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
if (command.GetReplace())
|
if (command.GetReplace())
|
||||||
{
|
{
|
||||||
// Update existing agent with new PublicKey, agent version.
|
// Update existing agent with new PublicKey, agent version.
|
||||||
agent = UpdateExistingAgent(agent, publicKey, userLabels);
|
agent = UpdateExistingAgent(agent, publicKey, userLabels, runnerSettings.Ephemeral);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -233,7 +236,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Create a new agent.
|
// Create a new agent.
|
||||||
agent = CreateNewAgent(runnerSettings.AgentName, publicKey, userLabels);
|
agent = CreateNewAgent(runnerSettings.AgentName, publicKey, userLabels, runnerSettings.Ephemeral);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -327,6 +330,38 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delete .runner and .credentials files
|
||||||
|
public void DeleteLocalRunnerConfig()
|
||||||
|
{
|
||||||
|
bool isConfigured = _store.IsConfigured();
|
||||||
|
bool hasCredentials = _store.HasCredentials();
|
||||||
|
//delete credential config files
|
||||||
|
var currentAction = "Removing .credentials";
|
||||||
|
if (hasCredentials)
|
||||||
|
{
|
||||||
|
_store.DeleteCredential();
|
||||||
|
var keyManager = HostContext.GetService<IRSAKeyManager>();
|
||||||
|
keyManager.DeleteKey();
|
||||||
|
_term.WriteSuccessMessage("Removed .credentials");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_term.WriteLine("Does not exist. Skipping " + currentAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
//delete settings config file
|
||||||
|
currentAction = "Removing .runner";
|
||||||
|
if (isConfigured)
|
||||||
|
{
|
||||||
|
_store.DeleteSettings();
|
||||||
|
_term.WriteSuccessMessage("Removed .runner");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_term.WriteLine("Does not exist. Skipping " + currentAction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task UnconfigureAsync(CommandSettings command)
|
public async Task UnconfigureAsync(CommandSettings command)
|
||||||
{
|
{
|
||||||
string currentAction = string.Empty;
|
string currentAction = string.Empty;
|
||||||
@@ -346,12 +381,9 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
|
|
||||||
_term.WriteLine();
|
_term.WriteLine();
|
||||||
_term.WriteSuccessMessage("Runner service removed");
|
_term.WriteSuccessMessage("Runner service removed");
|
||||||
#elif OS_LINUX
|
#else
|
||||||
// unconfig system D service first
|
// unconfig systemd or osx service first
|
||||||
throw new Exception("Unconfigure service first");
|
throw new Exception("Uninstall service first");
|
||||||
#elif OS_OSX
|
|
||||||
// unconfig osx service first
|
|
||||||
throw new Exception("Unconfigure service first");
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,7 +415,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
// Determine the service deployment type based on connection data. (Hosted/OnPremises)
|
// Determine the service deployment type based on connection data. (Hosted/OnPremises)
|
||||||
await _runnerServer.ConnectAsync(new Uri(settings.ServerUrl), creds);
|
await _runnerServer.ConnectAsync(new Uri(settings.ServerUrl), creds);
|
||||||
|
|
||||||
var agents = await _runnerServer.GetAgentsAsync(settings.PoolId, settings.AgentName);
|
var agents = await _runnerServer.GetAgentsAsync(settings.AgentName);
|
||||||
Trace.Verbose("Returns {0} agents", agents.Count);
|
Trace.Verbose("Returns {0} agents", agents.Count);
|
||||||
TaskAgent agent = agents.FirstOrDefault();
|
TaskAgent agent = agents.FirstOrDefault();
|
||||||
if (agent == null)
|
if (agent == null)
|
||||||
@@ -392,7 +424,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await _runnerServer.DeleteAgentAsync(settings.PoolId, settings.AgentId);
|
await _runnerServer.DeleteAgentAsync(settings.AgentId);
|
||||||
|
|
||||||
_term.WriteLine();
|
_term.WriteLine();
|
||||||
_term.WriteSuccessMessage("Runner removed successfully");
|
_term.WriteSuccessMessage("Runner removed successfully");
|
||||||
@@ -403,31 +435,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
_term.WriteLine("Cannot connect to server, because config files are missing. Skipping removing runner from the server.");
|
_term.WriteLine("Cannot connect to server, because config files are missing. Skipping removing runner from the server.");
|
||||||
}
|
}
|
||||||
|
|
||||||
//delete credential config files
|
DeleteLocalRunnerConfig();
|
||||||
currentAction = "Removing .credentials";
|
|
||||||
if (hasCredentials)
|
|
||||||
{
|
|
||||||
_store.DeleteCredential();
|
|
||||||
var keyManager = HostContext.GetService<IRSAKeyManager>();
|
|
||||||
keyManager.DeleteKey();
|
|
||||||
_term.WriteSuccessMessage("Removed .credentials");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_term.WriteLine("Does not exist. Skipping " + currentAction);
|
|
||||||
}
|
|
||||||
|
|
||||||
//delete settings config file
|
|
||||||
currentAction = "Removing .runner";
|
|
||||||
if (isConfigured)
|
|
||||||
{
|
|
||||||
_store.DeleteSettings();
|
|
||||||
_term.WriteSuccessMessage("Removed .runner");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_term.WriteLine("Does not exist. Skipping " + currentAction);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
@@ -458,7 +466,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private TaskAgent UpdateExistingAgent(TaskAgent agent, RSAParameters publicKey, ISet<string> userLabels)
|
private TaskAgent UpdateExistingAgent(TaskAgent agent, RSAParameters publicKey, ISet<string> userLabels, bool ephemeral)
|
||||||
{
|
{
|
||||||
ArgUtil.NotNull(agent, nameof(agent));
|
ArgUtil.NotNull(agent, nameof(agent));
|
||||||
agent.Authorization = new TaskAgentAuthorization
|
agent.Authorization = new TaskAgentAuthorization
|
||||||
@@ -469,6 +477,8 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
// update should replace the existing labels
|
// update should replace the existing labels
|
||||||
agent.Version = BuildConstants.RunnerPackage.Version;
|
agent.Version = BuildConstants.RunnerPackage.Version;
|
||||||
agent.OSDescription = RuntimeInformation.OSDescription;
|
agent.OSDescription = RuntimeInformation.OSDescription;
|
||||||
|
agent.Ephemeral = ephemeral;
|
||||||
|
agent.MaxParallelism = 1;
|
||||||
|
|
||||||
agent.Labels.Clear();
|
agent.Labels.Clear();
|
||||||
|
|
||||||
@@ -484,7 +494,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
return agent;
|
return agent;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TaskAgent CreateNewAgent(string agentName, RSAParameters publicKey, ISet<string> userLabels)
|
private TaskAgent CreateNewAgent(string agentName, RSAParameters publicKey, ISet<string> userLabels, bool ephemeral)
|
||||||
{
|
{
|
||||||
TaskAgent agent = new TaskAgent(agentName)
|
TaskAgent agent = new TaskAgent(agentName)
|
||||||
{
|
{
|
||||||
@@ -495,6 +505,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
MaxParallelism = 1,
|
MaxParallelism = 1,
|
||||||
Version = BuildConstants.RunnerPackage.Version,
|
Version = BuildConstants.RunnerPackage.Version,
|
||||||
OSDescription = RuntimeInformation.OSDescription,
|
OSDescription = RuntimeInformation.OSDescription,
|
||||||
|
Ephemeral = ephemeral,
|
||||||
};
|
};
|
||||||
|
|
||||||
agent.Labels.Add(new AgentLabel("self-hosted", LabelType.System));
|
agent.Labels.Add(new AgentLabel("self-hosted", LabelType.System));
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
|
|
||||||
public string GetUniqueRunnerGroupName()
|
public string GetUniqueRunnerGroupName()
|
||||||
{
|
{
|
||||||
return RunnerServiceLocalGroupPrefix + IOUtil.GetPathHash(HostContext.GetDirectory(WellKnownDirectory.Bin)).Substring(0, 5);
|
return RunnerServiceLocalGroupPrefix + IOUtil.GetSha256Hash(HostContext.GetDirectory(WellKnownDirectory.Bin)).Substring(0, 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool LocalGroupExists(string groupName)
|
public bool LocalGroupExists(string groupName)
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
// Write the message prompt.
|
// Write the message prompt.
|
||||||
_terminal.Write($"{description} ", ConsoleColor.White);
|
_terminal.Write($"{description} ");
|
||||||
|
|
||||||
if(!string.IsNullOrEmpty(defaultValue))
|
if(!string.IsNullOrEmpty(defaultValue))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -27,24 +27,27 @@ namespace GitHub.Runner.Listener
|
|||||||
Task ShutdownAsync();
|
Task ShutdownAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
// This implementation of IDobDispatcher is not thread safe.
|
// This implementation of IJobDispatcher is not thread safe.
|
||||||
// It is base on the fact that the current design of runner is dequeue
|
// It is based on the fact that the current design of the runner is a dequeue
|
||||||
// and process one message from message queue everytime.
|
// and processes one message from the message queue at a time.
|
||||||
// In addition, it only execute one job every time,
|
// In addition, it only executes one job every time,
|
||||||
// and server will not send another job while this one is still running.
|
// and the server will not send another job while this one is still running.
|
||||||
public sealed class JobDispatcher : RunnerService, IJobDispatcher
|
public sealed class JobDispatcher : RunnerService, IJobDispatcher
|
||||||
{
|
{
|
||||||
private readonly Lazy<Dictionary<long, TaskResult>> _localRunJobResult = new Lazy<Dictionary<long, TaskResult>>();
|
private readonly Lazy<Dictionary<long, TaskResult>> _localRunJobResult = new Lazy<Dictionary<long, TaskResult>>();
|
||||||
private int _poolId;
|
private int _poolId;
|
||||||
RunnerSettings _runnerSetting;
|
|
||||||
|
IConfigurationStore _configurationStore;
|
||||||
|
|
||||||
|
RunnerSettings _runnerSettings;
|
||||||
private static readonly string _workerProcessName = $"Runner.Worker{IOUtil.ExeExtension}";
|
private static readonly string _workerProcessName = $"Runner.Worker{IOUtil.ExeExtension}";
|
||||||
|
|
||||||
// this is not thread-safe
|
// this is not thread-safe
|
||||||
private readonly Queue<Guid> _jobDispatchedQueue = new Queue<Guid>();
|
private readonly Queue<Guid> _jobDispatchedQueue = new Queue<Guid>();
|
||||||
private readonly ConcurrentDictionary<Guid, WorkerDispatcher> _jobInfos = new ConcurrentDictionary<Guid, WorkerDispatcher>();
|
private readonly ConcurrentDictionary<Guid, WorkerDispatcher> _jobInfos = new ConcurrentDictionary<Guid, WorkerDispatcher>();
|
||||||
|
|
||||||
//allow up to 30sec for any data to be transmitted over the process channel
|
// allow up to 30sec for any data to be transmitted over the process channel
|
||||||
//timeout limit can be overwrite by environment GITHUB_ACTIONS_RUNNER_CHANNEL_TIMEOUT
|
// timeout limit can be overwritten by environment GITHUB_ACTIONS_RUNNER_CHANNEL_TIMEOUT
|
||||||
private TimeSpan _channelTimeout;
|
private TimeSpan _channelTimeout;
|
||||||
|
|
||||||
private TaskCompletionSource<bool> _runOnceJobCompleted = new TaskCompletionSource<bool>();
|
private TaskCompletionSource<bool> _runOnceJobCompleted = new TaskCompletionSource<bool>();
|
||||||
@@ -54,9 +57,9 @@ namespace GitHub.Runner.Listener
|
|||||||
base.Initialize(hostContext);
|
base.Initialize(hostContext);
|
||||||
|
|
||||||
// get pool id from config
|
// get pool id from config
|
||||||
var configurationStore = hostContext.GetService<IConfigurationStore>();
|
_configurationStore = hostContext.GetService<IConfigurationStore>();
|
||||||
_runnerSetting = configurationStore.GetSettings();
|
_runnerSettings = _configurationStore.GetSettings();
|
||||||
_poolId = _runnerSetting.PoolId;
|
_poolId = _runnerSettings.PoolId;
|
||||||
|
|
||||||
int channelTimeoutSeconds;
|
int channelTimeoutSeconds;
|
||||||
if (!int.TryParse(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_CHANNEL_TIMEOUT") ?? string.Empty, out channelTimeoutSeconds))
|
if (!int.TryParse(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_CHANNEL_TIMEOUT") ?? string.Empty, out channelTimeoutSeconds))
|
||||||
@@ -64,7 +67,7 @@ namespace GitHub.Runner.Listener
|
|||||||
channelTimeoutSeconds = 30;
|
channelTimeoutSeconds = 30;
|
||||||
}
|
}
|
||||||
|
|
||||||
// _channelTimeout should in range [30, 300] seconds
|
// _channelTimeout should be in range [30, 300] seconds
|
||||||
_channelTimeout = TimeSpan.FromSeconds(Math.Min(Math.Max(channelTimeoutSeconds, 30), 300));
|
_channelTimeout = TimeSpan.FromSeconds(Math.Min(Math.Max(channelTimeoutSeconds, 30), 300));
|
||||||
Trace.Info($"Set runner/worker IPC timeout to {_channelTimeout.TotalSeconds} seconds.");
|
Trace.Info($"Set runner/worker IPC timeout to {_channelTimeout.TotalSeconds} seconds.");
|
||||||
}
|
}
|
||||||
@@ -230,10 +233,12 @@ namespace GitHub.Runner.Listener
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// base on the current design, server will only send one job for a given runner everytime.
|
// based on the current design, server will only send one job for a given runner at a time.
|
||||||
// if the runner received a new job request while a previous job request is still running, this typically indicate two situations
|
// if the runner received a new job request while a previous job request is still running, this typically indicates two situations
|
||||||
// 1. an runner bug cause server and runner mismatch on the state of the job request, ex. runner not renew jobrequest properly but think it still own the job reqest, however server already abandon the jobrequest.
|
// 1. a runner bug caused a server and runner mismatch on the state of the job request, e.g. the runner didn't renew the jobrequest
|
||||||
// 2. a server bug or design change that allow server send more than one job request to an given runner that haven't finish previous job request.
|
// properly but thinks it still owns the job reqest, however the server has already abandoned the jobrequest.
|
||||||
|
// 2. a server bug or design change that allowed the server to send more than one job request to an given runner that hasn't finished
|
||||||
|
//. a previous job request.
|
||||||
var runnerServer = HostContext.GetService<IRunnerServer>();
|
var runnerServer = HostContext.GetService<IRunnerServer>();
|
||||||
TaskAgentJobRequest request = null;
|
TaskAgentJobRequest request = null;
|
||||||
try
|
try
|
||||||
@@ -245,7 +250,7 @@ namespace GitHub.Runner.Listener
|
|||||||
Trace.Error($"Catch job-not-found exception while checking jobrequest {jobDispatch.JobId} status. Cancel running worker right away.");
|
Trace.Error($"Catch job-not-found exception while checking jobrequest {jobDispatch.JobId} status. Cancel running worker right away.");
|
||||||
Trace.Error(ex);
|
Trace.Error(ex);
|
||||||
jobDispatch.WorkerCancellationTokenSource.Cancel();
|
jobDispatch.WorkerCancellationTokenSource.Cancel();
|
||||||
// make sure worker process exit before we return, otherwise we might leave orphan worker process behind.
|
// make sure worker process exits before we return, otherwise we might leave an orphan worker process behind.
|
||||||
await jobDispatch.WorkerDispatch;
|
await jobDispatch.WorkerDispatch;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -256,7 +261,7 @@ namespace GitHub.Runner.Listener
|
|||||||
Trace.Error(ex);
|
Trace.Error(ex);
|
||||||
|
|
||||||
jobDispatch.WorkerCancellationTokenSource.Cancel();
|
jobDispatch.WorkerCancellationTokenSource.Cancel();
|
||||||
// make sure worker process exit before we rethrow, otherwise we might leave orphan worker process behind.
|
// make sure the worker process exits before we rethrow, otherwise we might leave orphan worker process behind.
|
||||||
await jobDispatch.WorkerDispatch;
|
await jobDispatch.WorkerDispatch;
|
||||||
|
|
||||||
// rethrow original exception
|
// rethrow original exception
|
||||||
@@ -265,8 +270,8 @@ namespace GitHub.Runner.Listener
|
|||||||
|
|
||||||
if (request.Result != null)
|
if (request.Result != null)
|
||||||
{
|
{
|
||||||
// job request has been finished, the server already has result.
|
// job request has been finished, the server already has the result.
|
||||||
// this means runner is busted since it still running that request.
|
// this means the runner is busted since it is still running that request.
|
||||||
// cancel the zombie worker, run next job request.
|
// cancel the zombie worker, run next job request.
|
||||||
Trace.Error($"Received job request while previous job {jobDispatch.JobId} still running on worker. Cancel the previous job since the job request have been finished on server side with result: {request.Result.Value}.");
|
Trace.Error($"Received job request while previous job {jobDispatch.JobId} still running on worker. Cancel the previous job since the job request have been finished on server side with result: {request.Result.Value}.");
|
||||||
jobDispatch.WorkerCancellationTokenSource.Cancel();
|
jobDispatch.WorkerCancellationTokenSource.Cancel();
|
||||||
@@ -505,7 +510,20 @@ namespace GitHub.Runner.Listener
|
|||||||
{
|
{
|
||||||
detailInfo = string.Join(Environment.NewLine, workerOutput);
|
detailInfo = string.Join(Environment.NewLine, workerOutput);
|
||||||
Trace.Info($"Return code {returnCode} indicate worker encounter an unhandled exception or app crash, attach worker stdout/stderr to JobRequest result.");
|
Trace.Info($"Return code {returnCode} indicate worker encounter an unhandled exception or app crash, attach worker stdout/stderr to JobRequest result.");
|
||||||
await LogWorkerProcessUnhandledException(message, detailInfo);
|
|
||||||
|
var jobServer = HostContext.GetService<IJobServer>();
|
||||||
|
VssCredentials jobServerCredential = VssUtil.GetVssCredential(systemConnection);
|
||||||
|
VssConnection jobConnection = VssUtil.CreateConnection(systemConnection.Url, jobServerCredential);
|
||||||
|
await jobServer.ConnectAsync(jobConnection);
|
||||||
|
|
||||||
|
await LogWorkerProcessUnhandledException(jobServer, message, detailInfo);
|
||||||
|
|
||||||
|
// Go ahead to finish the job with result 'Failed' if the STDERR from worker is System.IO.IOException, since it typically means we are running out of disk space.
|
||||||
|
if (detailInfo.Contains(typeof(System.IO.IOException).ToString(), StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
Trace.Info($"Finish job with result 'Failed' due to IOException.");
|
||||||
|
await ForceFailJob(jobServer, message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TaskResult result = TaskResultUtil.TranslateFromReturnCode(returnCode);
|
TaskResult result = TaskResultUtil.TranslateFromReturnCode(returnCode);
|
||||||
@@ -646,13 +664,15 @@ namespace GitHub.Runner.Listener
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
request = await runnerServer.RenewAgentRequestAsync(poolId, requestId, lockToken, orchestrationId, token);
|
request = await runnerServer.RenewAgentRequestAsync(poolId, requestId, lockToken, orchestrationId, token);
|
||||||
|
|
||||||
Trace.Info($"Successfully renew job request {requestId}, job is valid till {request.LockedUntil.Value}");
|
Trace.Info($"Successfully renew job request {requestId}, job is valid till {request.LockedUntil.Value}");
|
||||||
|
|
||||||
if (!firstJobRequestRenewed.Task.IsCompleted)
|
if (!firstJobRequestRenewed.Task.IsCompleted)
|
||||||
{
|
{
|
||||||
// fire first renew succeed event.
|
// fire first renew succeed event.
|
||||||
firstJobRequestRenewed.TrySetResult(0);
|
firstJobRequestRenewed.TrySetResult(0);
|
||||||
|
|
||||||
|
// Update settings if the runner name has been changed server-side
|
||||||
|
UpdateAgentNameIfNeeded(request.ReservedAgent?.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (encounteringError > 0)
|
if (encounteringError > 0)
|
||||||
@@ -752,6 +772,27 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdateAgentNameIfNeeded(string agentName)
|
||||||
|
{
|
||||||
|
var isNewAgentName = !string.Equals(_runnerSettings.AgentName, agentName, StringComparison.Ordinal);
|
||||||
|
if (!isNewAgentName || string.IsNullOrEmpty(agentName))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_runnerSettings.AgentName = agentName;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_configurationStore.SaveSettings(_runnerSettings);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Trace.Error("Cannot update the settings file:");
|
||||||
|
Trace.Error(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// Best effort upload any logs for this job.
|
// Best effort upload any logs for this job.
|
||||||
private async Task TryUploadUnfinishedLogs(Pipelines.AgentJobRequestMessage message)
|
private async Task TryUploadUnfinishedLogs(Pipelines.AgentJobRequestMessage message)
|
||||||
{
|
{
|
||||||
@@ -913,53 +954,16 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
|
|
||||||
// log an error issue to job level timeline record
|
// log an error issue to job level timeline record
|
||||||
private async Task LogWorkerProcessUnhandledException(Pipelines.AgentJobRequestMessage message, string errorMessage)
|
private async Task LogWorkerProcessUnhandledException(IJobServer jobServer, Pipelines.AgentJobRequestMessage message, string errorMessage)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var systemConnection = message.Resources.Endpoints.SingleOrDefault(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection));
|
|
||||||
ArgUtil.NotNull(systemConnection, nameof(systemConnection));
|
|
||||||
|
|
||||||
var jobServer = HostContext.GetService<IJobServer>();
|
|
||||||
VssCredentials jobServerCredential = VssUtil.GetVssCredential(systemConnection);
|
|
||||||
VssConnection jobConnection = VssUtil.CreateConnection(systemConnection.Url, jobServerCredential);
|
|
||||||
|
|
||||||
/* Below is the legacy 'OnPremises' code that is currently unused by the runner
|
|
||||||
ToDo: re-implement code as appropriate once GHES support is added.
|
|
||||||
// Make sure SystemConnection Url match Config Url base for OnPremises server
|
|
||||||
if (!message.Variables.ContainsKey(Constants.Variables.System.ServerType) ||
|
|
||||||
string.Equals(message.Variables[Constants.Variables.System.ServerType]?.Value, "OnPremises", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Uri result = null;
|
|
||||||
Uri configUri = new Uri(_runnerSetting.ServerUrl);
|
|
||||||
if (Uri.TryCreate(new Uri(configUri.GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped)), jobServerUrl.PathAndQuery, out result))
|
|
||||||
{
|
|
||||||
//replace the schema and host portion of messageUri with the host from the
|
|
||||||
//server URI (which was set at config time)
|
|
||||||
jobServerUrl = result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (InvalidOperationException ex)
|
|
||||||
{
|
|
||||||
//cannot parse the Uri - not a fatal error
|
|
||||||
Trace.Error(ex);
|
|
||||||
}
|
|
||||||
catch (UriFormatException ex)
|
|
||||||
{
|
|
||||||
//cannot parse the Uri - not a fatal error
|
|
||||||
Trace.Error(ex);
|
|
||||||
}
|
|
||||||
} */
|
|
||||||
|
|
||||||
await jobServer.ConnectAsync(jobConnection);
|
|
||||||
|
|
||||||
var timeline = await jobServer.GetTimelineAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, message.Timeline.Id, CancellationToken.None);
|
var timeline = await jobServer.GetTimelineAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, message.Timeline.Id, CancellationToken.None);
|
||||||
|
|
||||||
ArgUtil.NotNull(timeline, nameof(timeline));
|
ArgUtil.NotNull(timeline, nameof(timeline));
|
||||||
|
|
||||||
TimelineRecord jobRecord = timeline.Records.FirstOrDefault(x => x.Id == message.JobId && x.RecordType == "Job");
|
TimelineRecord jobRecord = timeline.Records.FirstOrDefault(x => x.Id == message.JobId && x.RecordType == "Job");
|
||||||
ArgUtil.NotNull(jobRecord, nameof(jobRecord));
|
ArgUtil.NotNull(jobRecord, nameof(jobRecord));
|
||||||
|
|
||||||
var unhandledExceptionIssue = new Issue() { Type = IssueType.Error, Message = errorMessage };
|
var unhandledExceptionIssue = new Issue() { Type = IssueType.Error, Message = errorMessage };
|
||||||
unhandledExceptionIssue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.WorkerCrash;
|
unhandledExceptionIssue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.WorkerCrash;
|
||||||
jobRecord.ErrorCount++;
|
jobRecord.ErrorCount++;
|
||||||
@@ -973,6 +977,21 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// raise job completed event to fail the job.
|
||||||
|
private async Task ForceFailJob(IJobServer jobServer, Pipelines.AgentJobRequestMessage message)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var jobCompletedEvent = new JobCompletedEvent(message.RequestId, message.JobId, TaskResult.Failed);
|
||||||
|
await jobServer.RaisePlanEventAsync<JobCompletedEvent>(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, jobCompletedEvent, CancellationToken.None);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Trace.Error("Fail to raise JobCompletedEvent back to service.");
|
||||||
|
Trace.Error(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class WorkerDispatcher : IDisposable
|
private class WorkerDispatcher : IDisposable
|
||||||
{
|
{
|
||||||
public long RequestId { get; }
|
public long RequestId { get; }
|
||||||
|
|||||||
@@ -233,8 +233,14 @@ namespace GitHub.Runner.Listener
|
|||||||
Trace.Info($"Set runner startup type - {startType}");
|
Trace.Info($"Set runner startup type - {startType}");
|
||||||
HostContext.StartupType = startType;
|
HostContext.StartupType = startType;
|
||||||
|
|
||||||
|
if (command.RunOnce)
|
||||||
|
{
|
||||||
|
_term.WriteLine("Warning: '--once' is going to be deprecated in the future, please consider using '--ephemeral' during runner registration.", ConsoleColor.Yellow);
|
||||||
|
_term.WriteLine("https://docs.github.com/en/actions/hosting-your-own-runners/autoscaling-with-self-hosted-runners#using-ephemeral-runners-for-autoscaling", ConsoleColor.Yellow);
|
||||||
|
}
|
||||||
|
|
||||||
// Run the runner interactively or as service
|
// Run the runner interactively or as service
|
||||||
return await RunAsync(settings, command.RunOnce);
|
return await RunAsync(settings, command.RunOnce || settings.Ephemeral);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -310,6 +316,9 @@ namespace GitHub.Runner.Listener
|
|||||||
|
|
||||||
IJobDispatcher jobDispatcher = null;
|
IJobDispatcher jobDispatcher = null;
|
||||||
CancellationTokenSource messageQueueLoopTokenSource = CancellationTokenSource.CreateLinkedTokenSource(HostContext.RunnerShutdownToken);
|
CancellationTokenSource messageQueueLoopTokenSource = CancellationTokenSource.CreateLinkedTokenSource(HostContext.RunnerShutdownToken);
|
||||||
|
|
||||||
|
// Should we try to cleanup ephemeral runners
|
||||||
|
bool runOnceJobCompleted = false;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var notification = HostContext.GetService<IJobNotification>();
|
var notification = HostContext.GetService<IJobNotification>();
|
||||||
@@ -371,6 +380,7 @@ namespace GitHub.Runner.Listener
|
|||||||
Task completeTask = await Task.WhenAny(getNextMessage, jobDispatcher.RunOnceJobCompleted.Task);
|
Task completeTask = await Task.WhenAny(getNextMessage, jobDispatcher.RunOnceJobCompleted.Task);
|
||||||
if (completeTask == jobDispatcher.RunOnceJobCompleted.Task)
|
if (completeTask == jobDispatcher.RunOnceJobCompleted.Task)
|
||||||
{
|
{
|
||||||
|
runOnceJobCompleted = true;
|
||||||
Trace.Info("Job has finished at backend, the runner will exit since it is running under onetime use mode.");
|
Trace.Info("Job has finished at backend, the runner will exit since it is running under onetime use mode.");
|
||||||
Trace.Info("Stop message queue looping.");
|
Trace.Info("Stop message queue looping.");
|
||||||
messageQueueLoopTokenSource.Cancel();
|
messageQueueLoopTokenSource.Cancel();
|
||||||
@@ -466,10 +476,24 @@ namespace GitHub.Runner.Listener
|
|||||||
await jobDispatcher.ShutdownAsync();
|
await jobDispatcher.ShutdownAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: make sure we don't mask more important exception
|
try
|
||||||
await _listener.DeleteSessionAsync();
|
{
|
||||||
|
await _listener.DeleteSessionAsync();
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (runOnce)
|
||||||
|
{
|
||||||
|
// ignore exception during delete session for ephemeral runner since the runner might already be deleted from the server side
|
||||||
|
// and the delete session call will ends up with 401.
|
||||||
|
Trace.Info($"Ignore any exception during DeleteSession for an ephemeral runner. {ex}");
|
||||||
|
}
|
||||||
|
|
||||||
messageQueueLoopTokenSource.Dispose();
|
messageQueueLoopTokenSource.Dispose();
|
||||||
|
|
||||||
|
if (settings.Ephemeral && runOnceJobCompleted)
|
||||||
|
{
|
||||||
|
var configManager = HostContext.GetService<IConfigurationManager>();
|
||||||
|
configManager.DeleteLocalRunnerConfig();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (TaskAgentAccessTokenExpiredException)
|
catch (TaskAgentAccessTokenExpiredException)
|
||||||
@@ -512,7 +536,9 @@ Config Options:
|
|||||||
--labels string Extra labels in addition to the default: 'self-hosted,{Constants.Runner.Platform},{Constants.Runner.PlatformArchitecture}'
|
--labels string Extra labels in addition to the default: 'self-hosted,{Constants.Runner.Platform},{Constants.Runner.PlatformArchitecture}'
|
||||||
--work string Relative runner work directory (default {Constants.Path.WorkDirectory})
|
--work string Relative runner work directory (default {Constants.Path.WorkDirectory})
|
||||||
--replace Replace any existing runner with the same name (default false)
|
--replace Replace any existing runner with the same name (default false)
|
||||||
--pat GitHub personal access token used for checking network connectivity when executing `.{separator}run.{ext} --check`");
|
--pat GitHub personal access token used for checking network connectivity when executing `.{separator}run.{ext} --check`
|
||||||
|
--ephemeral Configure the runner to only take one job and then let the service un-configure the runner after the job finishes (default false)");
|
||||||
|
|
||||||
#if OS_WINDOWS
|
#if OS_WINDOWS
|
||||||
_term.WriteLine($@" --runasservice Run the runner as a service");
|
_term.WriteLine($@" --runasservice Run the runner as a service");
|
||||||
_term.WriteLine($@" --windowslogonaccount string Account to run the service as. Requires runasservice");
|
_term.WriteLine($@" --windowslogonaccount string Account to run the service as. Requires runasservice");
|
||||||
|
|||||||
@@ -74,10 +74,12 @@ namespace GitHub.Runner.Listener
|
|||||||
await jobDispatcher.WaitAsync(token);
|
await jobDispatcher.WaitAsync(token);
|
||||||
Trace.Info($"All running job has exited.");
|
Trace.Info($"All running job has exited.");
|
||||||
|
|
||||||
|
// We need to keep runner backup around for macOS until we fixed https://github.com/actions/runner/issues/743
|
||||||
|
#if !OS_OSX
|
||||||
// delete runner backup
|
// delete runner backup
|
||||||
DeletePreviousVersionRunnerBackup(token);
|
DeletePreviousVersionRunnerBackup(token);
|
||||||
Trace.Info($"Delete old version runner backup.");
|
Trace.Info($"Delete old version runner backup.");
|
||||||
|
#endif
|
||||||
// generate update script from template
|
// generate update script from template
|
||||||
await UpdateRunnerUpdateStateAsync("Generate and execute update script.");
|
await UpdateRunnerUpdateStateAsync("Generate and execute update script.");
|
||||||
|
|
||||||
@@ -96,7 +98,7 @@ namespace GitHub.Runner.Listener
|
|||||||
invokeScript.Start();
|
invokeScript.Start();
|
||||||
Trace.Info($"Update script start running");
|
Trace.Info($"Update script start running");
|
||||||
|
|
||||||
await UpdateRunnerUpdateStateAsync("Runner will exit shortly for update, should back online within 10 seconds.");
|
await UpdateRunnerUpdateStateAsync("Runner will exit shortly for update, should be back online within 10 seconds.");
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ namespace GitHub.Runner.Sdk
|
|||||||
return StringUtil.ConvertFromJson<T>(json);
|
return StringUtil.ConvertFromJson<T>(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetPathHash(string path)
|
public static string GetSha256Hash(string path)
|
||||||
{
|
{
|
||||||
string hashString = path.ToLowerInvariant();
|
string hashString = path.ToLowerInvariant();
|
||||||
using (SHA256 sha256hash = SHA256.Create())
|
using (SHA256 sha256hash = SHA256.Create())
|
||||||
|
|||||||
@@ -115,11 +115,15 @@ namespace GitHub.Runner.Sdk
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trace?.Info("Not found.");
|
#if OS_WINDOWS
|
||||||
|
trace?.Info($"{command}: command not found. Make sure '{command}' is installed and its location included in the 'Path' environment variable.");
|
||||||
|
#else
|
||||||
|
trace?.Info($"{command}: command not found. Make sure '{command}' is installed and its location included in the 'PATH' environment variable.");
|
||||||
|
#endif
|
||||||
if (require)
|
if (require)
|
||||||
{
|
{
|
||||||
throw new FileNotFoundException(
|
throw new FileNotFoundException(
|
||||||
message: $"File not found: '{command}'",
|
message: $"{command}: command not found",
|
||||||
fileName: command);
|
fileName: command);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
using GitHub.DistributedTask.Pipelines;
|
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||||
using GitHub.DistributedTask.Pipelines.ContextData;
|
|
||||||
using GitHub.DistributedTask.WebApi;
|
using GitHub.DistributedTask.WebApi;
|
||||||
using GitHub.Runner.Common.Util;
|
|
||||||
using GitHub.Runner.Worker.Container;
|
using GitHub.Runner.Worker.Container;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -75,11 +73,19 @@ namespace GitHub.Runner.Worker
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// process action command in serialize order.
|
if (!ActionCommandManager.EnhancedAnnotationsEnabled(context) && actionCommand.Command == "notice")
|
||||||
|
{
|
||||||
|
context.Debug($"Enhanced Annotations not enabled on the server: 'notice' command will not be processed.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize order
|
||||||
lock (_commandSerializeLock)
|
lock (_commandSerializeLock)
|
||||||
{
|
{
|
||||||
|
// Currently stopped
|
||||||
if (_stopProcessCommand)
|
if (_stopProcessCommand)
|
||||||
{
|
{
|
||||||
|
// Resume token
|
||||||
if (!string.IsNullOrEmpty(_stopToken) &&
|
if (!string.IsNullOrEmpty(_stopToken) &&
|
||||||
string.Equals(actionCommand.Command, _stopToken, StringComparison.OrdinalIgnoreCase))
|
string.Equals(actionCommand.Command, _stopToken, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
@@ -96,17 +102,27 @@ namespace GitHub.Runner.Worker
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Currently processing
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// Stop command
|
||||||
if (string.Equals(actionCommand.Command, _stopCommand, StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(actionCommand.Command, _stopCommand, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
context.Output(input);
|
ValidateStopToken(context, actionCommand.Data);
|
||||||
context.Debug("Paused processing commands until '##[{actionCommand.Data}]' is received");
|
|
||||||
_stopToken = actionCommand.Data;
|
_stopToken = actionCommand.Data;
|
||||||
_stopProcessCommand = true;
|
_stopProcessCommand = true;
|
||||||
_registeredCommands.Add(_stopToken);
|
_registeredCommands.Add(_stopToken);
|
||||||
|
if (_stopToken.Length > 6)
|
||||||
|
{
|
||||||
|
HostContext.SecretMasker.AddValue(_stopToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Output(input);
|
||||||
|
context.Debug("Paused processing commands until the token you called ::stopCommands:: with is received");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
// Found command
|
||||||
else if (_commandExtensions.TryGetValue(actionCommand.Command, out IActionCommandExtension extension))
|
else if (_commandExtensions.TryGetValue(actionCommand.Command, out IActionCommandExtension extension))
|
||||||
{
|
{
|
||||||
if (context.EchoOnActionCommand && !extension.OmitEcho)
|
if (context.EchoOnActionCommand && !extension.OmitEcho)
|
||||||
@@ -126,6 +142,7 @@ namespace GitHub.Runner.Worker
|
|||||||
context.CommandResult = TaskResult.Failed;
|
context.CommandResult = TaskResult.Failed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Command not found
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
context.Warning($"Can't find command extension for ##[{actionCommand.Command}.command].");
|
context.Warning($"Can't find command extension for ##[{actionCommand.Command}.command].");
|
||||||
@@ -135,6 +152,45 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ValidateStopToken(IExecutionContext context, string stopToken)
|
||||||
|
{
|
||||||
|
#if OS_WINDOWS
|
||||||
|
var envContext = context.ExpressionValues["env"] as DictionaryContextData;
|
||||||
|
#else
|
||||||
|
var envContext = context.ExpressionValues["env"] as CaseSensitiveDictionaryContextData;
|
||||||
|
#endif
|
||||||
|
var allowUnsecureStopCommandTokens = false;
|
||||||
|
allowUnsecureStopCommandTokens = StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable(Constants.Variables.Actions.AllowUnsupportedStopCommandTokens));
|
||||||
|
if (!allowUnsecureStopCommandTokens && envContext.ContainsKey(Constants.Variables.Actions.AllowUnsupportedStopCommandTokens))
|
||||||
|
{
|
||||||
|
allowUnsecureStopCommandTokens = StringUtil.ConvertToBoolean(envContext[Constants.Variables.Actions.AllowUnsupportedStopCommandTokens].ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isTokenInvalid = _registeredCommands.Contains(stopToken)
|
||||||
|
|| string.IsNullOrEmpty(stopToken)
|
||||||
|
|| string.Equals(stopToken, "pause-logging", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
if (isTokenInvalid)
|
||||||
|
{
|
||||||
|
var telemetry = new JobTelemetry
|
||||||
|
{
|
||||||
|
Message = $"Invoked ::stopCommand:: with token: [{stopToken}]",
|
||||||
|
Type = JobTelemetryType.ActionCommand
|
||||||
|
};
|
||||||
|
context.JobTelemetry.Add(telemetry);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isTokenInvalid && !allowUnsecureStopCommandTokens)
|
||||||
|
{
|
||||||
|
throw new Exception(Constants.Runner.UnsupportedStopCommandTokenDisabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static bool EnhancedAnnotationsEnabled(IExecutionContext context)
|
||||||
|
{
|
||||||
|
return context.Global.Variables.GetBoolean("DistributedTask.EnhancedAnnotations") ?? false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IActionCommandExtension : IExtension
|
public interface IActionCommandExtension : IExtension
|
||||||
@@ -279,8 +335,21 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
throw new Exception("Required field 'name' is missing in ##[save-state] command.");
|
throw new Exception("Required field 'name' is missing in ##[save-state] command.");
|
||||||
}
|
}
|
||||||
|
// Embedded steps (composite) keep track of the state at the root level
|
||||||
context.IntraActionState[stateName] = command.Data;
|
if (context.IsEmbedded)
|
||||||
|
{
|
||||||
|
var id = context.EmbeddedId;
|
||||||
|
if (!context.Root.EmbeddedIntraActionState.ContainsKey(id))
|
||||||
|
{
|
||||||
|
context.Root.EmbeddedIntraActionState[id] = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
context.Root.EmbeddedIntraActionState[id][stateName] = command.Data;
|
||||||
|
}
|
||||||
|
// Otherwise modify the ExecutionContext
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.IntraActionState[stateName] = command.Data;
|
||||||
|
}
|
||||||
context.Debug($"Save intra-action state {stateName} = {command.Data}");
|
context.Debug($"Save intra-action state {stateName} = {command.Data}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -492,6 +561,13 @@ namespace GitHub.Runner.Worker
|
|||||||
public override string Command => "error";
|
public override string Command => "error";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class NoticeCommandExtension : IssueCommandExtension
|
||||||
|
{
|
||||||
|
public override IssueType Type => IssueType.Notice;
|
||||||
|
|
||||||
|
public override string Command => "notice";
|
||||||
|
}
|
||||||
|
|
||||||
public abstract class IssueCommandExtension : RunnerService, IActionCommandExtension
|
public abstract class IssueCommandExtension : RunnerService, IActionCommandExtension
|
||||||
{
|
{
|
||||||
public abstract IssueType Type { get; }
|
public abstract IssueType Type { get; }
|
||||||
@@ -506,6 +582,11 @@ namespace GitHub.Runner.Worker
|
|||||||
command.Properties.TryGetValue(IssueCommandProperties.Line, out string line);
|
command.Properties.TryGetValue(IssueCommandProperties.Line, out string line);
|
||||||
command.Properties.TryGetValue(IssueCommandProperties.Column, out string column);
|
command.Properties.TryGetValue(IssueCommandProperties.Column, out string column);
|
||||||
|
|
||||||
|
if (!ActionCommandManager.EnhancedAnnotationsEnabled(context))
|
||||||
|
{
|
||||||
|
context.Debug("Enhanced Annotations not enabled on the server. The 'title', 'end_line', and 'end_column' fields are unsupported.");
|
||||||
|
}
|
||||||
|
|
||||||
Issue issue = new Issue()
|
Issue issue = new Issue()
|
||||||
{
|
{
|
||||||
Category = "General",
|
Category = "General",
|
||||||
@@ -557,13 +638,73 @@ namespace GitHub.Runner.Worker
|
|||||||
context.AddIssue(issue);
|
context.AddIssue(issue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void ValidateLinesAndColumns(ActionCommand command, IExecutionContext context)
|
||||||
|
{
|
||||||
|
command.Properties.TryGetValue(IssueCommandProperties.Line, out string line);
|
||||||
|
command.Properties.TryGetValue(IssueCommandProperties.EndLine, out string endLine);
|
||||||
|
command.Properties.TryGetValue(IssueCommandProperties.Column, out string column);
|
||||||
|
command.Properties.TryGetValue(IssueCommandProperties.EndColumn, out string endColumn);
|
||||||
|
|
||||||
|
var hasStartLine = int.TryParse(line, out int lineNumber);
|
||||||
|
var hasEndLine = int.TryParse(endLine, out int endLineNumber);
|
||||||
|
var hasStartColumn = int.TryParse(column, out int columnNumber);
|
||||||
|
var hasEndColumn = int.TryParse(endColumn, out int endColumnNumber);
|
||||||
|
var hasColumn = hasStartColumn || hasEndColumn;
|
||||||
|
|
||||||
|
if (hasEndLine && !hasStartLine)
|
||||||
|
{
|
||||||
|
context.Debug($"Invalid {command.Command} command value. '{IssueCommandProperties.EndLine}' can only be set if '{IssueCommandProperties.Line}' is provided");
|
||||||
|
command.Properties[IssueCommandProperties.Line] = endLine;
|
||||||
|
hasStartLine = true;
|
||||||
|
line = endLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasEndColumn && !hasStartColumn)
|
||||||
|
{
|
||||||
|
context.Debug($"Invalid {command.Command} command value. '{IssueCommandProperties.EndColumn}' can only be set if '{IssueCommandProperties.Column}' is provided");
|
||||||
|
command.Properties[IssueCommandProperties.Column] = endColumn;
|
||||||
|
hasStartColumn = true;
|
||||||
|
column = endColumn;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasStartLine && hasColumn)
|
||||||
|
{
|
||||||
|
context.Debug($"Invalid {command.Command} command value. '{IssueCommandProperties.Column}' and '{IssueCommandProperties.EndColumn}' can only be set if '{IssueCommandProperties.Line}' value is provided.");
|
||||||
|
command.Properties.Remove(IssueCommandProperties.Column);
|
||||||
|
command.Properties.Remove(IssueCommandProperties.EndColumn);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasEndLine && line != endLine && hasColumn)
|
||||||
|
{
|
||||||
|
context.Debug($"Invalid {command.Command} command value. '{IssueCommandProperties.Column}' and '{IssueCommandProperties.EndColumn}' cannot be set if '{IssueCommandProperties.Line}' and '{IssueCommandProperties.EndLine}' are different values.");
|
||||||
|
command.Properties.Remove(IssueCommandProperties.Column);
|
||||||
|
command.Properties.Remove(IssueCommandProperties.EndColumn);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasStartLine && hasEndLine && endLineNumber < lineNumber)
|
||||||
|
{
|
||||||
|
context.Debug($"Invalid {command.Command} command value. '{IssueCommandProperties.EndLine}' cannot be less than '{IssueCommandProperties.Line}'.");
|
||||||
|
command.Properties.Remove(IssueCommandProperties.Line);
|
||||||
|
command.Properties.Remove(IssueCommandProperties.EndLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasStartColumn && hasEndColumn && endColumnNumber < columnNumber)
|
||||||
|
{
|
||||||
|
context.Debug($"Invalid {command.Command} command value. '{IssueCommandProperties.EndColumn}' cannot be less than '{IssueCommandProperties.Column}'.");
|
||||||
|
command.Properties.Remove(IssueCommandProperties.Column);
|
||||||
|
command.Properties.Remove(IssueCommandProperties.EndColumn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static class IssueCommandProperties
|
private static class IssueCommandProperties
|
||||||
{
|
{
|
||||||
public const String File = "file";
|
public const String File = "file";
|
||||||
public const String Line = "line";
|
public const String Line = "line";
|
||||||
|
public const String EndLine = "endLine";
|
||||||
public const String Column = "col";
|
public const String Column = "col";
|
||||||
|
public const String EndColumn = "endColumn";
|
||||||
|
public const String Title = "title";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class GroupCommandExtension : GroupingCommandExtension
|
public sealed class GroupCommandExtension : GroupingCommandExtension
|
||||||
|
|||||||
@@ -37,7 +37,10 @@ namespace GitHub.Runner.Worker
|
|||||||
public interface IActionManager : IRunnerService
|
public interface IActionManager : IRunnerService
|
||||||
{
|
{
|
||||||
Dictionary<Guid, ContainerInfo> CachedActionContainers { get; }
|
Dictionary<Guid, ContainerInfo> CachedActionContainers { get; }
|
||||||
Task<PrepareResult> PrepareActionsAsync(IExecutionContext executionContext, IEnumerable<Pipelines.JobStep> steps);
|
Dictionary<Guid, List<Pipelines.ActionStep>> CachedEmbeddedPreSteps { get; }
|
||||||
|
Dictionary<Guid, List<Guid>> CachedEmbeddedStepIds { get; }
|
||||||
|
Dictionary<Guid, Stack<Pipelines.ActionStep>> CachedEmbeddedPostSteps { get; }
|
||||||
|
Task<PrepareResult> PrepareActionsAsync(IExecutionContext executionContext, IEnumerable<Pipelines.JobStep> steps, Guid rootStepId = default(Guid));
|
||||||
Definition LoadAction(IExecutionContext executionContext, Pipelines.ActionStep action);
|
Definition LoadAction(IExecutionContext executionContext, Pipelines.ActionStep action);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,35 +51,98 @@ namespace GitHub.Runner.Worker
|
|||||||
//81920 is the default used by System.IO.Stream.CopyTo and is under the large object heap threshold (85k).
|
//81920 is the default used by System.IO.Stream.CopyTo and is under the large object heap threshold (85k).
|
||||||
private const int _defaultCopyBufferSize = 81920;
|
private const int _defaultCopyBufferSize = 81920;
|
||||||
private const string _dotcomApiUrl = "https://api.github.com";
|
private const string _dotcomApiUrl = "https://api.github.com";
|
||||||
private readonly Dictionary<Guid, ContainerInfo> _cachedActionContainers = new Dictionary<Guid, ContainerInfo>();
|
|
||||||
|
|
||||||
|
private readonly Dictionary<Guid, ContainerInfo> _cachedActionContainers = new Dictionary<Guid, ContainerInfo>();
|
||||||
public Dictionary<Guid, ContainerInfo> CachedActionContainers => _cachedActionContainers;
|
public Dictionary<Guid, ContainerInfo> CachedActionContainers => _cachedActionContainers;
|
||||||
public async Task<PrepareResult> PrepareActionsAsync(IExecutionContext executionContext, IEnumerable<Pipelines.JobStep> steps)
|
|
||||||
|
private readonly Dictionary<Guid, List<Pipelines.ActionStep>> _cachedEmbeddedPreSteps = new Dictionary<Guid, List<Pipelines.ActionStep>>();
|
||||||
|
public Dictionary<Guid, List<Pipelines.ActionStep>> CachedEmbeddedPreSteps => _cachedEmbeddedPreSteps;
|
||||||
|
|
||||||
|
private readonly Dictionary<Guid, List<Guid>> _cachedEmbeddedStepIds = new Dictionary<Guid, List<Guid>>();
|
||||||
|
public Dictionary<Guid, List<Guid>> CachedEmbeddedStepIds => _cachedEmbeddedStepIds;
|
||||||
|
|
||||||
|
private readonly Dictionary<Guid, Stack<Pipelines.ActionStep>> _cachedEmbeddedPostSteps = new Dictionary<Guid, Stack<Pipelines.ActionStep>>();
|
||||||
|
public Dictionary<Guid, Stack<Pipelines.ActionStep>> CachedEmbeddedPostSteps => _cachedEmbeddedPostSteps;
|
||||||
|
|
||||||
|
public async Task<PrepareResult> PrepareActionsAsync(IExecutionContext executionContext, IEnumerable<Pipelines.JobStep> steps, Guid rootStepId = default(Guid))
|
||||||
{
|
{
|
||||||
|
// Assert inputs
|
||||||
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
||||||
ArgUtil.NotNull(steps, nameof(steps));
|
ArgUtil.NotNull(steps, nameof(steps));
|
||||||
|
var state = new PrepareActionsState
|
||||||
executionContext.Output("Prepare all required actions");
|
|
||||||
Dictionary<string, List<Guid>> imagesToPull = new Dictionary<string, List<Guid>>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
Dictionary<string, List<Guid>> imagesToBuild = new Dictionary<string, List<Guid>>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
Dictionary<string, ActionContainer> imagesToBuildInfo = new Dictionary<string, ActionContainer>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
List<JobExtensionRunner> containerSetupSteps = new List<JobExtensionRunner>();
|
|
||||||
Dictionary<Guid, IActionRunner> preStepTracker = new Dictionary<Guid, IActionRunner>();
|
|
||||||
IEnumerable<Pipelines.ActionStep> actions = steps.OfType<Pipelines.ActionStep>();
|
|
||||||
|
|
||||||
// TODO: Deprecate the PREVIEW_ACTION_TOKEN
|
|
||||||
// Log even if we aren't using it to ensure users know.
|
|
||||||
if (!string.IsNullOrEmpty(executionContext.Global.Variables.Get("PREVIEW_ACTION_TOKEN")))
|
|
||||||
{
|
{
|
||||||
executionContext.Warning("The 'PREVIEW_ACTION_TOKEN' secret is deprecated. Please remove it from the repository's secrets");
|
ImagesToBuild = new Dictionary<string, List<Guid>>(StringComparer.OrdinalIgnoreCase),
|
||||||
|
ImagesToPull = new Dictionary<string, List<Guid>>(StringComparer.OrdinalIgnoreCase),
|
||||||
|
ImagesToBuildInfo = new Dictionary<string, ActionContainer>(StringComparer.OrdinalIgnoreCase),
|
||||||
|
PreStepTracker = new Dictionary<Guid, IActionRunner>()
|
||||||
|
};
|
||||||
|
var containerSetupSteps = new List<JobExtensionRunner>();
|
||||||
|
var depth = 0;
|
||||||
|
// We are running at the start of a job
|
||||||
|
if (rootStepId == default(Guid))
|
||||||
|
{
|
||||||
|
IOUtil.DeleteDirectory(HostContext.GetDirectory(WellKnownDirectory.Actions), executionContext.CancellationToken);
|
||||||
|
}
|
||||||
|
// We are running mid job due to a local composite action
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!_cachedEmbeddedStepIds.ContainsKey(rootStepId))
|
||||||
|
{
|
||||||
|
_cachedEmbeddedStepIds[rootStepId] = new List<Guid>();
|
||||||
|
foreach (var compositeStep in steps)
|
||||||
|
{
|
||||||
|
var guid = Guid.NewGuid();
|
||||||
|
compositeStep.Id = guid;
|
||||||
|
_cachedEmbeddedStepIds[rootStepId].Add(guid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
depth = 1;
|
||||||
|
}
|
||||||
|
IEnumerable<Pipelines.ActionStep> actions = steps.OfType<Pipelines.ActionStep>();
|
||||||
|
executionContext.Output("Prepare all required actions");
|
||||||
|
var result = await PrepareActionsRecursiveAsync(executionContext, state, actions, depth, rootStepId);
|
||||||
|
if (state.ImagesToPull.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (var imageToPull in result.ImagesToPull)
|
||||||
|
{
|
||||||
|
Trace.Info($"{imageToPull.Value.Count} steps need to pull image '{imageToPull.Key}'");
|
||||||
|
containerSetupSteps.Add(new JobExtensionRunner(runAsync: this.PullActionContainerAsync,
|
||||||
|
condition: $"{PipelineTemplateConstants.Success}()",
|
||||||
|
displayName: $"Pull {imageToPull.Key}",
|
||||||
|
data: new ContainerSetupInfo(imageToPull.Value, imageToPull.Key)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear the cache (for self-hosted runners)
|
if (result.ImagesToBuild.Count > 0)
|
||||||
IOUtil.DeleteDirectory(HostContext.GetDirectory(WellKnownDirectory.Actions), executionContext.CancellationToken);
|
{
|
||||||
|
foreach (var imageToBuild in result.ImagesToBuild)
|
||||||
|
{
|
||||||
|
var setupInfo = result.ImagesToBuildInfo[imageToBuild.Key];
|
||||||
|
Trace.Info($"{imageToBuild.Value.Count} steps need to build image from '{setupInfo.Dockerfile}'");
|
||||||
|
containerSetupSteps.Add(new JobExtensionRunner(runAsync: this.BuildActionContainerAsync,
|
||||||
|
condition: $"{PipelineTemplateConstants.Success}()",
|
||||||
|
displayName: $"Build {setupInfo.ActionRepository}",
|
||||||
|
data: new ContainerSetupInfo(imageToBuild.Value, setupInfo.Dockerfile, setupInfo.WorkingDirectory)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// todo: Remove when feature flag DistributedTask.NewActionMetadata is removed
|
#if !OS_LINUX
|
||||||
var newActionMetadata = executionContext.Global.Variables.GetBoolean("DistributedTask.NewActionMetadata") ?? false;
|
if (containerSetupSteps.Count > 0)
|
||||||
|
{
|
||||||
|
executionContext.Output("Container action is only supported on Linux, skip pull and build docker images.");
|
||||||
|
containerSetupSteps.Clear();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return new PrepareResult(containerSetupSteps, result.PreStepTracker);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<PrepareActionsState> PrepareActionsRecursiveAsync(IExecutionContext executionContext, PrepareActionsState state, IEnumerable<Pipelines.ActionStep> actions, Int32 depth = 0, Guid parentStepId = default(Guid))
|
||||||
|
{
|
||||||
|
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
||||||
|
if (depth > Constants.CompositeActionsMaxDepth)
|
||||||
|
{
|
||||||
|
throw new Exception($"Composite action depth exceeded max depth {Constants.CompositeActionsMaxDepth}");
|
||||||
|
}
|
||||||
var repositoryActions = new List<Pipelines.ActionStep>();
|
var repositoryActions = new List<Pipelines.ActionStep>();
|
||||||
|
|
||||||
foreach (var action in actions)
|
foreach (var action in actions)
|
||||||
@@ -88,66 +154,15 @@ namespace GitHub.Runner.Worker
|
|||||||
ArgUtil.NotNull(containerReference, nameof(containerReference));
|
ArgUtil.NotNull(containerReference, nameof(containerReference));
|
||||||
ArgUtil.NotNullOrEmpty(containerReference.Image, nameof(containerReference.Image));
|
ArgUtil.NotNullOrEmpty(containerReference.Image, nameof(containerReference.Image));
|
||||||
|
|
||||||
if (!imagesToPull.ContainsKey(containerReference.Image))
|
if (!state.ImagesToPull.ContainsKey(containerReference.Image))
|
||||||
{
|
{
|
||||||
imagesToPull[containerReference.Image] = new List<Guid>();
|
state.ImagesToPull[containerReference.Image] = new List<Guid>();
|
||||||
}
|
}
|
||||||
|
|
||||||
Trace.Info($"Action {action.Name} ({action.Id}) needs to pull image '{containerReference.Image}'");
|
Trace.Info($"Action {action.Name} ({action.Id}) needs to pull image '{containerReference.Image}'");
|
||||||
imagesToPull[containerReference.Image].Add(action.Id);
|
state.ImagesToPull[containerReference.Image].Add(action.Id);
|
||||||
}
|
}
|
||||||
// todo: Remove when feature flag DistributedTask.NewActionMetadata is removed
|
else if (action.Reference.Type == Pipelines.ActionSourceType.Repository)
|
||||||
else if (action.Reference.Type == Pipelines.ActionSourceType.Repository && !newActionMetadata)
|
|
||||||
{
|
|
||||||
// only download the repository archive
|
|
||||||
await DownloadRepositoryActionAsync(executionContext, action);
|
|
||||||
|
|
||||||
// more preparation base on content in the repository (action.yml)
|
|
||||||
var setupInfo = PrepareRepositoryActionAsync(executionContext, action);
|
|
||||||
if (setupInfo != null)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(setupInfo.Image))
|
|
||||||
{
|
|
||||||
if (!imagesToPull.ContainsKey(setupInfo.Image))
|
|
||||||
{
|
|
||||||
imagesToPull[setupInfo.Image] = new List<Guid>();
|
|
||||||
}
|
|
||||||
|
|
||||||
Trace.Info($"Action {action.Name} ({action.Id}) from repository '{setupInfo.ActionRepository}' needs to pull image '{setupInfo.Image}'");
|
|
||||||
imagesToPull[setupInfo.Image].Add(action.Id);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ArgUtil.NotNullOrEmpty(setupInfo.ActionRepository, nameof(setupInfo.ActionRepository));
|
|
||||||
|
|
||||||
if (!imagesToBuild.ContainsKey(setupInfo.ActionRepository))
|
|
||||||
{
|
|
||||||
imagesToBuild[setupInfo.ActionRepository] = new List<Guid>();
|
|
||||||
}
|
|
||||||
|
|
||||||
Trace.Info($"Action {action.Name} ({action.Id}) from repository '{setupInfo.ActionRepository}' needs to build image '{setupInfo.Dockerfile}'");
|
|
||||||
imagesToBuild[setupInfo.ActionRepository].Add(action.Id);
|
|
||||||
imagesToBuildInfo[setupInfo.ActionRepository] = setupInfo;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var repoAction = action.Reference as Pipelines.RepositoryPathReference;
|
|
||||||
if (repoAction.RepositoryType != Pipelines.PipelineConstants.SelfAlias)
|
|
||||||
{
|
|
||||||
var definition = LoadAction(executionContext, action);
|
|
||||||
if (definition.Data.Execution.HasPre)
|
|
||||||
{
|
|
||||||
var actionRunner = HostContext.CreateService<IActionRunner>();
|
|
||||||
actionRunner.Action = action;
|
|
||||||
actionRunner.Stage = ActionRunStage.Pre;
|
|
||||||
actionRunner.Condition = definition.Data.Execution.InitCondition;
|
|
||||||
|
|
||||||
Trace.Info($"Add 'pre' execution for {action.Id}");
|
|
||||||
preStepTracker[action.Id] = actionRunner;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (action.Reference.Type == Pipelines.ActionSourceType.Repository && newActionMetadata)
|
|
||||||
{
|
{
|
||||||
repositoryActions.Add(action);
|
repositoryActions.Add(action);
|
||||||
}
|
}
|
||||||
@@ -179,85 +194,83 @@ namespace GitHub.Runner.Worker
|
|||||||
foreach (var action in repositoryActions)
|
foreach (var action in repositoryActions)
|
||||||
{
|
{
|
||||||
var setupInfo = PrepareRepositoryActionAsync(executionContext, action);
|
var setupInfo = PrepareRepositoryActionAsync(executionContext, action);
|
||||||
if (setupInfo != null)
|
if (setupInfo != null && setupInfo.Container != null)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(setupInfo.Image))
|
if (!string.IsNullOrEmpty(setupInfo.Container.Image))
|
||||||
{
|
{
|
||||||
if (!imagesToPull.ContainsKey(setupInfo.Image))
|
if (!state.ImagesToPull.ContainsKey(setupInfo.Container.Image))
|
||||||
{
|
{
|
||||||
imagesToPull[setupInfo.Image] = new List<Guid>();
|
state.ImagesToPull[setupInfo.Container.Image] = new List<Guid>();
|
||||||
}
|
}
|
||||||
|
|
||||||
Trace.Info($"Action {action.Name} ({action.Id}) from repository '{setupInfo.ActionRepository}' needs to pull image '{setupInfo.Image}'");
|
Trace.Info($"Action {action.Name} ({action.Id}) from repository '{setupInfo.Container.ActionRepository}' needs to pull image '{setupInfo.Container.Image}'");
|
||||||
imagesToPull[setupInfo.Image].Add(action.Id);
|
state.ImagesToPull[setupInfo.Container.Image].Add(action.Id);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ArgUtil.NotNullOrEmpty(setupInfo.ActionRepository, nameof(setupInfo.ActionRepository));
|
ArgUtil.NotNullOrEmpty(setupInfo.Container.ActionRepository, nameof(setupInfo.Container.ActionRepository));
|
||||||
|
|
||||||
if (!imagesToBuild.ContainsKey(setupInfo.ActionRepository))
|
if (!state.ImagesToBuild.ContainsKey(setupInfo.Container.ActionRepository))
|
||||||
{
|
{
|
||||||
imagesToBuild[setupInfo.ActionRepository] = new List<Guid>();
|
state.ImagesToBuild[setupInfo.Container.ActionRepository] = new List<Guid>();
|
||||||
}
|
}
|
||||||
|
|
||||||
Trace.Info($"Action {action.Name} ({action.Id}) from repository '{setupInfo.ActionRepository}' needs to build image '{setupInfo.Dockerfile}'");
|
Trace.Info($"Action {action.Name} ({action.Id}) from repository '{setupInfo.Container.ActionRepository}' needs to build image '{setupInfo.Container.Dockerfile}'");
|
||||||
imagesToBuild[setupInfo.ActionRepository].Add(action.Id);
|
state.ImagesToBuild[setupInfo.Container.ActionRepository].Add(action.Id);
|
||||||
imagesToBuildInfo[setupInfo.ActionRepository] = setupInfo;
|
state.ImagesToBuildInfo[setupInfo.Container.ActionRepository] = setupInfo.Container;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (setupInfo != null && setupInfo.Steps != null && setupInfo.Steps.Count > 0)
|
||||||
|
{
|
||||||
|
state = await PrepareActionsRecursiveAsync(executionContext, state, setupInfo.Steps, depth + 1, action.Id);
|
||||||
|
}
|
||||||
var repoAction = action.Reference as Pipelines.RepositoryPathReference;
|
var repoAction = action.Reference as Pipelines.RepositoryPathReference;
|
||||||
if (repoAction.RepositoryType != Pipelines.PipelineConstants.SelfAlias)
|
if (repoAction.RepositoryType != Pipelines.PipelineConstants.SelfAlias)
|
||||||
{
|
{
|
||||||
var definition = LoadAction(executionContext, action);
|
var definition = LoadAction(executionContext, action);
|
||||||
if (definition.Data.Execution.HasPre)
|
if (definition.Data.Execution.HasPre)
|
||||||
{
|
{
|
||||||
var actionRunner = HostContext.CreateService<IActionRunner>();
|
|
||||||
actionRunner.Action = action;
|
|
||||||
actionRunner.Stage = ActionRunStage.Pre;
|
|
||||||
actionRunner.Condition = definition.Data.Execution.InitCondition;
|
|
||||||
|
|
||||||
Trace.Info($"Add 'pre' execution for {action.Id}");
|
Trace.Info($"Add 'pre' execution for {action.Id}");
|
||||||
preStepTracker[action.Id] = actionRunner;
|
// Root Step
|
||||||
|
if (depth < 1)
|
||||||
|
{
|
||||||
|
var actionRunner = HostContext.CreateService<IActionRunner>();
|
||||||
|
actionRunner.Action = action;
|
||||||
|
actionRunner.Stage = ActionRunStage.Pre;
|
||||||
|
actionRunner.Condition = definition.Data.Execution.InitCondition;
|
||||||
|
state.PreStepTracker[action.Id] = actionRunner;
|
||||||
|
}
|
||||||
|
// Embedded Step
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!_cachedEmbeddedPreSteps.ContainsKey(parentStepId))
|
||||||
|
{
|
||||||
|
_cachedEmbeddedPreSteps[parentStepId] = new List<Pipelines.ActionStep>();
|
||||||
|
}
|
||||||
|
// Clone action so we can modify the condition without affecting the original
|
||||||
|
var clonedAction = action.Clone() as Pipelines.ActionStep;
|
||||||
|
clonedAction.Condition = definition.Data.Execution.InitCondition;
|
||||||
|
_cachedEmbeddedPreSteps[parentStepId].Add(clonedAction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (definition.Data.Execution.HasPost && depth > 0)
|
||||||
|
{
|
||||||
|
if (!_cachedEmbeddedPostSteps.ContainsKey(parentStepId))
|
||||||
|
{
|
||||||
|
// If we haven't done so already, add the parent to the post steps
|
||||||
|
_cachedEmbeddedPostSteps[parentStepId] = new Stack<Pipelines.ActionStep>();
|
||||||
|
}
|
||||||
|
// Clone action so we can modify the condition without affecting the original
|
||||||
|
var clonedAction = action.Clone() as Pipelines.ActionStep;
|
||||||
|
clonedAction.Condition = definition.Data.Execution.CleanupCondition;
|
||||||
|
_cachedEmbeddedPostSteps[parentStepId].Push(clonedAction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (imagesToPull.Count > 0)
|
return state;
|
||||||
{
|
|
||||||
foreach (var imageToPull in imagesToPull)
|
|
||||||
{
|
|
||||||
Trace.Info($"{imageToPull.Value.Count} steps need to pull image '{imageToPull.Key}'");
|
|
||||||
containerSetupSteps.Add(new JobExtensionRunner(runAsync: this.PullActionContainerAsync,
|
|
||||||
condition: $"{PipelineTemplateConstants.Success}()",
|
|
||||||
displayName: $"Pull {imageToPull.Key}",
|
|
||||||
data: new ContainerSetupInfo(imageToPull.Value, imageToPull.Key)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (imagesToBuild.Count > 0)
|
|
||||||
{
|
|
||||||
foreach (var imageToBuild in imagesToBuild)
|
|
||||||
{
|
|
||||||
var setupInfo = imagesToBuildInfo[imageToBuild.Key];
|
|
||||||
Trace.Info($"{imageToBuild.Value.Count} steps need to build image from '{setupInfo.Dockerfile}'");
|
|
||||||
containerSetupSteps.Add(new JobExtensionRunner(runAsync: this.BuildActionContainerAsync,
|
|
||||||
condition: $"{PipelineTemplateConstants.Success}()",
|
|
||||||
displayName: $"Build {setupInfo.ActionRepository}",
|
|
||||||
data: new ContainerSetupInfo(imageToBuild.Value, setupInfo.Dockerfile, setupInfo.WorkingDirectory)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if !OS_LINUX
|
|
||||||
if (containerSetupSteps.Count > 0)
|
|
||||||
{
|
|
||||||
executionContext.Output("Container action is only supported on Linux, skip pull and build docker images.");
|
|
||||||
containerSetupSteps.Clear();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return new PrepareResult(containerSetupSteps, preStepTracker);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Definition LoadAction(IExecutionContext executionContext, Pipelines.ActionStep action)
|
public Definition LoadAction(IExecutionContext executionContext, Pipelines.ActionStep action)
|
||||||
@@ -402,6 +415,29 @@ namespace GitHub.Runner.Worker
|
|||||||
Trace.Verbose($"Details: {StringUtil.ConvertToJson(compositeAction?.Steps)}");
|
Trace.Verbose($"Details: {StringUtil.ConvertToJson(compositeAction?.Steps)}");
|
||||||
Trace.Info($"Load: {compositeAction.Outputs?.Count ?? 0} number of outputs");
|
Trace.Info($"Load: {compositeAction.Outputs?.Count ?? 0} number of outputs");
|
||||||
Trace.Info($"Details: {StringUtil.ConvertToJson(compositeAction?.Outputs)}");
|
Trace.Info($"Details: {StringUtil.ConvertToJson(compositeAction?.Outputs)}");
|
||||||
|
|
||||||
|
if (CachedEmbeddedPreSteps.TryGetValue(action.Id, out var preSteps))
|
||||||
|
{
|
||||||
|
compositeAction.PreSteps = preSteps;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CachedEmbeddedPostSteps.TryGetValue(action.Id, out var postSteps))
|
||||||
|
{
|
||||||
|
compositeAction.PostSteps = postSteps;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_cachedEmbeddedStepIds.ContainsKey(action.Id))
|
||||||
|
{
|
||||||
|
for (var i = 0; i < compositeAction.Steps.Count; i++)
|
||||||
|
{
|
||||||
|
// Store Id's for later load actions
|
||||||
|
compositeAction.Steps[i].Id = _cachedEmbeddedStepIds[action.Id][i];
|
||||||
|
if (string.IsNullOrEmpty(executionContext.Global.Variables.Get("DistributedTask.EnableCompositeActions")) && compositeAction.Steps[i].Reference.Type != Pipelines.ActionSourceType.Script)
|
||||||
|
{
|
||||||
|
throw new Exception("`uses:` keyword is not currently supported.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -574,6 +610,7 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
NameWithOwner = repositoryReference.Name,
|
NameWithOwner = repositoryReference.Name,
|
||||||
Ref = repositoryReference.Ref,
|
Ref = repositoryReference.Ref,
|
||||||
|
Path = repositoryReference.Path,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.ToList();
|
.ToList();
|
||||||
@@ -596,7 +633,12 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
catch (Exception ex) when (!executionContext.CancellationToken.IsCancellationRequested) // Do not retry if the run is canceled.
|
catch (Exception ex) when (!executionContext.CancellationToken.IsCancellationRequested) // Do not retry if the run is canceled.
|
||||||
{
|
{
|
||||||
if (attempt < 3)
|
// UnresolvableActionDownloadInfoException is a 422 client error, don't retry
|
||||||
|
// Some possible cases are:
|
||||||
|
// * Repo is rate limited
|
||||||
|
// * Repo or tag doesn't exist, or isn't public
|
||||||
|
// * Policy validation failed
|
||||||
|
if (attempt < 3 && !(ex is WebApi.UnresolvableActionDownloadInfoException))
|
||||||
{
|
{
|
||||||
executionContext.Output($"Failed to resolve action download info. Error: {ex.Message}");
|
executionContext.Output($"Failed to resolve action download info. Error: {ex.Message}");
|
||||||
executionContext.Debug(ex.ToString());
|
executionContext.Debug(ex.ToString());
|
||||||
@@ -612,6 +654,7 @@ namespace GitHub.Runner.Worker
|
|||||||
// Some possible cases are:
|
// Some possible cases are:
|
||||||
// * Repo is rate limited
|
// * Repo is rate limited
|
||||||
// * Repo or tag doesn't exist, or isn't public
|
// * Repo or tag doesn't exist, or isn't public
|
||||||
|
// * Policy validation failed
|
||||||
if (ex is WebApi.UnresolvableActionDownloadInfoException)
|
if (ex is WebApi.UnresolvableActionDownloadInfoException)
|
||||||
{
|
{
|
||||||
throw;
|
throw;
|
||||||
@@ -647,90 +690,6 @@ namespace GitHub.Runner.Worker
|
|||||||
return actionDownloadInfos.Actions;
|
return actionDownloadInfos.Actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: Remove when feature flag DistributedTask.NewActionMetadata is removed
|
|
||||||
private async Task DownloadRepositoryActionAsync(IExecutionContext executionContext, Pipelines.ActionStep repositoryAction)
|
|
||||||
{
|
|
||||||
Trace.Entering();
|
|
||||||
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
|
||||||
|
|
||||||
var repositoryReference = repositoryAction.Reference as Pipelines.RepositoryPathReference;
|
|
||||||
ArgUtil.NotNull(repositoryReference, nameof(repositoryReference));
|
|
||||||
|
|
||||||
if (string.Equals(repositoryReference.RepositoryType, Pipelines.PipelineConstants.SelfAlias, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
Trace.Info($"Repository action is in 'self' repository.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.Equals(repositoryReference.RepositoryType, Pipelines.RepositoryTypes.GitHub, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
throw new NotSupportedException(repositoryReference.RepositoryType);
|
|
||||||
}
|
|
||||||
|
|
||||||
ArgUtil.NotNullOrEmpty(repositoryReference.Name, nameof(repositoryReference.Name));
|
|
||||||
ArgUtil.NotNullOrEmpty(repositoryReference.Ref, nameof(repositoryReference.Ref));
|
|
||||||
|
|
||||||
string destDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Actions), repositoryReference.Name.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar), repositoryReference.Ref);
|
|
||||||
string watermarkFile = GetWatermarkFilePath(destDirectory);
|
|
||||||
if (File.Exists(watermarkFile))
|
|
||||||
{
|
|
||||||
executionContext.Debug($"Action '{repositoryReference.Name}@{repositoryReference.Ref}' already downloaded at '{destDirectory}'.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// make sure we get a clean folder ready to use.
|
|
||||||
IOUtil.DeleteDirectory(destDirectory, executionContext.CancellationToken);
|
|
||||||
Directory.CreateDirectory(destDirectory);
|
|
||||||
executionContext.Output($"Download action repository '{repositoryReference.Name}@{repositoryReference.Ref}'");
|
|
||||||
}
|
|
||||||
|
|
||||||
var configurationStore = HostContext.GetService<IConfigurationStore>();
|
|
||||||
var isHostedServer = configurationStore.GetSettings().IsHostedServer;
|
|
||||||
if (isHostedServer)
|
|
||||||
{
|
|
||||||
string apiUrl = GetApiUrl(executionContext);
|
|
||||||
string archiveLink = BuildLinkToActionArchive(apiUrl, repositoryReference.Name, repositoryReference.Ref);
|
|
||||||
var downloadDetails = new ActionDownloadDetails(archiveLink, ConfigureAuthorizationFromContext);
|
|
||||||
await DownloadRepositoryActionAsync(executionContext, downloadDetails, null, destDirectory);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
string apiUrl = GetApiUrl(executionContext);
|
|
||||||
|
|
||||||
// URLs to try:
|
|
||||||
var downloadAttempts = new List<ActionDownloadDetails> {
|
|
||||||
// A built-in action or an action the user has created, on their GHES instance
|
|
||||||
// Example: https://my-ghes/api/v3/repos/my-org/my-action/tarball/v1
|
|
||||||
new ActionDownloadDetails(
|
|
||||||
BuildLinkToActionArchive(apiUrl, repositoryReference.Name, repositoryReference.Ref),
|
|
||||||
ConfigureAuthorizationFromContext),
|
|
||||||
|
|
||||||
// The same action, on GitHub.com
|
|
||||||
// Example: https://api.github.com/repos/my-org/my-action/tarball/v1
|
|
||||||
new ActionDownloadDetails(
|
|
||||||
BuildLinkToActionArchive(_dotcomApiUrl, repositoryReference.Name, repositoryReference.Ref),
|
|
||||||
configureAuthorization: (e,h) => { /* no authorization for dotcom */ })
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach (var downloadAttempt in downloadAttempts)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await DownloadRepositoryActionAsync(executionContext, downloadAttempt, null, destDirectory);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
catch (ActionNotFoundException)
|
|
||||||
{
|
|
||||||
Trace.Info($"Failed to find the action '{repositoryReference.Name}' at ref '{repositoryReference.Ref}' at {downloadAttempt.ArchiveLink}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new ActionNotFoundException($"Failed to find the action '{repositoryReference.Name}' at ref '{repositoryReference.Ref}'. Paths attempted: {string.Join(", ", downloadAttempts.Select(d => d.ArchiveLink))}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task DownloadRepositoryActionAsync(IExecutionContext executionContext, WebApi.ActionDownloadInfo downloadInfo)
|
private async Task DownloadRepositoryActionAsync(IExecutionContext executionContext, WebApi.ActionDownloadInfo downloadInfo)
|
||||||
{
|
{
|
||||||
Trace.Entering();
|
Trace.Entering();
|
||||||
@@ -751,10 +710,10 @@ namespace GitHub.Runner.Worker
|
|||||||
// make sure we get a clean folder ready to use.
|
// make sure we get a clean folder ready to use.
|
||||||
IOUtil.DeleteDirectory(destDirectory, executionContext.CancellationToken);
|
IOUtil.DeleteDirectory(destDirectory, executionContext.CancellationToken);
|
||||||
Directory.CreateDirectory(destDirectory);
|
Directory.CreateDirectory(destDirectory);
|
||||||
executionContext.Output($"Download action repository '{downloadInfo.NameWithOwner}@{downloadInfo.Ref}'");
|
executionContext.Output($"Download action repository '{downloadInfo.NameWithOwner}@{downloadInfo.Ref}' (SHA:{downloadInfo.ResolvedSha})");
|
||||||
}
|
}
|
||||||
|
|
||||||
await DownloadRepositoryActionAsync(executionContext, null, downloadInfo, destDirectory);
|
await DownloadRepositoryActionAsync(executionContext, downloadInfo, destDirectory);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetApiUrl(IExecutionContext executionContext)
|
private string GetApiUrl(IExecutionContext executionContext)
|
||||||
@@ -777,8 +736,7 @@ namespace GitHub.Runner.Worker
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: Remove the parameter "actionDownloadDetails" when feature flag DistributedTask.NewActionMetadata is removed
|
private async Task DownloadRepositoryActionAsync(IExecutionContext executionContext, WebApi.ActionDownloadInfo downloadInfo, string destDirectory)
|
||||||
private async Task DownloadRepositoryActionAsync(IExecutionContext executionContext, ActionDownloadDetails actionDownloadDetails, WebApi.ActionDownloadInfo downloadInfo, string destDirectory)
|
|
||||||
{
|
{
|
||||||
//download and extract action in a temp folder and rename it on success
|
//download and extract action in a temp folder and rename it on success
|
||||||
string tempDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Actions), "_temp_" + Guid.NewGuid());
|
string tempDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Actions), "_temp_" + Guid.NewGuid());
|
||||||
@@ -786,10 +744,10 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
#if OS_WINDOWS
|
#if OS_WINDOWS
|
||||||
string archiveFile = Path.Combine(tempDirectory, $"{Guid.NewGuid()}.zip");
|
string archiveFile = Path.Combine(tempDirectory, $"{Guid.NewGuid()}.zip");
|
||||||
string link = downloadInfo?.ZipballUrl ?? actionDownloadDetails.ArchiveLink;
|
string link = downloadInfo?.ZipballUrl;
|
||||||
#else
|
#else
|
||||||
string archiveFile = Path.Combine(tempDirectory, $"{Guid.NewGuid()}.tar.gz");
|
string archiveFile = Path.Combine(tempDirectory, $"{Guid.NewGuid()}.tar.gz");
|
||||||
string link = downloadInfo?.TarballUrl ?? actionDownloadDetails.ArchiveLink;
|
string link = downloadInfo?.TarballUrl;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Trace.Info($"Save archive '{link}' into {archiveFile}.");
|
Trace.Info($"Save archive '{link}' into {archiveFile}.");
|
||||||
@@ -811,16 +769,7 @@ namespace GitHub.Runner.Worker
|
|||||||
using (var httpClientHandler = HostContext.CreateHttpClientHandler())
|
using (var httpClientHandler = HostContext.CreateHttpClientHandler())
|
||||||
using (var httpClient = new HttpClient(httpClientHandler))
|
using (var httpClient = new HttpClient(httpClientHandler))
|
||||||
{
|
{
|
||||||
// Legacy
|
httpClient.DefaultRequestHeaders.Authorization = CreateAuthHeader(downloadInfo.Authentication?.Token);
|
||||||
if (downloadInfo == null)
|
|
||||||
{
|
|
||||||
actionDownloadDetails.ConfigureAuthorization(executionContext, httpClient);
|
|
||||||
}
|
|
||||||
// FF DistributedTask.NewActionMetadata
|
|
||||||
else
|
|
||||||
{
|
|
||||||
httpClient.DefaultRequestHeaders.Authorization = CreateAuthHeader(downloadInfo.Authentication?.Token);
|
|
||||||
}
|
|
||||||
|
|
||||||
httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents);
|
httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents);
|
||||||
using (var response = await httpClient.GetAsync(link))
|
using (var response = await httpClient.GetAsync(link))
|
||||||
@@ -960,7 +909,6 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: Remove when feature flag DistributedTask.NewActionMetadata is removed
|
|
||||||
private void ConfigureAuthorizationFromContext(IExecutionContext executionContext, HttpClient httpClient)
|
private void ConfigureAuthorizationFromContext(IExecutionContext executionContext, HttpClient httpClient)
|
||||||
{
|
{
|
||||||
var authToken = Environment.GetEnvironmentVariable("_GITHUB_ACTION_TOKEN");
|
var authToken = Environment.GetEnvironmentVariable("_GITHUB_ACTION_TOKEN");
|
||||||
@@ -986,7 +934,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
private string GetWatermarkFilePath(string directory) => directory + ".completed";
|
private string GetWatermarkFilePath(string directory) => directory + ".completed";
|
||||||
|
|
||||||
private ActionContainer PrepareRepositoryActionAsync(IExecutionContext executionContext, Pipelines.ActionStep repositoryAction)
|
private ActionSetupInfo PrepareRepositoryActionAsync(IExecutionContext executionContext, Pipelines.ActionStep repositoryAction)
|
||||||
{
|
{
|
||||||
var repositoryReference = repositoryAction.Reference as Pipelines.RepositoryPathReference;
|
var repositoryReference = repositoryAction.Reference as Pipelines.RepositoryPathReference;
|
||||||
if (string.Equals(repositoryReference.RepositoryType, Pipelines.PipelineConstants.SelfAlias, StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(repositoryReference.RepositoryType, Pipelines.PipelineConstants.SelfAlias, StringComparison.OrdinalIgnoreCase))
|
||||||
@@ -994,8 +942,8 @@ namespace GitHub.Runner.Worker
|
|||||||
Trace.Info($"Repository action is in 'self' repository.");
|
Trace.Info($"Repository action is in 'self' repository.");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
var setupInfo = new ActionSetupInfo();
|
||||||
var setupInfo = new ActionContainer();
|
var actionContainer = new ActionContainer();
|
||||||
string destDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Actions), repositoryReference.Name.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar), repositoryReference.Ref);
|
string destDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Actions), repositoryReference.Name.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar), repositoryReference.Ref);
|
||||||
string actionEntryDirectory = destDirectory;
|
string actionEntryDirectory = destDirectory;
|
||||||
string dockerFileRelativePath = repositoryReference.Name;
|
string dockerFileRelativePath = repositoryReference.Name;
|
||||||
@@ -1004,11 +952,11 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
actionEntryDirectory = Path.Combine(destDirectory, repositoryReference.Path);
|
actionEntryDirectory = Path.Combine(destDirectory, repositoryReference.Path);
|
||||||
dockerFileRelativePath = $"{dockerFileRelativePath}/{repositoryReference.Path}";
|
dockerFileRelativePath = $"{dockerFileRelativePath}/{repositoryReference.Path}";
|
||||||
setupInfo.ActionRepository = $"{repositoryReference.Name}/{repositoryReference.Path}@{repositoryReference.Ref}";
|
actionContainer.ActionRepository = $"{repositoryReference.Name}/{repositoryReference.Path}@{repositoryReference.Ref}";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
setupInfo.ActionRepository = $"{repositoryReference.Name}@{repositoryReference.Ref}";
|
actionContainer.ActionRepository = $"{repositoryReference.Name}@{repositoryReference.Ref}";
|
||||||
}
|
}
|
||||||
|
|
||||||
// find the docker file or action.yml file
|
// find the docker file or action.yml file
|
||||||
@@ -1038,8 +986,9 @@ namespace GitHub.Runner.Worker
|
|||||||
var dockerFileFullPath = Path.Combine(actionEntryDirectory, containerAction.Image);
|
var dockerFileFullPath = Path.Combine(actionEntryDirectory, containerAction.Image);
|
||||||
executionContext.Debug($"Dockerfile for action: '{dockerFileFullPath}'.");
|
executionContext.Debug($"Dockerfile for action: '{dockerFileFullPath}'.");
|
||||||
|
|
||||||
setupInfo.Dockerfile = dockerFileFullPath;
|
actionContainer.Dockerfile = dockerFileFullPath;
|
||||||
setupInfo.WorkingDirectory = destDirectory;
|
actionContainer.WorkingDirectory = destDirectory;
|
||||||
|
setupInfo.Container = actionContainer;
|
||||||
return setupInfo;
|
return setupInfo;
|
||||||
}
|
}
|
||||||
else if (containerAction.Image.StartsWith("docker://", StringComparison.OrdinalIgnoreCase))
|
else if (containerAction.Image.StartsWith("docker://", StringComparison.OrdinalIgnoreCase))
|
||||||
@@ -1048,7 +997,8 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
executionContext.Debug($"Container image for action: '{actionImage}'.");
|
executionContext.Debug($"Container image for action: '{actionImage}'.");
|
||||||
|
|
||||||
setupInfo.Image = actionImage;
|
actionContainer.Image = actionImage;
|
||||||
|
setupInfo.Container = actionContainer;
|
||||||
return setupInfo;
|
return setupInfo;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -1068,8 +1018,31 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
else if (actionDefinitionData.Execution.ExecutionType == ActionExecutionType.Composite)
|
else if (actionDefinitionData.Execution.ExecutionType == ActionExecutionType.Composite)
|
||||||
{
|
{
|
||||||
Trace.Info($"Action composite: {(actionDefinitionData.Execution as CompositeActionExecutionData).Steps}, no more preparation.");
|
Trace.Info($"Loading Composite steps");
|
||||||
return null;
|
var compositeAction = actionDefinitionData.Execution as CompositeActionExecutionData;
|
||||||
|
setupInfo.Steps = compositeAction.Steps;
|
||||||
|
|
||||||
|
// cache steps ids if not done so already
|
||||||
|
if (!_cachedEmbeddedStepIds.ContainsKey(repositoryAction.Id))
|
||||||
|
{
|
||||||
|
_cachedEmbeddedStepIds[repositoryAction.Id] = new List<Guid>();
|
||||||
|
foreach (var compositeStep in compositeAction.Steps)
|
||||||
|
{
|
||||||
|
var guid = Guid.NewGuid();
|
||||||
|
compositeStep.Id = guid;
|
||||||
|
_cachedEmbeddedStepIds[repositoryAction.Id].Add(guid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: remove once we remove the DistributedTask.EnableCompositeActions FF
|
||||||
|
foreach (var step in compositeAction.Steps)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(executionContext.Global.Variables.Get("DistributedTask.EnableCompositeActions")) && step.Reference.Type != Pipelines.ActionSourceType.Script)
|
||||||
|
{
|
||||||
|
throw new Exception("`uses:` keyword is not currently supported.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return setupInfo;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1079,15 +1052,17 @@ namespace GitHub.Runner.Worker
|
|||||||
else if (File.Exists(dockerFile))
|
else if (File.Exists(dockerFile))
|
||||||
{
|
{
|
||||||
executionContext.Debug($"Dockerfile for action: '{dockerFile}'.");
|
executionContext.Debug($"Dockerfile for action: '{dockerFile}'.");
|
||||||
setupInfo.Dockerfile = dockerFile;
|
actionContainer.Dockerfile = dockerFile;
|
||||||
setupInfo.WorkingDirectory = destDirectory;
|
actionContainer.WorkingDirectory = destDirectory;
|
||||||
|
setupInfo.Container = actionContainer;
|
||||||
return setupInfo;
|
return setupInfo;
|
||||||
}
|
}
|
||||||
else if (File.Exists(dockerFileLowerCase))
|
else if (File.Exists(dockerFileLowerCase))
|
||||||
{
|
{
|
||||||
executionContext.Debug($"Dockerfile for action: '{dockerFileLowerCase}'.");
|
executionContext.Debug($"Dockerfile for action: '{dockerFileLowerCase}'.");
|
||||||
setupInfo.Dockerfile = dockerFileLowerCase;
|
actionContainer.Dockerfile = dockerFileLowerCase;
|
||||||
setupInfo.WorkingDirectory = destDirectory;
|
actionContainer.WorkingDirectory = destDirectory;
|
||||||
|
setupInfo.Container = actionContainer;
|
||||||
return setupInfo;
|
return setupInfo;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -1140,20 +1115,6 @@ namespace GitHub.Runner.Worker
|
|||||||
HostContext.SecretMasker.AddValue(base64EncodingToken);
|
HostContext.SecretMasker.AddValue(base64EncodingToken);
|
||||||
return new AuthenticationHeaderValue("Basic", base64EncodingToken);
|
return new AuthenticationHeaderValue("Basic", base64EncodingToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: Remove when feature flag DistributedTask.NewActionMetadata is removed
|
|
||||||
private class ActionDownloadDetails
|
|
||||||
{
|
|
||||||
public string ArchiveLink { get; }
|
|
||||||
|
|
||||||
public Action<IExecutionContext, HttpClient> ConfigureAuthorization { get; }
|
|
||||||
|
|
||||||
public ActionDownloadDetails(string archiveLink, Action<IExecutionContext, HttpClient> configureAuthorization)
|
|
||||||
{
|
|
||||||
ArchiveLink = archiveLink;
|
|
||||||
ConfigureAuthorization = configureAuthorization;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class Definition
|
public sealed class Definition
|
||||||
@@ -1241,9 +1202,11 @@ namespace GitHub.Runner.Worker
|
|||||||
public sealed class CompositeActionExecutionData : ActionExecutionData
|
public sealed class CompositeActionExecutionData : ActionExecutionData
|
||||||
{
|
{
|
||||||
public override ActionExecutionType ExecutionType => ActionExecutionType.Composite;
|
public override ActionExecutionType ExecutionType => ActionExecutionType.Composite;
|
||||||
public override bool HasPre => false;
|
public override bool HasPre => PreSteps.Count > 0;
|
||||||
public override bool HasPost => false;
|
public override bool HasPost => PostSteps.Count > 0;
|
||||||
|
public List<Pipelines.ActionStep> PreSteps { get; set; }
|
||||||
public List<Pipelines.ActionStep> Steps { get; set; }
|
public List<Pipelines.ActionStep> Steps { get; set; }
|
||||||
|
public Stack<Pipelines.ActionStep> PostSteps { get; set; }
|
||||||
public MappingToken Outputs { get; set; }
|
public MappingToken Outputs { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1303,4 +1266,18 @@ namespace GitHub.Runner.Worker
|
|||||||
public string WorkingDirectory { get; set; }
|
public string WorkingDirectory { get; set; }
|
||||||
public string ActionRepository { get; set; }
|
public string ActionRepository { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class ActionSetupInfo
|
||||||
|
{
|
||||||
|
public ActionContainer Container { get; set; }
|
||||||
|
public List<Pipelines.ActionStep> Steps { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PrepareActionsState
|
||||||
|
{
|
||||||
|
public Dictionary<string, List<Guid>> ImagesToPull;
|
||||||
|
public Dictionary<string, List<Guid>> ImagesToBuild;
|
||||||
|
public Dictionary<string, ActionContainer> ImagesToBuildInfo;
|
||||||
|
public Dictionary<Guid, IActionRunner> PreStepTracker;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -480,6 +480,10 @@ namespace GitHub.Runner.Worker
|
|||||||
return new CompositeActionExecutionData()
|
return new CompositeActionExecutionData()
|
||||||
{
|
{
|
||||||
Steps = steps.Cast<Pipelines.ActionStep>().ToList(),
|
Steps = steps.Cast<Pipelines.ActionStep>().ToList(),
|
||||||
|
PreSteps = new List<Pipelines.ActionStep>(),
|
||||||
|
PostSteps = new Stack<Pipelines.ActionStep>(),
|
||||||
|
InitCondition = "always()",
|
||||||
|
CleanupCondition = "always()",
|
||||||
Outputs = outputs
|
Outputs = outputs
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,6 +82,28 @@ namespace GitHub.Runner.Worker
|
|||||||
ActionExecutionData handlerData = definition.Data?.Execution;
|
ActionExecutionData handlerData = definition.Data?.Execution;
|
||||||
ArgUtil.NotNull(handlerData, nameof(handlerData));
|
ArgUtil.NotNull(handlerData, nameof(handlerData));
|
||||||
|
|
||||||
|
List<JobExtensionRunner> localActionContainerSetupSteps = null;
|
||||||
|
// Handle Composite Local Actions
|
||||||
|
// Need to download and expand the tree of referenced actions
|
||||||
|
if (handlerData.ExecutionType == ActionExecutionType.Composite &&
|
||||||
|
handlerData is CompositeActionExecutionData compositeHandlerData &&
|
||||||
|
Stage == ActionRunStage.Main &&
|
||||||
|
Action.Reference is Pipelines.RepositoryPathReference localAction &&
|
||||||
|
string.Equals(localAction.RepositoryType, Pipelines.PipelineConstants.SelfAlias, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var actionManager = HostContext.GetService<IActionManager>();
|
||||||
|
var prepareResult = await actionManager.PrepareActionsAsync(ExecutionContext, compositeHandlerData.Steps, ExecutionContext.Id);
|
||||||
|
|
||||||
|
// Reload definition since post may exist now (from embedded steps that were JIT downloaded)
|
||||||
|
definition = taskManager.LoadAction(ExecutionContext, Action);
|
||||||
|
ArgUtil.NotNull(definition, nameof(definition));
|
||||||
|
handlerData = definition.Data?.Execution;
|
||||||
|
ArgUtil.NotNull(handlerData, nameof(handlerData));
|
||||||
|
|
||||||
|
// Save container setup steps so we can reference them later
|
||||||
|
localActionContainerSetupSteps = prepareResult.ContainerSetupSteps;
|
||||||
|
}
|
||||||
|
|
||||||
if (handlerData.HasPre &&
|
if (handlerData.HasPre &&
|
||||||
Action.Reference is Pipelines.RepositoryPathReference repoAction &&
|
Action.Reference is Pipelines.RepositoryPathReference repoAction &&
|
||||||
string.Equals(repoAction.RepositoryType, Pipelines.PipelineConstants.SelfAlias, StringComparison.OrdinalIgnoreCase))
|
string.Equals(repoAction.RepositoryType, Pipelines.PipelineConstants.SelfAlias, StringComparison.OrdinalIgnoreCase))
|
||||||
@@ -249,7 +271,8 @@ namespace GitHub.Runner.Worker
|
|||||||
inputs,
|
inputs,
|
||||||
environment,
|
environment,
|
||||||
ExecutionContext.Global.Variables,
|
ExecutionContext.Global.Variables,
|
||||||
actionDirectory: definition.Directory);
|
actionDirectory: definition.Directory,
|
||||||
|
localActionContainerSetupSteps: localActionContainerSetupSteps);
|
||||||
|
|
||||||
// Print out action details
|
// Print out action details
|
||||||
handler.PrintActionDetails(Stage);
|
handler.PrintActionDetails(Stage);
|
||||||
|
|||||||
47
src/Runner.Worker/ConditionTraceWriter.cs
Normal file
47
src/Runner.Worker/ConditionTraceWriter.cs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
using GitHub.DistributedTask.Pipelines;
|
||||||
|
using GitHub.Runner.Common;
|
||||||
|
using GitHub.Runner.Sdk;
|
||||||
|
using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating;
|
||||||
|
|
||||||
|
namespace GitHub.Runner.Worker
|
||||||
|
{
|
||||||
|
public sealed class ConditionTraceWriter : ObjectTemplating::ITraceWriter
|
||||||
|
{
|
||||||
|
private readonly IExecutionContext _executionContext;
|
||||||
|
private readonly Tracing _trace;
|
||||||
|
private readonly StringBuilder _traceBuilder = new StringBuilder();
|
||||||
|
|
||||||
|
public string Trace => _traceBuilder.ToString();
|
||||||
|
|
||||||
|
public ConditionTraceWriter(Tracing trace, IExecutionContext executionContext)
|
||||||
|
{
|
||||||
|
ArgUtil.NotNull(trace, nameof(trace));
|
||||||
|
_trace = trace;
|
||||||
|
_executionContext = executionContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Error(string format, params Object[] args)
|
||||||
|
{
|
||||||
|
var message = StringUtil.Format(format, args);
|
||||||
|
_trace.Error(message);
|
||||||
|
_executionContext?.Debug(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Info(string format, params Object[] args)
|
||||||
|
{
|
||||||
|
var message = StringUtil.Format(format, args);
|
||||||
|
_trace.Info(message);
|
||||||
|
_executionContext?.Debug(message);
|
||||||
|
_traceBuilder.AppendLine(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Verbose(string format, params Object[] args)
|
||||||
|
{
|
||||||
|
var message = StringUtil.Format(format, args);
|
||||||
|
_trace.Verbose(message);
|
||||||
|
_executionContext?.Debug(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -54,7 +54,7 @@ namespace GitHub.Runner.Worker.Container
|
|||||||
_pathMappings.Add(new PathMapping(hostContext.GetDirectory(WellKnownDirectory.Externals), "/__e"));
|
_pathMappings.Add(new PathMapping(hostContext.GetDirectory(WellKnownDirectory.Externals), "/__e"));
|
||||||
if (this.IsJobContainer)
|
if (this.IsJobContainer)
|
||||||
{
|
{
|
||||||
this.MountVolumes.Add(new MountVolume("/var/run/docker.sock", "/var/run/docker.sock"));
|
// this.MountVolumes.Add(new MountVolume("/var/run/docker.sock", "/var/run/docker.sock"));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if (container.Ports?.Count > 0)
|
if (container.Ports?.Count > 0)
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ namespace GitHub.Runner.Worker.Container
|
|||||||
{
|
{
|
||||||
base.Initialize(hostContext);
|
base.Initialize(hostContext);
|
||||||
DockerPath = WhichUtil.Which("docker", true, Trace);
|
DockerPath = WhichUtil.Which("docker", true, Trace);
|
||||||
DockerInstanceLabel = IOUtil.GetPathHash(hostContext.GetDirectory(WellKnownDirectory.Root)).Substring(0, 6);
|
DockerInstanceLabel = IOUtil.GetSha256Hash(hostContext.GetDirectory(WellKnownDirectory.Root)).Substring(0, 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<DockerVersion> DockerVersion(IExecutionContext context)
|
public async Task<DockerVersion> DockerVersion(IExecutionContext context)
|
||||||
|
|||||||
@@ -12,9 +12,88 @@ using GitHub.Runner.Sdk;
|
|||||||
using GitHub.DistributedTask.Pipelines.ContextData;
|
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
using GitHub.DistributedTask.Pipelines.ObjectTemplating;
|
using GitHub.DistributedTask.Pipelines.ObjectTemplating;
|
||||||
|
using System.Threading.Channels;
|
||||||
|
using GitHub.Services.WebApi;
|
||||||
|
using System.Text;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
namespace GitHub.Runner.Worker
|
namespace GitHub.Runner.Worker
|
||||||
{
|
{
|
||||||
|
[DataContract]
|
||||||
|
public class ContainerEngineHandlerInput
|
||||||
|
{
|
||||||
|
[DataMember]
|
||||||
|
public string Command { get; set; }
|
||||||
|
|
||||||
|
[DataMember]
|
||||||
|
public ContainersCreationInput CreationInput { get; set; }
|
||||||
|
|
||||||
|
[DataMember]
|
||||||
|
public JobContainerExecInput ExecInput { get; set; }
|
||||||
|
|
||||||
|
[DataMember]
|
||||||
|
public ContainersRemoveInput RemoveInput { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[DataContract]
|
||||||
|
public class ContainersCreationInput
|
||||||
|
{
|
||||||
|
[DataMember]
|
||||||
|
public List<ContainerInfo> Containers { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[DataContract]
|
||||||
|
public class JobContainerExecInput
|
||||||
|
{
|
||||||
|
[DataMember]
|
||||||
|
public ContainerInfo JobContainer { get; set; }
|
||||||
|
|
||||||
|
[DataMember]
|
||||||
|
public string WorkingDirectory { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
[DataMember]
|
||||||
|
public string FileName { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
[DataMember]
|
||||||
|
public string Arguments { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
[DataMember]
|
||||||
|
public List<string> EnvironmentKeys { get; set; }
|
||||||
|
|
||||||
|
[DataMember]
|
||||||
|
public Dictionary<string, string> EnvironmentVariables { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[DataContract]
|
||||||
|
public class ContainersRemoveInput
|
||||||
|
{
|
||||||
|
[DataMember]
|
||||||
|
public string Network { get; set; }
|
||||||
|
[DataMember]
|
||||||
|
public string JobContainerId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[DataContract]
|
||||||
|
public class ContainersCreationOutput
|
||||||
|
{
|
||||||
|
[DataMember]
|
||||||
|
public string Network { get; set; }
|
||||||
|
[DataMember]
|
||||||
|
public string JobContainerId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[DataContract]
|
||||||
|
public class ContainerEngineHandlerOutput
|
||||||
|
{
|
||||||
|
[DataMember]
|
||||||
|
public ContainersCreationOutput CreationOutput { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
[ServiceLocator(Default = typeof(ContainerOperationProvider))]
|
[ServiceLocator(Default = typeof(ContainerOperationProvider))]
|
||||||
public interface IContainerOperationProvider : IRunnerService
|
public interface IContainerOperationProvider : IRunnerService
|
||||||
{
|
{
|
||||||
@@ -24,25 +103,57 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
public class ContainerOperationProvider : RunnerService, IContainerOperationProvider
|
public class ContainerOperationProvider : RunnerService, IContainerOperationProvider
|
||||||
{
|
{
|
||||||
private IDockerCommandManager _dockerManager;
|
private IDockerCommandManager _dockerManager = null;
|
||||||
|
|
||||||
public override void Initialize(IHostContext hostContext)
|
public override void Initialize(IHostContext hostContext)
|
||||||
{
|
{
|
||||||
base.Initialize(hostContext);
|
base.Initialize(hostContext);
|
||||||
_dockerManager = HostContext.GetService<IDockerCommandManager>();
|
// _dockerManager = HostContext.GetService<IDockerCommandManager>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task StartContainersAsync(IExecutionContext executionContext, object data)
|
public async Task StartContainersAsync(IExecutionContext executionContext, object data)
|
||||||
{
|
{
|
||||||
Trace.Entering();
|
Trace.Entering();
|
||||||
if (!Constants.Runner.Platform.Equals(Constants.OSPlatform.Linux))
|
// if (!Constants.Runner.Platform.Equals(Constants.OSPlatform.Linux))
|
||||||
{
|
// {
|
||||||
throw new NotSupportedException("Container operations are only supported on Linux runners");
|
// throw new NotSupportedException("Container operations are only supported on Linux runners");
|
||||||
}
|
// }
|
||||||
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
||||||
List<ContainerInfo> containers = data as List<ContainerInfo>;
|
List<ContainerInfo> containers = data as List<ContainerInfo>;
|
||||||
ArgUtil.NotNull(containers, nameof(containers));
|
ArgUtil.NotNull(containers, nameof(containers));
|
||||||
|
|
||||||
|
foreach (var container in containers)
|
||||||
|
{
|
||||||
|
if (container.IsJobContainer)
|
||||||
|
{
|
||||||
|
// Configure job container - Mount workspace and tools, set up environment, and start long running process
|
||||||
|
var githubContext = executionContext.ExpressionValues["github"] as GitHubContext;
|
||||||
|
ArgUtil.NotNull(githubContext, nameof(githubContext));
|
||||||
|
var workingDirectory = githubContext["workspace"] as StringContextData;
|
||||||
|
ArgUtil.NotNullOrEmpty(workingDirectory, nameof(workingDirectory));
|
||||||
|
container.MountVolumes.Add(new MountVolume(HostContext.GetDirectory(WellKnownDirectory.Work), container.TranslateToContainerPath(HostContext.GetDirectory(WellKnownDirectory.Work))));
|
||||||
|
container.MountVolumes.Add(new MountVolume(HostContext.GetDirectory(WellKnownDirectory.Externals), container.TranslateToContainerPath(HostContext.GetDirectory(WellKnownDirectory.Externals)), true));
|
||||||
|
container.MountVolumes.Add(new MountVolume(HostContext.GetDirectory(WellKnownDirectory.Temp), container.TranslateToContainerPath(HostContext.GetDirectory(WellKnownDirectory.Temp))));
|
||||||
|
// container.MountVolumes.Add(new MountVolume(HostContext.GetDirectory(WellKnownDirectory.Actions), container.TranslateToContainerPath(HostContext.GetDirectory(WellKnownDirectory.Actions))));
|
||||||
|
container.MountVolumes.Add(new MountVolume(HostContext.GetDirectory(WellKnownDirectory.Tools), container.TranslateToContainerPath(HostContext.GetDirectory(WellKnownDirectory.Tools))));
|
||||||
|
|
||||||
|
var tempHomeDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Temp), "_github_home");
|
||||||
|
Directory.CreateDirectory(tempHomeDirectory);
|
||||||
|
container.MountVolumes.Add(new MountVolume(tempHomeDirectory, "/github/home"));
|
||||||
|
container.AddPathTranslateMapping(tempHomeDirectory, "/github/home");
|
||||||
|
container.ContainerEnvironmentVariables["HOME"] = container.TranslateToContainerPath(tempHomeDirectory);
|
||||||
|
|
||||||
|
var tempWorkflowDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Temp), "_github_workflow");
|
||||||
|
Directory.CreateDirectory(tempWorkflowDirectory);
|
||||||
|
container.MountVolumes.Add(new MountVolume(tempWorkflowDirectory, "/github/workflow"));
|
||||||
|
container.AddPathTranslateMapping(tempWorkflowDirectory, "/github/workflow");
|
||||||
|
|
||||||
|
container.ContainerWorkDirectory = container.TranslateToContainerPath(workingDirectory);
|
||||||
|
container.ContainerEntryPoint = "tail";
|
||||||
|
container.ContainerEntryPointArgs = "-f /dev/null";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var postJobStep = new JobExtensionRunner(runAsync: this.StopContainersAsync,
|
var postJobStep = new JobExtensionRunner(runAsync: this.StopContainersAsync,
|
||||||
condition: $"{PipelineTemplateConstants.Always}()",
|
condition: $"{PipelineTemplateConstants.Always}()",
|
||||||
displayName: "Stop containers",
|
displayName: "Stop containers",
|
||||||
@@ -51,9 +162,71 @@ namespace GitHub.Runner.Worker
|
|||||||
executionContext.Debug($"Register post job cleanup for stopping/deleting containers.");
|
executionContext.Debug($"Register post job cleanup for stopping/deleting containers.");
|
||||||
executionContext.RegisterPostJobStep(postJobStep);
|
executionContext.RegisterPostJobStep(postJobStep);
|
||||||
|
|
||||||
// Check whether we are inside a container.
|
var podManHandler = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Bin), "kubectlHandler", "index.js");
|
||||||
// Our container feature requires to map working directory from host to the container.
|
if (File.Exists(podManHandler))
|
||||||
// If we are already inside a container, we will not able to find out the real working direcotry path on the host.
|
{
|
||||||
|
var podmanInput = new ContainerEngineHandlerInput()
|
||||||
|
{
|
||||||
|
Command = "Create",
|
||||||
|
CreationInput = new ContainersCreationInput()
|
||||||
|
{
|
||||||
|
Containers = containers
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ContainerEngineHandlerOutput podmanOutput = null;
|
||||||
|
using (var processInvoker = HostContext.CreateService<IProcessInvoker>())
|
||||||
|
{
|
||||||
|
var redirectStandardIn = Channel.CreateUnbounded<string>(new UnboundedChannelOptions() { SingleReader = true, SingleWriter = true });
|
||||||
|
redirectStandardIn.Writer.TryWrite(JsonUtility.ToString(podmanInput));
|
||||||
|
|
||||||
|
processInvoker.OutputDataReceived += delegate (object sender, ProcessDataReceivedEventArgs message)
|
||||||
|
{
|
||||||
|
executionContext.Output(message.Data);
|
||||||
|
};
|
||||||
|
|
||||||
|
processInvoker.ErrorDataReceived += delegate (object sender, ProcessDataReceivedEventArgs message)
|
||||||
|
{
|
||||||
|
executionContext.Output(message.Data);
|
||||||
|
if (podmanOutput == null && message.Data.IndexOf("___CONTAINER_ENGINE_HANDLER_OUTPUT___") >= 0)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
podmanOutput = JsonUtility.FromString<ContainerEngineHandlerOutput>(message.Data.Replace("___CONTAINER_ENGINE_HANDLER_OUTPUT___", ""));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
executionContext.Error(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Execute the process. Exit code 0 should always be returned.
|
||||||
|
// A non-zero exit code indicates infrastructural failure.
|
||||||
|
// Task failure should be communicated over STDOUT using ## commands.
|
||||||
|
await processInvoker.ExecuteAsync(workingDirectory: HostContext.GetDirectory(WellKnownDirectory.Bin),
|
||||||
|
fileName: Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), "node12", "bin", $"node{IOUtil.ExeExtension}"),
|
||||||
|
arguments: podManHandler,
|
||||||
|
environment: null,
|
||||||
|
requireExitCodeZero: false,
|
||||||
|
outputEncoding: Encoding.UTF8,
|
||||||
|
killProcessOnCancel: false,
|
||||||
|
redirectStandardIn: redirectStandardIn,
|
||||||
|
cancellationToken: executionContext.CancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (podmanOutput != null)
|
||||||
|
{
|
||||||
|
executionContext.JobContext.Container["network"] = new StringContextData(podmanOutput.CreationOutput.Network);
|
||||||
|
executionContext.JobContext.Container["id"] = new StringContextData(podmanOutput.CreationOutput.JobContainerId);
|
||||||
|
executionContext.Global.Container.ContainerId = podmanOutput.CreationOutput.JobContainerId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Check whether we are inside a container.
|
||||||
|
// Our container feature requires to map working directory from host to the container.
|
||||||
|
// If we are already inside a container, we will not able to find out the real working direcotry path on the host.
|
||||||
#if OS_WINDOWS
|
#if OS_WINDOWS
|
||||||
// service CExecSvc is Container Execution Agent.
|
// service CExecSvc is Container Execution Agent.
|
||||||
ServiceController[] scServices = ServiceController.GetServices();
|
ServiceController[] scServices = ServiceController.GetServices();
|
||||||
@@ -62,11 +235,11 @@ namespace GitHub.Runner.Worker
|
|||||||
throw new NotSupportedException("Container feature is not supported when runner is already running inside container.");
|
throw new NotSupportedException("Container feature is not supported when runner is already running inside container.");
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
var initProcessCgroup = File.ReadLines("/proc/1/cgroup");
|
var initProcessCgroup = File.ReadLines("/proc/1/cgroup");
|
||||||
if (initProcessCgroup.Any(x => x.IndexOf(":/docker/", StringComparison.OrdinalIgnoreCase) >= 0))
|
if (initProcessCgroup.Any(x => x.IndexOf(":/docker/", StringComparison.OrdinalIgnoreCase) >= 0))
|
||||||
{
|
{
|
||||||
throw new NotSupportedException("Container feature is not supported when runner is already running inside container.");
|
throw new NotSupportedException("Container feature is not supported when runner is already running inside container.");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if OS_WINDOWS
|
#if OS_WINDOWS
|
||||||
@@ -90,68 +263,69 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Check docker client/server version
|
// Check docker client/server version
|
||||||
executionContext.Output("##[group]Checking docker version");
|
executionContext.Output("##[group]Checking docker version");
|
||||||
DockerVersion dockerVersion = await _dockerManager.DockerVersion(executionContext);
|
DockerVersion dockerVersion = await _dockerManager.DockerVersion(executionContext);
|
||||||
executionContext.Output("##[endgroup]");
|
executionContext.Output("##[endgroup]");
|
||||||
|
|
||||||
ArgUtil.NotNull(dockerVersion.ServerVersion, nameof(dockerVersion.ServerVersion));
|
ArgUtil.NotNull(dockerVersion.ServerVersion, nameof(dockerVersion.ServerVersion));
|
||||||
ArgUtil.NotNull(dockerVersion.ClientVersion, nameof(dockerVersion.ClientVersion));
|
ArgUtil.NotNull(dockerVersion.ClientVersion, nameof(dockerVersion.ClientVersion));
|
||||||
|
|
||||||
#if OS_WINDOWS
|
#if OS_WINDOWS
|
||||||
Version requiredDockerEngineAPIVersion = new Version(1, 30); // Docker-EE version 17.6
|
Version requiredDockerEngineAPIVersion = new Version(1, 30); // Docker-EE version 17.6
|
||||||
#else
|
#else
|
||||||
Version requiredDockerEngineAPIVersion = new Version(1, 35); // Docker-CE version 17.12
|
Version requiredDockerEngineAPIVersion = new Version(1, 35); // Docker-CE version 17.12
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (dockerVersion.ServerVersion < requiredDockerEngineAPIVersion)
|
if (dockerVersion.ServerVersion < requiredDockerEngineAPIVersion)
|
||||||
{
|
|
||||||
throw new NotSupportedException($"Min required docker engine API server version is '{requiredDockerEngineAPIVersion}', your docker ('{_dockerManager.DockerPath}') server version is '{dockerVersion.ServerVersion}'");
|
|
||||||
}
|
|
||||||
if (dockerVersion.ClientVersion < requiredDockerEngineAPIVersion)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException($"Min required docker engine API client version is '{requiredDockerEngineAPIVersion}', your docker ('{_dockerManager.DockerPath}') client version is '{dockerVersion.ClientVersion}'");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up containers left by previous runs
|
|
||||||
executionContext.Output("##[group]Clean up resources from previous jobs");
|
|
||||||
var staleContainers = await _dockerManager.DockerPS(executionContext, $"--all --quiet --no-trunc --filter \"label={_dockerManager.DockerInstanceLabel}\"");
|
|
||||||
foreach (var staleContainer in staleContainers)
|
|
||||||
{
|
|
||||||
int containerRemoveExitCode = await _dockerManager.DockerRemove(executionContext, staleContainer);
|
|
||||||
if (containerRemoveExitCode != 0)
|
|
||||||
{
|
{
|
||||||
executionContext.Warning($"Delete stale containers failed, docker rm fail with exit code {containerRemoveExitCode} for container {staleContainer}");
|
throw new NotSupportedException($"Min required docker engine API server version is '{requiredDockerEngineAPIVersion}', your docker ('{_dockerManager.DockerPath}') server version is '{dockerVersion.ServerVersion}'");
|
||||||
|
}
|
||||||
|
if (dockerVersion.ClientVersion < requiredDockerEngineAPIVersion)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException($"Min required docker engine API client version is '{requiredDockerEngineAPIVersion}', your docker ('{_dockerManager.DockerPath}') client version is '{dockerVersion.ClientVersion}'");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
int networkPruneExitCode = await _dockerManager.DockerNetworkPrune(executionContext);
|
// Clean up containers left by previous runs
|
||||||
if (networkPruneExitCode != 0)
|
executionContext.Output("##[group]Clean up resources from previous jobs");
|
||||||
{
|
var staleContainers = await _dockerManager.DockerPS(executionContext, $"--all --quiet --no-trunc --filter \"label={_dockerManager.DockerInstanceLabel}\"");
|
||||||
executionContext.Warning($"Delete stale container networks failed, docker network prune fail with exit code {networkPruneExitCode}");
|
foreach (var staleContainer in staleContainers)
|
||||||
}
|
{
|
||||||
executionContext.Output("##[endgroup]");
|
int containerRemoveExitCode = await _dockerManager.DockerRemove(executionContext, staleContainer);
|
||||||
|
if (containerRemoveExitCode != 0)
|
||||||
|
{
|
||||||
|
executionContext.Warning($"Delete stale containers failed, docker rm fail with exit code {containerRemoveExitCode} for container {staleContainer}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create local docker network for this job to avoid port conflict when multiple runners run on same machine.
|
int networkPruneExitCode = await _dockerManager.DockerNetworkPrune(executionContext);
|
||||||
// All containers within a job join the same network
|
if (networkPruneExitCode != 0)
|
||||||
executionContext.Output("##[group]Create local container network");
|
{
|
||||||
var containerNetwork = $"github_network_{Guid.NewGuid().ToString("N")}";
|
executionContext.Warning($"Delete stale container networks failed, docker network prune fail with exit code {networkPruneExitCode}");
|
||||||
await CreateContainerNetworkAsync(executionContext, containerNetwork);
|
}
|
||||||
executionContext.JobContext.Container["network"] = new StringContextData(containerNetwork);
|
executionContext.Output("##[endgroup]");
|
||||||
executionContext.Output("##[endgroup]");
|
|
||||||
|
|
||||||
foreach (var container in containers)
|
// Create local docker network for this job to avoid port conflict when multiple runners run on same machine.
|
||||||
{
|
// All containers within a job join the same network
|
||||||
container.ContainerNetwork = containerNetwork;
|
executionContext.Output("##[group]Create local container network");
|
||||||
await StartContainerAsync(executionContext, container);
|
var containerNetwork = $"github_network_{Guid.NewGuid().ToString("N")}";
|
||||||
}
|
await CreateContainerNetworkAsync(executionContext, containerNetwork);
|
||||||
|
executionContext.JobContext.Container["network"] = new StringContextData(containerNetwork);
|
||||||
|
executionContext.Output("##[endgroup]");
|
||||||
|
|
||||||
executionContext.Output("##[group]Waiting for all services to be ready");
|
foreach (var container in containers)
|
||||||
foreach (var container in containers.Where(c => !c.IsJobContainer))
|
{
|
||||||
{
|
container.ContainerNetwork = containerNetwork;
|
||||||
await ContainerHealthcheck(executionContext, container);
|
await StartContainerAsync(executionContext, container);
|
||||||
|
}
|
||||||
|
|
||||||
|
executionContext.Output("##[group]Waiting for all services to be ready");
|
||||||
|
foreach (var container in containers.Where(c => !c.IsJobContainer))
|
||||||
|
{
|
||||||
|
await ContainerHealthcheck(executionContext, container);
|
||||||
|
}
|
||||||
|
executionContext.Output("##[endgroup]");
|
||||||
}
|
}
|
||||||
executionContext.Output("##[endgroup]");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task StopContainersAsync(IExecutionContext executionContext, object data)
|
public async Task StopContainersAsync(IExecutionContext executionContext, object data)
|
||||||
@@ -162,12 +336,69 @@ namespace GitHub.Runner.Worker
|
|||||||
List<ContainerInfo> containers = data as List<ContainerInfo>;
|
List<ContainerInfo> containers = data as List<ContainerInfo>;
|
||||||
ArgUtil.NotNull(containers, nameof(containers));
|
ArgUtil.NotNull(containers, nameof(containers));
|
||||||
|
|
||||||
foreach (var container in containers)
|
var podManHandler = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Bin), "kubectlHandler", "index.js");
|
||||||
|
if (File.Exists(podManHandler))
|
||||||
{
|
{
|
||||||
await StopContainerAsync(executionContext, container);
|
var podmanInput = new ContainerEngineHandlerInput()
|
||||||
|
{
|
||||||
|
Command = "Remove",
|
||||||
|
RemoveInput = new ContainersRemoveInput()
|
||||||
|
{
|
||||||
|
Network = executionContext.JobContext.Container["network"].ToString(),
|
||||||
|
JobContainerId = executionContext.JobContext.Container["id"].ToString()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ContainerEngineHandlerOutput podmanOutput = null;
|
||||||
|
using (var processInvoker = HostContext.CreateService<IProcessInvoker>())
|
||||||
|
{
|
||||||
|
var redirectStandardIn = Channel.CreateUnbounded<string>(new UnboundedChannelOptions() { SingleReader = true, SingleWriter = true });
|
||||||
|
redirectStandardIn.Writer.TryWrite(JsonUtility.ToString(podmanInput));
|
||||||
|
|
||||||
|
processInvoker.OutputDataReceived += delegate (object sender, ProcessDataReceivedEventArgs message)
|
||||||
|
{
|
||||||
|
executionContext.Output(message.Data);
|
||||||
|
};
|
||||||
|
|
||||||
|
processInvoker.ErrorDataReceived += delegate (object sender, ProcessDataReceivedEventArgs message)
|
||||||
|
{
|
||||||
|
executionContext.Output(message.Data);
|
||||||
|
if (podmanOutput == null && message.Data.IndexOf("___CONTAINER_ENGINE_HANDLER_OUTPUT___") >= 0)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
podmanOutput = JsonUtility.FromString<ContainerEngineHandlerOutput>(message.Data.Replace("___CONTAINER_ENGINE_HANDLER_OUTPUT___", ""));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
executionContext.Error(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Execute the process. Exit code 0 should always be returned.
|
||||||
|
// A non-zero exit code indicates infrastructural failure.
|
||||||
|
// Task failure should be communicated over STDOUT using ## commands.
|
||||||
|
await processInvoker.ExecuteAsync(workingDirectory: HostContext.GetDirectory(WellKnownDirectory.Work),
|
||||||
|
fileName: Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), "node12", "bin", $"node{IOUtil.ExeExtension}"),
|
||||||
|
arguments: podManHandler,
|
||||||
|
environment: null,
|
||||||
|
requireExitCodeZero: false,
|
||||||
|
outputEncoding: Encoding.UTF8,
|
||||||
|
killProcessOnCancel: false,
|
||||||
|
redirectStandardIn: redirectStandardIn,
|
||||||
|
cancellationToken: executionContext.CancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var container in containers)
|
||||||
|
{
|
||||||
|
await StopContainerAsync(executionContext, container);
|
||||||
|
}
|
||||||
|
// Remove the container network
|
||||||
|
await RemoveContainerNetworkAsync(executionContext, containers.First().ContainerNetwork);
|
||||||
}
|
}
|
||||||
// Remove the container network
|
|
||||||
await RemoveContainerNetworkAsync(executionContext, containers.First().ContainerNetwork);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task StartContainerAsync(IExecutionContext executionContext, ContainerInfo container)
|
private async Task StartContainerAsync(IExecutionContext executionContext, ContainerInfo container)
|
||||||
@@ -494,7 +725,8 @@ namespace GitHub.Runner.Worker
|
|||||||
private void UpdateRegistryAuthForGitHubToken(IExecutionContext executionContext, ContainerInfo container)
|
private void UpdateRegistryAuthForGitHubToken(IExecutionContext executionContext, ContainerInfo container)
|
||||||
{
|
{
|
||||||
var registryIsTokenCompatible = container.RegistryServer.Equals("ghcr.io", StringComparison.OrdinalIgnoreCase) || container.RegistryServer.Equals("containers.pkg.github.com", StringComparison.OrdinalIgnoreCase);
|
var registryIsTokenCompatible = container.RegistryServer.Equals("ghcr.io", StringComparison.OrdinalIgnoreCase) || container.RegistryServer.Equals("containers.pkg.github.com", StringComparison.OrdinalIgnoreCase);
|
||||||
if (!registryIsTokenCompatible)
|
var isFallbackTokenFromHostedGithub = HostContext.GetService<IConfigurationStore>().GetSettings().IsHostedServer;
|
||||||
|
if (!registryIsTokenCompatible || !isFallbackTokenFromHostedGithub)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,9 @@ namespace GitHub.Runner.Worker
|
|||||||
public interface IExecutionContext : IRunnerService
|
public interface IExecutionContext : IRunnerService
|
||||||
{
|
{
|
||||||
Guid Id { get; }
|
Guid Id { get; }
|
||||||
|
Guid EmbeddedId { get; }
|
||||||
string ScopeName { get; }
|
string ScopeName { get; }
|
||||||
|
string SiblingScopeName { get; }
|
||||||
string ContextName { get; }
|
string ContextName { get; }
|
||||||
Task ForceCompleted { get; }
|
Task ForceCompleted { get; }
|
||||||
TaskResult? Result { get; set; }
|
TaskResult? Result { get; set; }
|
||||||
@@ -49,6 +51,8 @@ namespace GitHub.Runner.Worker
|
|||||||
Dictionary<string, string> IntraActionState { get; }
|
Dictionary<string, string> IntraActionState { get; }
|
||||||
Dictionary<string, VariableValue> JobOutputs { get; }
|
Dictionary<string, VariableValue> JobOutputs { get; }
|
||||||
ActionsEnvironmentReference ActionsEnvironment { get; }
|
ActionsEnvironmentReference ActionsEnvironment { get; }
|
||||||
|
List<ActionsStepTelemetry> ActionsStepsTelemetry { get; }
|
||||||
|
List<JobTelemetry> JobTelemetry { get; }
|
||||||
DictionaryContextData ExpressionValues { get; }
|
DictionaryContextData ExpressionValues { get; }
|
||||||
IList<IFunctionInfo> ExpressionFunctions { get; }
|
IList<IFunctionInfo> ExpressionFunctions { get; }
|
||||||
JobContext JobContext { get; }
|
JobContext JobContext { get; }
|
||||||
@@ -58,6 +62,10 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
// Only job level ExecutionContext has PostJobSteps
|
// Only job level ExecutionContext has PostJobSteps
|
||||||
Stack<IStep> PostJobSteps { get; }
|
Stack<IStep> PostJobSteps { get; }
|
||||||
|
HashSet<Guid> EmbeddedStepsWithPostRegistered{ get; }
|
||||||
|
|
||||||
|
// Keep track of embedded steps states
|
||||||
|
Dictionary<Guid, Dictionary<string, string>> EmbeddedIntraActionState { get; }
|
||||||
|
|
||||||
bool EchoOnActionCommand { get; set; }
|
bool EchoOnActionCommand { get; set; }
|
||||||
|
|
||||||
@@ -68,8 +76,8 @@ namespace GitHub.Runner.Worker
|
|||||||
// Initialize
|
// Initialize
|
||||||
void InitializeJob(Pipelines.AgentJobRequestMessage message, CancellationToken token);
|
void InitializeJob(Pipelines.AgentJobRequestMessage message, CancellationToken token);
|
||||||
void CancelToken();
|
void CancelToken();
|
||||||
IExecutionContext CreateChild(Guid recordId, string displayName, string refName, string scopeName, string contextName, Dictionary<string, string> intraActionState = null, int? recordOrder = null, IPagingLogger logger = null, bool isEmbedded = false, CancellationTokenSource cancellationTokenSource = null);
|
IExecutionContext CreateChild(Guid recordId, string displayName, string refName, string scopeName, string contextName, Dictionary<string, string> intraActionState = null, int? recordOrder = null, IPagingLogger logger = null, bool isEmbedded = false, CancellationTokenSource cancellationTokenSource = null, Guid embeddedId = default(Guid), string siblingScopeName = null);
|
||||||
IExecutionContext CreateEmbeddedChild(string scopeName, string contextName);
|
IExecutionContext CreateEmbeddedChild(string scopeName, string contextName, Guid embeddedId, Dictionary<string, string> intraActionState = null, string siblingScopeName = null);
|
||||||
|
|
||||||
// logging
|
// logging
|
||||||
long Write(string tag, string message);
|
long Write(string tag, string message);
|
||||||
@@ -132,7 +140,9 @@ namespace GitHub.Runner.Worker
|
|||||||
private long _totalThrottlingDelayInMilliseconds = 0;
|
private long _totalThrottlingDelayInMilliseconds = 0;
|
||||||
|
|
||||||
public Guid Id => _record.Id;
|
public Guid Id => _record.Id;
|
||||||
|
public Guid EmbeddedId { get; private set; }
|
||||||
public string ScopeName { get; private set; }
|
public string ScopeName { get; private set; }
|
||||||
|
public string SiblingScopeName { get; private set; }
|
||||||
public string ContextName { get; private set; }
|
public string ContextName { get; private set; }
|
||||||
public Task ForceCompleted => _forceCompleted.Task;
|
public Task ForceCompleted => _forceCompleted.Task;
|
||||||
public CancellationToken CancellationToken => _cancellationTokenSource.Token;
|
public CancellationToken CancellationToken => _cancellationTokenSource.Token;
|
||||||
@@ -140,6 +150,8 @@ namespace GitHub.Runner.Worker
|
|||||||
public Dictionary<string, VariableValue> JobOutputs { get; private set; }
|
public Dictionary<string, VariableValue> JobOutputs { get; private set; }
|
||||||
|
|
||||||
public ActionsEnvironmentReference ActionsEnvironment { get; private set; }
|
public ActionsEnvironmentReference ActionsEnvironment { get; private set; }
|
||||||
|
public List<ActionsStepTelemetry> ActionsStepsTelemetry { get; private set; }
|
||||||
|
public List<JobTelemetry> JobTelemetry { get; private set; }
|
||||||
public DictionaryContextData ExpressionValues { get; } = new DictionaryContextData();
|
public DictionaryContextData ExpressionValues { get; } = new DictionaryContextData();
|
||||||
public IList<IFunctionInfo> ExpressionFunctions { get; } = new List<IFunctionInfo>();
|
public IList<IFunctionInfo> ExpressionFunctions { get; } = new List<IFunctionInfo>();
|
||||||
|
|
||||||
@@ -155,6 +167,11 @@ namespace GitHub.Runner.Worker
|
|||||||
// Only job level ExecutionContext has StepsWithPostRegistered
|
// Only job level ExecutionContext has StepsWithPostRegistered
|
||||||
public HashSet<Guid> StepsWithPostRegistered { get; private set; }
|
public HashSet<Guid> StepsWithPostRegistered { get; private set; }
|
||||||
|
|
||||||
|
// Only job level ExecutionContext has EmbeddedStepsWithPostRegistered
|
||||||
|
public HashSet<Guid> EmbeddedStepsWithPostRegistered { get; private set; }
|
||||||
|
|
||||||
|
public Dictionary<Guid, Dictionary<string, string>> EmbeddedIntraActionState { get; private set; }
|
||||||
|
|
||||||
public bool EchoOnActionCommand { get; set; }
|
public bool EchoOnActionCommand { get; set; }
|
||||||
|
|
||||||
// An embedded execution context shares the same record ID, record name, and logger
|
// An embedded execution context shares the same record ID, record name, and logger
|
||||||
@@ -245,17 +262,30 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
public void RegisterPostJobStep(IStep step)
|
public void RegisterPostJobStep(IStep step)
|
||||||
{
|
{
|
||||||
if (step is IActionRunner actionRunner && !Root.StepsWithPostRegistered.Add(actionRunner.Action.Id))
|
string siblingScopeName = null;
|
||||||
|
if (this.IsEmbedded)
|
||||||
|
{
|
||||||
|
if (step is IActionRunner actionRunner && !Root.EmbeddedStepsWithPostRegistered.Add(actionRunner.Action.Id))
|
||||||
|
{
|
||||||
|
Trace.Info($"'post' of '{actionRunner.DisplayName}' already push to child post step stack.");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (step is IActionRunner actionRunner && !Root.StepsWithPostRegistered.Add(actionRunner.Action.Id))
|
||||||
{
|
{
|
||||||
Trace.Info($"'post' of '{actionRunner.DisplayName}' already push to post step stack.");
|
Trace.Info($"'post' of '{actionRunner.DisplayName}' already push to post step stack.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (step is IActionRunner runner)
|
||||||
|
{
|
||||||
|
siblingScopeName = runner.Action.ContextName;
|
||||||
|
}
|
||||||
|
|
||||||
step.ExecutionContext = Root.CreatePostChild(step.DisplayName, IntraActionState);
|
step.ExecutionContext = Root.CreatePostChild(step.DisplayName, IntraActionState, siblingScopeName);
|
||||||
Root.PostJobSteps.Push(step);
|
Root.PostJobSteps.Push(step);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IExecutionContext CreateChild(Guid recordId, string displayName, string refName, string scopeName, string contextName, Dictionary<string, string> intraActionState = null, int? recordOrder = null, IPagingLogger logger = null, bool isEmbedded = false, CancellationTokenSource cancellationTokenSource = null)
|
public IExecutionContext CreateChild(Guid recordId, string displayName, string refName, string scopeName, string contextName, Dictionary<string, string> intraActionState = null, int? recordOrder = null, IPagingLogger logger = null, bool isEmbedded = false, CancellationTokenSource cancellationTokenSource = null, Guid embeddedId = default(Guid), string siblingScopeName = null)
|
||||||
{
|
{
|
||||||
Trace.Entering();
|
Trace.Entering();
|
||||||
|
|
||||||
@@ -264,6 +294,9 @@ namespace GitHub.Runner.Worker
|
|||||||
child.Global = Global;
|
child.Global = Global;
|
||||||
child.ScopeName = scopeName;
|
child.ScopeName = scopeName;
|
||||||
child.ContextName = contextName;
|
child.ContextName = contextName;
|
||||||
|
child.EmbeddedId = embeddedId;
|
||||||
|
child.SiblingScopeName = siblingScopeName;
|
||||||
|
child.JobTelemetry = JobTelemetry;
|
||||||
if (intraActionState == null)
|
if (intraActionState == null)
|
||||||
{
|
{
|
||||||
child.IntraActionState = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
child.IntraActionState = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
@@ -311,9 +344,9 @@ namespace GitHub.Runner.Worker
|
|||||||
/// An embedded execution context shares the same record ID, record name, logger,
|
/// An embedded execution context shares the same record ID, record name, logger,
|
||||||
/// and a linked cancellation token.
|
/// and a linked cancellation token.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IExecutionContext CreateEmbeddedChild(string scopeName, string contextName)
|
public IExecutionContext CreateEmbeddedChild(string scopeName, string contextName, Guid embeddedId, Dictionary<string, string> intraActionState = null, string siblingScopeName = null)
|
||||||
{
|
{
|
||||||
return Root.CreateChild(_record.Id, _record.Name, _record.Id.ToString("N"), scopeName, contextName, logger: _logger, isEmbedded: true, cancellationTokenSource: CancellationTokenSource.CreateLinkedTokenSource(_cancellationTokenSource.Token));
|
return Root.CreateChild(_record.Id, _record.Name, _record.Id.ToString("N"), scopeName, contextName, logger: _logger, isEmbedded: true, cancellationTokenSource: CancellationTokenSource.CreateLinkedTokenSource(_cancellationTokenSource.Token), intraActionState: intraActionState, embeddedId: embeddedId, siblingScopeName: siblingScopeName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Start(string currentOperation = null)
|
public void Start(string currentOperation = null)
|
||||||
@@ -369,7 +402,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
_logger.End();
|
_logger.End();
|
||||||
|
|
||||||
// Skip if generated context name. Generated context names start with "__". After M271-ish the server will never send an empty context name.
|
// Skip if generated context name. Generated context names start with "__". After 3.2 the server will never send an empty context name.
|
||||||
if (!string.IsNullOrEmpty(ContextName) && !ContextName.StartsWith("__", StringComparison.Ordinal))
|
if (!string.IsNullOrEmpty(ContextName) && !ContextName.StartsWith("__", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
Global.StepsContext.SetOutcome(ScopeName, ContextName, (Outcome ?? Result ?? TaskResult.Succeeded).ToActionResult());
|
Global.StepsContext.SetOutcome(ScopeName, ContextName, (Outcome ?? Result ?? TaskResult.Succeeded).ToActionResult());
|
||||||
@@ -432,7 +465,7 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
ArgUtil.NotNullOrEmpty(name, nameof(name));
|
ArgUtil.NotNullOrEmpty(name, nameof(name));
|
||||||
|
|
||||||
// Skip if generated context name. Generated context names start with "__". After M271-ish the server will never send an empty context name.
|
// Skip if generated context name. Generated context names start with "__". After 3.2 the server will never send an empty context name.
|
||||||
if (string.IsNullOrEmpty(ContextName) || ContextName.StartsWith("__", StringComparison.Ordinal))
|
if (string.IsNullOrEmpty(ContextName) || ContextName.StartsWith("__", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
reference = null;
|
reference = null;
|
||||||
@@ -511,6 +544,24 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
_record.WarningCount++;
|
_record.WarningCount++;
|
||||||
}
|
}
|
||||||
|
else if (issue.Type == IssueType.Notice)
|
||||||
|
{
|
||||||
|
|
||||||
|
// tracking line number for each issue in log file
|
||||||
|
// log UI use this to navigate from issue to log
|
||||||
|
if (!string.IsNullOrEmpty(logMessage))
|
||||||
|
{
|
||||||
|
long logLineNumber = Write(WellKnownTags.Notice, logMessage);
|
||||||
|
issue.Data["logFileLineNumber"] = logLineNumber.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_record.NoticeCount < _maxIssueCount)
|
||||||
|
{
|
||||||
|
_record.Issues.Add(issue);
|
||||||
|
}
|
||||||
|
|
||||||
|
_record.NoticeCount++;
|
||||||
|
}
|
||||||
|
|
||||||
_jobServerQueue.QueueTimelineRecordUpdate(_mainTimelineId, _record);
|
_jobServerQueue.QueueTimelineRecordUpdate(_mainTimelineId, _record);
|
||||||
}
|
}
|
||||||
@@ -599,6 +650,11 @@ namespace GitHub.Runner.Worker
|
|||||||
// Actions environment
|
// Actions environment
|
||||||
ActionsEnvironment = message.ActionsEnvironment;
|
ActionsEnvironment = message.ActionsEnvironment;
|
||||||
|
|
||||||
|
// ActionsStepTelemetry
|
||||||
|
ActionsStepsTelemetry = new List<ActionsStepTelemetry>();
|
||||||
|
|
||||||
|
JobTelemetry = new List<JobTelemetry>();
|
||||||
|
|
||||||
// Service container info
|
// Service container info
|
||||||
Global.ServiceContainers = new List<ContainerInfo>();
|
Global.ServiceContainers = new List<ContainerInfo>();
|
||||||
|
|
||||||
@@ -659,6 +715,12 @@ namespace GitHub.Runner.Worker
|
|||||||
// StepsWithPostRegistered for job ExecutionContext
|
// StepsWithPostRegistered for job ExecutionContext
|
||||||
StepsWithPostRegistered = new HashSet<Guid>();
|
StepsWithPostRegistered = new HashSet<Guid>();
|
||||||
|
|
||||||
|
// EmbeddedStepsWithPostRegistered for job ExecutionContext
|
||||||
|
EmbeddedStepsWithPostRegistered = new HashSet<Guid>();
|
||||||
|
|
||||||
|
// EmbeddedIntraActionState for job ExecutionContext
|
||||||
|
EmbeddedIntraActionState = new Dictionary<Guid, Dictionary<string,string>>();
|
||||||
|
|
||||||
// Job timeline record.
|
// Job timeline record.
|
||||||
InitializeTimelineRecord(
|
InitializeTimelineRecord(
|
||||||
timelineId: message.Timeline.Id,
|
timelineId: message.Timeline.Id,
|
||||||
@@ -835,6 +897,7 @@ namespace GitHub.Runner.Worker
|
|||||||
_record.State = TimelineRecordState.Pending;
|
_record.State = TimelineRecordState.Pending;
|
||||||
_record.ErrorCount = 0;
|
_record.ErrorCount = 0;
|
||||||
_record.WarningCount = 0;
|
_record.WarningCount = 0;
|
||||||
|
_record.NoticeCount = 0;
|
||||||
|
|
||||||
if (parentTimelineRecordId != null && parentTimelineRecordId.Value != Guid.Empty)
|
if (parentTimelineRecordId != null && parentTimelineRecordId.Value != Guid.Empty)
|
||||||
{
|
{
|
||||||
@@ -864,7 +927,7 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IExecutionContext CreatePostChild(string displayName, Dictionary<string, string> intraActionState)
|
private IExecutionContext CreatePostChild(string displayName, Dictionary<string, string> intraActionState, string siblingScopeName = null)
|
||||||
{
|
{
|
||||||
if (!_expandedForPostJob)
|
if (!_expandedForPostJob)
|
||||||
{
|
{
|
||||||
@@ -874,7 +937,7 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
|
|
||||||
var newGuid = Guid.NewGuid();
|
var newGuid = Guid.NewGuid();
|
||||||
return CreateChild(newGuid, displayName, newGuid.ToString("N"), null, null, intraActionState, _childTimelineRecordOrder - Root.PostJobSteps.Count);
|
return CreateChild(newGuid, displayName, newGuid.ToString("N"), null, null, intraActionState, _childTimelineRecordOrder - Root.PostJobSteps.Count, siblingScopeName: siblingScopeName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1006,6 +1069,7 @@ namespace GitHub.Runner.Worker
|
|||||||
public static readonly string Command = "##[command]";
|
public static readonly string Command = "##[command]";
|
||||||
public static readonly string Error = "##[error]";
|
public static readonly string Error = "##[error]";
|
||||||
public static readonly string Warning = "##[warning]";
|
public static readonly string Warning = "##[warning]";
|
||||||
|
public static readonly string Notice = "##[notice]";
|
||||||
public static readonly string Debug = "##[debug]";
|
public static readonly string Debug = "##[debug]";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,9 +23,13 @@ namespace GitHub.Runner.Worker
|
|||||||
"job",
|
"job",
|
||||||
"path",
|
"path",
|
||||||
"ref",
|
"ref",
|
||||||
|
"ref_name",
|
||||||
|
"ref_protected",
|
||||||
|
"ref_type",
|
||||||
"repository",
|
"repository",
|
||||||
"repository_owner",
|
"repository_owner",
|
||||||
"retention_days",
|
"retention_days",
|
||||||
|
"run_attempt",
|
||||||
"run_id",
|
"run_id",
|
||||||
"run_number",
|
"run_number",
|
||||||
"server_url",
|
"server_url",
|
||||||
@@ -38,9 +42,16 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
foreach (var data in this)
|
foreach (var data in this)
|
||||||
{
|
{
|
||||||
if (_contextEnvAllowlist.Contains(data.Key) && data.Value is StringContextData value)
|
if (_contextEnvAllowlist.Contains(data.Key))
|
||||||
{
|
{
|
||||||
yield return new KeyValuePair<string, string>($"GITHUB_{data.Key.ToUpperInvariant()}", value);
|
if (data.Value is StringContextData value)
|
||||||
|
{
|
||||||
|
yield return new KeyValuePair<string, string>($"GITHUB_{data.Key.ToUpperInvariant()}", value);
|
||||||
|
}
|
||||||
|
else if (data.Value is BooleanContextData booleanValue)
|
||||||
|
{
|
||||||
|
yield return new KeyValuePair<string, string>($"GITHUB_{data.Key.ToUpperInvariant()}", booleanValue.ToString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,11 +5,16 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using GitHub.DistributedTask.Expressions2;
|
||||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||||
using GitHub.DistributedTask.Pipelines.ContextData;
|
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||||
|
using GitHub.DistributedTask.Pipelines.ObjectTemplating;
|
||||||
using GitHub.DistributedTask.WebApi;
|
using GitHub.DistributedTask.WebApi;
|
||||||
using GitHub.Runner.Common;
|
using GitHub.Runner.Common;
|
||||||
|
using GitHub.Runner.Common.Util;
|
||||||
using GitHub.Runner.Sdk;
|
using GitHub.Runner.Sdk;
|
||||||
|
using GitHub.Runner.Worker;
|
||||||
|
using GitHub.Runner.Worker.Expressions;
|
||||||
using Pipelines = GitHub.DistributedTask.Pipelines;
|
using Pipelines = GitHub.DistributedTask.Pipelines;
|
||||||
|
|
||||||
|
|
||||||
@@ -30,7 +35,66 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
Trace.Entering();
|
Trace.Entering();
|
||||||
ArgUtil.NotNull(ExecutionContext, nameof(ExecutionContext));
|
ArgUtil.NotNull(ExecutionContext, nameof(ExecutionContext));
|
||||||
ArgUtil.NotNull(Inputs, nameof(Inputs));
|
ArgUtil.NotNull(Inputs, nameof(Inputs));
|
||||||
ArgUtil.NotNull(Data.Steps, nameof(Data.Steps));
|
|
||||||
|
List<Pipelines.ActionStep> steps;
|
||||||
|
|
||||||
|
if (stage == ActionRunStage.Pre)
|
||||||
|
{
|
||||||
|
ArgUtil.NotNull(Data.PreSteps, nameof(Data.PreSteps));
|
||||||
|
steps = Data.PreSteps;
|
||||||
|
}
|
||||||
|
else if (stage == ActionRunStage.Post)
|
||||||
|
{
|
||||||
|
ArgUtil.NotNull(Data.PostSteps, nameof(Data.PostSteps));
|
||||||
|
steps = new List<Pipelines.ActionStep>();
|
||||||
|
// Only register post steps for steps that actually ran
|
||||||
|
foreach (var step in Data.PostSteps.ToList())
|
||||||
|
{
|
||||||
|
if (ExecutionContext.Root.EmbeddedStepsWithPostRegistered.Contains(step.Id))
|
||||||
|
{
|
||||||
|
steps.Add(step);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Trace.Info($"Skipping executing post step id: {step.Id}, name: ${step.DisplayName}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ArgUtil.NotNull(Data.Steps, nameof(Data.Steps));
|
||||||
|
steps = Data.Steps;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add Telemetry to JobContext to send with JobCompleteMessage
|
||||||
|
if (stage == ActionRunStage.Main)
|
||||||
|
{
|
||||||
|
var hasRunsStep = false;
|
||||||
|
var hasUsesStep = false;
|
||||||
|
foreach (var step in steps)
|
||||||
|
{
|
||||||
|
if (step.Reference.Type == Pipelines.ActionSourceType.Script)
|
||||||
|
{
|
||||||
|
hasRunsStep = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
hasUsesStep = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var pathReference = Action as Pipelines.RepositoryPathReference;
|
||||||
|
var telemetry = new ActionsStepTelemetry {
|
||||||
|
Ref = GetActionRef(),
|
||||||
|
HasPreStep = Data.HasPre,
|
||||||
|
HasPostStep = Data.HasPost,
|
||||||
|
IsEmbedded = ExecutionContext.IsEmbedded,
|
||||||
|
Type = "composite",
|
||||||
|
HasRunsStep = hasRunsStep,
|
||||||
|
HasUsesStep = hasUsesStep,
|
||||||
|
StepCount = steps.Count
|
||||||
|
};
|
||||||
|
ExecutionContext.Root.ActionsStepsTelemetry.Add(telemetry);
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -41,7 +105,7 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
inputsData[i.Key] = new StringContextData(i.Value);
|
inputsData[i.Key] = new StringContextData(i.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Temporary hack until after M271-ish. After M271-ish the server will never send an empty
|
// Temporary hack until after 3.2. After 3.2 the server will never send an empty
|
||||||
// context name. Generated context names start with "__"
|
// context name. Generated context names start with "__"
|
||||||
var childScopeName = ExecutionContext.GetFullyQualifiedContextName();
|
var childScopeName = ExecutionContext.GetFullyQualifiedContextName();
|
||||||
if (string.IsNullOrEmpty(childScopeName))
|
if (string.IsNullOrEmpty(childScopeName))
|
||||||
@@ -51,15 +115,44 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
|
|
||||||
// Create embedded steps
|
// Create embedded steps
|
||||||
var embeddedSteps = new List<IStep>();
|
var embeddedSteps = new List<IStep>();
|
||||||
foreach (Pipelines.ActionStep stepData in Data.Steps)
|
|
||||||
|
// If we need to setup containers beforehand, do it
|
||||||
|
// only relevant for local composite actions that need to JIT download/setup containers
|
||||||
|
if (LocalActionContainerSetupSteps != null && LocalActionContainerSetupSteps.Count > 0)
|
||||||
{
|
{
|
||||||
|
foreach (var step in LocalActionContainerSetupSteps)
|
||||||
|
{
|
||||||
|
ArgUtil.NotNull(step, step.DisplayName);
|
||||||
|
var stepId = $"__{Guid.NewGuid()}";
|
||||||
|
step.ExecutionContext = ExecutionContext.CreateEmbeddedChild(childScopeName, stepId, Guid.NewGuid());
|
||||||
|
embeddedSteps.Add(step);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach (Pipelines.ActionStep stepData in steps)
|
||||||
|
{
|
||||||
|
// Compute child sibling scope names for post steps
|
||||||
|
// We need to use the main's scope to keep step context correct, makes inputs flow correctly
|
||||||
|
string siblingScopeName = null;
|
||||||
|
if (!String.IsNullOrEmpty(ExecutionContext.SiblingScopeName) && stage == ActionRunStage.Post)
|
||||||
|
{
|
||||||
|
siblingScopeName = $"{ExecutionContext.SiblingScopeName}.{stepData.ContextName}";
|
||||||
|
}
|
||||||
|
|
||||||
var step = HostContext.CreateService<IActionRunner>();
|
var step = HostContext.CreateService<IActionRunner>();
|
||||||
step.Action = stepData;
|
step.Action = stepData;
|
||||||
step.Stage = stage;
|
step.Stage = stage;
|
||||||
step.Condition = stepData.Condition;
|
step.Condition = stepData.Condition;
|
||||||
step.ExecutionContext = ExecutionContext.CreateEmbeddedChild(childScopeName, stepData.ContextName);
|
ExecutionContext.Root.EmbeddedIntraActionState.TryGetValue(step.Action.Id, out var intraActionState);
|
||||||
|
step.ExecutionContext = ExecutionContext.CreateEmbeddedChild(childScopeName, stepData.ContextName, step.Action.Id, intraActionState: intraActionState, siblingScopeName: siblingScopeName);
|
||||||
step.ExecutionContext.ExpressionValues["inputs"] = inputsData;
|
step.ExecutionContext.ExpressionValues["inputs"] = inputsData;
|
||||||
step.ExecutionContext.ExpressionValues["steps"] = ExecutionContext.Global.StepsContext.GetScope(childScopeName);
|
if (!String.IsNullOrEmpty(ExecutionContext.SiblingScopeName))
|
||||||
|
{
|
||||||
|
step.ExecutionContext.ExpressionValues["steps"] = ExecutionContext.Global.StepsContext.GetScope(ExecutionContext.SiblingScopeName);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
step.ExecutionContext.ExpressionValues["steps"] = ExecutionContext.Global.StepsContext.GetScope(childScopeName);
|
||||||
|
}
|
||||||
|
|
||||||
// Shallow copy github context
|
// Shallow copy github context
|
||||||
var gitHubContext = step.ExecutionContext.ExpressionValues["github"] as GitHubContext;
|
var gitHubContext = step.ExecutionContext.ExpressionValues["github"] as GitHubContext;
|
||||||
@@ -74,13 +167,12 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Run embedded steps
|
// Run embedded steps
|
||||||
await RunStepsAsync(embeddedSteps);
|
await RunStepsAsync(embeddedSteps, stage);
|
||||||
|
|
||||||
// Set outputs
|
// Set outputs
|
||||||
ExecutionContext.ExpressionValues["inputs"] = inputsData;
|
ExecutionContext.ExpressionValues["inputs"] = inputsData;
|
||||||
ExecutionContext.ExpressionValues["steps"] = ExecutionContext.Global.StepsContext.GetScope(childScopeName);
|
ExecutionContext.ExpressionValues["steps"] = ExecutionContext.Global.StepsContext.GetScope(childScopeName);
|
||||||
ProcessOutputs();
|
ProcessOutputs();
|
||||||
ExecutionContext.Global.StepsContext.ClearScope(childScopeName);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -134,7 +226,7 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RunStepsAsync(List<IStep> embeddedSteps)
|
private async Task RunStepsAsync(List<IStep> embeddedSteps, ActionRunStage stage)
|
||||||
{
|
{
|
||||||
ArgUtil.NotNull(embeddedSteps, nameof(embeddedSteps));
|
ArgUtil.NotNull(embeddedSteps, nameof(embeddedSteps));
|
||||||
|
|
||||||
@@ -142,6 +234,13 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
{
|
{
|
||||||
Trace.Info($"Processing embedded step: DisplayName='{step.DisplayName}'");
|
Trace.Info($"Processing embedded step: DisplayName='{step.DisplayName}'");
|
||||||
|
|
||||||
|
// Add Expression Functions
|
||||||
|
step.ExecutionContext.ExpressionFunctions.Add(new FunctionInfo<HashFilesFunction>(PipelineTemplateConstants.HashFiles, 1, byte.MaxValue));
|
||||||
|
step.ExecutionContext.ExpressionFunctions.Add(new FunctionInfo<AlwaysFunction>(PipelineTemplateConstants.Always, 0, 0));
|
||||||
|
step.ExecutionContext.ExpressionFunctions.Add(new FunctionInfo<CancelledFunction>(PipelineTemplateConstants.Cancelled, 0, 0));
|
||||||
|
step.ExecutionContext.ExpressionFunctions.Add(new FunctionInfo<FailureFunction>(PipelineTemplateConstants.Failure, 0, 0));
|
||||||
|
step.ExecutionContext.ExpressionFunctions.Add(new FunctionInfo<SuccessFunction>(PipelineTemplateConstants.Success, 0, 0));
|
||||||
|
|
||||||
// Initialize env context
|
// Initialize env context
|
||||||
Trace.Info("Initialize Env context for embedded step");
|
Trace.Info("Initialize Env context for embedded step");
|
||||||
#if OS_WINDOWS
|
#if OS_WINDOWS
|
||||||
@@ -171,16 +270,17 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var actionStep = step as IActionRunner;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Evaluate and merge embedded-step env
|
if (step is IActionRunner actionStep)
|
||||||
var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator();
|
|
||||||
var actionEnvironment = templateEvaluator.EvaluateStepEnvironment(actionStep.Action.Environment, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions, Common.Util.VarUtil.EnvironmentVariableKeyComparer);
|
|
||||||
foreach (var env in actionEnvironment)
|
|
||||||
{
|
{
|
||||||
envContext[env.Key] = new StringContextData(env.Value ?? string.Empty);
|
// Evaluate and merge embedded-step env
|
||||||
|
var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator();
|
||||||
|
var actionEnvironment = templateEvaluator.EvaluateStepEnvironment(actionStep.Action.Environment, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions, Common.Util.VarUtil.EnvironmentVariableKeyComparer);
|
||||||
|
foreach (var env in actionEnvironment)
|
||||||
|
{
|
||||||
|
envContext[env.Key] = new StringContextData(env.Value ?? string.Empty);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -191,13 +291,133 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
step.ExecutionContext.Complete(TaskResult.Failed);
|
step.ExecutionContext.Complete(TaskResult.Failed);
|
||||||
}
|
}
|
||||||
|
|
||||||
await RunStepAsync(step);
|
// Register Callback
|
||||||
|
CancellationTokenRegistration? jobCancelRegister = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// For main steps just run the action
|
||||||
|
if (stage == ActionRunStage.Main)
|
||||||
|
{
|
||||||
|
await RunStepAsync(step);
|
||||||
|
}
|
||||||
|
// We need to evaluate conditions for pre/post steps
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Register job cancellation call back only if job cancellation token not been fire before each step run
|
||||||
|
if (!ExecutionContext.Root.CancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
// Test the condition again. The job was canceled after the condition was originally evaluated.
|
||||||
|
jobCancelRegister = ExecutionContext.Root.CancellationToken.Register(() =>
|
||||||
|
{
|
||||||
|
// Mark job as cancelled
|
||||||
|
ExecutionContext.Root.Result = TaskResult.Canceled;
|
||||||
|
ExecutionContext.Root.JobContext.Status = ExecutionContext.Root.Result?.ToActionResult();
|
||||||
|
|
||||||
|
step.ExecutionContext.Debug($"Re-evaluate condition on job cancellation for step: '{step.DisplayName}'.");
|
||||||
|
var conditionReTestTraceWriter = new ConditionTraceWriter(Trace, null); // host tracing only
|
||||||
|
var conditionReTestResult = false;
|
||||||
|
if (HostContext.RunnerShutdownToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
step.ExecutionContext.Debug($"Skip Re-evaluate condition on runner shutdown.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator(conditionReTestTraceWriter);
|
||||||
|
var condition = new BasicExpressionToken(null, null, null, step.Condition);
|
||||||
|
conditionReTestResult = templateEvaluator.EvaluateStepIf(condition, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions, step.ExecutionContext.ToExpressionState());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Cancel the step since we get exception while re-evaluate step condition
|
||||||
|
Trace.Info("Caught exception from expression when re-test condition on job cancellation.");
|
||||||
|
step.ExecutionContext.Error(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!conditionReTestResult)
|
||||||
|
{
|
||||||
|
// Cancel the step
|
||||||
|
Trace.Info("Cancel current running step.");
|
||||||
|
step.ExecutionContext.CancelToken();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (ExecutionContext.Root.Result != TaskResult.Canceled)
|
||||||
|
{
|
||||||
|
// Mark job as cancelled
|
||||||
|
ExecutionContext.Root.Result = TaskResult.Canceled;
|
||||||
|
ExecutionContext.Root.JobContext.Status = ExecutionContext.Root.Result?.ToActionResult();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Evaluate condition
|
||||||
|
step.ExecutionContext.Debug($"Evaluating condition for step: '{step.DisplayName}'");
|
||||||
|
var conditionTraceWriter = new ConditionTraceWriter(Trace, step.ExecutionContext);
|
||||||
|
var conditionResult = false;
|
||||||
|
var conditionEvaluateError = default(Exception);
|
||||||
|
if (HostContext.RunnerShutdownToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
step.ExecutionContext.Debug($"Skip evaluate condition on runner shutdown.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator(conditionTraceWriter);
|
||||||
|
var condition = new BasicExpressionToken(null, null, null, step.Condition);
|
||||||
|
conditionResult = templateEvaluator.EvaluateStepIf(condition, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions, step.ExecutionContext.ToExpressionState());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Trace.Info("Caught exception from expression.");
|
||||||
|
Trace.Error(ex);
|
||||||
|
conditionEvaluateError = ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!conditionResult && conditionEvaluateError == null)
|
||||||
|
{
|
||||||
|
// Condition is false
|
||||||
|
Trace.Info("Skipping step due to condition evaluation.");
|
||||||
|
step.ExecutionContext.Result = TaskResult.Skipped;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (conditionEvaluateError != null)
|
||||||
|
{
|
||||||
|
// Condition error
|
||||||
|
step.ExecutionContext.Error(conditionEvaluateError);
|
||||||
|
step.ExecutionContext.Result = TaskResult.Failed;
|
||||||
|
ExecutionContext.Result = TaskResult.Failed;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await RunStepAsync(step);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (jobCancelRegister != null)
|
||||||
|
{
|
||||||
|
jobCancelRegister?.Dispose();
|
||||||
|
jobCancelRegister = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check failed or canceled
|
// Check failed or canceled
|
||||||
if (step.ExecutionContext.Result == TaskResult.Failed || step.ExecutionContext.Result == TaskResult.Canceled)
|
if (step.ExecutionContext.Result == TaskResult.Failed || step.ExecutionContext.Result == TaskResult.Canceled)
|
||||||
{
|
{
|
||||||
ExecutionContext.Result = step.ExecutionContext.Result;
|
Trace.Info($"Update job result with current composite step result '{step.ExecutionContext.Result}'.");
|
||||||
break;
|
ExecutionContext.Result = TaskResultUtil.MergeTaskResults(ExecutionContext.Result, step.ExecutionContext.Result.Value);
|
||||||
|
|
||||||
|
// We should run cleanup even if one of the cleanup step fails
|
||||||
|
if (stage != ActionRunStage.Post)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System;
|
using System;
|
||||||
@@ -69,6 +69,20 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
Data.Image = imageName;
|
Data.Image = imageName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string type = Action.Type == Pipelines.ActionSourceType.Repository ? "Dockerfile" : "DockerHub";
|
||||||
|
// Add Telemetry to JobContext to send with JobCompleteMessage
|
||||||
|
if (stage == ActionRunStage.Main)
|
||||||
|
{
|
||||||
|
var telemetry = new ActionsStepTelemetry {
|
||||||
|
Ref = GetActionRef(),
|
||||||
|
HasPreStep = Data.HasPre,
|
||||||
|
HasPostStep = Data.HasPost,
|
||||||
|
IsEmbedded = ExecutionContext.IsEmbedded,
|
||||||
|
Type = type
|
||||||
|
};
|
||||||
|
ExecutionContext.Root.ActionsStepsTelemetry.Add(telemetry);
|
||||||
|
}
|
||||||
|
|
||||||
// run container
|
// run container
|
||||||
var container = new ContainerInfo(HostContext)
|
var container = new ContainerInfo(HostContext)
|
||||||
{
|
{
|
||||||
@@ -200,6 +214,11 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
{
|
{
|
||||||
Environment["ACTIONS_CACHE_URL"] = cacheUrl;
|
Environment["ACTIONS_CACHE_URL"] = cacheUrl;
|
||||||
}
|
}
|
||||||
|
if (systemConnection.Data.TryGetValue("GenerateIdTokenUrl", out var generateIdTokenUrl) && !string.IsNullOrEmpty(generateIdTokenUrl))
|
||||||
|
{
|
||||||
|
Environment["ACTIONS_ID_TOKEN_REQUEST_URL"] = generateIdTokenUrl;
|
||||||
|
Environment["ACTIONS_ID_TOKEN_REQUEST_TOKEN"] = systemConnection.Authorization.Parameters[EndpointAuthorizationParameters.AccessToken];
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var variable in this.Environment)
|
foreach (var variable in this.Environment)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
IStepHost StepHost { get; set; }
|
IStepHost StepHost { get; set; }
|
||||||
Dictionary<string, string> Inputs { get; set; }
|
Dictionary<string, string> Inputs { get; set; }
|
||||||
string ActionDirectory { get; set; }
|
string ActionDirectory { get; set; }
|
||||||
|
List<JobExtensionRunner> LocalActionContainerSetupSteps { get; set; }
|
||||||
Task RunAsync(ActionRunStage stage);
|
Task RunAsync(ActionRunStage stage);
|
||||||
void PrintActionDetails(ActionRunStage stage);
|
void PrintActionDetails(ActionRunStage stage);
|
||||||
}
|
}
|
||||||
@@ -41,9 +42,44 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
public IStepHost StepHost { get; set; }
|
public IStepHost StepHost { get; set; }
|
||||||
public Dictionary<string, string> Inputs { get; set; }
|
public Dictionary<string, string> Inputs { get; set; }
|
||||||
public string ActionDirectory { get; set; }
|
public string ActionDirectory { get; set; }
|
||||||
|
public List<JobExtensionRunner> LocalActionContainerSetupSteps { get; set; }
|
||||||
|
|
||||||
|
public virtual string GetActionRef()
|
||||||
|
{
|
||||||
|
if (Action.Type == Pipelines.ActionSourceType.ContainerRegistry)
|
||||||
|
{
|
||||||
|
var registryAction = Action as Pipelines.ContainerRegistryReference;
|
||||||
|
return registryAction.Image;
|
||||||
|
}
|
||||||
|
else if (Action.Type == Pipelines.ActionSourceType.Repository)
|
||||||
|
{
|
||||||
|
var repoAction = Action as Pipelines.RepositoryPathReference;
|
||||||
|
if (string.Equals(repoAction.RepositoryType, Pipelines.PipelineConstants.SelfAlias, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return repoAction.Path;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(repoAction.Path))
|
||||||
|
{
|
||||||
|
return $"{repoAction.Name}@{repoAction.Ref}";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $"{repoAction.Name}/{repoAction.Path}@{repoAction.Ref}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// this should never happen
|
||||||
|
Trace.Error($"Can't generate ref for {Action.Type.ToString()}");
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
public virtual void PrintActionDetails(ActionRunStage stage)
|
public virtual void PrintActionDetails(ActionRunStage stage)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (stage == ActionRunStage.Post)
|
if (stage == ActionRunStage.Post)
|
||||||
{
|
{
|
||||||
ExecutionContext.Output($"Post job cleanup.");
|
ExecutionContext.Output($"Post job cleanup.");
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
Dictionary<string, string> inputs,
|
Dictionary<string, string> inputs,
|
||||||
Dictionary<string, string> environment,
|
Dictionary<string, string> environment,
|
||||||
Variables runtimeVariables,
|
Variables runtimeVariables,
|
||||||
string actionDirectory);
|
string actionDirectory,
|
||||||
|
List<JobExtensionRunner> localActionContainerSetupSteps);
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class HandlerFactory : RunnerService, IHandlerFactory
|
public sealed class HandlerFactory : RunnerService, IHandlerFactory
|
||||||
@@ -32,7 +33,8 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
Dictionary<string, string> inputs,
|
Dictionary<string, string> inputs,
|
||||||
Dictionary<string, string> environment,
|
Dictionary<string, string> environment,
|
||||||
Variables runtimeVariables,
|
Variables runtimeVariables,
|
||||||
string actionDirectory)
|
string actionDirectory,
|
||||||
|
List<JobExtensionRunner> localActionContainerSetupSteps)
|
||||||
{
|
{
|
||||||
// Validate args.
|
// Validate args.
|
||||||
Trace.Entering();
|
Trace.Entering();
|
||||||
@@ -84,6 +86,7 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
handler.StepHost = stepHost;
|
handler.StepHost = stepHost;
|
||||||
handler.Inputs = inputs;
|
handler.Inputs = inputs;
|
||||||
handler.ActionDirectory = actionDirectory;
|
handler.ActionDirectory = actionDirectory;
|
||||||
|
handler.LocalActionContainerSetupSteps = localActionContainerSetupSteps;
|
||||||
return handler;
|
return handler;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,11 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
{
|
{
|
||||||
Environment["ACTIONS_CACHE_URL"] = cacheUrl;
|
Environment["ACTIONS_CACHE_URL"] = cacheUrl;
|
||||||
}
|
}
|
||||||
|
if (systemConnection.Data.TryGetValue("GenerateIdTokenUrl", out var generateIdTokenUrl) && !string.IsNullOrEmpty(generateIdTokenUrl))
|
||||||
|
{
|
||||||
|
Environment["ACTIONS_ID_TOKEN_REQUEST_URL"] = generateIdTokenUrl;
|
||||||
|
Environment["ACTIONS_ID_TOKEN_REQUEST_TOKEN"] = systemConnection.Authorization.Parameters[EndpointAuthorizationParameters.AccessToken];
|
||||||
|
}
|
||||||
|
|
||||||
// Resolve the target script.
|
// Resolve the target script.
|
||||||
string target = null;
|
string target = null;
|
||||||
@@ -69,6 +74,20 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
target = Data.Post;
|
target = Data.Post;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add Telemetry to JobContext to send with JobCompleteMessage
|
||||||
|
if (stage == ActionRunStage.Main)
|
||||||
|
{
|
||||||
|
var telemetry = new ActionsStepTelemetry
|
||||||
|
{
|
||||||
|
Ref = GetActionRef(),
|
||||||
|
HasPreStep = Data.HasPre,
|
||||||
|
HasPostStep = Data.HasPost,
|
||||||
|
IsEmbedded = ExecutionContext.IsEmbedded,
|
||||||
|
Type = "node12"
|
||||||
|
};
|
||||||
|
ExecutionContext.Root.ActionsStepsTelemetry.Add(telemetry);
|
||||||
|
}
|
||||||
|
|
||||||
ArgUtil.NotNullOrEmpty(target, nameof(target));
|
ArgUtil.NotNullOrEmpty(target, nameof(target));
|
||||||
target = Path.Combine(ActionDirectory, target);
|
target = Path.Combine(ActionDirectory, target);
|
||||||
ArgUtil.File(target, nameof(target));
|
ArgUtil.File(target, nameof(target));
|
||||||
|
|||||||
@@ -210,6 +210,10 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
{
|
{
|
||||||
issueType = DTWebApi.IssueType.Warning;
|
issueType = DTWebApi.IssueType.Warning;
|
||||||
}
|
}
|
||||||
|
else if (string.Equals(match.Severity, "notice", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
issueType = DTWebApi.IssueType.Notice;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_executionContext.Debug($"Skipped logging an issue for the matched line because the severity '{match.Severity}' is not supported.");
|
_executionContext.Debug($"Skipped logging an issue for the matched line because the severity '{match.Severity}' is not supported.");
|
||||||
|
|||||||
@@ -23,18 +23,6 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
|
|
||||||
public override void PrintActionDetails(ActionRunStage stage)
|
public override void PrintActionDetails(ActionRunStage stage)
|
||||||
{
|
{
|
||||||
// We don't want to display the internal workings if composite (similar/equivalent information can be found in debug)
|
|
||||||
void writeDetails(string message)
|
|
||||||
{
|
|
||||||
if (ExecutionContext.IsEmbedded)
|
|
||||||
{
|
|
||||||
ExecutionContext.Debug(message);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ExecutionContext.Output(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stage == ActionRunStage.Post)
|
if (stage == ActionRunStage.Post)
|
||||||
{
|
{
|
||||||
@@ -52,7 +40,7 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
firstLine = firstLine.Substring(0, firstNewLine);
|
firstLine = firstLine.Substring(0, firstNewLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
writeDetails(ExecutionContext.IsEmbedded ? $"Run {firstLine}" : $"##[group]Run {firstLine}");
|
ExecutionContext.Output($"##[group]Run {firstLine}");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -63,7 +51,7 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
foreach (var line in multiLines)
|
foreach (var line in multiLines)
|
||||||
{
|
{
|
||||||
// Bright Cyan color
|
// Bright Cyan color
|
||||||
writeDetails($"\x1b[36;1m{line}\x1b[0m");
|
ExecutionContext.Output($"\x1b[36;1m{line}\x1b[0m");
|
||||||
}
|
}
|
||||||
|
|
||||||
string argFormat;
|
string argFormat;
|
||||||
@@ -122,23 +110,23 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
|
|
||||||
if (!string.IsNullOrEmpty(shellCommandPath))
|
if (!string.IsNullOrEmpty(shellCommandPath))
|
||||||
{
|
{
|
||||||
writeDetails($"shell: {shellCommandPath} {argFormat}");
|
ExecutionContext.Output($"shell: {shellCommandPath} {argFormat}");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
writeDetails($"shell: {shellCommand} {argFormat}");
|
ExecutionContext.Output($"shell: {shellCommand} {argFormat}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.Environment?.Count > 0)
|
if (this.Environment?.Count > 0)
|
||||||
{
|
{
|
||||||
writeDetails("env:");
|
ExecutionContext.Output("env:");
|
||||||
foreach (var env in this.Environment)
|
foreach (var env in this.Environment)
|
||||||
{
|
{
|
||||||
writeDetails($" {env.Key}: {env.Value}");
|
ExecutionContext.Output($" {env.Key}: {env.Value}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
writeDetails(ExecutionContext.IsEmbedded ? "" : "##[endgroup]");
|
ExecutionContext.Output("##[endgroup]");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RunAsync(ActionRunStage stage)
|
public async Task RunAsync(ActionRunStage stage)
|
||||||
@@ -156,6 +144,17 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
var githubContext = ExecutionContext.ExpressionValues["github"] as GitHubContext;
|
var githubContext = ExecutionContext.ExpressionValues["github"] as GitHubContext;
|
||||||
ArgUtil.NotNull(githubContext, nameof(githubContext));
|
ArgUtil.NotNull(githubContext, nameof(githubContext));
|
||||||
|
|
||||||
|
// Add Telemetry to JobContext to send with JobCompleteMessage
|
||||||
|
if (stage == ActionRunStage.Main)
|
||||||
|
{
|
||||||
|
var telemetry = new ActionsStepTelemetry
|
||||||
|
{
|
||||||
|
IsEmbedded = ExecutionContext.IsEmbedded,
|
||||||
|
Type = "run",
|
||||||
|
};
|
||||||
|
ExecutionContext.Root.ActionsStepsTelemetry.Add(telemetry);
|
||||||
|
}
|
||||||
|
|
||||||
var tempDirectory = HostContext.GetDirectory(WellKnownDirectory.Temp);
|
var tempDirectory = HostContext.GetDirectory(WellKnownDirectory.Temp);
|
||||||
|
|
||||||
Inputs.TryGetValue("script", out var contents);
|
Inputs.TryGetValue("script", out var contents);
|
||||||
@@ -278,6 +277,13 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
fileName = node12;
|
fileName = node12;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
var systemConnection = ExecutionContext.Global.Endpoints.Single(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
|
||||||
|
if (systemConnection.Data.TryGetValue("GenerateIdTokenUrl", out var generateIdTokenUrl) && !string.IsNullOrEmpty(generateIdTokenUrl))
|
||||||
|
{
|
||||||
|
Environment["ACTIONS_ID_TOKEN_REQUEST_URL"] = generateIdTokenUrl;
|
||||||
|
Environment["ACTIONS_ID_TOKEN_REQUEST_TOKEN"] = systemConnection.Authorization.Parameters[EndpointAuthorizationParameters.AccessToken];
|
||||||
|
}
|
||||||
|
|
||||||
ExecutionContext.Debug($"{fileName} {arguments}");
|
ExecutionContext.Debug($"{fileName} {arguments}");
|
||||||
|
|
||||||
using (var stdoutManager = new OutputManager(ExecutionContext, ActionCommandManager))
|
using (var stdoutManager = new OutputManager(ExecutionContext, ActionCommandManager))
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
event EventHandler<ProcessDataReceivedEventArgs> OutputDataReceived;
|
event EventHandler<ProcessDataReceivedEventArgs> OutputDataReceived;
|
||||||
event EventHandler<ProcessDataReceivedEventArgs> ErrorDataReceived;
|
event EventHandler<ProcessDataReceivedEventArgs> ErrorDataReceived;
|
||||||
|
|
||||||
|
IExecutionContext ExecutionContext { get; set; }
|
||||||
|
|
||||||
string ResolvePathForStepHost(string path);
|
string ResolvePathForStepHost(string path);
|
||||||
|
|
||||||
Task<string> DetermineNodeRuntimeVersion(IExecutionContext executionContext);
|
Task<string> DetermineNodeRuntimeVersion(IExecutionContext executionContext);
|
||||||
@@ -53,6 +55,8 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
public event EventHandler<ProcessDataReceivedEventArgs> OutputDataReceived;
|
public event EventHandler<ProcessDataReceivedEventArgs> OutputDataReceived;
|
||||||
public event EventHandler<ProcessDataReceivedEventArgs> ErrorDataReceived;
|
public event EventHandler<ProcessDataReceivedEventArgs> ErrorDataReceived;
|
||||||
|
|
||||||
|
public IExecutionContext ExecutionContext { get; set; }
|
||||||
|
|
||||||
public string ResolvePathForStepHost(string path)
|
public string ResolvePathForStepHost(string path)
|
||||||
{
|
{
|
||||||
return path;
|
return path;
|
||||||
@@ -99,6 +103,8 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
public event EventHandler<ProcessDataReceivedEventArgs> OutputDataReceived;
|
public event EventHandler<ProcessDataReceivedEventArgs> OutputDataReceived;
|
||||||
public event EventHandler<ProcessDataReceivedEventArgs> ErrorDataReceived;
|
public event EventHandler<ProcessDataReceivedEventArgs> ErrorDataReceived;
|
||||||
|
|
||||||
|
public IExecutionContext ExecutionContext { get; set; }
|
||||||
|
|
||||||
public string ResolvePathForStepHost(string path)
|
public string ResolvePathForStepHost(string path)
|
||||||
{
|
{
|
||||||
// make sure container exist.
|
// make sure container exist.
|
||||||
@@ -174,69 +180,138 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
ArgUtil.NotNull(Container, nameof(Container));
|
ArgUtil.NotNull(Container, nameof(Container));
|
||||||
ArgUtil.NotNullOrEmpty(Container.ContainerId, nameof(Container.ContainerId));
|
ArgUtil.NotNullOrEmpty(Container.ContainerId, nameof(Container.ContainerId));
|
||||||
|
|
||||||
var dockerManager = HostContext.GetService<IDockerCommandManager>();
|
var podManHandler = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Bin), "kubectlHandler", "index.js");
|
||||||
string dockerClientPath = dockerManager.DockerPath;
|
if (File.Exists(podManHandler))
|
||||||
|
|
||||||
// Usage: docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
|
|
||||||
IList<string> dockerCommandArgs = new List<string>();
|
|
||||||
dockerCommandArgs.Add($"exec");
|
|
||||||
|
|
||||||
// [OPTIONS]
|
|
||||||
dockerCommandArgs.Add($"-i");
|
|
||||||
dockerCommandArgs.Add($"--workdir {workingDirectory}");
|
|
||||||
foreach (var env in environment)
|
|
||||||
{
|
{
|
||||||
// e.g. -e MY_SECRET maps the value into the exec'ed process without exposing
|
var podmanInput = new ContainerEngineHandlerInput()
|
||||||
// the value directly in the command
|
{
|
||||||
dockerCommandArgs.Add($"-e {env.Key}");
|
Command = "Exec",
|
||||||
|
ExecInput = new JobContainerExecInput()
|
||||||
|
{
|
||||||
|
JobContainer = this.Container,
|
||||||
|
WorkingDirectory = workingDirectory,
|
||||||
|
FileName = fileName,
|
||||||
|
Arguments = arguments,
|
||||||
|
EnvironmentKeys = environment.Keys.ToList(),
|
||||||
|
EnvironmentVariables = environment.ToDictionary(x => x.Key, y => y.Value)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// make sure all env are using container path
|
||||||
|
foreach (var envKey in environment.Keys.ToList())
|
||||||
|
{
|
||||||
|
environment[envKey] = this.Container.TranslateToContainerPath(environment[envKey]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainerEngineHandlerOutput podmanOutput = null;
|
||||||
|
using (var processInvoker = HostContext.CreateService<IProcessInvoker>())
|
||||||
|
{
|
||||||
|
var redirectStandardIn = Channel.CreateUnbounded<string>(new UnboundedChannelOptions() { SingleReader = true, SingleWriter = true });
|
||||||
|
redirectStandardIn.Writer.TryWrite(JsonUtility.ToString(podmanInput));
|
||||||
|
|
||||||
|
// processInvoker.OutputDataReceived += delegate (object sender, ProcessDataReceivedEventArgs message)
|
||||||
|
// {
|
||||||
|
// ExecutionContext.Output(message.Data);
|
||||||
|
// };
|
||||||
|
|
||||||
|
// processInvoker.ErrorDataReceived += delegate (object sender, ProcessDataReceivedEventArgs message)
|
||||||
|
// {
|
||||||
|
// executionContext.Output(message.Data);
|
||||||
|
// if (podmanOutput == null && message.Data.IndexOf("___CONTAINER_ENGINE_HANDLER_OUTPUT___") >= 0)
|
||||||
|
// {
|
||||||
|
// try
|
||||||
|
// {
|
||||||
|
// podmanOutput = JsonUtility.FromString<ContainerEngineHandlerOutput>(message.Data.Replace("___CONTAINER_ENGINE_HANDLER_OUTPUT___", ""));
|
||||||
|
// }
|
||||||
|
// catch (Exception ex)
|
||||||
|
// {
|
||||||
|
// executionContext.Error(ex);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
processInvoker.OutputDataReceived += OutputDataReceived;
|
||||||
|
processInvoker.ErrorDataReceived += ErrorDataReceived;
|
||||||
|
|
||||||
|
// Execute the process. Exit code 0 should always be returned.
|
||||||
|
// A non-zero exit code indicates infrastructural failure.
|
||||||
|
// Task failure should be communicated over STDOUT using ## commands.
|
||||||
|
return await processInvoker.ExecuteAsync(workingDirectory: HostContext.GetDirectory(WellKnownDirectory.Work),
|
||||||
|
fileName: Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), "node12", "bin", $"node{IOUtil.ExeExtension}"),
|
||||||
|
arguments: podManHandler,
|
||||||
|
environment: environment,
|
||||||
|
requireExitCodeZero: requireExitCodeZero,
|
||||||
|
outputEncoding: Encoding.UTF8,
|
||||||
|
killProcessOnCancel: killProcessOnCancel,
|
||||||
|
redirectStandardIn: redirectStandardIn,
|
||||||
|
cancellationToken: cancellationToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!string.IsNullOrEmpty(PrependPath))
|
else
|
||||||
{
|
{
|
||||||
// Prepend tool paths to container's PATH
|
var dockerManager = HostContext.GetService<IDockerCommandManager>();
|
||||||
var fullPath = !string.IsNullOrEmpty(Container.ContainerRuntimePath) ? $"{PrependPath}:{Container.ContainerRuntimePath}" : PrependPath;
|
string dockerClientPath = dockerManager.DockerPath;
|
||||||
dockerCommandArgs.Add($"-e PATH=\"{fullPath}\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
// CONTAINER
|
// Usage: docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
|
||||||
dockerCommandArgs.Add($"{Container.ContainerId}");
|
IList<string> dockerCommandArgs = new List<string>();
|
||||||
|
dockerCommandArgs.Add($"exec");
|
||||||
|
|
||||||
// COMMAND
|
// [OPTIONS]
|
||||||
dockerCommandArgs.Add(fileName);
|
dockerCommandArgs.Add($"-i");
|
||||||
|
dockerCommandArgs.Add($"--workdir {workingDirectory}");
|
||||||
|
foreach (var env in environment)
|
||||||
|
{
|
||||||
|
// e.g. -e MY_SECRET maps the value into the exec'ed process without exposing
|
||||||
|
// the value directly in the command
|
||||||
|
dockerCommandArgs.Add($"-e {env.Key}");
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrEmpty(PrependPath))
|
||||||
|
{
|
||||||
|
// Prepend tool paths to container's PATH
|
||||||
|
var fullPath = !string.IsNullOrEmpty(Container.ContainerRuntimePath) ? $"{PrependPath}:{Container.ContainerRuntimePath}" : PrependPath;
|
||||||
|
dockerCommandArgs.Add($"-e PATH=\"{fullPath}\"");
|
||||||
|
}
|
||||||
|
|
||||||
// [ARG...]
|
// CONTAINER
|
||||||
dockerCommandArgs.Add(arguments);
|
dockerCommandArgs.Add($"{Container.ContainerId}");
|
||||||
|
|
||||||
string dockerCommandArgstring = string.Join(" ", dockerCommandArgs);
|
// COMMAND
|
||||||
|
dockerCommandArgs.Add(fileName);
|
||||||
|
|
||||||
// make sure all env are using container path
|
// [ARG...]
|
||||||
foreach (var envKey in environment.Keys.ToList())
|
dockerCommandArgs.Add(arguments);
|
||||||
{
|
|
||||||
environment[envKey] = this.Container.TranslateToContainerPath(environment[envKey]);
|
|
||||||
}
|
|
||||||
|
|
||||||
using (var processInvoker = HostContext.CreateService<IProcessInvoker>())
|
string dockerCommandArgstring = string.Join(" ", dockerCommandArgs);
|
||||||
{
|
|
||||||
processInvoker.OutputDataReceived += OutputDataReceived;
|
// make sure all env are using container path
|
||||||
processInvoker.ErrorDataReceived += ErrorDataReceived;
|
foreach (var envKey in environment.Keys.ToList())
|
||||||
|
{
|
||||||
|
environment[envKey] = this.Container.TranslateToContainerPath(environment[envKey]);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var processInvoker = HostContext.CreateService<IProcessInvoker>())
|
||||||
|
{
|
||||||
|
processInvoker.OutputDataReceived += OutputDataReceived;
|
||||||
|
processInvoker.ErrorDataReceived += ErrorDataReceived;
|
||||||
|
|
||||||
#if OS_WINDOWS
|
#if OS_WINDOWS
|
||||||
// It appears that node.exe outputs UTF8 when not in TTY mode.
|
// It appears that node.exe outputs UTF8 when not in TTY mode.
|
||||||
outputEncoding = Encoding.UTF8;
|
outputEncoding = Encoding.UTF8;
|
||||||
#else
|
#else
|
||||||
// Let .NET choose the default.
|
// Let .NET choose the default.
|
||||||
outputEncoding = null;
|
outputEncoding = null;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return await processInvoker.ExecuteAsync(workingDirectory: HostContext.GetDirectory(WellKnownDirectory.Work),
|
return await processInvoker.ExecuteAsync(workingDirectory: HostContext.GetDirectory(WellKnownDirectory.Work),
|
||||||
fileName: dockerClientPath,
|
fileName: dockerClientPath,
|
||||||
arguments: dockerCommandArgstring,
|
arguments: dockerCommandArgstring,
|
||||||
environment: environment,
|
environment: environment,
|
||||||
requireExitCodeZero: requireExitCodeZero,
|
requireExitCodeZero: requireExitCodeZero,
|
||||||
outputEncoding: outputEncoding,
|
outputEncoding: outputEncoding,
|
||||||
killProcessOnCancel: killProcessOnCancel,
|
killProcessOnCancel: killProcessOnCancel,
|
||||||
redirectStandardIn: null,
|
redirectStandardIn: null,
|
||||||
inheritConsoleHandler: inheritConsoleHandler,
|
inheritConsoleHandler: inheritConsoleHandler,
|
||||||
cancellationToken: cancellationToken);
|
cancellationToken: cancellationToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -350,6 +350,7 @@ namespace GitHub.Runner.Worker
|
|||||||
case "":
|
case "":
|
||||||
case "ERROR":
|
case "ERROR":
|
||||||
case "WARNING":
|
case "WARNING":
|
||||||
|
case "NOTICE":
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new ArgumentException($"Matcher '{_owner}' contains unexpected default severity '{_severity}'");
|
throw new ArgumentException($"Matcher '{_owner}' contains unexpected default severity '{_severity}'");
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
@@ -106,6 +107,9 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
jobContext.SetRunnerContext("os", VarUtil.OS);
|
jobContext.SetRunnerContext("os", VarUtil.OS);
|
||||||
|
|
||||||
|
var runnerSettings = HostContext.GetService<IConfigurationStore>().GetSettings();
|
||||||
|
jobContext.SetRunnerContext("name", runnerSettings.AgentName);
|
||||||
|
|
||||||
string toolsDirectory = HostContext.GetDirectory(WellKnownDirectory.Tools);
|
string toolsDirectory = HostContext.GetDirectory(WellKnownDirectory.Tools);
|
||||||
Directory.CreateDirectory(toolsDirectory);
|
Directory.CreateDirectory(toolsDirectory);
|
||||||
jobContext.SetRunnerContext("tool_cache", toolsDirectory);
|
jobContext.SetRunnerContext("tool_cache", toolsDirectory);
|
||||||
@@ -145,6 +149,16 @@ namespace GitHub.Runner.Worker
|
|||||||
Trace.Verbose($"Job steps: '{string.Join(", ", jobSteps.Select(x => x.DisplayName))}'");
|
Trace.Verbose($"Job steps: '{string.Join(", ", jobSteps.Select(x => x.DisplayName))}'");
|
||||||
HostContext.WritePerfCounter($"WorkerJobInitialized_{message.RequestId.ToString()}");
|
HostContext.WritePerfCounter($"WorkerJobInitialized_{message.RequestId.ToString()}");
|
||||||
|
|
||||||
|
if (systemConnection.Data.TryGetValue("GenerateIdTokenUrl", out var generateIdTokenUrl) &&
|
||||||
|
!string.IsNullOrEmpty(generateIdTokenUrl))
|
||||||
|
{
|
||||||
|
// Server won't issue ID_TOKEN for non-inprogress job.
|
||||||
|
// If the job is trying to use OIDC feature, we want the job to be marked as in-progress before running any customer's steps as much as we can.
|
||||||
|
// Timeline record update background process runs every 500ms, so delay 1000ms is enough for most of the cases
|
||||||
|
Trace.Info($"Waiting for job to be marked as started.");
|
||||||
|
await Task.WhenAny(_jobServerQueue.JobRecordUpdated.Task, Task.Delay(1000));
|
||||||
|
}
|
||||||
|
|
||||||
// Run all job steps
|
// Run all job steps
|
||||||
Trace.Info("Run all job steps.");
|
Trace.Info("Run all job steps.");
|
||||||
var stepsRunner = HostContext.GetService<IStepsRunner>();
|
var stepsRunner = HostContext.GetService<IStepsRunner>();
|
||||||
@@ -215,8 +229,15 @@ namespace GitHub.Runner.Worker
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load any upgrade telemetry
|
||||||
|
LoadFromTelemetryFile(jobContext.JobTelemetry);
|
||||||
|
|
||||||
|
// Make sure we don't submit secrets as telemetry
|
||||||
|
MaskTelemetrySecrets(jobContext.JobTelemetry);
|
||||||
|
|
||||||
Trace.Info("Raising job completed event.");
|
Trace.Info("Raising job completed event.");
|
||||||
var jobCompletedEvent = new JobCompletedEvent(message.RequestId, message.JobId, result, jobContext.JobOutputs, jobContext.ActionsEnvironment);
|
var jobCompletedEvent = new JobCompletedEvent(message.RequestId, message.JobId, result, jobContext.JobOutputs, jobContext.ActionsEnvironment, jobContext.ActionsStepsTelemetry, jobContext.JobTelemetry);
|
||||||
|
|
||||||
|
|
||||||
var completeJobRetryLimit = 5;
|
var completeJobRetryLimit = 5;
|
||||||
var exceptions = new List<Exception>();
|
var exceptions = new List<Exception>();
|
||||||
@@ -260,6 +281,38 @@ namespace GitHub.Runner.Worker
|
|||||||
throw new AggregateException(exceptions);
|
throw new AggregateException(exceptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void MaskTelemetrySecrets(List<JobTelemetry> jobTelemetry)
|
||||||
|
{
|
||||||
|
foreach (var telemetryItem in jobTelemetry)
|
||||||
|
{
|
||||||
|
telemetryItem.Message = HostContext.SecretMasker.MaskSecrets(telemetryItem.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadFromTelemetryFile(List<JobTelemetry> jobTelemetry)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var telemetryFilePath = HostContext.GetConfigFile(WellKnownConfigFile.Telemetry);
|
||||||
|
if (File.Exists(telemetryFilePath))
|
||||||
|
{
|
||||||
|
var telemetryData = File.ReadAllText(telemetryFilePath, Encoding.UTF8);
|
||||||
|
var telemetry = new JobTelemetry
|
||||||
|
{
|
||||||
|
Message = $"Runner File Telemetry:\n{telemetryData}",
|
||||||
|
Type = JobTelemetryType.General
|
||||||
|
};
|
||||||
|
jobTelemetry.Add(telemetry);
|
||||||
|
IOUtil.DeleteFile(telemetryFilePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Trace.Error("Error when trying to load telemetry from telemetry file");
|
||||||
|
Trace.Error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task ShutdownQueue(bool throwOnFailure)
|
private async Task ShutdownQueue(bool throwOnFailure)
|
||||||
{
|
{
|
||||||
if (_jobServerQueue != null)
|
if (_jobServerQueue != null)
|
||||||
|
|||||||
@@ -354,43 +354,5 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
executionContext.Complete(result, resultCode: resultCode);
|
executionContext.Complete(result, resultCode: resultCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class ConditionTraceWriter : ObjectTemplating::ITraceWriter
|
|
||||||
{
|
|
||||||
private readonly IExecutionContext _executionContext;
|
|
||||||
private readonly Tracing _trace;
|
|
||||||
private readonly StringBuilder _traceBuilder = new StringBuilder();
|
|
||||||
|
|
||||||
public string Trace => _traceBuilder.ToString();
|
|
||||||
|
|
||||||
public ConditionTraceWriter(Tracing trace, IExecutionContext executionContext)
|
|
||||||
{
|
|
||||||
ArgUtil.NotNull(trace, nameof(trace));
|
|
||||||
_trace = trace;
|
|
||||||
_executionContext = executionContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Error(string format, params Object[] args)
|
|
||||||
{
|
|
||||||
var message = StringUtil.Format(format, args);
|
|
||||||
_trace.Error(message);
|
|
||||||
_executionContext?.Debug(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Info(string format, params Object[] args)
|
|
||||||
{
|
|
||||||
var message = StringUtil.Format(format, args);
|
|
||||||
_trace.Info(message);
|
|
||||||
_executionContext?.Debug(message);
|
|
||||||
_traceBuilder.AppendLine(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Verbose(string format, params Object[] args)
|
|
||||||
{
|
|
||||||
var message = StringUtil.Format(format, args);
|
|
||||||
_trace.Verbose(message);
|
|
||||||
_executionContext?.Debug(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,7 +63,6 @@ namespace GitHub.Runner.Worker
|
|||||||
Trace.Info("Message received.");
|
Trace.Info("Message received.");
|
||||||
ArgUtil.Equal(MessageType.NewJobRequest, channelMessage.MessageType, nameof(channelMessage.MessageType));
|
ArgUtil.Equal(MessageType.NewJobRequest, channelMessage.MessageType, nameof(channelMessage.MessageType));
|
||||||
ArgUtil.NotNullOrEmpty(channelMessage.Body, nameof(channelMessage.Body));
|
ArgUtil.NotNullOrEmpty(channelMessage.Body, nameof(channelMessage.Body));
|
||||||
Trace.Info(channelMessage.Body);
|
|
||||||
var jobMessage = StringUtil.ConvertFromJson<Pipelines.AgentJobRequestMessage>(channelMessage.Body);
|
var jobMessage = StringUtil.ConvertFromJson<Pipelines.AgentJobRequestMessage>(channelMessage.Body);
|
||||||
ArgUtil.NotNull(jobMessage, nameof(jobMessage));
|
ArgUtil.NotNull(jobMessage, nameof(jobMessage));
|
||||||
HostContext.WritePerfCounter($"WorkerJobMessageReceived_{jobMessage.RequestId.ToString()}");
|
HostContext.WritePerfCounter($"WorkerJobMessageReceived_{jobMessage.RequestId.ToString()}");
|
||||||
|
|||||||
@@ -112,7 +112,13 @@
|
|||||||
"item-type": "composite-step"
|
"item-type": "composite-step"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"composite-step": {
|
"composite-step":{
|
||||||
|
"one-of": [
|
||||||
|
"run-step",
|
||||||
|
"uses-step"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"run-step": {
|
||||||
"mapping": {
|
"mapping": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"name": "string-steps-context",
|
"name": "string-steps-context",
|
||||||
@@ -130,6 +136,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"uses-step": {
|
||||||
|
"mapping": {
|
||||||
|
"properties": {
|
||||||
|
"name": "string-steps-context",
|
||||||
|
"id": "non-empty-string",
|
||||||
|
"uses": {
|
||||||
|
"type": "non-empty-string",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"with": "step-with",
|
||||||
|
"env": "step-env"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"container-runs-context": {
|
"container-runs-context": {
|
||||||
"context": [
|
"context": [
|
||||||
"inputs"
|
"inputs"
|
||||||
@@ -195,6 +215,23 @@
|
|||||||
"loose-key-type": "non-empty-string",
|
"loose-key-type": "non-empty-string",
|
||||||
"loose-value-type": "string"
|
"loose-value-type": "string"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"step-with": {
|
||||||
|
"context": [
|
||||||
|
"github",
|
||||||
|
"inputs",
|
||||||
|
"strategy",
|
||||||
|
"matrix",
|
||||||
|
"steps",
|
||||||
|
"job",
|
||||||
|
"runner",
|
||||||
|
"env",
|
||||||
|
"hashFiles(1,255)"
|
||||||
|
],
|
||||||
|
"mapping": {
|
||||||
|
"loose-key-type": "non-empty-string",
|
||||||
|
"loose-value-type": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Security;
|
using System.Security;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Logging
|
namespace GitHub.DistributedTask.Logging
|
||||||
@@ -80,6 +81,65 @@ namespace GitHub.DistributedTask.Logging
|
|||||||
return trimmed;
|
return trimmed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String PowerShellPreAmpersandEscape(String value)
|
||||||
|
{
|
||||||
|
// if the secret is passed to PS as a command and it causes an error, sections of it can be surrounded by color codes
|
||||||
|
// or printed individually.
|
||||||
|
|
||||||
|
// The secret secretpart1&secretpart2&secretpart3 would be split into 2 sections:
|
||||||
|
// 'secretpart1&secretpart2&' and 'secretpart3'. This method masks for the first section.
|
||||||
|
|
||||||
|
// The secret secretpart1&+secretpart2&secretpart3 would be split into 2 sections:
|
||||||
|
// 'secretpart1&+' and (no 's') 'ecretpart2&secretpart3'. This method masks for the first section.
|
||||||
|
|
||||||
|
var trimmed = string.Empty;
|
||||||
|
if (!string.IsNullOrEmpty(value) && value.Contains("&"))
|
||||||
|
{
|
||||||
|
var secretSection = string.Empty;
|
||||||
|
if (value.Contains("&+"))
|
||||||
|
{
|
||||||
|
secretSection = value.Substring(0, value.IndexOf("&+") + "&+".Length);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
secretSection = value.Substring(0, value.LastIndexOf("&") + "&".Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't mask short secrets
|
||||||
|
if (secretSection.Length >= 6)
|
||||||
|
{
|
||||||
|
trimmed = secretSection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String PowerShellPostAmpersandEscape(String value)
|
||||||
|
{
|
||||||
|
var trimmed = string.Empty;
|
||||||
|
if (!string.IsNullOrEmpty(value) && value.Contains("&"))
|
||||||
|
{
|
||||||
|
var secretSection = string.Empty;
|
||||||
|
if (value.Contains("&+"))
|
||||||
|
{
|
||||||
|
// +1 to skip the letter that got colored
|
||||||
|
secretSection = value.Substring(value.IndexOf("&+") + "&+".Length + 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
secretSection = value.Substring(value.LastIndexOf("&") + "&".Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (secretSection.Length >= 6)
|
||||||
|
{
|
||||||
|
trimmed = secretSection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
private static string Base64StringEscapeShift(String value, int shift)
|
private static string Base64StringEscapeShift(String value, int shift)
|
||||||
{
|
{
|
||||||
var bytes = Encoding.UTF8.GetBytes(value);
|
var bytes = Encoding.UTF8.GetBytes(value);
|
||||||
|
|||||||
@@ -18,5 +18,12 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[DataMember]
|
||||||
|
public string Path
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
36
src/Sdk/DTWebApi/WebApi/ActionsStepTelemetry.cs
Normal file
36
src/Sdk/DTWebApi/WebApi/ActionsStepTelemetry.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
|
namespace GitHub.DistributedTask.WebApi
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Information about a step run on the runner
|
||||||
|
/// </summary>
|
||||||
|
[DataContract]
|
||||||
|
public class ActionsStepTelemetry
|
||||||
|
{
|
||||||
|
|
||||||
|
[DataMember(EmitDefaultValue = false)]
|
||||||
|
public string Ref { get; set; }
|
||||||
|
|
||||||
|
[DataMember(EmitDefaultValue = false)]
|
||||||
|
public string Type { get; set; }
|
||||||
|
|
||||||
|
[DataMember(EmitDefaultValue = false)]
|
||||||
|
public bool? HasRunsStep { get; set; }
|
||||||
|
|
||||||
|
[DataMember(EmitDefaultValue = false)]
|
||||||
|
public bool? HasUsesStep { get; set; }
|
||||||
|
|
||||||
|
[DataMember(EmitDefaultValue = false)]
|
||||||
|
public bool IsEmbedded { get; set; }
|
||||||
|
|
||||||
|
[DataMember(EmitDefaultValue = false)]
|
||||||
|
public bool? HasPreStep { get; set; }
|
||||||
|
|
||||||
|
[DataMember(EmitDefaultValue = false)]
|
||||||
|
public bool? HasPostStep { get; set; }
|
||||||
|
|
||||||
|
[DataMember(EmitDefaultValue = false)]
|
||||||
|
public int? StepCount { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,9 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
Error = 1,
|
Error = 1,
|
||||||
|
|
||||||
[EnumMember]
|
[EnumMember]
|
||||||
Warning = 2
|
Warning = 2,
|
||||||
|
|
||||||
|
[EnumMember]
|
||||||
|
Notice = 3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -142,6 +142,32 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
this.ActionsEnvironment = actionsEnvironment;
|
this.ActionsEnvironment = actionsEnvironment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public JobCompletedEvent(
|
||||||
|
Int64 requestId,
|
||||||
|
Guid jobId,
|
||||||
|
TaskResult result,
|
||||||
|
Dictionary<String, VariableValue> outputs,
|
||||||
|
ActionsEnvironmentReference actionsEnvironment,
|
||||||
|
List<ActionsStepTelemetry> actionsStepsTelemetry)
|
||||||
|
: this(requestId, jobId, result, outputs)
|
||||||
|
{
|
||||||
|
this.ActionsEnvironment = actionsEnvironment;
|
||||||
|
this.ActionsStepsTelemetry = actionsStepsTelemetry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JobCompletedEvent(
|
||||||
|
Int64 requestId,
|
||||||
|
Guid jobId,
|
||||||
|
TaskResult result,
|
||||||
|
Dictionary<String, VariableValue> outputs,
|
||||||
|
ActionsEnvironmentReference actionsEnvironment,
|
||||||
|
List<ActionsStepTelemetry> actionsStepsTelemetry,
|
||||||
|
List<JobTelemetry> jobTelemetry)
|
||||||
|
: this(requestId, jobId, result, outputs, actionsEnvironment, actionsStepsTelemetry)
|
||||||
|
{
|
||||||
|
this.JobTelemetry = jobTelemetry;
|
||||||
|
}
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
[DataMember(EmitDefaultValue = false)]
|
||||||
public Int64 RequestId
|
public Int64 RequestId
|
||||||
{
|
{
|
||||||
@@ -169,6 +195,20 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[DataMember(EmitDefaultValue = false)]
|
||||||
|
public List<ActionsStepTelemetry> ActionsStepsTelemetry
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
|
[DataMember(EmitDefaultValue = false)]
|
||||||
|
public List<JobTelemetry> JobTelemetry
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[DataContract]
|
[DataContract]
|
||||||
|
|||||||
17
src/Sdk/DTWebApi/WebApi/JobTelemetry.cs
Normal file
17
src/Sdk/DTWebApi/WebApi/JobTelemetry.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
|
namespace GitHub.DistributedTask.WebApi
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Information about a job run on the runner
|
||||||
|
/// </summary>
|
||||||
|
[DataContract]
|
||||||
|
public class JobTelemetry
|
||||||
|
{
|
||||||
|
[DataMember(EmitDefaultValue = false)]
|
||||||
|
public string Message { get; set; }
|
||||||
|
|
||||||
|
[DataMember(EmitDefaultValue = false)]
|
||||||
|
public JobTelemetryType Type { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/Sdk/DTWebApi/WebApi/JobTelemetryType.cs
Normal file
13
src/Sdk/DTWebApi/WebApi/JobTelemetryType.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
|
namespace GitHub.DistributedTask.WebApi
|
||||||
|
{
|
||||||
|
public enum JobTelemetryType
|
||||||
|
{
|
||||||
|
[EnumMember]
|
||||||
|
General = 0,
|
||||||
|
|
||||||
|
[EnumMember]
|
||||||
|
ActionCommand = 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,6 +24,7 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
this.OSDescription = referenceToBeCloned.OSDescription;
|
this.OSDescription = referenceToBeCloned.OSDescription;
|
||||||
this.ProvisioningState = referenceToBeCloned.ProvisioningState;
|
this.ProvisioningState = referenceToBeCloned.ProvisioningState;
|
||||||
this.AccessPoint = referenceToBeCloned.AccessPoint;
|
this.AccessPoint = referenceToBeCloned.AccessPoint;
|
||||||
|
this.Ephemeral = referenceToBeCloned.Ephemeral;
|
||||||
|
|
||||||
if (referenceToBeCloned.m_links != null)
|
if (referenceToBeCloned.m_links != null)
|
||||||
{
|
{
|
||||||
@@ -81,6 +82,16 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Signifies that this Agent can only run one job and will be removed by the server after that one job finish.
|
||||||
|
/// </summary>
|
||||||
|
[DataMember]
|
||||||
|
public bool? Ephemeral
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether or not the agent is online.
|
/// Whether or not the agent is online.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user