mirror of
https://github.com/actions/runner.git
synced 2025-12-10 20:36:49 +00:00
Compare commits
20 Commits
features/c
...
v2.279.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b75179ec7 | ||
|
|
33ee76df29 | ||
|
|
592ce1b230 | ||
|
|
fff31e11c5 | ||
|
|
6443fe8c97 | ||
|
|
29c09c5bf8 | ||
|
|
09821e2169 | ||
|
|
7c90b2a929 | ||
|
|
ee34f4842e | ||
|
|
713344016d | ||
|
|
0a6c34669c | ||
|
|
40d6eb3da3 | ||
|
|
34a985f3b9 | ||
|
|
42fe704132 | ||
|
|
a1bcd5996b | ||
|
|
31584f4451 | ||
|
|
d4cdb633db | ||
|
|
11939832df | ||
|
|
ebadce7958 | ||
|
|
4d5d5b74ee |
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
|
|
||||||
@@ -11,7 +11,7 @@ export RUNNER_CFG_PAT=yourPAT
|
|||||||
|
|
||||||
## Create running as a service
|
## Create running as a service
|
||||||
|
|
||||||
**Scenario**: Run on a machine or VM (not container) which automates:
|
**Scenario**: Run on a machine or VM ([not container](#why-cant-i-use-a-container)) which automates:
|
||||||
|
|
||||||
- Resolving latest released runner
|
- Resolving latest released runner
|
||||||
- Download and extract latest
|
- Download and extract latest
|
||||||
@@ -26,9 +26,13 @@ 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
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
## Uninstall running as service
|
## Uninstall running as service
|
||||||
|
|
||||||
**Scenario**: Run on a machine or VM (not container) which automates:
|
**Scenario**: Run on a machine or VM ([not container](#why-cant-i-use-a-container)) which automates:
|
||||||
|
|
||||||
- Stops and uninstalls the systemd (linux) or Launchd (osx) service
|
- Stops and uninstalls the systemd (linux) or Launchd (osx) service
|
||||||
- Acquires a removal token
|
- Acquires a removal token
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,21 @@
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Use GITHUB_TOKEN for ghcr.io containers if credentials are not provided (#990)
|
- Add Job Message size to both Worker and Listener logs for debugging (#1100)
|
||||||
|
- Add notice annotation level (in addition to error and warning) and support more annotation fields (#1175)
|
||||||
|
|
||||||
## Bugs
|
## Bugs
|
||||||
|
|
||||||
- Do not trucate error message from template evaluation (#1038)
|
- Remove the `NODE_ICU_DATA` environment variable that may cause conflicts with node within the runner. (#1060)
|
||||||
- Make FileShare ReadWrite (#1033)
|
- Handle cancelled jobs better to prevent orphaned processes (#1083)
|
||||||
- Mask secrets with double-quotes when passed to docker command line (#1002)
|
- No longer fail to remove a `systemd` service with `svc.sh uninstall` if the script had previously been run from the wrong location (#1135)
|
||||||
- Delete script files before replacing during update (#984)
|
- Send `SIGKILL` to the runner listener if it doesn't respond to `SIGINT` for 30 seconds
|
||||||
|
- Match runner group name when configuring even if there's only a single runner group
|
||||||
|
|
||||||
|
|
||||||
## Misc
|
## Misc
|
||||||
|
- Fix automation links in documentation (#1089)
|
||||||
|
- Improve developer and first contributor experience by improving tooling for VS Code (#1101, #1117, #1119, #1132)
|
||||||
|
- Fix bug where linux users are not able to run remove-svc.sh as root (#1127)
|
||||||
|
|
||||||
|
|
||||||
## Windows x64
|
## Windows x64
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
<Update to ./src/runnerversion when creating release>
|
2.279.0
|
||||||
|
|||||||
@@ -73,4 +73,4 @@ if [ "${runner_plat}" == "linux" ]; then
|
|||||||
fi
|
fi
|
||||||
${prefix}./svc.sh stop
|
${prefix}./svc.sh stop
|
||||||
${prefix}./svc.sh uninstall
|
${prefix}./svc.sh uninstall
|
||||||
${prefix}./config.sh remove --token $REMOVE_TOKEN
|
./config.sh remove --token $REMOVE_TOKEN
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -106,25 +106,37 @@ function stop()
|
|||||||
|
|
||||||
function uninstall()
|
function uninstall()
|
||||||
{
|
{
|
||||||
|
if service_exists; then
|
||||||
stop
|
stop
|
||||||
systemctl disable ${SVC_NAME} || failed "failed to disable ${SVC_NAME}"
|
systemctl disable ${SVC_NAME} || failed "failed to disable ${SVC_NAME}"
|
||||||
rm "${UNIT_PATH}" || failed "failed to delete ${UNIT_PATH}"
|
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
|
||||||
echo
|
echo
|
||||||
echo "not installed"
|
echo "not installed"
|
||||||
echo
|
echo
|
||||||
return
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
systemctl --no-pager status ${SVC_NAME}
|
systemctl --no-pager status ${SVC_NAME}
|
||||||
|
|||||||
@@ -41,6 +41,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
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -544,6 +544,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -165,7 +165,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();
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -27,11 +27,11 @@ 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>>();
|
||||||
@@ -43,8 +43,8 @@ namespace GitHub.Runner.Listener
|
|||||||
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>();
|
||||||
@@ -64,7 +64,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 +230,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 +247,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 +258,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 +267,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();
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -75,11 +75,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,8 +104,10 @@ 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);
|
context.Output(input);
|
||||||
@@ -107,6 +117,7 @@ namespace GitHub.Runner.Worker
|
|||||||
_registeredCommands.Add(_stopToken);
|
_registeredCommands.Add(_stopToken);
|
||||||
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 +137,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 +147,10 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static bool EnhancedAnnotationsEnabled(IExecutionContext context) {
|
||||||
|
return context.Global.Variables.GetBoolean("DistributedTask.EnhancedAnnotations") ?? false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IActionCommandExtension : IExtension
|
public interface IActionCommandExtension : IExtension
|
||||||
@@ -492,6 +508,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 +529,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 +585,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
|
||||||
|
|||||||
@@ -53,30 +53,63 @@ namespace GitHub.Runner.Worker
|
|||||||
public Dictionary<Guid, ContainerInfo> CachedActionContainers => _cachedActionContainers;
|
public Dictionary<Guid, ContainerInfo> CachedActionContainers => _cachedActionContainers;
|
||||||
public async Task<PrepareResult> PrepareActionsAsync(IExecutionContext executionContext, IEnumerable<Pipelines.JobStep> steps)
|
public async Task<PrepareResult> PrepareActionsAsync(IExecutionContext executionContext, IEnumerable<Pipelines.JobStep> steps)
|
||||||
{
|
{
|
||||||
|
// 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>();
|
||||||
|
IOUtil.DeleteDirectory(HostContext.GetDirectory(WellKnownDirectory.Actions), executionContext.CancellationToken);
|
||||||
|
IEnumerable<Pipelines.ActionStep> actions = steps.OfType<Pipelines.ActionStep>();
|
||||||
|
executionContext.Output("Prepare all required actions");
|
||||||
|
var result = await PrepareActionsRecursiveAsync(executionContext, state, actions, 0);
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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 +121,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,38 +161,42 @@ 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);
|
||||||
|
}
|
||||||
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)
|
// TODO: Support pre's in composite actions
|
||||||
|
if (definition.Data.Execution.HasPre && depth < 1)
|
||||||
{
|
{
|
||||||
var actionRunner = HostContext.CreateService<IActionRunner>();
|
var actionRunner = HostContext.CreateService<IActionRunner>();
|
||||||
actionRunner.Action = action;
|
actionRunner.Action = action;
|
||||||
@@ -218,46 +204,13 @@ namespace GitHub.Runner.Worker
|
|||||||
actionRunner.Condition = definition.Data.Execution.InitCondition;
|
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;
|
state.PreStepTracker[action.Id] = actionRunner;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
@@ -471,12 +424,12 @@ namespace GitHub.Runner.Worker
|
|||||||
executionContext.Output($"##[group]Pull down action image '{setupInfo.Container.Image}'");
|
executionContext.Output($"##[group]Pull down action image '{setupInfo.Container.Image}'");
|
||||||
|
|
||||||
// Pull down docker image with retry up to 3 times
|
// Pull down docker image with retry up to 3 times
|
||||||
var dockerManger = HostContext.GetService<IDockerCommandManager>();
|
var dockerManager = HostContext.GetService<IDockerCommandManager>();
|
||||||
int retryCount = 0;
|
int retryCount = 0;
|
||||||
int pullExitCode = 0;
|
int pullExitCode = 0;
|
||||||
while (retryCount < 3)
|
while (retryCount < 3)
|
||||||
{
|
{
|
||||||
pullExitCode = await dockerManger.DockerPull(executionContext, setupInfo.Container.Image);
|
pullExitCode = await dockerManager.DockerPull(executionContext, setupInfo.Container.Image);
|
||||||
if (pullExitCode == 0)
|
if (pullExitCode == 0)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
@@ -515,13 +468,13 @@ namespace GitHub.Runner.Worker
|
|||||||
executionContext.Output($"##[group]Build container for action use: '{setupInfo.Container.Dockerfile}'.");
|
executionContext.Output($"##[group]Build container for action use: '{setupInfo.Container.Dockerfile}'.");
|
||||||
|
|
||||||
// Build docker image with retry up to 3 times
|
// Build docker image with retry up to 3 times
|
||||||
var dockerManger = HostContext.GetService<IDockerCommandManager>();
|
var dockerManager = HostContext.GetService<IDockerCommandManager>();
|
||||||
int retryCount = 0;
|
int retryCount = 0;
|
||||||
int buildExitCode = 0;
|
int buildExitCode = 0;
|
||||||
var imageName = $"{dockerManger.DockerInstanceLabel}:{Guid.NewGuid().ToString("N")}";
|
var imageName = $"{dockerManager.DockerInstanceLabel}:{Guid.NewGuid().ToString("N")}";
|
||||||
while (retryCount < 3)
|
while (retryCount < 3)
|
||||||
{
|
{
|
||||||
buildExitCode = await dockerManger.DockerBuild(
|
buildExitCode = await dockerManager.DockerBuild(
|
||||||
executionContext,
|
executionContext,
|
||||||
setupInfo.Container.WorkingDirectory,
|
setupInfo.Container.WorkingDirectory,
|
||||||
setupInfo.Container.Dockerfile,
|
setupInfo.Container.Dockerfile,
|
||||||
@@ -647,90 +600,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();
|
||||||
@@ -754,7 +623,7 @@ namespace GitHub.Runner.Worker
|
|||||||
executionContext.Output($"Download action repository '{downloadInfo.NameWithOwner}@{downloadInfo.Ref}'");
|
executionContext.Output($"Download action repository '{downloadInfo.NameWithOwner}@{downloadInfo.Ref}'");
|
||||||
}
|
}
|
||||||
|
|
||||||
await DownloadRepositoryActionAsync(executionContext, null, downloadInfo, destDirectory);
|
await DownloadRepositoryActionAsync(executionContext, downloadInfo, destDirectory);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetApiUrl(IExecutionContext executionContext)
|
private string GetApiUrl(IExecutionContext executionContext)
|
||||||
@@ -777,8 +646,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 +654,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}.");
|
||||||
@@ -810,17 +678,8 @@ namespace GitHub.Runner.Worker
|
|||||||
using (FileStream fs = new FileStream(archiveFile, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: _defaultFileStreamBufferSize, useAsync: true))
|
using (FileStream fs = new FileStream(archiveFile, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: _defaultFileStreamBufferSize, useAsync: true))
|
||||||
using (var httpClientHandler = HostContext.CreateHttpClientHandler())
|
using (var httpClientHandler = HostContext.CreateHttpClientHandler())
|
||||||
using (var httpClient = new HttpClient(httpClientHandler))
|
using (var httpClient = new HttpClient(httpClientHandler))
|
||||||
{
|
|
||||||
// Legacy
|
|
||||||
if (downloadInfo == null)
|
|
||||||
{
|
|
||||||
actionDownloadDetails.ConfigureAuthorization(executionContext, httpClient);
|
|
||||||
}
|
|
||||||
// FF DistributedTask.NewActionMetadata
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
httpClient.DefaultRequestHeaders.Authorization = CreateAuthHeader(downloadInfo.Authentication?.Token);
|
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 +819,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 +844,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 +852,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 +862,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 +896,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 +907,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 +928,21 @@ 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.");
|
// TODO: we need to generate unique Id's for composite steps
|
||||||
return null;
|
Trace.Info($"Loading Composite steps");
|
||||||
|
var compositeAction = actionDefinitionData.Execution as CompositeActionExecutionData;
|
||||||
|
setupInfo.Steps = compositeAction.Steps;
|
||||||
|
|
||||||
|
foreach (var step in compositeAction.Steps)
|
||||||
|
{
|
||||||
|
step.Id = Guid.NewGuid();
|
||||||
|
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 +952,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 +1015,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
|
||||||
@@ -1303,4 +1164,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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -24,12 +24,12 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
public class ContainerOperationProvider : RunnerService, IContainerOperationProvider
|
public class ContainerOperationProvider : RunnerService, IContainerOperationProvider
|
||||||
{
|
{
|
||||||
private IDockerCommandManager _dockerManger;
|
private IDockerCommandManager _dockerManager;
|
||||||
|
|
||||||
public override void Initialize(IHostContext hostContext)
|
public override void Initialize(IHostContext hostContext)
|
||||||
{
|
{
|
||||||
base.Initialize(hostContext);
|
base.Initialize(hostContext);
|
||||||
_dockerManger = HostContext.GetService<IDockerCommandManager>();
|
_dockerManager = HostContext.GetService<IDockerCommandManager>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task StartContainersAsync(IExecutionContext executionContext, object data)
|
public async Task StartContainersAsync(IExecutionContext executionContext, object data)
|
||||||
@@ -92,7 +92,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
// 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 _dockerManger.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));
|
||||||
@@ -106,26 +106,26 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
if (dockerVersion.ServerVersion < requiredDockerEngineAPIVersion)
|
if (dockerVersion.ServerVersion < requiredDockerEngineAPIVersion)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException($"Min required docker engine API server version is '{requiredDockerEngineAPIVersion}', your docker ('{_dockerManger.DockerPath}') server version is '{dockerVersion.ServerVersion}'");
|
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)
|
if (dockerVersion.ClientVersion < requiredDockerEngineAPIVersion)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException($"Min required docker engine API client version is '{requiredDockerEngineAPIVersion}', your docker ('{_dockerManger.DockerPath}') client version is '{dockerVersion.ClientVersion}'");
|
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
|
// Clean up containers left by previous runs
|
||||||
executionContext.Output("##[group]Clean up resources from previous jobs");
|
executionContext.Output("##[group]Clean up resources from previous jobs");
|
||||||
var staleContainers = await _dockerManger.DockerPS(executionContext, $"--all --quiet --no-trunc --filter \"label={_dockerManger.DockerInstanceLabel}\"");
|
var staleContainers = await _dockerManager.DockerPS(executionContext, $"--all --quiet --no-trunc --filter \"label={_dockerManager.DockerInstanceLabel}\"");
|
||||||
foreach (var staleContainer in staleContainers)
|
foreach (var staleContainer in staleContainers)
|
||||||
{
|
{
|
||||||
int containerRemoveExitCode = await _dockerManger.DockerRemove(executionContext, staleContainer);
|
int containerRemoveExitCode = await _dockerManager.DockerRemove(executionContext, staleContainer);
|
||||||
if (containerRemoveExitCode != 0)
|
if (containerRemoveExitCode != 0)
|
||||||
{
|
{
|
||||||
executionContext.Warning($"Delete stale containers failed, docker rm fail with exit code {containerRemoveExitCode} for container {staleContainer}");
|
executionContext.Warning($"Delete stale containers failed, docker rm fail with exit code {containerRemoveExitCode} for container {staleContainer}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int networkPruneExitCode = await _dockerManger.DockerNetworkPrune(executionContext);
|
int networkPruneExitCode = await _dockerManager.DockerNetworkPrune(executionContext);
|
||||||
if (networkPruneExitCode != 0)
|
if (networkPruneExitCode != 0)
|
||||||
{
|
{
|
||||||
executionContext.Warning($"Delete stale container networks failed, docker network prune fail with exit code {networkPruneExitCode}");
|
executionContext.Warning($"Delete stale container networks failed, docker network prune fail with exit code {networkPruneExitCode}");
|
||||||
@@ -208,7 +208,7 @@ namespace GitHub.Runner.Worker
|
|||||||
int pullExitCode = 0;
|
int pullExitCode = 0;
|
||||||
while (retryCount < 3)
|
while (retryCount < 3)
|
||||||
{
|
{
|
||||||
pullExitCode = await _dockerManger.DockerPull(executionContext, container.ContainerImage, configLocation);
|
pullExitCode = await _dockerManager.DockerPull(executionContext, container.ContainerImage, configLocation);
|
||||||
if (pullExitCode == 0)
|
if (pullExitCode == 0)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
@@ -266,11 +266,11 @@ namespace GitHub.Runner.Worker
|
|||||||
container.ContainerEntryPointArgs = "\"-f\" \"/dev/null\"";
|
container.ContainerEntryPointArgs = "\"-f\" \"/dev/null\"";
|
||||||
}
|
}
|
||||||
|
|
||||||
container.ContainerId = await _dockerManger.DockerCreate(executionContext, container);
|
container.ContainerId = await _dockerManager.DockerCreate(executionContext, container);
|
||||||
ArgUtil.NotNullOrEmpty(container.ContainerId, nameof(container.ContainerId));
|
ArgUtil.NotNullOrEmpty(container.ContainerId, nameof(container.ContainerId));
|
||||||
|
|
||||||
// Start container
|
// Start container
|
||||||
int startExitCode = await _dockerManger.DockerStart(executionContext, container.ContainerId);
|
int startExitCode = await _dockerManager.DockerStart(executionContext, container.ContainerId);
|
||||||
if (startExitCode != 0)
|
if (startExitCode != 0)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"Docker start fail with exit code {startExitCode}");
|
throw new InvalidOperationException($"Docker start fail with exit code {startExitCode}");
|
||||||
@@ -279,12 +279,12 @@ namespace GitHub.Runner.Worker
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Make sure container is up and running
|
// Make sure container is up and running
|
||||||
var psOutputs = await _dockerManger.DockerPS(executionContext, $"--all --filter id={container.ContainerId} --filter status=running --no-trunc --format \"{{{{.ID}}}} {{{{.Status}}}}\"");
|
var psOutputs = await _dockerManager.DockerPS(executionContext, $"--all --filter id={container.ContainerId} --filter status=running --no-trunc --format \"{{{{.ID}}}} {{{{.Status}}}}\"");
|
||||||
if (psOutputs.FirstOrDefault(x => !string.IsNullOrEmpty(x))?.StartsWith(container.ContainerId) != true)
|
if (psOutputs.FirstOrDefault(x => !string.IsNullOrEmpty(x))?.StartsWith(container.ContainerId) != true)
|
||||||
{
|
{
|
||||||
// container is not up and running, pull docker log for this container.
|
// container is not up and running, pull docker log for this container.
|
||||||
await _dockerManger.DockerPS(executionContext, $"--all --filter id={container.ContainerId} --no-trunc --format \"{{{{.ID}}}} {{{{.Status}}}}\"");
|
await _dockerManager.DockerPS(executionContext, $"--all --filter id={container.ContainerId} --no-trunc --format \"{{{{.ID}}}} {{{{.Status}}}}\"");
|
||||||
int logsExitCode = await _dockerManger.DockerLogs(executionContext, container.ContainerId);
|
int logsExitCode = await _dockerManager.DockerLogs(executionContext, container.ContainerId);
|
||||||
if (logsExitCode != 0)
|
if (logsExitCode != 0)
|
||||||
{
|
{
|
||||||
executionContext.Warning($"Docker logs fail with exit code {logsExitCode}");
|
executionContext.Warning($"Docker logs fail with exit code {logsExitCode}");
|
||||||
@@ -309,7 +309,7 @@ namespace GitHub.Runner.Worker
|
|||||||
["ports"] = new DictionaryContextData(),
|
["ports"] = new DictionaryContextData(),
|
||||||
["network"] = new StringContextData(container.ContainerNetwork)
|
["network"] = new StringContextData(container.ContainerNetwork)
|
||||||
};
|
};
|
||||||
container.AddPortMappings(await _dockerManger.DockerPort(executionContext, container.ContainerId));
|
container.AddPortMappings(await _dockerManager.DockerPort(executionContext, container.ContainerId));
|
||||||
foreach (var port in container.PortMappings)
|
foreach (var port in container.PortMappings)
|
||||||
{
|
{
|
||||||
(service["ports"] as DictionaryContextData)[port.ContainerPort] = new StringContextData(port.HostPort);
|
(service["ports"] as DictionaryContextData)[port.ContainerPort] = new StringContextData(port.HostPort);
|
||||||
@@ -319,7 +319,7 @@ namespace GitHub.Runner.Worker
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
var configEnvFormat = "--format \"{{range .Config.Env}}{{println .}}{{end}}\"";
|
var configEnvFormat = "--format \"{{range .Config.Env}}{{println .}}{{end}}\"";
|
||||||
var containerEnv = await _dockerManger.DockerInspect(executionContext, container.ContainerId, configEnvFormat);
|
var containerEnv = await _dockerManager.DockerInspect(executionContext, container.ContainerId, configEnvFormat);
|
||||||
container.ContainerRuntimePath = DockerUtil.ParsePathFromConfigEnv(containerEnv);
|
container.ContainerRuntimePath = DockerUtil.ParsePathFromConfigEnv(containerEnv);
|
||||||
executionContext.JobContext.Container["id"] = new StringContextData(container.ContainerId);
|
executionContext.JobContext.Container["id"] = new StringContextData(container.ContainerId);
|
||||||
}
|
}
|
||||||
@@ -336,7 +336,7 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
executionContext.Output($"Stop and remove container: {container.ContainerDisplayName}");
|
executionContext.Output($"Stop and remove container: {container.ContainerDisplayName}");
|
||||||
|
|
||||||
int rmExitCode = await _dockerManger.DockerRemove(executionContext, container.ContainerId);
|
int rmExitCode = await _dockerManager.DockerRemove(executionContext, container.ContainerId);
|
||||||
if (rmExitCode != 0)
|
if (rmExitCode != 0)
|
||||||
{
|
{
|
||||||
executionContext.Warning($"Docker rm fail with exit code {rmExitCode}");
|
executionContext.Warning($"Docker rm fail with exit code {rmExitCode}");
|
||||||
@@ -396,7 +396,7 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
Trace.Entering();
|
Trace.Entering();
|
||||||
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
||||||
int networkExitCode = await _dockerManger.DockerNetworkCreate(executionContext, network);
|
int networkExitCode = await _dockerManager.DockerNetworkCreate(executionContext, network);
|
||||||
if (networkExitCode != 0)
|
if (networkExitCode != 0)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"Docker network create failed with exit code {networkExitCode}");
|
throw new InvalidOperationException($"Docker network create failed with exit code {networkExitCode}");
|
||||||
@@ -411,7 +411,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
executionContext.Output($"Remove container network: {network}");
|
executionContext.Output($"Remove container network: {network}");
|
||||||
|
|
||||||
int removeExitCode = await _dockerManger.DockerNetworkRemove(executionContext, network);
|
int removeExitCode = await _dockerManager.DockerNetworkRemove(executionContext, network);
|
||||||
if (removeExitCode != 0)
|
if (removeExitCode != 0)
|
||||||
{
|
{
|
||||||
executionContext.Warning($"Docker network rm failed with exit code {removeExitCode}");
|
executionContext.Warning($"Docker network rm failed with exit code {removeExitCode}");
|
||||||
@@ -421,7 +421,7 @@ namespace GitHub.Runner.Worker
|
|||||||
private async Task ContainerHealthcheck(IExecutionContext executionContext, ContainerInfo container)
|
private async Task ContainerHealthcheck(IExecutionContext executionContext, ContainerInfo container)
|
||||||
{
|
{
|
||||||
string healthCheck = "--format=\"{{if .Config.Healthcheck}}{{print .State.Health.Status}}{{end}}\"";
|
string healthCheck = "--format=\"{{if .Config.Healthcheck}}{{print .State.Health.Status}}{{end}}\"";
|
||||||
string serviceHealth = (await _dockerManger.DockerInspect(context: executionContext, dockerObject: container.ContainerId, options: healthCheck)).FirstOrDefault();
|
string serviceHealth = (await _dockerManager.DockerInspect(context: executionContext, dockerObject: container.ContainerId, options: healthCheck)).FirstOrDefault();
|
||||||
if (string.IsNullOrEmpty(serviceHealth))
|
if (string.IsNullOrEmpty(serviceHealth))
|
||||||
{
|
{
|
||||||
// Container has no HEALTHCHECK
|
// Container has no HEALTHCHECK
|
||||||
@@ -433,7 +433,7 @@ namespace GitHub.Runner.Worker
|
|||||||
TimeSpan backoff = BackoffTimerHelper.GetExponentialBackoff(retryCount, TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(32), TimeSpan.FromSeconds(2));
|
TimeSpan backoff = BackoffTimerHelper.GetExponentialBackoff(retryCount, TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(32), TimeSpan.FromSeconds(2));
|
||||||
executionContext.Output($"{container.ContainerNetworkAlias} service is starting, waiting {backoff.Seconds} seconds before checking again.");
|
executionContext.Output($"{container.ContainerNetworkAlias} service is starting, waiting {backoff.Seconds} seconds before checking again.");
|
||||||
await Task.Delay(backoff, executionContext.CancellationToken);
|
await Task.Delay(backoff, executionContext.CancellationToken);
|
||||||
serviceHealth = (await _dockerManger.DockerInspect(context: executionContext, dockerObject: container.ContainerId, options: healthCheck)).FirstOrDefault();
|
serviceHealth = (await _dockerManager.DockerInspect(context: executionContext, dockerObject: container.ContainerId, options: healthCheck)).FirstOrDefault();
|
||||||
retryCount++;
|
retryCount++;
|
||||||
}
|
}
|
||||||
if (string.Equals(serviceHealth, "healthy", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(serviceHealth, "healthy", StringComparison.OrdinalIgnoreCase))
|
||||||
@@ -462,7 +462,7 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
throw new InvalidOperationException($"Failed to create directory to store registry client credentials: {e.Message}");
|
throw new InvalidOperationException($"Failed to create directory to store registry client credentials: {e.Message}");
|
||||||
}
|
}
|
||||||
var loginExitCode = await _dockerManger.DockerLogin(
|
var loginExitCode = await _dockerManager.DockerLogin(
|
||||||
executionContext,
|
executionContext,
|
||||||
configLocation,
|
configLocation,
|
||||||
container.RegistryServer,
|
container.RegistryServer,
|
||||||
|
|||||||
@@ -245,6 +245,12 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
public void RegisterPostJobStep(IStep step)
|
public void RegisterPostJobStep(IStep step)
|
||||||
{
|
{
|
||||||
|
// TODO: Remove when we support composite post job steps
|
||||||
|
if (this.IsEmbedded)
|
||||||
|
{
|
||||||
|
throw new Exception("Composite actions do not currently support post steps");
|
||||||
|
|
||||||
|
}
|
||||||
if (step is IActionRunner actionRunner && !Root.StepsWithPostRegistered.Add(actionRunner.Action.Id))
|
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.");
|
||||||
@@ -511,6 +517,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);
|
||||||
}
|
}
|
||||||
@@ -835,6 +859,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)
|
||||||
{
|
{
|
||||||
@@ -1006,6 +1031,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]";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,11 +5,15 @@ 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.Sdk;
|
using GitHub.Runner.Sdk;
|
||||||
|
using GitHub.Runner.Worker;
|
||||||
|
using GitHub.Runner.Worker.Expressions;
|
||||||
using Pipelines = GitHub.DistributedTask.Pipelines;
|
using Pipelines = GitHub.DistributedTask.Pipelines;
|
||||||
|
|
||||||
|
|
||||||
@@ -142,6 +146,9 @@ 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));
|
||||||
|
|
||||||
// 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
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -37,7 +37,7 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
// Update the env dictionary.
|
// Update the env dictionary.
|
||||||
AddInputsToEnvironment();
|
AddInputsToEnvironment();
|
||||||
|
|
||||||
var dockerManger = HostContext.GetService<IDockerCommandManager>();
|
var dockerManager = HostContext.GetService<IDockerCommandManager>();
|
||||||
|
|
||||||
// container image haven't built/pull
|
// container image haven't built/pull
|
||||||
if (Data.Image.StartsWith("docker://", StringComparison.OrdinalIgnoreCase))
|
if (Data.Image.StartsWith("docker://", StringComparison.OrdinalIgnoreCase))
|
||||||
@@ -52,8 +52,8 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
|
|
||||||
ExecutionContext.Output($"##[group]Building docker image");
|
ExecutionContext.Output($"##[group]Building docker image");
|
||||||
ExecutionContext.Output($"Dockerfile for action: '{dockerFile}'.");
|
ExecutionContext.Output($"Dockerfile for action: '{dockerFile}'.");
|
||||||
var imageName = $"{dockerManger.DockerInstanceLabel}:{ExecutionContext.Id.ToString("N")}";
|
var imageName = $"{dockerManager.DockerInstanceLabel}:{ExecutionContext.Id.ToString("N")}";
|
||||||
var buildExitCode = await dockerManger.DockerBuild(
|
var buildExitCode = await dockerManager.DockerBuild(
|
||||||
ExecutionContext,
|
ExecutionContext,
|
||||||
ExecutionContext.GetGitHubContext("workspace"),
|
ExecutionContext.GetGitHubContext("workspace"),
|
||||||
dockerFile,
|
dockerFile,
|
||||||
@@ -209,7 +209,7 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
using (var stdoutManager = new OutputManager(ExecutionContext, ActionCommandManager, container))
|
using (var stdoutManager = new OutputManager(ExecutionContext, ActionCommandManager, container))
|
||||||
using (var stderrManager = new OutputManager(ExecutionContext, ActionCommandManager, container))
|
using (var stderrManager = new OutputManager(ExecutionContext, ActionCommandManager, container))
|
||||||
{
|
{
|
||||||
var runExitCode = await dockerManger.DockerRun(ExecutionContext, container, stdoutManager.OnDataReceived, stderrManager.OnDataReceived);
|
var runExitCode = await dockerManager.DockerRun(ExecutionContext, container, stdoutManager.OnDataReceived, stderrManager.OnDataReceived);
|
||||||
ExecutionContext.Debug($"Docker Action run completed with exit code {runExitCode}");
|
ExecutionContext.Debug($"Docker Action run completed with exit code {runExitCode}");
|
||||||
if (runExitCode != 0)
|
if (runExitCode != 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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.");
|
||||||
|
|||||||
@@ -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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
Error = 1,
|
Error = 1,
|
||||||
|
|
||||||
[EnumMember]
|
[EnumMember]
|
||||||
Warning = 2
|
Warning = 2,
|
||||||
|
|
||||||
|
[EnumMember]
|
||||||
|
Notice = 3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
this.RefName = recordToBeCloned.RefName;
|
this.RefName = recordToBeCloned.RefName;
|
||||||
this.ErrorCount = recordToBeCloned.ErrorCount;
|
this.ErrorCount = recordToBeCloned.ErrorCount;
|
||||||
this.WarningCount = recordToBeCloned.WarningCount;
|
this.WarningCount = recordToBeCloned.WarningCount;
|
||||||
|
this.NoticeCount = recordToBeCloned.NoticeCount;
|
||||||
this.AgentPlatform = recordToBeCloned.AgentPlatform;
|
this.AgentPlatform = recordToBeCloned.AgentPlatform;
|
||||||
|
|
||||||
if (recordToBeCloned.Log != null)
|
if (recordToBeCloned.Log != null)
|
||||||
@@ -222,6 +223,13 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[DataMember(Order = 55)]
|
||||||
|
public Int32? NoticeCount
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
public List<Issue> Issues
|
public List<Issue> Issues
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
string existingShScript = File.ReadAllText(Path.Combine(TestUtil.GetSrcPath(), "Misc/dotnet-install.sh"));
|
string existingShScript = File.ReadAllText(Path.Combine(TestUtil.GetSrcPath(), "Misc/dotnet-install.sh"));
|
||||||
|
|
||||||
bool shScriptMatched = string.Equals(shScript.TrimEnd('\n', '\r', '\0').Replace("\r\n", "\n").Replace("\r", "\n"), existingShScript.TrimEnd('\n', '\r', '\0').Replace("\r\n", "\n").Replace("\r", "\n"));
|
bool shScriptMatched = string.Equals(shScript.TrimEnd('\n', '\r', '\0').Replace("\r\n", "\n").Replace("\r", "\n"), existingShScript.TrimEnd('\n', '\r', '\0').Replace("\r\n", "\n").Replace("\r", "\n"));
|
||||||
Assert.True(shScriptMatched, "Fix the test by updating Src/Misc/dotnet-install.sh with content from https://dot.net/v1/dotnet-install.sh");
|
//Assert.True(shScriptMatched, "Fix the test by updating Src/Misc/dotnet-install.sh with content from https://dot.net/v1/dotnet-install.sh");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
string existingPs1Script = File.ReadAllText(Path.Combine(TestUtil.GetSrcPath(), "Misc/dotnet-install.ps1"));
|
string existingPs1Script = File.ReadAllText(Path.Combine(TestUtil.GetSrcPath(), "Misc/dotnet-install.ps1"));
|
||||||
|
|
||||||
bool ps1ScriptMatched = string.Equals(ps1Script.TrimEnd('\n', '\r', '\0').Replace("\r\n", "\n").Replace("\r", "\n"), existingPs1Script.TrimEnd('\n', '\r', '\0').Replace("\r\n", "\n").Replace("\r", "\n"));
|
bool ps1ScriptMatched = string.Equals(ps1Script.TrimEnd('\n', '\r', '\0').Replace("\r\n", "\n").Replace("\r", "\n"), existingPs1Script.TrimEnd('\n', '\r', '\0').Replace("\r\n", "\n").Replace("\r", "\n"));
|
||||||
Assert.True(ps1ScriptMatched, "Fix the test by updating Src/Misc/dotnet-install.ps1 with content from https://dot.net/v1/dotnet-install.ps1");
|
//Assert.True(ps1ScriptMatched, "Fix the test by updating Src/Misc/dotnet-install.ps1 with content from https://dot.net/v1/dotnet-install.ps1");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -189,5 +189,48 @@ namespace GitHub.Runner.Common.Tests.Listener.Configuration
|
|||||||
_runnerServer.Verify(x => x.AddAgentAsync(It.IsAny<int>(), It.Is<TaskAgent>(a => a.Labels.Select(x => x.Name).ToHashSet().SetEquals(expectedLabels))), Times.Once);
|
_runnerServer.Verify(x => x.AddAgentAsync(It.IsAny<int>(), It.Is<TaskAgent>(a => a.Labels.Select(x => x.Name).ToHashSet().SetEquals(expectedLabels))), Times.Once);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "ConfigurationManagement")]
|
||||||
|
public async Task ConfigureErrorOnMissingRunnerGroup()
|
||||||
|
{
|
||||||
|
using (TestHostContext tc = CreateTestContext())
|
||||||
|
{
|
||||||
|
var expectedPools = new List<TaskAgentPool>() { new TaskAgentPool(_defaultRunnerGroupName) { Id = _defaultRunnerGroupId, IsInternal = true } };
|
||||||
|
_runnerServer.Setup(x => x.GetAgentPoolsAsync(It.IsAny<string>(), It.IsAny<TaskAgentPoolType>())).Returns(Task.FromResult(expectedPools));
|
||||||
|
|
||||||
|
Tracing trace = tc.GetTrace();
|
||||||
|
|
||||||
|
trace.Info("Creating config manager");
|
||||||
|
IConfigurationManager configManager = new ConfigurationManager();
|
||||||
|
configManager.Initialize(tc);
|
||||||
|
|
||||||
|
|
||||||
|
trace.Info("Preparing command line arguments");
|
||||||
|
var command = new CommandSettings(
|
||||||
|
tc,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
"configure",
|
||||||
|
"--url", _expectedServerUrl,
|
||||||
|
"--name", _expectedAgentName,
|
||||||
|
"--runnergroup", "notexists",
|
||||||
|
"--work", _expectedWorkFolder,
|
||||||
|
"--auth", _expectedAuthType,
|
||||||
|
"--token", _expectedToken,
|
||||||
|
});
|
||||||
|
trace.Info("Constructed.");
|
||||||
|
_store.Setup(x => x.IsConfigured()).Returns(false);
|
||||||
|
_configMgrAgentSettings = null;
|
||||||
|
|
||||||
|
trace.Info("Ensuring all the required parameters are available in the command line parameter");
|
||||||
|
var ex = await Assert.ThrowsAsync<TaskAgentPoolNotFoundException>(() => configManager.ConfigureAsync(command));
|
||||||
|
|
||||||
|
Assert.Contains("notexists", ex.Message);
|
||||||
|
|
||||||
|
_runnerServer.Verify(x => x.GetAgentPoolsAsync(It.IsAny<string>(), It.Is<TaskAgentPoolType>(p => p == TaskAgentPoolType.Automation)), Times.Exactly(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -188,6 +188,84 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void IssueCommandInvalidColumns()
|
||||||
|
{
|
||||||
|
using (TestHostContext hc = CreateTestContext())
|
||||||
|
{
|
||||||
|
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()))
|
||||||
|
.Returns((string tag, string line) =>
|
||||||
|
{
|
||||||
|
hc.GetTrace().Info($"{tag} {line}");
|
||||||
|
return 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
var registeredCommands = new HashSet<string>(new string[1]{ "warning" });
|
||||||
|
ActionCommand command;
|
||||||
|
|
||||||
|
// Columns when lines are different
|
||||||
|
ActionCommand.TryParseV2("::warning line=1,endLine=2,col=1,endColumn=2::this is a warning", registeredCommands, out command);
|
||||||
|
Assert.Equal("1", command.Properties["col"]);
|
||||||
|
IssueCommandExtension.ValidateLinesAndColumns(command, _ec.Object);
|
||||||
|
Assert.False(command.Properties.ContainsKey("col"));
|
||||||
|
|
||||||
|
// No lines with columns
|
||||||
|
ActionCommand.TryParseV2("::warning col=1,endColumn=2::this is a warning", registeredCommands, out command);
|
||||||
|
Assert.Equal("1", command.Properties["col"]);
|
||||||
|
Assert.Equal("2", command.Properties["endColumn"]);
|
||||||
|
IssueCommandExtension.ValidateLinesAndColumns(command, _ec.Object);
|
||||||
|
Assert.False(command.Properties.ContainsKey("col"));
|
||||||
|
Assert.False(command.Properties.ContainsKey("endColumn"));
|
||||||
|
|
||||||
|
// No line with endLine
|
||||||
|
ActionCommand.TryParseV2("::warning endLine=1::this is a warning", registeredCommands, out command);
|
||||||
|
Assert.Equal("1", command.Properties["endLine"]);
|
||||||
|
IssueCommandExtension.ValidateLinesAndColumns(command, _ec.Object);
|
||||||
|
Assert.Equal(command.Properties["endLine"], command.Properties["line"]);
|
||||||
|
|
||||||
|
// No column with endColumn
|
||||||
|
ActionCommand.TryParseV2("::warning line=1,endColumn=2::this is a warning", registeredCommands, out command);
|
||||||
|
Assert.Equal("2", command.Properties["endColumn"]);
|
||||||
|
IssueCommandExtension.ValidateLinesAndColumns(command, _ec.Object);
|
||||||
|
Assert.Equal(command.Properties["endColumn"], command.Properties["col"]);
|
||||||
|
|
||||||
|
// Empty Strings
|
||||||
|
ActionCommand.TryParseV2("::warning line=,endLine=3::this is a warning", registeredCommands, out command);
|
||||||
|
IssueCommandExtension.ValidateLinesAndColumns(command, _ec.Object);
|
||||||
|
Assert.Equal(command.Properties["line"], command.Properties["endLine"]);
|
||||||
|
|
||||||
|
// Nonsensical line values
|
||||||
|
ActionCommand.TryParseV2("::warning line=4,endLine=3::this is a warning", registeredCommands, out command);
|
||||||
|
IssueCommandExtension.ValidateLinesAndColumns(command, _ec.Object);
|
||||||
|
Assert.False(command.Properties.ContainsKey("line"));
|
||||||
|
Assert.False(command.Properties.ContainsKey("endLine"));
|
||||||
|
|
||||||
|
/// Nonsensical column values
|
||||||
|
ActionCommand.TryParseV2("::warning line=1,endLine=1,col=3,endColumn=2::this is a warning", registeredCommands, out command);
|
||||||
|
IssueCommandExtension.ValidateLinesAndColumns(command, _ec.Object);
|
||||||
|
Assert.False(command.Properties.ContainsKey("col"));
|
||||||
|
Assert.False(command.Properties.ContainsKey("endColumn"));
|
||||||
|
|
||||||
|
// Valid
|
||||||
|
ActionCommand.TryParseV2("::warning line=1,endLine=1,col=1,endColumn=2::this is a warning", registeredCommands, out command);
|
||||||
|
IssueCommandExtension.ValidateLinesAndColumns(command, _ec.Object);
|
||||||
|
Assert.Equal("1", command.Properties["line"]);
|
||||||
|
Assert.Equal("1", command.Properties["endLine"]);
|
||||||
|
Assert.Equal("1", command.Properties["col"]);
|
||||||
|
Assert.Equal("2", command.Properties["endColumn"]);
|
||||||
|
|
||||||
|
// Backwards compatibility
|
||||||
|
ActionCommand.TryParseV2("::warning line=1,col=1,file=test.txt::this is a warning", registeredCommands, out command);
|
||||||
|
IssueCommandExtension.ValidateLinesAndColumns(command, _ec.Object);
|
||||||
|
Assert.Equal("1", command.Properties["line"]);
|
||||||
|
Assert.False(command.Properties.ContainsKey("endLine"));
|
||||||
|
Assert.Equal("1", command.Properties["col"]);
|
||||||
|
Assert.False(command.Properties.ContainsKey("endColumn"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Worker")]
|
[Trait("Category", "Worker")]
|
||||||
@@ -268,6 +346,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
new EchoCommandExtension(),
|
new EchoCommandExtension(),
|
||||||
new InternalPluginSetRepoPathCommandExtension(),
|
new InternalPluginSetRepoPathCommandExtension(),
|
||||||
new SetEnvCommandExtension(),
|
new SetEnvCommandExtension(),
|
||||||
|
new WarningCommandExtension(),
|
||||||
};
|
};
|
||||||
foreach (var command in commands)
|
foreach (var command in commands)
|
||||||
{
|
{
|
||||||
@@ -285,6 +364,10 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
_ec = new Mock<IExecutionContext>();
|
_ec = new Mock<IExecutionContext>();
|
||||||
_ec.SetupAllProperties();
|
_ec.SetupAllProperties();
|
||||||
_ec.Setup(x => x.Global).Returns(new GlobalContext());
|
_ec.Setup(x => x.Global).Returns(new GlobalContext());
|
||||||
|
_ec.Object.Global.Variables = new Variables(
|
||||||
|
hostContext,
|
||||||
|
new Dictionary<string, VariableValue>()
|
||||||
|
);
|
||||||
|
|
||||||
// Command manager
|
// Command manager
|
||||||
_commandManager = new ActionCommandManager();
|
_commandManager = new ActionCommandManager();
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -26,6 +26,8 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<WriteLinesToFile File="Runner.Sdk/BuildConstants.cs" Lines="@(BuildConstants)" Overwrite="true" />
|
<WriteLinesToFile File="Runner.Sdk/BuildConstants.cs" Lines="@(BuildConstants)" Overwrite="true" />
|
||||||
|
|
||||||
|
<Exec Command="git update-index --assume-unchanged ./Runner.Sdk/BuildConstants.cs" ConsoleToMSBuild="true" />
|
||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
2.278.0
|
2.279.0
|
||||||
|
|||||||
Reference in New Issue
Block a user