mirror of
https://github.com/actions/runner.git
synced 2025-12-10 20:36:49 +00:00
Compare commits
12 Commits
v2.310.0
...
benwells/U
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0be495a2f9 | ||
|
|
9c8abf2a6e | ||
|
|
45d079e5f3 | ||
|
|
4da8c9e69a | ||
|
|
3ee541e26a | ||
|
|
71600dec6e | ||
|
|
62d0a70002 | ||
|
|
fc8a966a21 | ||
|
|
184098ac5d | ||
|
|
76e2904c63 | ||
|
|
2796fcdd87 | ||
|
|
c71eceaae3 |
@@ -1,24 +1,27 @@
|
||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
|
||||
{
|
||||
"name": "Actions Runner Devcontainer",
|
||||
"image": "mcr.microsoft.com/devcontainers/base:focal",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/docker-in-docker:1": {},
|
||||
"ghcr.io/devcontainers/features/dotnet": {
|
||||
"version": "6.0.414"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/node:1": {
|
||||
"version": "16"
|
||||
}
|
||||
},
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"ms-azuretools.vscode-docker",
|
||||
"ms-dotnettools.csharp",
|
||||
"eamodio.gitlens"
|
||||
]
|
||||
}
|
||||
},
|
||||
"postCreateCommand": "dotnet restore src/Test && dotnet restore src/Runner.PluginHost",
|
||||
"remoteUser": "vscode"
|
||||
}
|
||||
"name": "Actions Runner Devcontainer",
|
||||
"image": "mcr.microsoft.com/devcontainers/base:focal",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/docker-in-docker:1": {},
|
||||
"ghcr.io/devcontainers/features/dotnet": {
|
||||
"version": "6.0.405"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/node:1": {
|
||||
"version": "16"
|
||||
}
|
||||
},
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"ms-azuretools.vscode-docker",
|
||||
"ms-dotnettools.csharp",
|
||||
"eamodio.gitlens"
|
||||
]
|
||||
}
|
||||
},
|
||||
// dotnet restore to install dependencies so OmniSharp works out of the box
|
||||
// src/Test restores all other projects it references, src/Runner.PluginHost is not one of them
|
||||
"postCreateCommand": "dotnet restore src/Test && dotnet restore src/Runner.PluginHost",
|
||||
"remoteUser": "vscode"
|
||||
}
|
||||
20
.github/dependabot.yml
vendored
20
.github/dependabot.yml
vendored
@@ -1,20 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/images"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
target-branch: "main"
|
||||
- package-ecosystem: "nuget"
|
||||
directory: "/src"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
target-branch: "main"
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/src/Misc/expressionFunc/hashFiles"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
target-branch: "main"
|
||||
allow:
|
||||
- dependency-type: direct
|
||||
- dependency-type: production # check only dependencies, which are going to the compiled app, not supporting tools like @vue-cli
|
||||
306
.github/workflows/dotnet-upgrade.yml
vendored
306
.github/workflows/dotnet-upgrade.yml
vendored
@@ -1,306 +0,0 @@
|
||||
name: "DotNet SDK Upgrade"
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * 1'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
dotnet-update:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
SHOULD_UPDATE: ${{ steps.fetch_latest_version.outputs.SHOULD_UPDATE }}
|
||||
BRANCH_EXISTS: ${{ steps.fetch_latest_version.outputs.BRANCH_EXISTS }}
|
||||
DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION: ${{ steps.fetch_latest_version.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }}
|
||||
DOTNET_CURRENT_MAJOR_MINOR_VERSION: ${{ steps.fetch_current_version.outputs.DOTNET_CURRENT_MAJOR_MINOR_VERSION }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
- name: Get current major minor version
|
||||
id: fetch_current_version
|
||||
shell: bash
|
||||
run: |
|
||||
current_major_minor_patch_version=$(jq .sdk.version ./src/global.json | xargs)
|
||||
current_major_minor_version=$(cut -d '.' -f 1,2 <<< "$current_major_minor_patch_version")
|
||||
|
||||
echo "DOTNET_CURRENT_MAJOR_MINOR_PATCH_VERSION=${current_major_minor_patch_version}" >> $GITHUB_OUTPUT
|
||||
echo "DOTNET_CURRENT_MAJOR_MINOR_VERSION=${current_major_minor_version}" >> $GITHUB_OUTPUT
|
||||
- name: Check patch version
|
||||
id: fetch_latest_version
|
||||
shell: bash
|
||||
run: |
|
||||
latest_patch_version=$(curl -sb -H "Accept: application/json" "https://dotnetcli.blob.core.windows.net/dotnet/Sdk/${{ steps.fetch_current_version.outputs.DOTNET_CURRENT_MAJOR_MINOR_VERSION }}/latest.version")
|
||||
current_patch_version=${{ steps.fetch_current_version.outputs.DOTNET_CURRENT_MAJOR_MINOR_PATCH_VERSION }}
|
||||
|
||||
should_update=0
|
||||
[ "$current_patch_version" != "$latest_patch_version" ] && should_update=1
|
||||
|
||||
# check if git branch already exists for the upgrade
|
||||
branch_already_exists=0
|
||||
|
||||
if git ls-remote --heads --exit-code origin refs/heads/feature/dotnetsdk-upgrade/${latest_patch_version};
|
||||
then
|
||||
branch_already_exists=1
|
||||
should_update=0
|
||||
fi
|
||||
echo "DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION=${latest_patch_version}" >> $GITHUB_OUTPUT
|
||||
echo "SHOULD_UPDATE=${should_update}" >> $GITHUB_OUTPUT
|
||||
echo "BRANCH_EXISTS=${branch_already_exists}" >> $GITHUB_OUTPUT
|
||||
- name: Create an error annotation if branch exists
|
||||
if: ${{ steps.fetch_latest_version.outputs.BRANCH_EXISTS == 1 }}
|
||||
run: echo "::error links::feature/dotnet-sdk-upgrade${{ steps.fetch_latest_version.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }} https://github.com/actions/runner/tree/feature/dotnet-sdk-upgrade${{ steps.fetch_latest_version.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }}::Branch feature/dotnetsdk-upgrade/${{ steps.fetch_latest_version.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }} already exists. Please take a look and delete that branch if you wish to recreate"
|
||||
- name: Create a warning annotation if no need to update
|
||||
if: ${{ steps.fetch_latest_version.outputs.SHOULD_UPDATE == 0 && steps.fetch_latest_version.outputs.BRANCH_EXISTS == 0 }}
|
||||
run: echo "::warning ::Latest DotNet SDK patch is ${{ steps.fetch_latest_version.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }}, and we are on ${{ steps.fetch_latest_version.outputs.DOTNET_CURRENT_MAJOR_MINOR_PATCH_VERSION }}. No need to update"
|
||||
- name: Update patch version
|
||||
if: ${{ steps.fetch_latest_version.outputs.SHOULD_UPDATE == 1 && steps.fetch_latest_version.outputs.BRANCH_EXISTS == 0 }}
|
||||
shell: bash
|
||||
run: |
|
||||
patch_version="${{ steps.fetch_latest_version.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }}"
|
||||
current_version="${{ steps.fetch_current_version.outputs.DOTNET_CURRENT_MAJOR_MINOR_PATCH_VERSION }}"
|
||||
|
||||
# Update globals
|
||||
echo Updating globals
|
||||
globals_temp=$(mktemp)
|
||||
jq --unbuffered --arg patch_version "$patch_version" '.sdk.version = $patch_version' ./src/global.json > "$globals_temp" && mv "$globals_temp" ./src/global.json
|
||||
|
||||
# Update devcontainer
|
||||
echo Updating devcontainer
|
||||
devcontainer_temp=$(mktemp)
|
||||
jq --unbuffered --arg patch_version "$patch_version" '.features."ghcr.io/devcontainers/features/dotnet".version = $patch_version' ./.devcontainer/devcontainer.json > "$devcontainer_temp" && mv "$devcontainer_temp" ./.devcontainer/devcontainer.json
|
||||
|
||||
# Update dev.sh
|
||||
echo Updating start script
|
||||
sed -i "s/DOTNETSDK_VERSION=\"$current_version\"/DOTNETSDK_VERSION=\"$patch_version\"/g" ./src/dev.sh
|
||||
- name: GIT commit and push all changed files
|
||||
if: ${{ steps.fetch_latest_version.outputs.SHOULD_UPDATE == 1 && steps.fetch_latest_version.outputs.BRANCH_EXISTS == 0 }}
|
||||
id: create_branch
|
||||
run: |
|
||||
branch_name="feature/dotnetsdk-upgrade/${{ steps.fetch_latest_version.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }}"
|
||||
git config --global user.name "github-actions[bot]"
|
||||
git config --global user.email "<41898282+github-actions[bot]@users.noreply.github.com>"
|
||||
|
||||
git checkout -b $branch_name
|
||||
git commit -a -m "Upgrade dotnet sdk to v${{ steps.fetch_latest_version.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }}"
|
||||
git push --set-upstream origin $branch_name
|
||||
|
||||
build-hashes:
|
||||
if: ${{ needs.dotnet-update.outputs.SHOULD_UPDATE == 1 && needs.dotnet-update.outputs.BRANCH_EXISTS == 0 }}
|
||||
needs: [dotnet-update]
|
||||
outputs:
|
||||
# pass outputs from this job to create-pr for use
|
||||
DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION: ${{ needs.dotnet-update.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }}
|
||||
DOTNET_CURRENT_MAJOR_MINOR_VERSION: ${{ needs.dotnet-update.outputs.DOTNET_CURRENT_MAJOR_MINOR_VERSION }}
|
||||
NEEDS_HASH_UPDATE: ${{ steps.compute-hash.outputs.NEED_UPDATE }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
runtime: [ linux-x64, linux-arm64, linux-arm, win-x64, win-arm64, osx-x64, osx-arm64 ]
|
||||
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: osx-arm64
|
||||
os: macOS-latest
|
||||
devScript: ./dev.sh
|
||||
|
||||
- runtime: win-x64
|
||||
os: windows-2019
|
||||
devScript: ./dev
|
||||
|
||||
- runtime: win-arm64
|
||||
os: windows-latest
|
||||
devScript: ./dev
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: feature/dotnetsdk-upgrade/${{ needs.dotnet-update.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }}
|
||||
|
||||
# Build runner layout
|
||||
- name: Build & Layout Release
|
||||
run: |
|
||||
${{ matrix.devScript }} layout Release ${{ matrix.runtime }}
|
||||
working-directory: src
|
||||
|
||||
# Check runtime/externals hash
|
||||
- name: Compute/Compare runtime and externals Hash
|
||||
id: compute-hash
|
||||
continue-on-error: true
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Current dotnet runtime hash result: $DOTNET_RUNTIME_HASH"
|
||||
echo "Current Externals hash result: $EXTERNALS_HASH"
|
||||
|
||||
NeedUpdate=0
|
||||
if [ "$EXTERNALS_HASH" != "$(cat ./src/Misc/contentHash/externals/${{ matrix.runtime }})" ] ;then
|
||||
echo Hash mismatch, Update ./src/Misc/contentHash/externals/${{ matrix.runtime }} to $EXTERNALS_HASH
|
||||
|
||||
echo "EXTERNAL_HASH=$EXTERNALS_HASH" >> $GITHUB_OUTPUT
|
||||
NeedUpdate=1
|
||||
fi
|
||||
|
||||
if [ "$DOTNET_RUNTIME_HASH" != "$(cat ./src/Misc/contentHash/dotnetRuntime/${{ matrix.runtime }})" ] ;then
|
||||
echo Hash mismatch, Update ./src/Misc/contentHash/dotnetRuntime/${{ matrix.runtime }} to $DOTNET_RUNTIME_HASH
|
||||
|
||||
echo "DOTNET_RUNTIME_HASH=$DOTNET_RUNTIME_HASH" >> $GITHUB_OUTPUT
|
||||
NeedUpdate=1
|
||||
fi
|
||||
|
||||
echo "NEED_UPDATE=$NeedUpdate" >> $GITHUB_OUTPUT
|
||||
env:
|
||||
DOTNET_RUNTIME_HASH: ${{hashFiles('**/_layout_trims/runtime/**/*')}}
|
||||
EXTERNALS_HASH: ${{hashFiles('**/_layout_trims/externals/**/*')}}
|
||||
- name: update hash
|
||||
if: ${{ steps.compute-hash.outputs.NEED_UPDATE == 1 }}
|
||||
shell: bash
|
||||
run: |
|
||||
ExternalHash=${{ steps.compute-hash.outputs.EXTERNAL_HASH }}
|
||||
DotNetRuntimeHash=${{ steps.compute-hash.outputs.DOTNET_RUNTIME_HASH }}
|
||||
|
||||
if [ -n "$ExternalHash" ]; then
|
||||
echo "$ExternalHash" > ./src/Misc/contentHash/externals/${{ matrix.runtime }}
|
||||
fi
|
||||
|
||||
if [ -n "$DotNetRuntimeHash" ]; then
|
||||
echo "$DotNetRuntimeHash" > ./src/Misc/contentHash/dotnetRuntime/${{ matrix.runtime }}
|
||||
fi
|
||||
- name: cache updated hashes
|
||||
if: ${{ steps.compute-hash.outputs.NEED_UPDATE == 1 }}
|
||||
uses: actions/cache/save@v3
|
||||
with:
|
||||
enableCrossOsArchive: true
|
||||
path: |
|
||||
./src/Misc/contentHash/externals/${{ matrix.runtime }}
|
||||
./src/Misc/contentHash/dotnetRuntime/${{ matrix.runtime }}
|
||||
key: compute-hashes-${{ matrix.runtime }}-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}
|
||||
|
||||
|
||||
hash-update:
|
||||
needs: [build-hashes]
|
||||
if: ${{ needs.build-hashes.outputs.NEEDS_HASH_UPDATE == 1 }}
|
||||
outputs:
|
||||
# pass outputs from this job to create-pr for use
|
||||
DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION: ${{ needs.build-hashes.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }}
|
||||
DOTNET_CURRENT_MAJOR_MINOR_VERSION: ${{ needs.build-hashes.outputs.DOTNET_CURRENT_MAJOR_MINOR_VERSION }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: feature/dotnetsdk-upgrade/${{ needs.build-hashes.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }}
|
||||
- name: Restore cached hashes - linux-x64
|
||||
id: cache-restore-linux-x64
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
enableCrossOsArchive: true
|
||||
path: |
|
||||
./src/Misc/contentHash/externals/linux-x64
|
||||
./src/Misc/contentHash/dotnetRuntime/linux-x64
|
||||
key: compute-hashes-linux-x64-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}
|
||||
- name: Restore cached hashes - linux-arm64
|
||||
id: cache-restore-linux-arm64
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
enableCrossOsArchive: true
|
||||
path: |
|
||||
./src/Misc/contentHash/externals/linux-arm64
|
||||
./src/Misc/contentHash/dotnetRuntime/linux-arm64
|
||||
key: compute-hashes-linux-arm64-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}
|
||||
- name: Restore cached hashes - linux-arm
|
||||
id: cache-restore-linux-arm
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
enableCrossOsArchive: true
|
||||
path: |
|
||||
./src/Misc/contentHash/externals/linux-arm
|
||||
./src/Misc/contentHash/dotnetRuntime/linux-arm
|
||||
key: compute-hashes-linux-arm-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}
|
||||
- name: Restore cached hashes - osx-x64
|
||||
id: cache-restore-osx-x64
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
enableCrossOsArchive: true
|
||||
path: |
|
||||
./src/Misc/contentHash/externals/osx-x64
|
||||
./src/Misc/contentHash/dotnetRuntime/osx-x64
|
||||
key: compute-hashes-osx-x64-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}
|
||||
- name: Restore cached hashes - osx-arm64
|
||||
id: cache-restore-osx-arm64
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
enableCrossOsArchive: true
|
||||
path: |
|
||||
./src/Misc/contentHash/externals/osx-arm64
|
||||
./src/Misc/contentHash/dotnetRuntime/osx-arm64
|
||||
key: compute-hashes-osx-arm64-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}
|
||||
- name: Restore cached hashes - win-x64
|
||||
id: cache-restore-win-x64
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
enableCrossOsArchive: true
|
||||
path: |
|
||||
./src/Misc/contentHash/externals/win-x64
|
||||
./src/Misc/contentHash/dotnetRuntime/win-x64
|
||||
key: compute-hashes-win-x64-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}
|
||||
- name: Restore cached hashes - win-arm64
|
||||
id: cache-restore-win-arm64
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
enableCrossOsArchive: true
|
||||
path: |
|
||||
./src/Misc/contentHash/externals/win-arm64
|
||||
./src/Misc/contentHash/dotnetRuntime/win-arm64
|
||||
key: compute-hashes-win-arm64-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}
|
||||
- name: Fetch cached computed hashes
|
||||
if: steps.cache-restore-linux-x64.outputs.cache-hit == 'true' ||
|
||||
steps.cache-restore-linux-arm64.outputs.cache-hit == 'true' ||
|
||||
steps.cache-restore-linux-arm.outputs.cache-hit == 'true' ||
|
||||
steps.cache-restore-win-x64.outputs.cache-hit == 'true' ||
|
||||
steps.cache-restore-win-arm64.outputs.cache-hit == 'true' ||
|
||||
steps.cache-restore-osx-x64.outputs.cache-hit == 'true' ||
|
||||
steps.cache-restore-osx-arm64.outputs.cache-hit == 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
Environments=( "linux-x64" "linux-arm64" "linux-arm" "win-x64" "win-arm64" "osx-x64" "osx-arm64" )
|
||||
|
||||
git config --global user.name "github-actions[bot]"
|
||||
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git commit -a -m "Update computed hashes"
|
||||
git push --set-upstream origin feature/dotnetsdk-upgrade/${{ needs.build-hashes.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }}
|
||||
|
||||
create-pr:
|
||||
needs: [hash-update]
|
||||
outputs:
|
||||
# pass outputs from this job to run-tests for use
|
||||
DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION: ${{ needs.hash-update.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }}
|
||||
DOTNET_CURRENT_MAJOR_MINOR_VERSION: ${{ needs.hash-update.outputs.DOTNET_CURRENT_MAJOR_MINOR_VERSION }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: feature/dotnetsdk-upgrade/${{ needs.hash-update.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }}
|
||||
- name: Create Pull Request
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh pr create -B main -H feature/dotnetsdk-upgrade/${{ needs.hash-update.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }} --title "Update dotnet sdk to latest version @${{ needs.hash-update.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }}" --body "
|
||||
https://dotnetcli.blob.core.windows.net/dotnet/Sdk/${{ needs.hash-update.outputs.DOTNET_CURRENT_MAJOR_MINOR_VERSION }}/latest.version
|
||||
|
||||
|
||||
---
|
||||
|
||||
Autogenerated by [DotNet SDK Upgrade Workflow](https://github.com/actions/runner/blob/main/.github/workflows/dotnet-upgrade.yml)"
|
||||
3
.github/workflows/publish-image.yml
vendored
3
.github/workflows/publish-image.yml
vendored
@@ -53,9 +53,6 @@ jobs:
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: ./images
|
||||
platforms: |
|
||||
linux/amd64
|
||||
linux/arm64
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.image.outputs.version }}
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||
|
||||
3
.github/workflows/release.yml
vendored
3
.github/workflows/release.yml
vendored
@@ -699,9 +699,6 @@ jobs:
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: ./images
|
||||
platforms: |
|
||||
linux/amd64
|
||||
linux/arm64
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.image.outputs.version }}
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||
|
||||
16
.github/workflows/stale-bot.yml
vendored
16
.github/workflows/stale-bot.yml
vendored
@@ -1,16 +0,0 @@
|
||||
name: Stale Bot
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 0 * * 1' # every monday at midnight
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v8
|
||||
with:
|
||||
stale-issue-message: "This issue is stale because it has been open 365 days with no activity. Remove stale label or comment or this will be closed in 15 days."
|
||||
close-issue-message: "This issue was closed because it has been stalled for 15 days with no activity."
|
||||
exempt-issue-labels: "keep"
|
||||
days-before-stale: 365
|
||||
days-before-close: 15
|
||||
@@ -1,6 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
cd src/Misc/expressionFunc/hashFiles
|
||||
|
||||
npx lint-staged
|
||||
@@ -9,13 +9,11 @@ Make sure the runner has access to actions service for GitHub.com or GitHub Ente
|
||||
- The runner needs to access `https://api.github.com` for downloading actions.
|
||||
- The runner needs to access `https://vstoken.actions.githubusercontent.com/_apis/.../` for requesting an access token.
|
||||
- The runner needs to access `https://pipelines.actions.githubusercontent.com/_apis/.../` for receiving workflow jobs.
|
||||
---
|
||||
**NOTE:** for the full list of domains that are required to be in the firewall allow list refer to the [GitHub self-hosted runners requirements documentation](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners#communication-between-self-hosted-runners-and-github).
|
||||
|
||||
These can by tested by running the following `curl` commands from your self-hosted runner machine:
|
||||
|
||||
```
|
||||
curl -v https://api.github.com/zen
|
||||
curl -v https://api.github.com/api/v3/zen
|
||||
curl -v https://vstoken.actions.githubusercontent.com/_apis/health
|
||||
curl -v https://pipelines.actions.githubusercontent.com/_apis/health
|
||||
```
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
- A Proxy may try to modify the HTTPS request (like add or change some http headers) and causes the request become incompatible with the Actions Service (ASP.NetCore), Ex: [Nginx](https://github.com/dotnet/aspnetcore/issues/17081)
|
||||
|
||||
- Firewall rules that block action runner from accessing [certain hosts](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners#communication-between-self-hosted-runners-and-github), ex: `*.github.com`, `*.actions.githubusercontent.com`, etc
|
||||
- Firewall rules that block action runner from accessing certain hosts, ex: `*.github.com`, `*.actions.githubusercontent.com`, etc
|
||||
|
||||
|
||||
### Identify and solve these problems
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
|
||||
Make sure the built-in node.js has access to GitHub.com or GitHub Enterprise Server.
|
||||
|
||||
The runner carries its own copy of node.js executable under `<runner_root>/externals/node20/`.
|
||||
The runner carries its own copy of node.js executable under `<runner_root>/externals/node16/`.
|
||||
|
||||
All javascript base Actions will get executed by the built-in `node` at `<runner_root>/externals/node20/`.
|
||||
All javascript base Actions will get executed by the built-in `node` at `<runner_root>/externals/node16/`.
|
||||
|
||||
> Not the `node` from `$PATH`
|
||||
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
# Source: https://github.com/dotnet/dotnet-docker
|
||||
FROM mcr.microsoft.com/dotnet/runtime-deps:6.0-jammy as build
|
||||
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
ARG RUNNER_VERSION
|
||||
ARG RUNNER_CONTAINER_HOOKS_VERSION=0.4.0
|
||||
ARG DOCKER_VERSION=24.0.6
|
||||
ARG RUNNER_ARCH="x64"
|
||||
ARG RUNNER_CONTAINER_HOOKS_VERSION=0.3.2
|
||||
ARG DOCKER_VERSION=20.10.23
|
||||
|
||||
RUN apt update -y && apt install curl unzip -y
|
||||
|
||||
WORKDIR /actions-runner
|
||||
RUN export RUNNER_ARCH=${TARGETARCH} \
|
||||
&& if [ "$RUNNER_ARCH" = "amd64" ]; then export RUNNER_ARCH=x64 ; fi \
|
||||
&& curl -f -L -o runner.tar.gz https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-${TARGETOS}-${RUNNER_ARCH}-${RUNNER_VERSION}.tar.gz \
|
||||
RUN curl -f -L -o runner.tar.gz https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-${RUNNER_ARCH}-${RUNNER_VERSION}.tar.gz \
|
||||
&& tar xzf ./runner.tar.gz \
|
||||
&& rm runner.tar.gz
|
||||
|
||||
@@ -20,10 +17,9 @@ RUN curl -f -L -o runner-container-hooks.zip https://github.com/actions/runner-c
|
||||
&& unzip ./runner-container-hooks.zip -d ./k8s \
|
||||
&& rm runner-container-hooks.zip
|
||||
|
||||
RUN export RUNNER_ARCH=${TARGETARCH} \
|
||||
&& if [ "$RUNNER_ARCH" = "amd64" ]; then export DOCKER_ARCH=x86_64 ; fi \
|
||||
RUN export DOCKER_ARCH=x86_64 \
|
||||
&& if [ "$RUNNER_ARCH" = "arm64" ]; then export DOCKER_ARCH=aarch64 ; fi \
|
||||
&& curl -fLo docker.tgz https://download.docker.com/${TARGETOS}/static/stable/${DOCKER_ARCH}/docker-${DOCKER_VERSION}.tgz \
|
||||
&& curl -fLo docker.tgz https://download.docker.com/linux/static/stable/${DOCKER_ARCH}/docker-${DOCKER_VERSION}.tgz \
|
||||
&& tar zxvf docker.tgz \
|
||||
&& rm -rf docker.tgz
|
||||
|
||||
|
||||
@@ -1,44 +1,19 @@
|
||||
## What's Changed
|
||||
* Prepare runner release 2.309.0 by @johnsudol in https://github.com/actions/runner/pull/2833
|
||||
* remove debug-only flag from stale bot action by @ruvceskistefan in https://github.com/actions/runner/pull/2834
|
||||
* Calculate docker instance label based on the hash of the config by @nikola-jokic in https://github.com/actions/runner/pull/2683
|
||||
* Correcting `zen` address by @Pantelis-Santorinios in https://github.com/actions/runner/pull/2855
|
||||
* Update dotnet sdk to latest version @6.0.414 by @github-actions in https://github.com/actions/runner/pull/2852
|
||||
* Bump @typescript-eslint/parser from 6.4.1 to 6.7.0 in /src/Misc/expressionFunc/hashFiles by @dependabot in https://github.com/actions/runner/pull/2845
|
||||
* Bump @types/node from 20.5.6 to 20.6.2 in /src/Misc/expressionFunc/hashFiles by @dependabot in https://github.com/actions/runner/pull/2854
|
||||
* Bump eslint-plugin-github from 4.9.2 to 4.10.0 in /src/Misc/expressionFunc/hashFiles by @dependabot in https://github.com/actions/runner/pull/2808
|
||||
* Bump @typescript-eslint/parser from 6.7.0 to 6.7.2 in /src/Misc/expressionFunc/hashFiles by @dependabot in https://github.com/actions/runner/pull/2858
|
||||
* Bump prettier from 3.0.2 to 3.0.3 in /src/Misc/expressionFunc/hashFiles by @dependabot in https://github.com/actions/runner/pull/2860
|
||||
* Bump @vercel/ncc from 0.36.1 to 0.38.0 in /src/Misc/expressionFunc/hashFiles by @dependabot in https://github.com/actions/runner/pull/2859
|
||||
* Bump @typescript-eslint/eslint-plugin from 6.4.1 to 6.7.2 in /src/Misc/expressionFunc/hashFiles by @dependabot in https://github.com/actions/runner/pull/2861
|
||||
* Remove unused code in AgentManager. by @TingluoHuang in https://github.com/actions/runner/pull/2866
|
||||
* GetAgents from all runner groups durning config. by @TingluoHuang in https://github.com/actions/runner/pull/2865
|
||||
* Change alpine from vst blobs to OSS gha alpine build by @vanZeben in https://github.com/actions/runner/pull/2871
|
||||
* Bump node 16 to v16.20.2 by @vanZeben in https://github.com/actions/runner/pull/2872
|
||||
* Bump directly dotnet vulnerable packages by @nikola-jokic in https://github.com/actions/runner/pull/2870
|
||||
* Fix ArgumentOutOfRangeException in PowerShellPostAmpersandEscape. by @TingluoHuang in https://github.com/actions/runner/pull/2875
|
||||
* bump container hook version in runner image by @nikola-jokic in https://github.com/actions/runner/pull/2881
|
||||
* Use `Directory.EnumerateFiles` instead of `Directory.GetFiles` in WhichUtil. by @TingluoHuang in https://github.com/actions/runner/pull/2882
|
||||
* Add warning about node16 deprecation by @takost in https://github.com/actions/runner/pull/2887
|
||||
* Throw TimeoutException instead of OperationCanceledException on the final retry in DownloadRepositoryAction by @TingluoHuang in https://github.com/actions/runner/pull/2895
|
||||
* Update message when runners are deleted by @thboop in https://github.com/actions/runner/pull/2896
|
||||
* Do not give up if Results is powering logs by @yacaovsnc in https://github.com/actions/runner/pull/2893
|
||||
* Allow use action archive cache to speed up workflow jobs. by @TingluoHuang in https://github.com/actions/runner/pull/2857
|
||||
* Upgrade docker engine to 24.0.6 in the runner container image by @Link- in https://github.com/actions/runner/pull/2886
|
||||
* Collect telemetry to measure upload speed for different backend. by @TingluoHuang in https://github.com/actions/runner/pull/2912
|
||||
* Use RawHttpMessageHandler and VssHttpRetryMessageHandler in ResultsHttpClient by @yacaovsnc in https://github.com/actions/runner/pull/2908
|
||||
* Retries to lock Services database on Windows by @sugymt in https://github.com/actions/runner/pull/2880
|
||||
* Update default version to node20 by @takost in https://github.com/actions/runner/pull/2844
|
||||
* Fixed Attempt typo by @corycalahan in https://github.com/actions/runner/pull/2849
|
||||
* Fix typo by @rajbos in https://github.com/actions/runner/pull/2670
|
||||
## Features
|
||||
- Add warning to notify about forcing actions to run on node16 instead of node12 (#2678)
|
||||
|
||||
## New Contributors
|
||||
* @Pantelis-Santorinios made their first contribution in https://github.com/actions/runner/pull/2855
|
||||
* @github-actions made their first contribution in https://github.com/actions/runner/pull/2852
|
||||
* @sugymt made their first contribution in https://github.com/actions/runner/pull/2880
|
||||
* @corycalahan made their first contribution in https://github.com/actions/runner/pull/2849
|
||||
## Bugs
|
||||
- Remove job completion from runner listener (#2659)
|
||||
- Fix double error reporting (#2656)
|
||||
- Fix a bug with incorrect parsing of image values in a container action (#1873)
|
||||
- Fix error message reported on non-local action setup (#2668)
|
||||
- Extend github context with host-workspace (#2517)
|
||||
- Fixed a bug where a misplaced = character could bypass heredoc-style processing (#2627)
|
||||
|
||||
**Full Changelog**: https://github.com/actions/runner/compare/v2.309.0...v2.310.0
|
||||
## Misc
|
||||
- Send environment url to Run Service (#2650)
|
||||
- Reduce token service and unnecessary calls - send token to redirects (#2660)
|
||||
- Add 'http://' to http(s)_proxy if there is no protocol (#2663)
|
||||
- Remove extra result step for job itself (#2620)
|
||||
|
||||
_Note: Actions Runner follows a progressive release policy, so the latest release might not be available to your enterprise, organization, or repository yet.
|
||||
To confirm which version of the Actions Runner you should expect, please view the download instructions for your enterprise, organization, or repository.
|
||||
|
||||
@@ -1 +1 @@
|
||||
2.310.0
|
||||
<Update to ./src/runnerversion when creating release>
|
||||
|
||||
@@ -1 +1 @@
|
||||
7539d33c35b0bc94ee67e3c0de1a6bac5ef89ce8e8efaa110131fa0520a54fb4
|
||||
39f2a931565d6a10e695ac8ed14bb9dcbb568151410349b32dbf9c27bae29602
|
||||
@@ -1 +1 @@
|
||||
d71a31f9a17e1a41d6e1edea596edfa68a0db5948ed160e86f2154a547f4dd10
|
||||
29ffb303537d8ba674fbebc7729292c21c4ebd17b3198f91ed593ef4cbbb67b5
|
||||
@@ -1 +1 @@
|
||||
3c2f700d8a995efe7895614ee07d9c7880f872d214b45983ad6163e1931870ab
|
||||
de6868a836fa3cb9e5ddddbc079da1c25e819aa2d2fc193cc9931c353687c57c
|
||||
@@ -1 +1 @@
|
||||
b2d85c95ecad13d352f4c7d31c64dbb0d9c6381b48fa5874c4c72a43a025a8a1
|
||||
339d3e1a5fd28450c0fe6cb820cc7aae291f0f9e2d153ac34e1f7b080e35d30e
|
||||
@@ -1 +1 @@
|
||||
417d835c1a108619886b4bb5d25988cb6c138eb7b4c00320b1d9455c5630bff9
|
||||
dcb7f606c1d7d290381e5020ee73e7f16dcbd2f20ac9b431362ccbb5120d449c
|
||||
@@ -1 +1 @@
|
||||
8f35aaecfb53426ea10816442e23065142bab9dd0fb712a29e0fc471d13c44ac
|
||||
1bbcb0e9a2cf4be4b1fce77458de139b70ac58efcbb415a6db028b9373ae1673
|
||||
@@ -1 +1 @@
|
||||
811c7debdfc54d074385b063b83c997e5360c8a9160cd20fe777713968370063
|
||||
44cd25f3c104d0abb44d262397a80e0b2c4f206465c5d899a22eec043dac0fb3
|
||||
2
src/Misc/contentHash/externals/linux-arm
vendored
2
src/Misc/contentHash/externals/linux-arm
vendored
@@ -1 +1 @@
|
||||
97cbac637d592d3a5d20f6cd91a3afaf5257995c7f6fdc73ab1b5a3a464e4382
|
||||
3807dcbf947e840c33535fb466b096d76bf09e5c0254af8fc8cbbb24c6388222
|
||||
2
src/Misc/contentHash/externals/linux-arm64
vendored
2
src/Misc/contentHash/externals/linux-arm64
vendored
@@ -1 +1 @@
|
||||
25eaf1d30e72a521414384c24b7474037698325c233503671eceaacf6a56c6bd
|
||||
ee01eee80cd8a460a4b9780ee13fdd20f25c59e754b4ccd99df55fbba2a85634
|
||||
2
src/Misc/contentHash/externals/linux-x64
vendored
2
src/Misc/contentHash/externals/linux-x64
vendored
@@ -1 +1 @@
|
||||
93865f08e52e0fb0fe0119dca2363f221fbe10af5bd932a0fc3df999143a7f81
|
||||
a9fb9c14e24e79aec97d4da197dd7bfc6364297d6fce573afb2df48cc9a931f8
|
||||
2
src/Misc/contentHash/externals/osx-arm64
vendored
2
src/Misc/contentHash/externals/osx-arm64
vendored
@@ -1 +1 @@
|
||||
2574465a73ef1de75cd01da9232a96d4b6e9a0090e368978ff48d0629137610b
|
||||
a4e0e8fc62eba0967a39c7d693dcd0aeb8b2bed0765f9c38df80d42884f65341
|
||||
2
src/Misc/contentHash/externals/osx-x64
vendored
2
src/Misc/contentHash/externals/osx-x64
vendored
@@ -1 +1 @@
|
||||
ac60e452c01d99e23e696cc984f8e08b2602b649a370fc3ef1451f3958f2df0f
|
||||
17ac17fbe785b3d6fa2868d8d17185ebfe0c90b4b0ddf6b67eac70e42bcd989b
|
||||
2
src/Misc/contentHash/externals/win-arm64
vendored
2
src/Misc/contentHash/externals/win-arm64
vendored
@@ -1 +1 @@
|
||||
763d18de11c11fd299c0e75e98fefc8a0e6605ae0ad6aba3bbc110db2262ab41
|
||||
89f24657a550f1e818b0e9975e5b80edcf4dd22b7d4bccbb9e48e37f45d30fb1
|
||||
|
||||
2
src/Misc/contentHash/externals/win-x64
vendored
2
src/Misc/contentHash/externals/win-x64
vendored
@@ -1 +1 @@
|
||||
c7e94c3c73ccebf214497c5ae2b6aac6eb6677c0d2080929b0a87c576c6f3858
|
||||
24fd131b5dce33ef16038b771407bc0507da8682a72fb3b7780607235f76db0b
|
||||
@@ -1,19 +1,11 @@
|
||||
|
||||
{
|
||||
"printWidth": 80,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"bracketSpacing": false,
|
||||
"arrowParens": "avoid",
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.{js,ts,json}",
|
||||
"options": {
|
||||
"tabWidth": 2
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
"printWidth": 80,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"bracketSpacing": false,
|
||||
"arrowParens": "avoid",
|
||||
"parser": "typescript"
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
To compile this package (output will be stored in `Misc/layoutbin`) run `npm install && npm run prepare && npm run all`.
|
||||
To compile this package (output will be stored in `Misc/layoutbin`) run `npm install && npm run all`.
|
||||
|
||||
When you commit changes to the JSON or Typescript file, the javascript binary will be automatically re-compiled and added to the latest commit.
|
||||
> Note: this package also needs to be recompiled for dependabot PRs updating one of
|
||||
> its dependencies.
|
||||
|
||||
4586
src/Misc/expressionFunc/hashFiles/package-lock.json
generated
4586
src/Misc/expressionFunc/hashFiles/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -9,9 +9,7 @@
|
||||
"format-check": "prettier --check **/*.ts",
|
||||
"lint": "eslint src/**/*.ts",
|
||||
"pack": "ncc build -o ../../layoutbin/hashFiles",
|
||||
"all": "npm run format && npm run lint && npm run build && npm run pack",
|
||||
"prepare": "cd ../../../../ && husky install"
|
||||
|
||||
"all": "npm run build && npm run format && npm run lint && npm run pack"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -20,32 +18,18 @@
|
||||
"keywords": [
|
||||
"actions"
|
||||
],
|
||||
"lint-staged": {
|
||||
"*.md": [
|
||||
"prettier --write",
|
||||
"git add ."
|
||||
],
|
||||
"*.{ts,json}": [
|
||||
"sh -c 'npm run all'",
|
||||
"git add ."
|
||||
]
|
||||
},
|
||||
"author": "GitHub Actions",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/glob": "^0.4.0"
|
||||
"@actions/glob": "^0.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.6.2",
|
||||
"@typescript-eslint/eslint-plugin": "^6.7.2",
|
||||
"@typescript-eslint/parser": "^6.7.2",
|
||||
"@vercel/ncc": "^0.38.0",
|
||||
"eslint": "^8.47.0",
|
||||
"eslint-plugin-github": "^4.10.0",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"prettier": "^3.0.3",
|
||||
"typescript": "^5.2.2",
|
||||
"husky": "^8.0.3",
|
||||
"lint-staged": "^14.0.0"
|
||||
"@types/node": "^12.7.12",
|
||||
"@typescript-eslint/parser": "^5.15.0",
|
||||
"@vercel/ncc": "^0.36.0",
|
||||
"eslint": "^8.11.0",
|
||||
"eslint-plugin-github": "^4.3.5",
|
||||
"prettier": "^1.19.1",
|
||||
"typescript": "^3.6.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,13 +52,12 @@ async function run(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
;(async () => {
|
||||
try {
|
||||
const out = await run()
|
||||
run()
|
||||
.then(out => {
|
||||
console.log(out)
|
||||
process.exit(0)
|
||||
} catch (err) {
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
}
|
||||
})()
|
||||
})
|
||||
|
||||
@@ -4,11 +4,8 @@ PRECACHE=$2
|
||||
|
||||
NODE_URL=https://nodejs.org/dist
|
||||
UNOFFICIAL_NODE_URL=https://unofficial-builds.nodejs.org/download/release
|
||||
NODE_ALPINE_URL=https://github.com/actions/alpine_nodejs/releases/download
|
||||
NODE16_VERSION="16.20.2"
|
||||
NODE20_VERSION="20.5.0"
|
||||
# used only for win-arm64, remove node16 unofficial version when official version is available
|
||||
NODE16_UNOFFICIAL_VERSION="16.20.0"
|
||||
NODE12_VERSION="12.22.7"
|
||||
NODE16_VERSION="16.16.0"
|
||||
|
||||
get_abs_path() {
|
||||
# exploits the fact that pwd will print abs path when no args
|
||||
@@ -140,10 +137,10 @@ function acquireExternalTool() {
|
||||
|
||||
# Download the external tools only for Windows.
|
||||
if [[ "$PACKAGERUNTIME" == "win-x64" || "$PACKAGERUNTIME" == "win-x86" ]]; then
|
||||
acquireExternalTool "$NODE_URL/v${NODE12_VERSION}/$PACKAGERUNTIME/node.exe" node12/bin
|
||||
acquireExternalTool "$NODE_URL/v${NODE12_VERSION}/$PACKAGERUNTIME/node.lib" node12/bin
|
||||
acquireExternalTool "$NODE_URL/v${NODE16_VERSION}/$PACKAGERUNTIME/node.exe" node16/bin
|
||||
acquireExternalTool "$NODE_URL/v${NODE16_VERSION}/$PACKAGERUNTIME/node.lib" node16/bin
|
||||
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/$PACKAGERUNTIME/node.exe" node20/bin
|
||||
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/$PACKAGERUNTIME/node.lib" node20/bin
|
||||
if [[ "$PRECACHE" != "" ]]; then
|
||||
acquireExternalTool "https://github.com/microsoft/vswhere/releases/download/2.6.7/vswhere.exe" vswhere
|
||||
fi
|
||||
@@ -152,10 +149,8 @@ fi
|
||||
# Download the external tools only for Windows.
|
||||
if [[ "$PACKAGERUNTIME" == "win-arm64" ]]; then
|
||||
# todo: replace these with official release when available
|
||||
acquireExternalTool "$UNOFFICIAL_NODE_URL/v${NODE16_UNOFFICIAL_VERSION}/$PACKAGERUNTIME/node.exe" node16/bin
|
||||
acquireExternalTool "$UNOFFICIAL_NODE_URL/v${NODE16_UNOFFICIAL_VERSION}/$PACKAGERUNTIME/node.lib" node16/bin
|
||||
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/$PACKAGERUNTIME/node.exe" node20/bin
|
||||
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/$PACKAGERUNTIME/node.lib" node20/bin
|
||||
acquireExternalTool "$UNOFFICIAL_NODE_URL/v${NODE16_VERSION}/$PACKAGERUNTIME/node.exe" node16/bin
|
||||
acquireExternalTool "$UNOFFICIAL_NODE_URL/v${NODE16_VERSION}/$PACKAGERUNTIME/node.lib" node16/bin
|
||||
if [[ "$PRECACHE" != "" ]]; then
|
||||
acquireExternalTool "https://github.com/microsoft/vswhere/releases/download/2.6.7/vswhere.exe" vswhere
|
||||
fi
|
||||
@@ -163,30 +158,29 @@ fi
|
||||
|
||||
# Download the external tools only for OSX.
|
||||
if [[ "$PACKAGERUNTIME" == "osx-x64" ]]; then
|
||||
acquireExternalTool "$NODE_URL/v${NODE12_VERSION}/node-v${NODE12_VERSION}-darwin-x64.tar.gz" node12 fix_nested_dir
|
||||
acquireExternalTool "$NODE_URL/v${NODE16_VERSION}/node-v${NODE16_VERSION}-darwin-x64.tar.gz" node16 fix_nested_dir
|
||||
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-darwin-x64.tar.gz" node20 fix_nested_dir
|
||||
fi
|
||||
|
||||
if [[ "$PACKAGERUNTIME" == "osx-arm64" ]]; then
|
||||
# node.js v12 doesn't support macOS on arm64.
|
||||
acquireExternalTool "$NODE_URL/v${NODE16_VERSION}/node-v${NODE16_VERSION}-darwin-arm64.tar.gz" node16 fix_nested_dir
|
||||
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-darwin-arm64.tar.gz" node20 fix_nested_dir
|
||||
fi
|
||||
|
||||
# Download the external tools for Linux PACKAGERUNTIMEs.
|
||||
if [[ "$PACKAGERUNTIME" == "linux-x64" ]]; then
|
||||
acquireExternalTool "$NODE_URL/v${NODE12_VERSION}/node-v${NODE12_VERSION}-linux-x64.tar.gz" node12 fix_nested_dir
|
||||
acquireExternalTool "https://vstsagenttools.blob.core.windows.net/tools/nodejs/${NODE12_VERSION}/alpine/x64/node-v${NODE12_VERSION}-alpine-x64.tar.gz" node12_alpine
|
||||
acquireExternalTool "$NODE_URL/v${NODE16_VERSION}/node-v${NODE16_VERSION}-linux-x64.tar.gz" node16 fix_nested_dir
|
||||
acquireExternalTool "$NODE_ALPINE_URL/v${NODE16_VERSION}/node-v${NODE16_VERSION}-alpine-x64.tar.gz" node16_alpine
|
||||
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-linux-x64.tar.gz" node20 fix_nested_dir
|
||||
acquireExternalTool "$NODE_ALPINE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-alpine-x64.tar.gz" node20_alpine
|
||||
acquireExternalTool "https://vstsagenttools.blob.core.windows.net/tools/nodejs/${NODE16_VERSION}/alpine/x64/node-v${NODE16_VERSION}-alpine-x64.tar.gz" node16_alpine
|
||||
fi
|
||||
|
||||
if [[ "$PACKAGERUNTIME" == "linux-arm64" ]]; then
|
||||
acquireExternalTool "$NODE_URL/v${NODE12_VERSION}/node-v${NODE12_VERSION}-linux-arm64.tar.gz" node12 fix_nested_dir
|
||||
acquireExternalTool "$NODE_URL/v${NODE16_VERSION}/node-v${NODE16_VERSION}-linux-arm64.tar.gz" node16 fix_nested_dir
|
||||
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-linux-arm64.tar.gz" node20 fix_nested_dir
|
||||
fi
|
||||
|
||||
if [[ "$PACKAGERUNTIME" == "linux-arm" ]]; then
|
||||
acquireExternalTool "$NODE_URL/v${NODE12_VERSION}/node-v${NODE12_VERSION}-linux-armv7l.tar.gz" node12 fix_nested_dir
|
||||
acquireExternalTool "$NODE_URL/v${NODE16_VERSION}/node-v${NODE16_VERSION}-linux-armv7l.tar.gz" node16 fix_nested_dir
|
||||
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-linux-armv7l.tar.gz" node20 fix_nested_dir
|
||||
fi
|
||||
|
||||
@@ -6,29 +6,6 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
@@ -45,6 +22,13 @@ var __asyncValues = (this && this.__asyncValues) || function (o) {
|
||||
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
||||
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
||||
};
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
||||
result["default"] = mod;
|
||||
return result;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
const crypto = __importStar(__nccwpck_require__(6113));
|
||||
const fs = __importStar(__nccwpck_require__(7147));
|
||||
@@ -53,7 +37,7 @@ const path = __importStar(__nccwpck_require__(1017));
|
||||
const stream = __importStar(__nccwpck_require__(2781));
|
||||
const util = __importStar(__nccwpck_require__(3837));
|
||||
function run() {
|
||||
var _a, e_1, _b, _c;
|
||||
var e_1, _a;
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
// arg0 -> node
|
||||
// arg1 -> hashFiles.js
|
||||
@@ -72,10 +56,8 @@ function run() {
|
||||
let count = 0;
|
||||
const globber = yield glob.create(matchPatterns, { followSymbolicLinks });
|
||||
try {
|
||||
for (var _d = true, _e = __asyncValues(globber.globGenerator()), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
|
||||
_c = _f.value;
|
||||
_d = false;
|
||||
const file = _c;
|
||||
for (var _b = __asyncValues(globber.globGenerator()), _c; _c = yield _b.next(), !_c.done;) {
|
||||
const file = _c.value;
|
||||
console.log(file);
|
||||
if (!file.startsWith(`${githubWorkspace}${path.sep}`)) {
|
||||
console.log(`Ignore '${file}' since it is not under GITHUB_WORKSPACE.`);
|
||||
@@ -98,7 +80,7 @@ function run() {
|
||||
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
||||
finally {
|
||||
try {
|
||||
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
|
||||
if (_c && !_c.done && (_a = _b.return)) yield _a.call(_b);
|
||||
}
|
||||
finally { if (e_1) throw e_1.error; }
|
||||
}
|
||||
@@ -112,18 +94,15 @@ function run() {
|
||||
}
|
||||
});
|
||||
}
|
||||
;
|
||||
(() => __awaiter(void 0, void 0, void 0, function* () {
|
||||
try {
|
||||
const out = yield run();
|
||||
console.log(out);
|
||||
process.exit(0);
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
}))();
|
||||
run()
|
||||
.then(out => {
|
||||
console.log(out);
|
||||
process.exit(0);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
|
||||
/***/ }),
|
||||
@@ -267,6 +246,7 @@ const file_command_1 = __nccwpck_require__(717);
|
||||
const utils_1 = __nccwpck_require__(5278);
|
||||
const os = __importStar(__nccwpck_require__(2037));
|
||||
const path = __importStar(__nccwpck_require__(1017));
|
||||
const uuid_1 = __nccwpck_require__(5840);
|
||||
const oidc_utils_1 = __nccwpck_require__(8041);
|
||||
/**
|
||||
* The code to exit an action
|
||||
@@ -296,9 +276,20 @@ function exportVariable(name, val) {
|
||||
process.env[name] = convertedVal;
|
||||
const filePath = process.env['GITHUB_ENV'] || '';
|
||||
if (filePath) {
|
||||
return file_command_1.issueFileCommand('ENV', file_command_1.prepareKeyValueMessage(name, val));
|
||||
const delimiter = `ghadelimiter_${uuid_1.v4()}`;
|
||||
// These should realistically never happen, but just in case someone finds a way to exploit uuid generation let's not allow keys or values that contain the delimiter.
|
||||
if (name.includes(delimiter)) {
|
||||
throw new Error(`Unexpected input: name should not contain the delimiter "${delimiter}"`);
|
||||
}
|
||||
if (convertedVal.includes(delimiter)) {
|
||||
throw new Error(`Unexpected input: value should not contain the delimiter "${delimiter}"`);
|
||||
}
|
||||
const commandValue = `${name}<<${delimiter}${os.EOL}${convertedVal}${os.EOL}${delimiter}`;
|
||||
file_command_1.issueCommand('ENV', commandValue);
|
||||
}
|
||||
else {
|
||||
command_1.issueCommand('set-env', { name }, convertedVal);
|
||||
}
|
||||
command_1.issueCommand('set-env', { name }, convertedVal);
|
||||
}
|
||||
exports.exportVariable = exportVariable;
|
||||
/**
|
||||
@@ -316,7 +307,7 @@ exports.setSecret = setSecret;
|
||||
function addPath(inputPath) {
|
||||
const filePath = process.env['GITHUB_PATH'] || '';
|
||||
if (filePath) {
|
||||
file_command_1.issueFileCommand('PATH', inputPath);
|
||||
file_command_1.issueCommand('PATH', inputPath);
|
||||
}
|
||||
else {
|
||||
command_1.issueCommand('add-path', {}, inputPath);
|
||||
@@ -356,10 +347,7 @@ function getMultilineInput(name, options) {
|
||||
const inputs = getInput(name, options)
|
||||
.split('\n')
|
||||
.filter(x => x !== '');
|
||||
if (options && options.trimWhitespace === false) {
|
||||
return inputs;
|
||||
}
|
||||
return inputs.map(input => input.trim());
|
||||
return inputs;
|
||||
}
|
||||
exports.getMultilineInput = getMultilineInput;
|
||||
/**
|
||||
@@ -392,12 +380,8 @@ exports.getBooleanInput = getBooleanInput;
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function setOutput(name, value) {
|
||||
const filePath = process.env['GITHUB_OUTPUT'] || '';
|
||||
if (filePath) {
|
||||
return file_command_1.issueFileCommand('OUTPUT', file_command_1.prepareKeyValueMessage(name, value));
|
||||
}
|
||||
process.stdout.write(os.EOL);
|
||||
command_1.issueCommand('set-output', { name }, utils_1.toCommandValue(value));
|
||||
command_1.issueCommand('set-output', { name }, value);
|
||||
}
|
||||
exports.setOutput = setOutput;
|
||||
/**
|
||||
@@ -526,11 +510,7 @@ exports.group = group;
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function saveState(name, value) {
|
||||
const filePath = process.env['GITHUB_STATE'] || '';
|
||||
if (filePath) {
|
||||
return file_command_1.issueFileCommand('STATE', file_command_1.prepareKeyValueMessage(name, value));
|
||||
}
|
||||
command_1.issueCommand('save-state', { name }, utils_1.toCommandValue(value));
|
||||
command_1.issueCommand('save-state', { name }, value);
|
||||
}
|
||||
exports.saveState = saveState;
|
||||
/**
|
||||
@@ -596,14 +576,13 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
||||
return result;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.prepareKeyValueMessage = exports.issueFileCommand = void 0;
|
||||
exports.issueCommand = void 0;
|
||||
// We use any as a valid input type
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
const fs = __importStar(__nccwpck_require__(7147));
|
||||
const os = __importStar(__nccwpck_require__(2037));
|
||||
const uuid_1 = __nccwpck_require__(5840);
|
||||
const utils_1 = __nccwpck_require__(5278);
|
||||
function issueFileCommand(command, message) {
|
||||
function issueCommand(command, message) {
|
||||
const filePath = process.env[`GITHUB_${command}`];
|
||||
if (!filePath) {
|
||||
throw new Error(`Unable to find environment variable for file command ${command}`);
|
||||
@@ -615,22 +594,7 @@ function issueFileCommand(command, message) {
|
||||
encoding: 'utf8'
|
||||
});
|
||||
}
|
||||
exports.issueFileCommand = issueFileCommand;
|
||||
function prepareKeyValueMessage(key, value) {
|
||||
const delimiter = `ghadelimiter_${uuid_1.v4()}`;
|
||||
const convertedValue = utils_1.toCommandValue(value);
|
||||
// These should realistically never happen, but just in case someone finds a
|
||||
// way to exploit uuid generation let's not allow keys or values that contain
|
||||
// the delimiter.
|
||||
if (key.includes(delimiter)) {
|
||||
throw new Error(`Unexpected input: name should not contain the delimiter "${delimiter}"`);
|
||||
}
|
||||
if (convertedValue.includes(delimiter)) {
|
||||
throw new Error(`Unexpected input: value should not contain the delimiter "${delimiter}"`);
|
||||
}
|
||||
return `${key}<<${delimiter}${os.EOL}${convertedValue}${os.EOL}${delimiter}`;
|
||||
}
|
||||
exports.prepareKeyValueMessage = prepareKeyValueMessage;
|
||||
exports.issueCommand = issueCommand;
|
||||
//# sourceMappingURL=file-command.js.map
|
||||
|
||||
/***/ }),
|
||||
@@ -1136,9 +1100,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
||||
});
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.hashFiles = exports.create = void 0;
|
||||
const internal_globber_1 = __nccwpck_require__(8298);
|
||||
const internal_hash_files_1 = __nccwpck_require__(2448);
|
||||
/**
|
||||
* Constructs a globber
|
||||
*
|
||||
@@ -1151,56 +1113,17 @@ function create(patterns, options) {
|
||||
});
|
||||
}
|
||||
exports.create = create;
|
||||
/**
|
||||
* Computes the sha256 hash of a glob
|
||||
*
|
||||
* @param patterns Patterns separated by newlines
|
||||
* @param currentWorkspace Workspace used when matching files
|
||||
* @param options Glob options
|
||||
* @param verbose Enables verbose logging
|
||||
*/
|
||||
function hashFiles(patterns, currentWorkspace = '', options, verbose = false) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
let followSymbolicLinks = true;
|
||||
if (options && typeof options.followSymbolicLinks === 'boolean') {
|
||||
followSymbolicLinks = options.followSymbolicLinks;
|
||||
}
|
||||
const globber = yield create(patterns, { followSymbolicLinks });
|
||||
return internal_hash_files_1.hashFiles(globber, currentWorkspace, verbose);
|
||||
});
|
||||
}
|
||||
exports.hashFiles = hashFiles;
|
||||
//# sourceMappingURL=glob.js.map
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 1026:
|
||||
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
||||
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
|
||||
|
||||
"use strict";
|
||||
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.getOptions = void 0;
|
||||
const core = __importStar(__nccwpck_require__(2186));
|
||||
const core = __nccwpck_require__(2186);
|
||||
/**
|
||||
* Returns a copy with defaults filled in.
|
||||
*/
|
||||
@@ -1208,7 +1131,6 @@ function getOptions(copy) {
|
||||
const result = {
|
||||
followSymbolicLinks: true,
|
||||
implicitDescendants: true,
|
||||
matchDirectories: true,
|
||||
omitBrokenSymbolicLinks: true
|
||||
};
|
||||
if (copy) {
|
||||
@@ -1220,10 +1142,6 @@ function getOptions(copy) {
|
||||
result.implicitDescendants = copy.implicitDescendants;
|
||||
core.debug(`implicitDescendants '${result.implicitDescendants}'`);
|
||||
}
|
||||
if (typeof copy.matchDirectories === 'boolean') {
|
||||
result.matchDirectories = copy.matchDirectories;
|
||||
core.debug(`matchDirectories '${result.matchDirectories}'`);
|
||||
}
|
||||
if (typeof copy.omitBrokenSymbolicLinks === 'boolean') {
|
||||
result.omitBrokenSymbolicLinks = copy.omitBrokenSymbolicLinks;
|
||||
core.debug(`omitBrokenSymbolicLinks '${result.omitBrokenSymbolicLinks}'`);
|
||||
@@ -1241,25 +1159,6 @@ exports.getOptions = getOptions;
|
||||
|
||||
"use strict";
|
||||
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
@@ -1289,12 +1188,11 @@ var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _ar
|
||||
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.DefaultGlobber = void 0;
|
||||
const core = __importStar(__nccwpck_require__(2186));
|
||||
const fs = __importStar(__nccwpck_require__(7147));
|
||||
const globOptionsHelper = __importStar(__nccwpck_require__(1026));
|
||||
const path = __importStar(__nccwpck_require__(1017));
|
||||
const patternHelper = __importStar(__nccwpck_require__(9005));
|
||||
const core = __nccwpck_require__(2186);
|
||||
const fs = __nccwpck_require__(7147);
|
||||
const globOptionsHelper = __nccwpck_require__(1026);
|
||||
const path = __nccwpck_require__(1017);
|
||||
const patternHelper = __nccwpck_require__(9005);
|
||||
const internal_match_kind_1 = __nccwpck_require__(1063);
|
||||
const internal_pattern_1 = __nccwpck_require__(4536);
|
||||
const internal_search_state_1 = __nccwpck_require__(9117);
|
||||
@@ -1340,7 +1238,7 @@ class DefaultGlobber {
|
||||
if (options.implicitDescendants &&
|
||||
(pattern.trailingSeparator ||
|
||||
pattern.segments[pattern.segments.length - 1] !== '**')) {
|
||||
patterns.push(new internal_pattern_1.Pattern(pattern.negate, true, pattern.segments.concat('**')));
|
||||
patterns.push(new internal_pattern_1.Pattern(pattern.negate, pattern.segments.concat('**')));
|
||||
}
|
||||
}
|
||||
// Push the search paths
|
||||
@@ -1383,7 +1281,7 @@ class DefaultGlobber {
|
||||
// Directory
|
||||
if (stats.isDirectory()) {
|
||||
// Matched
|
||||
if (match & internal_match_kind_1.MatchKind.Directory && options.matchDirectories) {
|
||||
if (match & internal_match_kind_1.MatchKind.Directory) {
|
||||
yield yield __await(item.path);
|
||||
}
|
||||
// Descend?
|
||||
@@ -1478,117 +1376,12 @@ exports.DefaultGlobber = DefaultGlobber;
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 2448:
|
||||
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
var __asyncValues = (this && this.__asyncValues) || function (o) {
|
||||
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
||||
var m = o[Symbol.asyncIterator], i;
|
||||
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
|
||||
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
||||
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.hashFiles = void 0;
|
||||
const crypto = __importStar(__nccwpck_require__(6113));
|
||||
const core = __importStar(__nccwpck_require__(2186));
|
||||
const fs = __importStar(__nccwpck_require__(7147));
|
||||
const stream = __importStar(__nccwpck_require__(2781));
|
||||
const util = __importStar(__nccwpck_require__(3837));
|
||||
const path = __importStar(__nccwpck_require__(1017));
|
||||
function hashFiles(globber, currentWorkspace, verbose = false) {
|
||||
var e_1, _a;
|
||||
var _b;
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const writeDelegate = verbose ? core.info : core.debug;
|
||||
let hasMatch = false;
|
||||
const githubWorkspace = currentWorkspace
|
||||
? currentWorkspace
|
||||
: (_b = process.env['GITHUB_WORKSPACE']) !== null && _b !== void 0 ? _b : process.cwd();
|
||||
const result = crypto.createHash('sha256');
|
||||
let count = 0;
|
||||
try {
|
||||
for (var _c = __asyncValues(globber.globGenerator()), _d; _d = yield _c.next(), !_d.done;) {
|
||||
const file = _d.value;
|
||||
writeDelegate(file);
|
||||
if (!file.startsWith(`${githubWorkspace}${path.sep}`)) {
|
||||
writeDelegate(`Ignore '${file}' since it is not under GITHUB_WORKSPACE.`);
|
||||
continue;
|
||||
}
|
||||
if (fs.statSync(file).isDirectory()) {
|
||||
writeDelegate(`Skip directory '${file}'.`);
|
||||
continue;
|
||||
}
|
||||
const hash = crypto.createHash('sha256');
|
||||
const pipeline = util.promisify(stream.pipeline);
|
||||
yield pipeline(fs.createReadStream(file), hash);
|
||||
result.write(hash.digest());
|
||||
count++;
|
||||
if (!hasMatch) {
|
||||
hasMatch = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
||||
finally {
|
||||
try {
|
||||
if (_d && !_d.done && (_a = _c.return)) yield _a.call(_c);
|
||||
}
|
||||
finally { if (e_1) throw e_1.error; }
|
||||
}
|
||||
result.end();
|
||||
if (hasMatch) {
|
||||
writeDelegate(`Found ${count} files to hash.`);
|
||||
return result.digest('hex');
|
||||
}
|
||||
else {
|
||||
writeDelegate(`No matches found for glob`);
|
||||
return '';
|
||||
}
|
||||
});
|
||||
}
|
||||
exports.hashFiles = hashFiles;
|
||||
//# sourceMappingURL=internal-hash-files.js.map
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 1063:
|
||||
/***/ ((__unused_webpack_module, exports) => {
|
||||
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.MatchKind = void 0;
|
||||
/**
|
||||
* Indicates whether a pattern matches a path
|
||||
*/
|
||||
@@ -1608,36 +1401,13 @@ var MatchKind;
|
||||
/***/ }),
|
||||
|
||||
/***/ 1849:
|
||||
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
||||
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
|
||||
|
||||
"use strict";
|
||||
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.safeTrimTrailingSeparator = exports.normalizeSeparators = exports.hasRoot = exports.hasAbsoluteRoot = exports.ensureAbsoluteRoot = exports.dirname = void 0;
|
||||
const path = __importStar(__nccwpck_require__(1017));
|
||||
const assert_1 = __importDefault(__nccwpck_require__(9491));
|
||||
const assert = __nccwpck_require__(9491);
|
||||
const path = __nccwpck_require__(1017);
|
||||
const IS_WINDOWS = process.platform === 'win32';
|
||||
/**
|
||||
* Similar to path.dirname except normalizes the path separators and slightly better handling for Windows UNC paths.
|
||||
@@ -1677,8 +1447,8 @@ exports.dirname = dirname;
|
||||
* or `C:` are expanded based on the current working directory.
|
||||
*/
|
||||
function ensureAbsoluteRoot(root, itemPath) {
|
||||
assert_1.default(root, `ensureAbsoluteRoot parameter 'root' must not be empty`);
|
||||
assert_1.default(itemPath, `ensureAbsoluteRoot parameter 'itemPath' must not be empty`);
|
||||
assert(root, `ensureAbsoluteRoot parameter 'root' must not be empty`);
|
||||
assert(itemPath, `ensureAbsoluteRoot parameter 'itemPath' must not be empty`);
|
||||
// Already rooted
|
||||
if (hasAbsoluteRoot(itemPath)) {
|
||||
return itemPath;
|
||||
@@ -1688,7 +1458,7 @@ function ensureAbsoluteRoot(root, itemPath) {
|
||||
// Check for itemPath like C: or C:foo
|
||||
if (itemPath.match(/^[A-Z]:[^\\/]|^[A-Z]:$/i)) {
|
||||
let cwd = process.cwd();
|
||||
assert_1.default(cwd.match(/^[A-Z]:\\/i), `Expected current directory to start with an absolute drive root. Actual '${cwd}'`);
|
||||
assert(cwd.match(/^[A-Z]:\\/i), `Expected current directory to start with an absolute drive root. Actual '${cwd}'`);
|
||||
// Drive letter matches cwd? Expand to cwd
|
||||
if (itemPath[0].toUpperCase() === cwd[0].toUpperCase()) {
|
||||
// Drive only, e.g. C:
|
||||
@@ -1713,11 +1483,11 @@ function ensureAbsoluteRoot(root, itemPath) {
|
||||
// Check for itemPath like \ or \foo
|
||||
else if (normalizeSeparators(itemPath).match(/^\\$|^\\[^\\]/)) {
|
||||
const cwd = process.cwd();
|
||||
assert_1.default(cwd.match(/^[A-Z]:\\/i), `Expected current directory to start with an absolute drive root. Actual '${cwd}'`);
|
||||
assert(cwd.match(/^[A-Z]:\\/i), `Expected current directory to start with an absolute drive root. Actual '${cwd}'`);
|
||||
return `${cwd[0]}:\\${itemPath.substr(1)}`;
|
||||
}
|
||||
}
|
||||
assert_1.default(hasAbsoluteRoot(root), `ensureAbsoluteRoot parameter 'root' must have an absolute root`);
|
||||
assert(hasAbsoluteRoot(root), `ensureAbsoluteRoot parameter 'root' must have an absolute root`);
|
||||
// Otherwise ensure root ends with a separator
|
||||
if (root.endsWith('/') || (IS_WINDOWS && root.endsWith('\\'))) {
|
||||
// Intentionally empty
|
||||
@@ -1734,7 +1504,7 @@ exports.ensureAbsoluteRoot = ensureAbsoluteRoot;
|
||||
* `\\hello\share` and `C:\hello` (and using alternate separator).
|
||||
*/
|
||||
function hasAbsoluteRoot(itemPath) {
|
||||
assert_1.default(itemPath, `hasAbsoluteRoot parameter 'itemPath' must not be empty`);
|
||||
assert(itemPath, `hasAbsoluteRoot parameter 'itemPath' must not be empty`);
|
||||
// Normalize separators
|
||||
itemPath = normalizeSeparators(itemPath);
|
||||
// Windows
|
||||
@@ -1751,7 +1521,7 @@ exports.hasAbsoluteRoot = hasAbsoluteRoot;
|
||||
* `\`, `\hello`, `\\hello\share`, `C:`, and `C:\hello` (and using alternate separator).
|
||||
*/
|
||||
function hasRoot(itemPath) {
|
||||
assert_1.default(itemPath, `isRooted parameter 'itemPath' must not be empty`);
|
||||
assert(itemPath, `isRooted parameter 'itemPath' must not be empty`);
|
||||
// Normalize separators
|
||||
itemPath = normalizeSeparators(itemPath);
|
||||
// Windows
|
||||
@@ -1813,37 +1583,14 @@ exports.safeTrimTrailingSeparator = safeTrimTrailingSeparator;
|
||||
/***/ }),
|
||||
|
||||
/***/ 6836:
|
||||
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
||||
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
|
||||
|
||||
"use strict";
|
||||
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.Path = void 0;
|
||||
const path = __importStar(__nccwpck_require__(1017));
|
||||
const pathHelper = __importStar(__nccwpck_require__(1849));
|
||||
const assert_1 = __importDefault(__nccwpck_require__(9491));
|
||||
const assert = __nccwpck_require__(9491);
|
||||
const path = __nccwpck_require__(1017);
|
||||
const pathHelper = __nccwpck_require__(1849);
|
||||
const IS_WINDOWS = process.platform === 'win32';
|
||||
/**
|
||||
* Helper class for parsing paths into segments
|
||||
@@ -1857,7 +1604,7 @@ class Path {
|
||||
this.segments = [];
|
||||
// String
|
||||
if (typeof itemPath === 'string') {
|
||||
assert_1.default(itemPath, `Parameter 'itemPath' must not be empty`);
|
||||
assert(itemPath, `Parameter 'itemPath' must not be empty`);
|
||||
// Normalize slashes and trim unnecessary trailing slash
|
||||
itemPath = pathHelper.safeTrimTrailingSeparator(itemPath);
|
||||
// Not rooted
|
||||
@@ -1884,24 +1631,24 @@ class Path {
|
||||
// Array
|
||||
else {
|
||||
// Must not be empty
|
||||
assert_1.default(itemPath.length > 0, `Parameter 'itemPath' must not be an empty array`);
|
||||
assert(itemPath.length > 0, `Parameter 'itemPath' must not be an empty array`);
|
||||
// Each segment
|
||||
for (let i = 0; i < itemPath.length; i++) {
|
||||
let segment = itemPath[i];
|
||||
// Must not be empty
|
||||
assert_1.default(segment, `Parameter 'itemPath' must not contain any empty segments`);
|
||||
assert(segment, `Parameter 'itemPath' must not contain any empty segments`);
|
||||
// Normalize slashes
|
||||
segment = pathHelper.normalizeSeparators(itemPath[i]);
|
||||
// Root segment
|
||||
if (i === 0 && pathHelper.hasRoot(segment)) {
|
||||
segment = pathHelper.safeTrimTrailingSeparator(segment);
|
||||
assert_1.default(segment === pathHelper.dirname(segment), `Parameter 'itemPath' root segment contains information for multiple segments`);
|
||||
assert(segment === pathHelper.dirname(segment), `Parameter 'itemPath' root segment contains information for multiple segments`);
|
||||
this.segments.push(segment);
|
||||
}
|
||||
// All other segments
|
||||
else {
|
||||
// Must not contain slash
|
||||
assert_1.default(!segment.includes(path.sep), `Parameter 'itemPath' contains unexpected path separators`);
|
||||
assert(!segment.includes(path.sep), `Parameter 'itemPath' contains unexpected path separators`);
|
||||
this.segments.push(segment);
|
||||
}
|
||||
}
|
||||
@@ -1933,32 +1680,12 @@ exports.Path = Path;
|
||||
/***/ }),
|
||||
|
||||
/***/ 9005:
|
||||
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
||||
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
|
||||
|
||||
"use strict";
|
||||
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.partialMatch = exports.match = exports.getSearchPaths = void 0;
|
||||
const pathHelper = __importStar(__nccwpck_require__(1849));
|
||||
const pathHelper = __nccwpck_require__(1849);
|
||||
const internal_match_kind_1 = __nccwpck_require__(1063);
|
||||
const IS_WINDOWS = process.platform === 'win32';
|
||||
/**
|
||||
@@ -2034,44 +1761,21 @@ exports.partialMatch = partialMatch;
|
||||
/***/ }),
|
||||
|
||||
/***/ 4536:
|
||||
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
||||
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
|
||||
|
||||
"use strict";
|
||||
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.Pattern = void 0;
|
||||
const os = __importStar(__nccwpck_require__(2037));
|
||||
const path = __importStar(__nccwpck_require__(1017));
|
||||
const pathHelper = __importStar(__nccwpck_require__(1849));
|
||||
const assert_1 = __importDefault(__nccwpck_require__(9491));
|
||||
const assert = __nccwpck_require__(9491);
|
||||
const os = __nccwpck_require__(2037);
|
||||
const path = __nccwpck_require__(1017);
|
||||
const pathHelper = __nccwpck_require__(1849);
|
||||
const minimatch_1 = __nccwpck_require__(3973);
|
||||
const internal_match_kind_1 = __nccwpck_require__(1063);
|
||||
const internal_path_1 = __nccwpck_require__(6836);
|
||||
const IS_WINDOWS = process.platform === 'win32';
|
||||
class Pattern {
|
||||
constructor(patternOrNegate, isImplicitPattern = false, segments, homedir) {
|
||||
constructor(patternOrNegate, segments) {
|
||||
/**
|
||||
* Indicates whether matches should be excluded from the result set
|
||||
*/
|
||||
@@ -2085,9 +1789,9 @@ class Pattern {
|
||||
else {
|
||||
// Convert to pattern
|
||||
segments = segments || [];
|
||||
assert_1.default(segments.length, `Parameter 'segments' must not empty`);
|
||||
assert(segments.length, `Parameter 'segments' must not empty`);
|
||||
const root = Pattern.getLiteral(segments[0]);
|
||||
assert_1.default(root && pathHelper.hasAbsoluteRoot(root), `Parameter 'segments' first element must be a root path`);
|
||||
assert(root && pathHelper.hasAbsoluteRoot(root), `Parameter 'segments' first element must be a root path`);
|
||||
pattern = new internal_path_1.Path(segments).toString().trim();
|
||||
if (patternOrNegate) {
|
||||
pattern = `!${pattern}`;
|
||||
@@ -2099,7 +1803,7 @@ class Pattern {
|
||||
pattern = pattern.substr(1).trim();
|
||||
}
|
||||
// Normalize slashes and ensures absolute root
|
||||
pattern = Pattern.fixupPattern(pattern, homedir);
|
||||
pattern = Pattern.fixupPattern(pattern);
|
||||
// Segments
|
||||
this.segments = new internal_path_1.Path(pattern).segments;
|
||||
// Trailing slash indicates the pattern should only match directories, not regular files
|
||||
@@ -2115,7 +1819,6 @@ class Pattern {
|
||||
this.searchPath = new internal_path_1.Path(searchSegments).toString();
|
||||
// Root RegExp (required when determining partial match)
|
||||
this.rootRegExp = new RegExp(Pattern.regExpEscape(searchSegments[0]), IS_WINDOWS ? 'i' : '');
|
||||
this.isImplicitPattern = isImplicitPattern;
|
||||
// Create minimatch
|
||||
const minimatchOptions = {
|
||||
dot: true,
|
||||
@@ -2137,11 +1840,11 @@ class Pattern {
|
||||
// Normalize slashes
|
||||
itemPath = pathHelper.normalizeSeparators(itemPath);
|
||||
// Append a trailing slash. Otherwise Minimatch will not match the directory immediately
|
||||
// preceding the globstar. For example, given the pattern `/foo/**`, Minimatch returns
|
||||
// preceeding the globstar. For example, given the pattern `/foo/**`, Minimatch returns
|
||||
// false for `/foo` but returns true for `/foo/`. Append a trailing slash to handle that quirk.
|
||||
if (!itemPath.endsWith(path.sep) && this.isImplicitPattern === false) {
|
||||
if (!itemPath.endsWith(path.sep)) {
|
||||
// Note, this is safe because the constructor ensures the pattern has an absolute root.
|
||||
// For example, formats like C: and C:foo on Windows are resolved to an absolute root.
|
||||
// For example, formats like C: and C:foo on Windows are resolved to an aboslute root.
|
||||
itemPath = `${itemPath}${path.sep}`;
|
||||
}
|
||||
}
|
||||
@@ -2179,15 +1882,15 @@ class Pattern {
|
||||
/**
|
||||
* Normalizes slashes and ensures absolute root
|
||||
*/
|
||||
static fixupPattern(pattern, homedir) {
|
||||
static fixupPattern(pattern) {
|
||||
// Empty
|
||||
assert_1.default(pattern, 'pattern cannot be empty');
|
||||
assert(pattern, 'pattern cannot be empty');
|
||||
// Must not contain `.` segment, unless first segment
|
||||
// Must not contain `..` segment
|
||||
const literalSegments = new internal_path_1.Path(pattern).segments.map(x => Pattern.getLiteral(x));
|
||||
assert_1.default(literalSegments.every((x, i) => (x !== '.' || i === 0) && x !== '..'), `Invalid pattern '${pattern}'. Relative pathing '.' and '..' is not allowed.`);
|
||||
assert(literalSegments.every((x, i) => (x !== '.' || i === 0) && x !== '..'), `Invalid pattern '${pattern}'. Relative pathing '.' and '..' is not allowed.`);
|
||||
// Must not contain globs in root, e.g. Windows UNC path \\foo\b*r
|
||||
assert_1.default(!pathHelper.hasRoot(pattern) || literalSegments[0], `Invalid pattern '${pattern}'. Root segment must not contain globs.`);
|
||||
assert(!pathHelper.hasRoot(pattern) || literalSegments[0], `Invalid pattern '${pattern}'. Root segment must not contain globs.`);
|
||||
// Normalize slashes
|
||||
pattern = pathHelper.normalizeSeparators(pattern);
|
||||
// Replace leading `.` segment
|
||||
@@ -2196,9 +1899,9 @@ class Pattern {
|
||||
}
|
||||
// Replace leading `~` segment
|
||||
else if (pattern === '~' || pattern.startsWith(`~${path.sep}`)) {
|
||||
homedir = homedir || os.homedir();
|
||||
assert_1.default(homedir, 'Unable to determine HOME directory');
|
||||
assert_1.default(pathHelper.hasAbsoluteRoot(homedir), `Expected HOME directory to be a rooted path. Actual '${homedir}'`);
|
||||
const homedir = os.homedir();
|
||||
assert(homedir, 'Unable to determine HOME directory');
|
||||
assert(pathHelper.hasAbsoluteRoot(homedir), `Expected HOME directory to be a rooted path. Actual '${homedir}'`);
|
||||
pattern = Pattern.globEscape(homedir) + pattern.substr(1);
|
||||
}
|
||||
// Replace relative drive root, e.g. pattern is C: or C:foo
|
||||
@@ -2301,7 +2004,6 @@ exports.Pattern = Pattern;
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.SearchState = void 0;
|
||||
class SearchState {
|
||||
constructor(path, level) {
|
||||
this.path = path;
|
||||
@@ -2530,19 +2232,6 @@ class HttpClientResponse {
|
||||
}));
|
||||
});
|
||||
}
|
||||
readBodyBuffer() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
return new Promise((resolve) => __awaiter(this, void 0, void 0, function* () {
|
||||
const chunks = [];
|
||||
this.message.on('data', (chunk) => {
|
||||
chunks.push(chunk);
|
||||
});
|
||||
this.message.on('end', () => {
|
||||
resolve(Buffer.concat(chunks));
|
||||
});
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
exports.HttpClientResponse = HttpClientResponse;
|
||||
function isHttps(requestUrl) {
|
||||
@@ -3047,13 +2736,7 @@ function getProxyUrl(reqUrl) {
|
||||
}
|
||||
})();
|
||||
if (proxyVar) {
|
||||
try {
|
||||
return new URL(proxyVar);
|
||||
}
|
||||
catch (_a) {
|
||||
if (!proxyVar.startsWith('http://') && !proxyVar.startsWith('https://'))
|
||||
return new URL(`http://${proxyVar}`);
|
||||
}
|
||||
return new URL(proxyVar);
|
||||
}
|
||||
else {
|
||||
return undefined;
|
||||
@@ -3064,10 +2747,6 @@ function checkBypass(reqUrl) {
|
||||
if (!reqUrl.hostname) {
|
||||
return false;
|
||||
}
|
||||
const reqHost = reqUrl.hostname;
|
||||
if (isLoopbackAddress(reqHost)) {
|
||||
return true;
|
||||
}
|
||||
const noProxy = process.env['no_proxy'] || process.env['NO_PROXY'] || '';
|
||||
if (!noProxy) {
|
||||
return false;
|
||||
@@ -3093,24 +2772,13 @@ function checkBypass(reqUrl) {
|
||||
.split(',')
|
||||
.map(x => x.trim().toUpperCase())
|
||||
.filter(x => x)) {
|
||||
if (upperNoProxyItem === '*' ||
|
||||
upperReqHosts.some(x => x === upperNoProxyItem ||
|
||||
x.endsWith(`.${upperNoProxyItem}`) ||
|
||||
(upperNoProxyItem.startsWith('.') &&
|
||||
x.endsWith(`${upperNoProxyItem}`)))) {
|
||||
if (upperReqHosts.some(x => x === upperNoProxyItem)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
exports.checkBypass = checkBypass;
|
||||
function isLoopbackAddress(host) {
|
||||
const hostLower = host.toLowerCase();
|
||||
return (hostLower === 'localhost' ||
|
||||
hostLower.startsWith('127.') ||
|
||||
hostLower.startsWith('[::1]') ||
|
||||
hostLower.startsWith('[0:0:0:0:0:0:0:1]'));
|
||||
}
|
||||
//# sourceMappingURL=proxy.js.map
|
||||
|
||||
/***/ }),
|
||||
@@ -3149,9 +2817,6 @@ function range(a, b, str) {
|
||||
var i = ai;
|
||||
|
||||
if (ai >= 0 && bi > 0) {
|
||||
if(a===b) {
|
||||
return [ai, bi];
|
||||
}
|
||||
begs = [];
|
||||
left = str.length;
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ if [ -f ".path" ]; then
|
||||
echo ".path=${PATH}"
|
||||
fi
|
||||
|
||||
nodever=${GITHUB_ACTIONS_RUNNER_FORCED_NODE_VERSION:-node20}
|
||||
nodever=${GITHUB_ACTIONS_RUNNER_FORCED_NODE_VERSION:-node16}
|
||||
|
||||
# insert anything to setup env when running as a service
|
||||
# run the host process which keep the listener alive
|
||||
|
||||
@@ -135,17 +135,12 @@ if [[ "$currentplatform" == 'darwin' && restartinteractiverunner -eq 0 ]]; then
|
||||
then
|
||||
# inspect the open file handles to find the node process
|
||||
# we can't actually inspect the process using ps because it uses relative paths and doesn't follow symlinks
|
||||
nodever="node20"
|
||||
nodever="node16"
|
||||
path=$(lsof -a -g "$procgroup" -F n | grep $nodever/bin/node | grep externals | tail -1 | cut -c2-)
|
||||
if [[ $? -ne 0 || -z "$path" ]] # Fallback if RunnerService.js was started with node16
|
||||
if [[ $? -ne 0 || -z "$path" ]] # Fallback if RunnerService.js was started with node12
|
||||
then
|
||||
nodever="node16"
|
||||
nodever="node12"
|
||||
path=$(lsof -a -g "$procgroup" -F n | grep $nodever/bin/node | grep externals | tail -1 | cut -c2-)
|
||||
if [[ $? -ne 0 || -z "$path" ]] # Fallback if RunnerService.js was started with node12
|
||||
then
|
||||
nodever="node12"
|
||||
path=$(lsof -a -g "$procgroup" -F n | grep $nodever/bin/node | grep externals | tail -1 | cut -c2-)
|
||||
fi
|
||||
fi
|
||||
if [[ $? -eq 0 && -n "$path" ]]
|
||||
then
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
SET UPDATEFILE=update.finished
|
||||
"%~dp0\bin\Runner.Listener.exe" run %*
|
||||
|
||||
rem using `if %ERRORLEVEL% EQU N` instead of `if ERRORLEVEL N`
|
||||
rem using `if %ERRORLEVEL% EQU N` insterad of `if ERRORLEVEL N`
|
||||
rem `if ERRORLEVEL N` means: error level is N or MORE
|
||||
|
||||
if %ERRORLEVEL% EQU 0 (
|
||||
|
||||
@@ -75,8 +75,6 @@ mscordaccore.dll
|
||||
mscordaccore_amd64_amd64_6.0.522.21309.dll
|
||||
mscordaccore_arm64_arm64_6.0.522.21309.dll
|
||||
mscordaccore_amd64_amd64_6.0.1322.58009.dll
|
||||
mscordaccore_amd64_amd64_6.0.2023.32017.dll
|
||||
mscordaccore_amd64_amd64_6.0.2223.42425.dll
|
||||
mscordbi.dll
|
||||
mscorlib.dll
|
||||
mscorrc.debug.dll
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace GitHub.Runner.Common
|
||||
private bool? _isHostedServer;
|
||||
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public ulong AgentId { get; set; }
|
||||
public int AgentId { get; set; }
|
||||
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public string AgentName { get; set; }
|
||||
|
||||
@@ -69,8 +69,6 @@ namespace GitHub.Runner.Common
|
||||
public static readonly OSPlatform Platform = OSPlatform.OSX;
|
||||
#elif OS_WINDOWS
|
||||
public static readonly OSPlatform Platform = OSPlatform.Windows;
|
||||
#else
|
||||
public static readonly OSPlatform Platform = OSPlatform.Linux;
|
||||
#endif
|
||||
|
||||
#if X86
|
||||
@@ -81,8 +79,6 @@ namespace GitHub.Runner.Common
|
||||
public static readonly Architecture PlatformArchitecture = Architecture.Arm;
|
||||
#elif ARM64
|
||||
public static readonly Architecture PlatformArchitecture = Architecture.Arm64;
|
||||
#else
|
||||
public static readonly Architecture PlatformArchitecture = Architecture.X64;
|
||||
#endif
|
||||
|
||||
public static readonly TimeSpan ExitOnUnloadTimeout = TimeSpan.FromSeconds(30);
|
||||
@@ -158,7 +154,7 @@ namespace GitHub.Runner.Common
|
||||
public static class Features
|
||||
{
|
||||
public static readonly string DiskSpaceWarning = "runner.diskspace.warning";
|
||||
public static readonly string Node16Warning = "DistributedTask.AddWarningToNode16Action";
|
||||
public static readonly string Node12Warning = "DistributedTask.AddWarningToNode12Action";
|
||||
public static readonly string LogTemplateErrorsAsDebugMessages = "DistributedTask.LogTemplateErrorsAsDebugMessages";
|
||||
public static readonly string UseContainerPathForTemplate = "DistributedTask.UseContainerPathForTemplate";
|
||||
public static readonly string AllowRunnerContainerHooks = "DistributedTask.AllowRunnerContainerHooks";
|
||||
@@ -175,9 +171,7 @@ namespace GitHub.Runner.Common
|
||||
public static readonly string UnsupportedStopCommandTokenDisabled = "You cannot use a endToken that is an empty string, the string 'pause-logging', or another workflow command. For more information see: https://docs.github.com/actions/learn-github-actions/workflow-commands-for-github-actions#example-stopping-and-starting-workflow-commands or opt into insecure command execution by setting the `ACTIONS_ALLOW_UNSECURE_STOPCOMMAND_TOKENS` environment variable to `true`.";
|
||||
public static readonly string UnsupportedSummarySize = "$GITHUB_STEP_SUMMARY upload aborted, supports content up to a size of {0}k, got {1}k. For more information see: https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-markdown-summary";
|
||||
public static readonly string SummaryUploadError = "$GITHUB_STEP_SUMMARY upload aborted, an error occurred when uploading the summary. For more information see: https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-markdown-summary";
|
||||
public static readonly string DetectedNodeAfterEndOfLifeMessage = "Node.js 16 actions are deprecated. Please update the following actions to use Node.js 20: {0}. For more information see: https://github.blog/changelog/2023-09-22-github-actions-transitioning-from-node-16-to-node-20/.";
|
||||
public static readonly string DeprecatedNodeDetectedAfterEndOfLifeActions = "DeprecatedNodeActionsMessageWarnings";
|
||||
public static readonly string DeprecatedNodeVersion = "node16";
|
||||
public static readonly string Node12DetectedAfterEndOfLife = "Node.js 12 actions are deprecated. Please update the following actions to use Node.js 16: {0}. For more information see: https://github.blog/changelog/2022-09-22-github-actions-all-actions-will-begin-running-on-node16-instead-of-node12/.";
|
||||
public static readonly string EnforcedNode12DetectedAfterEndOfLife = "The following actions uses node12 which is deprecated and will be forced to run on node16: {0}. For more info: https://github.blog/changelog/2023-06-13-github-actions-all-actions-will-run-on-node16-instead-of-node12-by-default/";
|
||||
public static readonly string EnforcedNode12DetectedAfterEndOfLifeEnvVariable = "Node16ForceActionsWarnings";
|
||||
}
|
||||
@@ -257,11 +251,10 @@ namespace GitHub.Runner.Common
|
||||
{
|
||||
public static readonly string ToolsDirectory = "agent.ToolsDirectory";
|
||||
|
||||
// Set this env var to "node16" to downgrade the node version for internal functions (e.g hashfiles). This does NOT affect the version of node actions.
|
||||
// Set this env var to "node12" to downgrade the node version for internal functions (e.g hashfiles). This does NOT affect the version of node actions.
|
||||
public static readonly string ForcedInternalNodeVersion = "ACTIONS_RUNNER_FORCED_INTERNAL_NODE_VERSION";
|
||||
public static readonly string ForcedActionsNodeVersion = "ACTIONS_RUNNER_FORCE_ACTIONS_NODE_VERSION";
|
||||
public static readonly string PrintLogToStdout = "ACTIONS_RUNNER_PRINT_LOG_TO_STDOUT";
|
||||
public static readonly string ActionArchiveCacheDirectory = "ACTIONS_RUNNER_ACTION_ARCHIVE_CACHE";
|
||||
}
|
||||
|
||||
public static class System
|
||||
|
||||
@@ -203,7 +203,7 @@ namespace GitHub.Runner.Common
|
||||
|
||||
if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_TLS_NO_VERIFY")))
|
||||
{
|
||||
_trace.Warning($"Runner is running under insecure mode: HTTPS server certificate validation has been turned off by GITHUB_ACTIONS_RUNNER_TLS_NO_VERIFY environment variable.");
|
||||
_trace.Warning($"Runner is running under insecure mode: HTTPS server certifcate validation has been turned off by GITHUB_ACTIONS_RUNNER_TLS_NO_VERIFY environment variable.");
|
||||
}
|
||||
|
||||
var credFile = GetConfigFile(WellKnownConfigFile.Credentials);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
@@ -15,11 +14,10 @@ namespace GitHub.Runner.Common
|
||||
[ServiceLocator(Default = typeof(JobServerQueue))]
|
||||
public interface IJobServerQueue : IRunnerService, IThrottlingReporter
|
||||
{
|
||||
IList<JobTelemetry> JobTelemetries { get; }
|
||||
TaskCompletionSource<int> JobRecordUpdated { get; }
|
||||
event EventHandler<ThrottlingEventArgs> JobServerQueueThrottling;
|
||||
Task ShutdownAsync();
|
||||
void Start(Pipelines.AgentJobRequestMessage jobRequest, bool resultsServiceOnly = false, bool enableTelemetry = false);
|
||||
void Start(Pipelines.AgentJobRequestMessage jobRequest, bool resultServiceOnly = false);
|
||||
void QueueWebConsoleLine(Guid stepRecordId, string line, long? lineNumber = null);
|
||||
void QueueFileUpload(Guid timelineId, Guid timelineRecordId, string type, string name, string path, bool deleteSource);
|
||||
void QueueResultsUpload(Guid timelineRecordId, string name, string path, string type, bool deleteSource, bool finalize, bool firstBlock, long totalLines);
|
||||
@@ -71,18 +69,13 @@ namespace GitHub.Runner.Common
|
||||
private Task[] _allDequeueTasks;
|
||||
private readonly TaskCompletionSource<int> _jobCompletionSource = new();
|
||||
private readonly TaskCompletionSource<int> _jobRecordUpdated = new();
|
||||
private readonly List<JobTelemetry> _jobTelemetries = new();
|
||||
private bool _queueInProcess = false;
|
||||
private bool _resultsServiceOnly = false;
|
||||
private Stopwatch _resultsUploadTimer = new();
|
||||
private Stopwatch _actionsUploadTimer = new();
|
||||
|
||||
public TaskCompletionSource<int> JobRecordUpdated => _jobRecordUpdated;
|
||||
|
||||
public event EventHandler<ThrottlingEventArgs> JobServerQueueThrottling;
|
||||
|
||||
public IList<JobTelemetry> JobTelemetries => _jobTelemetries;
|
||||
|
||||
// Web console dequeue will start with process queue every 250ms for the first 60*4 times (~60 seconds).
|
||||
// Then the dequeue will happen every 500ms.
|
||||
// In this way, customer still can get instance live console output on job start,
|
||||
@@ -94,7 +87,6 @@ namespace GitHub.Runner.Common
|
||||
private bool _firstConsoleOutputs = true;
|
||||
|
||||
private bool _resultsClientInitiated = false;
|
||||
private bool _enableTelemetry = false;
|
||||
private delegate Task ResultsFileUploadHandler(ResultsUploadFileInfo file);
|
||||
|
||||
public override void Initialize(IHostContext hostContext)
|
||||
@@ -104,15 +96,14 @@ namespace GitHub.Runner.Common
|
||||
_resultsServer = hostContext.GetService<IResultsServer>();
|
||||
}
|
||||
|
||||
public void Start(Pipelines.AgentJobRequestMessage jobRequest, bool resultsServiceOnly = false, bool enableTelemetry = false)
|
||||
public void Start(Pipelines.AgentJobRequestMessage jobRequest, bool resultServiceOnly = false)
|
||||
{
|
||||
Trace.Entering();
|
||||
_resultsServiceOnly = resultsServiceOnly;
|
||||
_enableTelemetry = enableTelemetry;
|
||||
_resultsServiceOnly = resultServiceOnly;
|
||||
|
||||
var serviceEndPoint = jobRequest.Resources.Endpoints.Single(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (!resultsServiceOnly)
|
||||
if (!resultServiceOnly)
|
||||
{
|
||||
_jobServer.InitializeWebsocketClient(serviceEndPoint);
|
||||
}
|
||||
@@ -128,7 +119,7 @@ namespace GitHub.Runner.Common
|
||||
{
|
||||
string liveConsoleFeedUrl = null;
|
||||
Trace.Info("Initializing results client");
|
||||
if (resultsServiceOnly
|
||||
if (resultServiceOnly
|
||||
&& serviceEndPoint.Data.TryGetValue("FeedStreamUrl", out var feedStreamUrl)
|
||||
&& !string.IsNullOrEmpty(feedStreamUrl))
|
||||
{
|
||||
@@ -220,12 +211,6 @@ namespace GitHub.Runner.Common
|
||||
await _resultsServer.DisposeAsync();
|
||||
|
||||
Trace.Info("All queue process tasks have been stopped, and all queues are drained.");
|
||||
if (_enableTelemetry)
|
||||
{
|
||||
var uploadTimeComparison = $"Actions upload time: {_actionsUploadTimer.ElapsedMilliseconds} ms, Result upload time: {_resultsUploadTimer.ElapsedMilliseconds} ms";
|
||||
Trace.Info(uploadTimeComparison);
|
||||
_jobTelemetries.Add(new JobTelemetry() { Type = JobTelemetryType.General, Message = uploadTimeComparison });
|
||||
}
|
||||
}
|
||||
|
||||
public void QueueWebConsoleLine(Guid stepRecordId, string line, long? lineNumber)
|
||||
@@ -471,10 +456,6 @@ namespace GitHub.Runner.Common
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_enableTelemetry)
|
||||
{
|
||||
_actionsUploadTimer.Start();
|
||||
}
|
||||
await UploadFile(file);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -490,13 +471,6 @@ namespace GitHub.Runner.Common
|
||||
// _fileUploadQueue.Enqueue(file);
|
||||
//}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_enableTelemetry)
|
||||
{
|
||||
_actionsUploadTimer.Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Trace.Info("Try to upload {0} log files or attachments, success rate: {1}/{0}.", filesToUpload.Count, filesToUpload.Count - errorCount);
|
||||
@@ -543,10 +517,6 @@ namespace GitHub.Runner.Common
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_enableTelemetry)
|
||||
{
|
||||
_resultsUploadTimer.Start();
|
||||
}
|
||||
if (String.Equals(file.Type, ChecksAttachmentType.StepSummary, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
await UploadSummaryFile(file);
|
||||
@@ -571,19 +541,10 @@ namespace GitHub.Runner.Common
|
||||
Trace.Error(ex);
|
||||
errorCount++;
|
||||
|
||||
// If we hit any exceptions uploading to Results, let's skip any additional uploads to Results unless Results is serving logs
|
||||
if (!_resultsServiceOnly)
|
||||
{
|
||||
_resultsClientInitiated = false;
|
||||
SendResultsTelemetry(ex);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_enableTelemetry)
|
||||
{
|
||||
_resultsUploadTimer.Stop();
|
||||
}
|
||||
// If we hit any exceptions uploading to Results, let's skip any additional uploads to Results
|
||||
_resultsClientInitiated = false;
|
||||
|
||||
SendResultsTelemetry(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -699,11 +660,9 @@ namespace GitHub.Runner.Common
|
||||
{
|
||||
Trace.Info("Catch exception during update steps, skip update Results.");
|
||||
Trace.Error(e);
|
||||
if (!_resultsServiceOnly)
|
||||
{
|
||||
_resultsClientInitiated = false;
|
||||
SendResultsTelemetry(e);
|
||||
}
|
||||
_resultsClientInitiated = false;
|
||||
|
||||
SendResultsTelemetry(e);
|
||||
}
|
||||
|
||||
if (_bufferedRetryRecords.Remove(update.TimelineId))
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.Runner.Sdk;
|
||||
using GitHub.Services.Common;
|
||||
|
||||
namespace GitHub.Runner.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles redirects for Http requests
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public class RedirectMessageHandler : DelegatingHandler
|
||||
{
|
||||
public RedirectMessageHandler(ITraceWriter trace)
|
||||
{
|
||||
Trace = trace;
|
||||
}
|
||||
|
||||
protected override async Task<HttpResponseMessage> SendAsync(
|
||||
HttpRequestMessage request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
HttpResponseMessage response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (response != null &&
|
||||
IsRedirect(response.StatusCode) &&
|
||||
response.Headers.Location != null)
|
||||
{
|
||||
Trace.Info($"Redirecting to '{response.Headers.Location}'.");
|
||||
|
||||
request = await CloneAsync(request, response.Headers.Location).ConfigureAwait(false);
|
||||
|
||||
response.Dispose();
|
||||
|
||||
response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private static bool IsRedirect(HttpStatusCode statusCode)
|
||||
{
|
||||
return (int)statusCode >= 300 && (int)statusCode < 400;
|
||||
}
|
||||
|
||||
private static async Task<HttpRequestMessage> CloneAsync(HttpRequestMessage request, Uri requestUri)
|
||||
{
|
||||
var clone = new HttpRequestMessage(request.Method, requestUri)
|
||||
{
|
||||
Version = request.Version
|
||||
};
|
||||
|
||||
request.Headers.ForEach(header => clone.Headers.TryAddWithoutValidation(header.Key, header.Value));
|
||||
|
||||
request.Options.ForEach(option => clone.Options.Set(new HttpRequestOptionsKey<object>(option.Key), option.Value));
|
||||
|
||||
if (request.Content != null)
|
||||
{
|
||||
clone.Content = new ByteArrayContent(await request.Content.ReadAsByteArrayAsync().ConfigureAwait(false));
|
||||
|
||||
request.Content.Headers.ForEach(header => clone.Content.Headers.TryAddWithoutValidation(header.Key, header.Value));
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
private readonly ITraceWriter Trace;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.WebSockets;
|
||||
using System.Security;
|
||||
@@ -53,8 +52,8 @@ namespace GitHub.Runner.Common
|
||||
|
||||
public void InitializeResultsClient(Uri uri, string liveConsoleFeedUrl, string token)
|
||||
{
|
||||
this._resultsClient = CreateHttpClient(uri, token);
|
||||
|
||||
var httpMessageHandler = HostContext.CreateHttpClientHandler();
|
||||
this._resultsClient = new ResultsHttpClient(uri, httpMessageHandler, token, disposeHandler: true);
|
||||
_token = token;
|
||||
if (!string.IsNullOrEmpty(liveConsoleFeedUrl))
|
||||
{
|
||||
@@ -63,26 +62,6 @@ namespace GitHub.Runner.Common
|
||||
}
|
||||
}
|
||||
|
||||
public ResultsHttpClient CreateHttpClient(Uri uri, string token)
|
||||
{
|
||||
// Using default 100 timeout
|
||||
RawClientHttpRequestSettings settings = VssUtil.GetHttpRequestSettings(null);
|
||||
|
||||
// Create retry handler
|
||||
IEnumerable<DelegatingHandler> delegatingHandlers = new List<DelegatingHandler>();
|
||||
if (settings.MaxRetryRequest > 0)
|
||||
{
|
||||
delegatingHandlers = new DelegatingHandler[] { new VssHttpRetryMessageHandler(settings.MaxRetryRequest) };
|
||||
}
|
||||
|
||||
// Setup RawHttpMessageHandler without credentials
|
||||
var httpMessageHandler = new RawHttpMessageHandler(new NoOpCredentials(null), settings);
|
||||
|
||||
var pipeline = HttpClientFactory.CreatePipeline(httpMessageHandler, delegatingHandlers);
|
||||
|
||||
return new ResultsHttpClient(uri, pipeline, token, disposeHandler: true);
|
||||
}
|
||||
|
||||
public Task CreateResultsStepSummaryAsync(string planId, string jobId, Guid stepId, string file,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="4.4.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="4.4.0" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.4.0" />
|
||||
<PackageReference Include="System.Threading.Channels" Version="4.4.0" />
|
||||
|
||||
@@ -15,11 +15,12 @@ namespace GitHub.Runner.Common
|
||||
[ServiceLocator(Default = typeof(RunnerDotcomServer))]
|
||||
public interface IRunnerDotcomServer : IRunnerService
|
||||
{
|
||||
Task<List<TaskAgent>> GetRunnerByNameAsync(string githubUrl, string githubToken, string agentName);
|
||||
Task<List<TaskAgent>> GetRunnersAsync(int runnerGroupId, string githubUrl, string githubToken, string agentName);
|
||||
|
||||
Task<DistributedTask.WebApi.Runner> AddRunnerAsync(int runnerGroupId, TaskAgent agent, string githubUrl, string githubToken, string publicKey);
|
||||
Task<DistributedTask.WebApi.Runner> ReplaceRunnerAsync(int runnerGroupId, TaskAgent agent, string githubUrl, string githubToken, string publicKey);
|
||||
Task<List<TaskAgentPool>> GetRunnerGroupsAsync(string githubUrl, string githubToken);
|
||||
|
||||
string GetGitHubRequestId(HttpResponseHeaders headers);
|
||||
}
|
||||
|
||||
public enum RequestType
|
||||
@@ -41,7 +42,7 @@ namespace GitHub.Runner.Common
|
||||
}
|
||||
|
||||
|
||||
public async Task<List<TaskAgent>> GetRunnerByNameAsync(string githubUrl, string githubToken, string agentName)
|
||||
public async Task<List<TaskAgent>> GetRunnersAsync(int runnerGroupId, string githubUrl, string githubToken, string agentName = null)
|
||||
{
|
||||
var githubApiUrl = "";
|
||||
var gitHubUrlBuilder = new UriBuilder(githubUrl);
|
||||
@@ -51,11 +52,11 @@ namespace GitHub.Runner.Common
|
||||
// org runner
|
||||
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/orgs/{path[0]}/actions/runners?name={Uri.EscapeDataString(agentName)}";
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/orgs/{path[0]}/actions/runner-groups/{runnerGroupId}/runners";
|
||||
}
|
||||
else
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/orgs/{path[0]}/actions/runners?name={Uri.EscapeDataString(agentName)}";
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/orgs/{path[0]}/actions/runner-groups/{runnerGroupId}/runners";
|
||||
}
|
||||
}
|
||||
else if (path.Length == 2)
|
||||
@@ -68,11 +69,11 @@ namespace GitHub.Runner.Common
|
||||
|
||||
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/{path[0]}/{path[1]}/actions/runners?name={Uri.EscapeDataString(agentName)}";
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/{path[0]}/{path[1]}/actions/runner-groups/{runnerGroupId}/runners";
|
||||
}
|
||||
else
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/{path[0]}/{path[1]}/actions/runners?name={Uri.EscapeDataString(agentName)}";
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/{path[0]}/{path[1]}/actions/runner-groups/{runnerGroupId}/runners";
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -81,8 +82,14 @@ namespace GitHub.Runner.Common
|
||||
}
|
||||
|
||||
var runnersList = await RetryRequest<ListRunnersResponse>(githubApiUrl, githubToken, RequestType.Get, 3, "Failed to get agents pools");
|
||||
var agents = runnersList.ToTaskAgents();
|
||||
|
||||
return runnersList.ToTaskAgents();
|
||||
if (string.IsNullOrEmpty(agentName))
|
||||
{
|
||||
return agents;
|
||||
}
|
||||
|
||||
return agents.Where(x => string.Equals(x.Name, agentName, StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
}
|
||||
|
||||
public async Task<List<TaskAgentPool>> GetRunnerGroupsAsync(string githubUrl, string githubToken)
|
||||
@@ -130,16 +137,6 @@ namespace GitHub.Runner.Common
|
||||
}
|
||||
|
||||
public async Task<DistributedTask.WebApi.Runner> AddRunnerAsync(int runnerGroupId, TaskAgent agent, string githubUrl, string githubToken, string publicKey)
|
||||
{
|
||||
return await AddOrReplaceRunner(runnerGroupId, agent, githubUrl, githubToken, publicKey, false);
|
||||
}
|
||||
|
||||
public async Task<DistributedTask.WebApi.Runner> ReplaceRunnerAsync(int runnerGroupId, TaskAgent agent, string githubUrl, string githubToken, string publicKey)
|
||||
{
|
||||
return await AddOrReplaceRunner(runnerGroupId, agent, githubUrl, githubToken, publicKey, true);
|
||||
}
|
||||
|
||||
private async Task<DistributedTask.WebApi.Runner> AddOrReplaceRunner(int runnerGroupId, TaskAgent agent, string githubUrl, string githubToken, string publicKey, bool replace)
|
||||
{
|
||||
var gitHubUrlBuilder = new UriBuilder(githubUrl);
|
||||
var path = gitHubUrlBuilder.Path.Split('/', '\\', StringSplitOptions.RemoveEmptyEntries);
|
||||
@@ -162,15 +159,9 @@ namespace GitHub.Runner.Common
|
||||
{"updates_disabled", agent.DisableUpdate},
|
||||
{"ephemeral", agent.Ephemeral},
|
||||
{"labels", agent.Labels},
|
||||
{"public_key", publicKey},
|
||||
{"public_key", publicKey}
|
||||
};
|
||||
|
||||
if (replace)
|
||||
{
|
||||
bodyObject.Add("runner_id", agent.Id);
|
||||
bodyObject.Add("replace", replace);
|
||||
}
|
||||
|
||||
var body = new StringContent(StringUtil.ConvertToJson(bodyObject), null, "application/json");
|
||||
|
||||
return await RetryRequest<DistributedTask.WebApi.Runner>(githubApiUrl, githubToken, RequestType.Post, 3, "Failed to add agent", body);
|
||||
@@ -204,7 +195,7 @@ namespace GitHub.Runner.Common
|
||||
if (response != null)
|
||||
{
|
||||
responseStatus = response.StatusCode;
|
||||
var githubRequestId = UrlUtil.GetGitHubRequestId(response.Headers);
|
||||
var githubRequestId = GetGitHubRequestId(response.Headers);
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
@@ -224,7 +215,7 @@ namespace GitHub.Runner.Common
|
||||
}
|
||||
catch (Exception ex) when (retry < maxRetryAttemptsCount && responseStatus != System.Net.HttpStatusCode.NotFound)
|
||||
{
|
||||
Trace.Error($"{errorMessage} -- Attempt: {retry}");
|
||||
Trace.Error($"{errorMessage} -- Atempt: {retry}");
|
||||
Trace.Error(ex);
|
||||
}
|
||||
}
|
||||
@@ -233,5 +224,14 @@ namespace GitHub.Runner.Common
|
||||
await Task.Delay(backOff);
|
||||
}
|
||||
}
|
||||
|
||||
public string GetGitHubRequestId(HttpResponseHeaders headers)
|
||||
{
|
||||
if (headers.TryGetValues("x-github-request-id", out var headerValues))
|
||||
{
|
||||
return headerValues.FirstOrDefault();
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,8 +27,8 @@ namespace GitHub.Runner.Common
|
||||
|
||||
// Configuration
|
||||
Task<TaskAgent> AddAgentAsync(Int32 agentPoolId, TaskAgent agent);
|
||||
Task DeleteAgentAsync(int agentPoolId, ulong agentId);
|
||||
Task DeleteAgentAsync(ulong agentId);
|
||||
Task DeleteAgentAsync(int agentPoolId, int agentId);
|
||||
Task DeleteAgentAsync(int agentId);
|
||||
Task<List<TaskAgentPool>> GetAgentPoolsAsync(string agentPoolName = null, TaskAgentPoolType poolType = TaskAgentPoolType.Automation);
|
||||
Task<List<TaskAgent>> GetAgentsAsync(int agentPoolId, string agentName = null);
|
||||
Task<List<TaskAgent>> GetAgentsAsync(string agentName);
|
||||
@@ -50,7 +50,7 @@ namespace GitHub.Runner.Common
|
||||
Task<PackageMetadata> GetPackageAsync(string packageType, string platform, string version, bool includeToken, CancellationToken cancellationToken);
|
||||
|
||||
// agent update
|
||||
Task<TaskAgent> UpdateAgentUpdateStateAsync(int agentPoolId, ulong agentId, string currentState, string trace);
|
||||
Task<TaskAgent> UpdateAgentUpdateStateAsync(int agentPoolId, int agentId, string currentState, string trace);
|
||||
}
|
||||
|
||||
public sealed class RunnerServer : RunnerService, IRunnerServer
|
||||
@@ -239,13 +239,13 @@ namespace GitHub.Runner.Common
|
||||
return _genericTaskAgentClient.ReplaceAgentAsync(agentPoolId, agent);
|
||||
}
|
||||
|
||||
public Task DeleteAgentAsync(int agentPoolId, ulong agentId)
|
||||
public Task DeleteAgentAsync(int agentPoolId, int agentId)
|
||||
{
|
||||
CheckConnection(RunnerConnectionType.Generic);
|
||||
return _genericTaskAgentClient.DeleteAgentAsync(agentPoolId, agentId);
|
||||
}
|
||||
|
||||
public Task DeleteAgentAsync(ulong agentId)
|
||||
public Task DeleteAgentAsync(int agentId)
|
||||
{
|
||||
return DeleteAgentAsync(0, agentId); // agentPool is ignored server side
|
||||
}
|
||||
@@ -315,7 +315,7 @@ namespace GitHub.Runner.Common
|
||||
return _genericTaskAgentClient.GetPackageAsync(packageType, platform, version, includeToken, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
public Task<TaskAgent> UpdateAgentUpdateStateAsync(int agentPoolId, ulong agentId, string currentState, string trace)
|
||||
public Task<TaskAgent> UpdateAgentUpdateStateAsync(int agentPoolId, int agentId, string currentState, string trace)
|
||||
{
|
||||
CheckConnection(RunnerConnectionType.Generic);
|
||||
return _genericTaskAgentClient.UpdateAgentUpdateStateAsync(agentPoolId, agentId, currentState, trace);
|
||||
|
||||
@@ -5,8 +5,14 @@ namespace GitHub.Runner.Common.Util
|
||||
{
|
||||
public static class NodeUtil
|
||||
{
|
||||
private const string _defaultNodeVersion = "node20";
|
||||
public static readonly ReadOnlyCollection<string> BuiltInNodeVersions = new(new[] { "node16", "node20" });
|
||||
private const string _defaultNodeVersion = "node16";
|
||||
|
||||
#if (OS_OSX || OS_WINDOWS) && ARM64
|
||||
public static readonly ReadOnlyCollection<string> BuiltInNodeVersions = new(new[] { "node16" });
|
||||
#else
|
||||
public static readonly ReadOnlyCollection<string> BuiltInNodeVersions = new(new[] { "node12", "node16" });
|
||||
#endif
|
||||
|
||||
public static string GetInternalNodeVersion()
|
||||
{
|
||||
var forcedInternalNodeVersion = Environment.GetEnvironmentVariable(Constants.Variables.Agent.ForcedInternalNodeVersion);
|
||||
|
||||
@@ -97,7 +97,7 @@ namespace GitHub.Runner.Listener
|
||||
Trace.Info("Runner OAuth token has been revoked. Unable to pull message.");
|
||||
throw;
|
||||
}
|
||||
catch (AccessDeniedException e) when (e.ErrorCode == 1)
|
||||
catch (AccessDeniedException e) when (e.InnerException is InvalidTaskAgentVersionException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
@@ -108,7 +108,7 @@ namespace GitHub.Runner.Listener
|
||||
|
||||
if (!IsGetNextMessageExceptionRetriable(ex))
|
||||
{
|
||||
throw new NonRetryableException("Get next message failed with non-retryable error.", ex);
|
||||
throw;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -351,39 +351,21 @@ namespace GitHub.Runner.Listener.Check
|
||||
private readonly Dictionary<string, HashSet<string>> _ignoredEvent = new()
|
||||
{
|
||||
{
|
||||
"System.Net.Http",
|
||||
"Microsoft-System-Net-Http",
|
||||
new HashSet<string>
|
||||
{
|
||||
"Info",
|
||||
"Associate",
|
||||
"Enter",
|
||||
"Exit"
|
||||
}
|
||||
},
|
||||
{
|
||||
"System.Net.Security",
|
||||
new HashSet<string>
|
||||
{
|
||||
"Info",
|
||||
"DumpBuffer",
|
||||
"SslStreamCtor",
|
||||
"SecureChannelCtor",
|
||||
"NoDelegateNoClientCert",
|
||||
"CertsAfterFiltering",
|
||||
"UsingCachedCredential",
|
||||
"SspiSelectedCipherSuite"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Private.InternalDiagnostics.System.Net.Http",
|
||||
new HashSet<string>
|
||||
{
|
||||
"Info",
|
||||
"Associate",
|
||||
}
|
||||
},
|
||||
{
|
||||
"Private.InternalDiagnostics.System.Net.Security",
|
||||
"Microsoft-System-Net-Security",
|
||||
new HashSet<string>
|
||||
{
|
||||
"Enter",
|
||||
"Exit",
|
||||
"Info",
|
||||
"DumpBuffer",
|
||||
"SslStreamCtor",
|
||||
@@ -409,8 +391,8 @@ namespace GitHub.Runner.Listener.Check
|
||||
{
|
||||
base.OnEventSourceCreated(eventSource);
|
||||
|
||||
if (eventSource.Name.Contains("System.Net.Http") ||
|
||||
eventSource.Name.Contains("System.Net.Security"))
|
||||
if (eventSource.Name == "Microsoft-System-Net-Http" ||
|
||||
eventSource.Name == "Microsoft-System-Net-Security")
|
||||
{
|
||||
EnableEvents(eventSource, EventLevel.Verbose, EventKeywords.All);
|
||||
}
|
||||
|
||||
@@ -244,11 +244,11 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
List<TaskAgent> agents;
|
||||
if (runnerSettings.UseV2Flow)
|
||||
{
|
||||
agents = await _dotcomServer.GetRunnerByNameAsync(runnerSettings.GitHubUrl, registerToken, runnerSettings.AgentName);
|
||||
agents = await _dotcomServer.GetRunnersAsync(runnerSettings.PoolId, runnerSettings.GitHubUrl, registerToken, runnerSettings.AgentName);
|
||||
}
|
||||
else
|
||||
{
|
||||
agents = await _runnerServer.GetAgentsAsync(runnerSettings.AgentName);
|
||||
agents = await _runnerServer.GetAgentsAsync(runnerSettings.PoolId, runnerSettings.AgentName);
|
||||
}
|
||||
|
||||
Trace.Verbose("Returns {0} agents", agents.Count);
|
||||
@@ -263,23 +263,7 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
|
||||
try
|
||||
{
|
||||
if (runnerSettings.UseV2Flow)
|
||||
{
|
||||
var runner = await _dotcomServer.ReplaceRunnerAsync(runnerSettings.PoolId, agent, runnerSettings.GitHubUrl, registerToken, publicKeyXML);
|
||||
runnerSettings.ServerUrlV2 = runner.RunnerAuthorization.ServerUrl;
|
||||
|
||||
agent.Id = runner.Id;
|
||||
agent.Authorization = new TaskAgentAuthorization()
|
||||
{
|
||||
AuthorizationUrl = runner.RunnerAuthorization.AuthorizationUrl,
|
||||
ClientId = new Guid(runner.RunnerAuthorization.ClientId)
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
agent = await _runnerServer.ReplaceAgentAsync(runnerSettings.PoolId, agent);
|
||||
}
|
||||
|
||||
agent = await _runnerServer.ReplaceAgentAsync(runnerSettings.PoolId, agent);
|
||||
if (command.DisableUpdate &&
|
||||
command.DisableUpdate != agent.DisableUpdate)
|
||||
{
|
||||
@@ -725,7 +709,7 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
{
|
||||
var response = await httpClient.PostAsync(githubApiUrl, new StringContent(string.Empty));
|
||||
responseStatus = response.StatusCode;
|
||||
var githubRequestId = UrlUtil.GetGitHubRequestId(response.Headers);
|
||||
var githubRequestId = _dotcomServer.GetGitHubRequestId(response.Headers);
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
@@ -744,7 +728,7 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
catch (Exception ex) when (retryCount < 2 && responseStatus != System.Net.HttpStatusCode.NotFound)
|
||||
{
|
||||
retryCount++;
|
||||
Trace.Error($"Failed to get JIT runner token -- Attempt: {retryCount}");
|
||||
Trace.Error($"Failed to get JIT runner token -- Atempt: {retryCount}");
|
||||
Trace.Error(ex);
|
||||
}
|
||||
}
|
||||
@@ -788,7 +772,7 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
{
|
||||
var response = await httpClient.PostAsync(githubApiUrl, new StringContent(StringUtil.ConvertToJson(bodyObject), null, "application/json"));
|
||||
responseStatus = response.StatusCode;
|
||||
var githubRequestId = UrlUtil.GetGitHubRequestId(response.Headers);
|
||||
var githubRequestId = _dotcomServer.GetGitHubRequestId(response.Headers);
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
@@ -807,7 +791,7 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
catch (Exception ex) when (retryCount < 2 && responseStatus != System.Net.HttpStatusCode.NotFound)
|
||||
{
|
||||
retryCount++;
|
||||
Trace.Error($"Failed to get tenant credentials -- Attempt: {retryCount}");
|
||||
Trace.Error($"Failed to get tenant credentials -- Atempt: {retryCount}");
|
||||
Trace.Error(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
|
||||
if (!store.HasCredentials())
|
||||
{
|
||||
throw new InvalidOperationException("Credentials not stored. Must reconfigure.");
|
||||
throw new InvalidOperationException("Credentials not stored. Must reconfigure.");
|
||||
}
|
||||
|
||||
CredentialData credData = store.GetCredentials();
|
||||
|
||||
@@ -514,25 +514,9 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
failureActions.Add(new FailureAction(RecoverAction.Restart, 60000));
|
||||
|
||||
// Lock the Service Database
|
||||
int svcLockRetries = 10;
|
||||
int svcLockRetryTimeout = 5000;
|
||||
while (true)
|
||||
svcLock = LockServiceDatabase(scmHndl);
|
||||
if (svcLock.ToInt64() <= 0)
|
||||
{
|
||||
svcLock = LockServiceDatabase(scmHndl);
|
||||
if (svcLock.ToInt64() > 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
_term.WriteLine("Retrying Lock Service Database...");
|
||||
|
||||
svcLockRetries--;
|
||||
if (svcLockRetries > 0)
|
||||
{
|
||||
Thread.Sleep(svcLockRetryTimeout);
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new Exception("Failed to Lock Service Database for Write");
|
||||
}
|
||||
|
||||
|
||||
@@ -123,15 +123,8 @@ namespace GitHub.Runner.Listener
|
||||
Trace.Error("Catch exception during create session.");
|
||||
Trace.Error(ex);
|
||||
|
||||
if (ex is VssOAuthTokenRequestException vssOAuthEx && creds.Federated is VssOAuthCredential vssOAuthCred)
|
||||
if (ex is VssOAuthTokenRequestException && creds.Federated is VssOAuthCredential vssOAuthCred)
|
||||
{
|
||||
// "invalid_client" means the runner registration has been deleted from the server.
|
||||
if (string.Equals(vssOAuthEx.Error, "invalid_client", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_term.WriteError("Failed to create a session. The runner registration has been deleted from the server, please re-configure. Runner registrations are automatically deleted for runners that have not connected to the service recently.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check whether we get 401 because the runner registration already removed by the service.
|
||||
// If the runner registration get deleted, we can't exchange oauth token.
|
||||
Trace.Error("Test oauth app registration.");
|
||||
@@ -139,7 +132,7 @@ namespace GitHub.Runner.Listener
|
||||
var authError = await oauthTokenProvider.ValidateCredentialAsync(token);
|
||||
if (string.Equals(authError, "invalid_client", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_term.WriteError("Failed to create a session. The runner registration has been deleted from the server, please re-configure. Runner registrations are automatically deleted for runners that have not connected to the service recently.");
|
||||
_term.WriteError("Failed to create a session. The runner registration has been deleted from the server, please re-configure.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -252,7 +245,7 @@ namespace GitHub.Runner.Listener
|
||||
_accessTokenRevoked = true;
|
||||
throw;
|
||||
}
|
||||
catch (AccessDeniedException e) when (e.ErrorCode == 1)
|
||||
catch (AccessDeniedException e) when (e.InnerException is InvalidTaskAgentVersionException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ namespace GitHub.Runner.Listener
|
||||
}
|
||||
|
||||
}
|
||||
catch (AccessDeniedException e) when (e.ErrorCode == 1)
|
||||
catch (AccessDeniedException e) when (e.InnerException is InvalidTaskAgentVersionException)
|
||||
{
|
||||
terminal.WriteError($"An error occured: {e.Message}");
|
||||
trace.Error(e);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="4.4.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="4.4.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="4.4.0" />
|
||||
<PackageReference Include="System.ServiceProcess.ServiceController" Version="4.4.0" />
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace GitHub.Runner.Listener
|
||||
private ITerminal _terminal;
|
||||
private IRunnerServer _runnerServer;
|
||||
private int _poolId;
|
||||
private ulong _agentId;
|
||||
private int _agentId;
|
||||
private readonly ConcurrentQueue<string> _updateTrace = new();
|
||||
private Task _cloneAndCalculateContentHashTask;
|
||||
private string _dotnetRuntimeCloneDirectory;
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http.Headers;
|
||||
|
||||
namespace GitHub.Runner.Sdk
|
||||
{
|
||||
@@ -50,15 +48,5 @@ namespace GitHub.Runner.Sdk
|
||||
|
||||
return credUri.Uri;
|
||||
}
|
||||
|
||||
public static string GetGitHubRequestId(HttpResponseHeaders headers)
|
||||
{
|
||||
if (headers != null &&
|
||||
headers.TryGetValues("x-github-request-id", out var headerValues))
|
||||
{
|
||||
return headerValues.FirstOrDefault();
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,35 +85,6 @@ namespace GitHub.Runner.Sdk
|
||||
VssCredentials credentials,
|
||||
IEnumerable<DelegatingHandler> additionalDelegatingHandler = null,
|
||||
TimeSpan? timeout = null)
|
||||
{
|
||||
RawClientHttpRequestSettings settings = GetHttpRequestSettings(timeout);
|
||||
RawConnection connection = new(serverUri, new RawHttpMessageHandler(credentials.Federated, settings), additionalDelegatingHandler);
|
||||
return connection;
|
||||
}
|
||||
|
||||
public static VssCredentials GetVssCredential(ServiceEndpoint serviceEndpoint)
|
||||
{
|
||||
ArgUtil.NotNull(serviceEndpoint, nameof(serviceEndpoint));
|
||||
ArgUtil.NotNull(serviceEndpoint.Authorization, nameof(serviceEndpoint.Authorization));
|
||||
ArgUtil.NotNullOrEmpty(serviceEndpoint.Authorization.Scheme, nameof(serviceEndpoint.Authorization.Scheme));
|
||||
|
||||
if (serviceEndpoint.Authorization.Parameters.Count == 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(serviceEndpoint));
|
||||
}
|
||||
|
||||
VssCredentials credentials = null;
|
||||
string accessToken;
|
||||
if (serviceEndpoint.Authorization.Scheme == EndpointAuthorizationSchemes.OAuth &&
|
||||
serviceEndpoint.Authorization.Parameters.TryGetValue(EndpointAuthorizationParameters.AccessToken, out accessToken))
|
||||
{
|
||||
credentials = new VssCredentials(new VssOAuthAccessTokenCredential(accessToken), CredentialPromptType.DoNotPrompt);
|
||||
}
|
||||
|
||||
return credentials;
|
||||
}
|
||||
|
||||
public static RawClientHttpRequestSettings GetHttpRequestSettings(TimeSpan? timeout = null)
|
||||
{
|
||||
RawClientHttpRequestSettings settings = RawClientHttpRequestSettings.Default.Clone();
|
||||
|
||||
@@ -145,7 +116,30 @@ namespace GitHub.Runner.Sdk
|
||||
// settings are applied to an HttpRequestMessage.
|
||||
settings.AcceptLanguages.Remove(CultureInfo.InvariantCulture);
|
||||
|
||||
return settings;
|
||||
RawConnection connection = new(serverUri, new RawHttpMessageHandler(credentials.Federated, settings), additionalDelegatingHandler);
|
||||
return connection;
|
||||
}
|
||||
|
||||
public static VssCredentials GetVssCredential(ServiceEndpoint serviceEndpoint)
|
||||
{
|
||||
ArgUtil.NotNull(serviceEndpoint, nameof(serviceEndpoint));
|
||||
ArgUtil.NotNull(serviceEndpoint.Authorization, nameof(serviceEndpoint.Authorization));
|
||||
ArgUtil.NotNullOrEmpty(serviceEndpoint.Authorization.Scheme, nameof(serviceEndpoint.Authorization.Scheme));
|
||||
|
||||
if (serviceEndpoint.Authorization.Parameters.Count == 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(serviceEndpoint));
|
||||
}
|
||||
|
||||
VssCredentials credentials = null;
|
||||
string accessToken;
|
||||
if (serviceEndpoint.Authorization.Scheme == EndpointAuthorizationSchemes.OAuth &&
|
||||
serviceEndpoint.Authorization.Parameters.TryGetValue(EndpointAuthorizationParameters.AccessToken, out accessToken))
|
||||
{
|
||||
credentials = new VssCredentials(new VssOAuthAccessTokenCredential(accessToken), CredentialPromptType.DoNotPrompt);
|
||||
}
|
||||
|
||||
return credentials;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,128 +114,6 @@ namespace GitHub.Runner.Sdk
|
||||
}
|
||||
}
|
||||
|
||||
#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)
|
||||
{
|
||||
throw new FileNotFoundException(
|
||||
message: $"{command}: command not found",
|
||||
fileName: command);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string Which2(string command, bool require = false, ITraceWriter trace = null, string prependPath = null)
|
||||
{
|
||||
ArgUtil.NotNullOrEmpty(command, nameof(command));
|
||||
trace?.Info($"Which2: '{command}'");
|
||||
if (Path.IsPathFullyQualified(command) && File.Exists(command))
|
||||
{
|
||||
trace?.Info($"Fully qualified path: '{command}'");
|
||||
return command;
|
||||
}
|
||||
string path = Environment.GetEnvironmentVariable(PathUtil.PathVariable);
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
trace?.Info("PATH environment variable not defined.");
|
||||
path = path ?? string.Empty;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(prependPath))
|
||||
{
|
||||
path = PathUtil.PrependPath(prependPath, path);
|
||||
}
|
||||
|
||||
string[] pathSegments = path.Split(new Char[] { Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries);
|
||||
for (int i = 0; i < pathSegments.Length; i++)
|
||||
{
|
||||
pathSegments[i] = Environment.ExpandEnvironmentVariables(pathSegments[i]);
|
||||
}
|
||||
|
||||
foreach (string pathSegment in pathSegments)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(pathSegment) && Directory.Exists(pathSegment))
|
||||
{
|
||||
#if OS_WINDOWS
|
||||
string pathExt = Environment.GetEnvironmentVariable("PATHEXT");
|
||||
if (string.IsNullOrEmpty(pathExt))
|
||||
{
|
||||
// XP's system default value for PATHEXT system variable
|
||||
pathExt = ".com;.exe;.bat;.cmd;.vbs;.vbe;.js;.jse;.wsf;.wsh";
|
||||
}
|
||||
|
||||
string[] pathExtSegments = pathExt.Split(new string[] { ";" }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
// if command already has an extension.
|
||||
if (pathExtSegments.Any(ext => command.EndsWith(ext, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var file in Directory.EnumerateFiles(pathSegment, command))
|
||||
{
|
||||
if (IsPathValid(file, trace))
|
||||
{
|
||||
trace?.Info($"Location: '{file}'");
|
||||
return file;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
trace?.Info("Ignore UnauthorizedAccess exception during Which.");
|
||||
trace?.Verbose(ex.ToString());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string searchPattern;
|
||||
searchPattern = StringUtil.Format($"{command}.*");
|
||||
try
|
||||
{
|
||||
foreach (var file in Directory.EnumerateFiles(pathSegment, searchPattern))
|
||||
{
|
||||
// add extension.
|
||||
for (int i = 0; i < pathExtSegments.Length; i++)
|
||||
{
|
||||
string fullPath = Path.Combine(pathSegment, $"{command}{pathExtSegments[i]}");
|
||||
if (string.Equals(file, fullPath, StringComparison.OrdinalIgnoreCase) && IsPathValid(fullPath, trace))
|
||||
{
|
||||
trace?.Info($"Location: '{fullPath}'");
|
||||
return fullPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
trace?.Info("Ignore UnauthorizedAccess exception during Which.");
|
||||
trace?.Verbose(ex.ToString());
|
||||
}
|
||||
}
|
||||
#else
|
||||
try
|
||||
{
|
||||
foreach (var file in Directory.EnumerateFiles(pathSegment, command))
|
||||
{
|
||||
if (IsPathValid(file, trace))
|
||||
{
|
||||
trace?.Info($"Location: '{file}'");
|
||||
return file;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
trace?.Info("Ignore UnauthorizedAccess exception during Which.");
|
||||
trace?.Verbose(ex.ToString());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if OS_WINDOWS
|
||||
trace?.Info($"{command}: command not found. Make sure '{command}' is installed and its location included in the 'Path' environment variable.");
|
||||
#else
|
||||
@@ -256,12 +134,7 @@ namespace GitHub.Runner.Sdk
|
||||
{
|
||||
var fileInfo = new FileInfo(path);
|
||||
var linkTargetFullPath = fileInfo.Directory?.FullName + Path.DirectorySeparatorChar + fileInfo.LinkTarget;
|
||||
if (fileInfo.LinkTarget == null ||
|
||||
File.Exists(linkTargetFullPath) ||
|
||||
File.Exists(fileInfo.LinkTarget))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (fileInfo.LinkTarget == null || File.Exists(linkTargetFullPath) || File.Exists(fileInfo.LinkTarget)) return true;
|
||||
trace?.Info($"the target '{fileInfo.LinkTarget}' of the symbolic link '{path}', does not exist");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -10,15 +10,15 @@ using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
using GitHub.Runner.Common;
|
||||
using GitHub.Runner.Common.Util;
|
||||
using GitHub.Runner.Sdk;
|
||||
using GitHub.Runner.Worker.Container;
|
||||
using GitHub.Services.Common;
|
||||
using WebApi = GitHub.DistributedTask.WebApi;
|
||||
using Pipelines = GitHub.DistributedTask.Pipelines;
|
||||
using PipelineTemplateConstants = GitHub.DistributedTask.Pipelines.ObjectTemplating.PipelineTemplateConstants;
|
||||
using WebApi = GitHub.DistributedTask.WebApi;
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
|
||||
namespace GitHub.Runner.Worker
|
||||
{
|
||||
@@ -52,6 +52,7 @@ namespace GitHub.Runner.Worker
|
||||
|
||||
//81920 is the default used by System.IO.Stream.CopyTo and is under the large object heap threshold (85k).
|
||||
private const int _defaultCopyBufferSize = 81920;
|
||||
private const string _dotcomApiUrl = "https://api.github.com";
|
||||
|
||||
private readonly Dictionary<Guid, ContainerInfo> _cachedActionContainers = new();
|
||||
public Dictionary<Guid, ContainerInfo> CachedActionContainers => _cachedActionContainers;
|
||||
@@ -114,14 +115,6 @@ namespace GitHub.Runner.Worker
|
||||
executionContext.Result = TaskResult.Failed;
|
||||
throw;
|
||||
}
|
||||
catch (InvalidActionArchiveException ex)
|
||||
{
|
||||
// Log the error and fail the PrepareActionsAsync Initialization.
|
||||
Trace.Error($"Caught exception from PrepareActionsAsync Initialization: {ex}");
|
||||
executionContext.InfrastructureError(ex.Message);
|
||||
executionContext.Result = TaskResult.Failed;
|
||||
throw;
|
||||
}
|
||||
if (!FeatureManager.IsContainerHooksEnabled(executionContext.Global.Variables))
|
||||
{
|
||||
if (state.ImagesToPull.Count > 0)
|
||||
@@ -738,7 +731,10 @@ namespace GitHub.Runner.Worker
|
||||
|
||||
ArgUtil.NotNull(actionDownloadInfos, nameof(actionDownloadInfos));
|
||||
ArgUtil.NotNull(actionDownloadInfos.Actions, nameof(actionDownloadInfos.Actions));
|
||||
var apiUrl = GetApiUrl(executionContext);
|
||||
var defaultAccessToken = executionContext.GetGitHubContext("token");
|
||||
var configurationStore = HostContext.GetService<IConfigurationStore>();
|
||||
var runnerSettings = configurationStore.GetSettings();
|
||||
|
||||
foreach (var actionDownloadInfo in actionDownloadInfos.Actions.Values)
|
||||
{
|
||||
@@ -762,8 +758,6 @@ namespace GitHub.Runner.Worker
|
||||
ArgUtil.NotNull(downloadInfo, nameof(downloadInfo));
|
||||
ArgUtil.NotNullOrEmpty(downloadInfo.NameWithOwner, nameof(downloadInfo.NameWithOwner));
|
||||
ArgUtil.NotNullOrEmpty(downloadInfo.Ref, nameof(downloadInfo.Ref));
|
||||
ArgUtil.NotNullOrEmpty(downloadInfo.Ref, nameof(downloadInfo.ResolvedNameWithOwner));
|
||||
ArgUtil.NotNullOrEmpty(downloadInfo.Ref, nameof(downloadInfo.ResolvedSha));
|
||||
|
||||
string destDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Actions), downloadInfo.NameWithOwner.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar), downloadInfo.Ref);
|
||||
string watermarkFile = GetWatermarkFilePath(destDirectory);
|
||||
@@ -780,6 +774,31 @@ namespace GitHub.Runner.Worker
|
||||
executionContext.Output($"Download action repository '{downloadInfo.NameWithOwner}@{downloadInfo.Ref}' (SHA:{downloadInfo.ResolvedSha})");
|
||||
}
|
||||
|
||||
await DownloadRepositoryActionAsync(executionContext, downloadInfo, destDirectory);
|
||||
}
|
||||
|
||||
private string GetApiUrl(IExecutionContext executionContext)
|
||||
{
|
||||
string apiUrl = executionContext.GetGitHubContext("api_url");
|
||||
if (!string.IsNullOrEmpty(apiUrl))
|
||||
{
|
||||
return apiUrl;
|
||||
}
|
||||
// Once the api_url is set for hosted, we can remove this fallback (it doesn't make sense for GHES)
|
||||
return _dotcomApiUrl;
|
||||
}
|
||||
|
||||
private static string BuildLinkToActionArchive(string apiUrl, string repository, string @ref)
|
||||
{
|
||||
#if OS_WINDOWS
|
||||
return $"{apiUrl}/repos/{repository}/zipball/{@ref}";
|
||||
#else
|
||||
return $"{apiUrl}/repos/{repository}/tarball/{@ref}";
|
||||
#endif
|
||||
}
|
||||
|
||||
private async Task DownloadRepositoryActionAsync(IExecutionContext executionContext, WebApi.ActionDownloadInfo downloadInfo, string destDirectory)
|
||||
{
|
||||
//download and extract action in a temp folder and rename it on success
|
||||
string tempDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Actions), "_temp_" + Guid.NewGuid());
|
||||
Directory.CreateDirectory(tempDirectory);
|
||||
@@ -792,63 +811,97 @@ namespace GitHub.Runner.Worker
|
||||
string link = downloadInfo?.TarballUrl;
|
||||
#endif
|
||||
|
||||
Trace.Info($"Save archive '{link}' into {archiveFile}.");
|
||||
try
|
||||
{
|
||||
var useActionArchiveCache = false;
|
||||
if (executionContext.Global.Variables.GetBoolean("DistributedTask.UseActionArchiveCache") == true)
|
||||
int retryCount = 0;
|
||||
|
||||
// Allow up to 20 * 60s for any action to be downloaded from github graph.
|
||||
int timeoutSeconds = 20 * 60;
|
||||
while (retryCount < 3)
|
||||
{
|
||||
var hasActionArchiveCache = false;
|
||||
var actionArchiveCacheDir = Environment.GetEnvironmentVariable(Constants.Variables.Agent.ActionArchiveCacheDirectory);
|
||||
if (!string.IsNullOrEmpty(actionArchiveCacheDir) &&
|
||||
Directory.Exists(actionArchiveCacheDir))
|
||||
using (var actionDownloadTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(timeoutSeconds)))
|
||||
using (var actionDownloadCancellation = CancellationTokenSource.CreateLinkedTokenSource(actionDownloadTimeout.Token, executionContext.CancellationToken))
|
||||
{
|
||||
hasActionArchiveCache = true;
|
||||
Trace.Info($"Check if action archive '{downloadInfo.ResolvedNameWithOwner}@{downloadInfo.ResolvedSha}' already exists in cache directory '{actionArchiveCacheDir}'");
|
||||
#if OS_WINDOWS
|
||||
var cacheArchiveFile = Path.Combine(actionArchiveCacheDir, downloadInfo.ResolvedNameWithOwner.Replace(Path.DirectorySeparatorChar, '_').Replace(Path.AltDirectorySeparatorChar, '_'), $"{downloadInfo.ResolvedSha}.zip");
|
||||
#else
|
||||
var cacheArchiveFile = Path.Combine(actionArchiveCacheDir, downloadInfo.ResolvedNameWithOwner.Replace(Path.DirectorySeparatorChar, '_').Replace(Path.AltDirectorySeparatorChar, '_'), $"{downloadInfo.ResolvedSha}.tar.gz");
|
||||
#endif
|
||||
if (File.Exists(cacheArchiveFile))
|
||||
try
|
||||
{
|
||||
try
|
||||
//open zip stream in async mode
|
||||
using (FileStream fs = new(archiveFile, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: _defaultFileStreamBufferSize, useAsync: true))
|
||||
using (var httpClientHandler = HostContext.CreateHttpClientHandler())
|
||||
using (var httpClient = new HttpClient(httpClientHandler))
|
||||
{
|
||||
Trace.Info($"Found action archive '{cacheArchiveFile}' in cache directory '{actionArchiveCacheDir}'");
|
||||
File.Copy(cacheArchiveFile, archiveFile);
|
||||
useActionArchiveCache = true;
|
||||
executionContext.Debug($"Copied action archive '{cacheArchiveFile}' to '{archiveFile}'");
|
||||
httpClient.DefaultRequestHeaders.Authorization = CreateAuthHeader(downloadInfo.Authentication?.Token);
|
||||
|
||||
httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents);
|
||||
using (var response = await httpClient.GetAsync(link))
|
||||
{
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
using (var result = await response.Content.ReadAsStreamAsync())
|
||||
{
|
||||
await result.CopyToAsync(fs, _defaultCopyBufferSize, actionDownloadCancellation.Token);
|
||||
await fs.FlushAsync(actionDownloadCancellation.Token);
|
||||
|
||||
// download succeed, break out the retry loop.
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (response.StatusCode == HttpStatusCode.NotFound)
|
||||
{
|
||||
// It doesn't make sense to retry in this case, so just stop
|
||||
throw new ActionNotFoundException(new Uri(link));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Something else bad happened, let's go to our retry logic
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
}
|
||||
catch (OperationCanceledException) when (executionContext.CancellationToken.IsCancellationRequested)
|
||||
{
|
||||
Trace.Info("Action download has been cancelled.");
|
||||
throw;
|
||||
}
|
||||
catch (ActionNotFoundException)
|
||||
{
|
||||
Trace.Info($"The action at '{link}' does not exist");
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex) when (retryCount < 2)
|
||||
{
|
||||
retryCount++;
|
||||
Trace.Error($"Fail to download archive '{link}' -- Attempt: {retryCount}");
|
||||
Trace.Error(ex);
|
||||
if (actionDownloadTimeout.Token.IsCancellationRequested)
|
||||
{
|
||||
Trace.Error($"Failed to copy action archive '{cacheArchiveFile}' to '{archiveFile}'. Error: {ex}");
|
||||
// action download didn't finish within timeout
|
||||
executionContext.Warning($"Action '{link}' didn't finish download within {timeoutSeconds} seconds.");
|
||||
}
|
||||
else
|
||||
{
|
||||
executionContext.Warning($"Failed to download action '{link}'. Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
executionContext.Global.JobTelemetry.Add(new JobTelemetry()
|
||||
if (String.IsNullOrEmpty(Environment.GetEnvironmentVariable("_GITHUB_ACTION_DOWNLOAD_NO_BACKOFF")))
|
||||
{
|
||||
Type = JobTelemetryType.General,
|
||||
Message = $"Action archive cache usage: {downloadInfo.ResolvedNameWithOwner}@{downloadInfo.ResolvedSha} use cache {useActionArchiveCache} has cache {hasActionArchiveCache}"
|
||||
});
|
||||
var backOff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(30));
|
||||
executionContext.Warning($"Back off {backOff.TotalSeconds} seconds before retry.");
|
||||
await Task.Delay(backOff);
|
||||
}
|
||||
}
|
||||
|
||||
if (!useActionArchiveCache)
|
||||
{
|
||||
await DownloadRepositoryArchive(executionContext, link, downloadInfo.Authentication?.Token, archiveFile);
|
||||
}
|
||||
ArgUtil.NotNullOrEmpty(archiveFile, nameof(archiveFile));
|
||||
executionContext.Debug($"Download '{link}' to '{archiveFile}'");
|
||||
|
||||
var stagingDirectory = Path.Combine(tempDirectory, "_staging");
|
||||
Directory.CreateDirectory(stagingDirectory);
|
||||
|
||||
#if OS_WINDOWS
|
||||
try
|
||||
{
|
||||
ZipFile.ExtractToDirectory(archiveFile, stagingDirectory);
|
||||
}
|
||||
catch (InvalidDataException e)
|
||||
{
|
||||
throw new InvalidActionArchiveException($"Can't un-zip archive file: {archiveFile}. action being checked out: {downloadInfo.NameWithOwner}@{downloadInfo.Ref}. error: {e}.");
|
||||
}
|
||||
ZipFile.ExtractToDirectory(archiveFile, stagingDirectory);
|
||||
#else
|
||||
string tar = WhichUtil.Which("tar", require: true, trace: Trace);
|
||||
|
||||
@@ -874,7 +927,7 @@ namespace GitHub.Runner.Worker
|
||||
int exitCode = await processInvoker.ExecuteAsync(stagingDirectory, tar, $"-xzf \"{archiveFile}\"", null, executionContext.CancellationToken);
|
||||
if (exitCode != 0)
|
||||
{
|
||||
throw new InvalidActionArchiveException($"Can't use 'tar -xzf' extract archive file: {archiveFile}. Action being checked out: {downloadInfo.NameWithOwner}@{downloadInfo.Ref}. return code: {exitCode}.");
|
||||
throw new NotSupportedException($"Can't use 'tar -xzf' extract archive file: {archiveFile}. return code: {exitCode}.");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -892,6 +945,7 @@ namespace GitHub.Runner.Worker
|
||||
}
|
||||
|
||||
Trace.Verbose("Create watermark file indicate action download succeed.");
|
||||
string watermarkFile = GetWatermarkFilePath(destDirectory);
|
||||
File.WriteAllText(watermarkFile, DateTime.UtcNow.ToString());
|
||||
|
||||
executionContext.Debug($"Archive '{archiveFile}' has been unzipped into '{destDirectory}'.");
|
||||
@@ -916,6 +970,29 @@ namespace GitHub.Runner.Worker
|
||||
}
|
||||
}
|
||||
|
||||
private void ConfigureAuthorizationFromContext(IExecutionContext executionContext, HttpClient httpClient)
|
||||
{
|
||||
var authToken = Environment.GetEnvironmentVariable("_GITHUB_ACTION_TOKEN");
|
||||
if (string.IsNullOrEmpty(authToken))
|
||||
{
|
||||
// TODO: Deprecate the PREVIEW_ACTION_TOKEN
|
||||
authToken = executionContext.Global.Variables.Get("PREVIEW_ACTION_TOKEN");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(authToken))
|
||||
{
|
||||
HostContext.SecretMasker.AddValue(authToken);
|
||||
var base64EncodingToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"PAT:{authToken}"));
|
||||
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", base64EncodingToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
var accessToken = executionContext.GetGitHubContext("token");
|
||||
var base64EncodingToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"x-access-token:{accessToken}"));
|
||||
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", base64EncodingToken);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetWatermarkFilePath(string directory) => directory + ".completed";
|
||||
|
||||
private ActionSetupInfo PrepareRepositoryActionAsync(IExecutionContext executionContext, Pipelines.ActionStep repositoryAction)
|
||||
@@ -1088,6 +1165,13 @@ namespace GitHub.Runner.Worker
|
||||
return $"{repositoryReference.Name}@{repositoryReference.Ref}";
|
||||
}
|
||||
|
||||
private static string GetDownloadInfoLookupKey(WebApi.ActionDownloadInfo info)
|
||||
{
|
||||
ArgUtil.NotNullOrEmpty(info.NameWithOwner, nameof(info.NameWithOwner));
|
||||
ArgUtil.NotNullOrEmpty(info.Ref, nameof(info.Ref));
|
||||
return $"{info.NameWithOwner}@{info.Ref}";
|
||||
}
|
||||
|
||||
private AuthenticationHeaderValue CreateAuthHeader(string token)
|
||||
{
|
||||
if (string.IsNullOrEmpty(token))
|
||||
@@ -1099,104 +1183,6 @@ namespace GitHub.Runner.Worker
|
||||
HostContext.SecretMasker.AddValue(base64EncodingToken);
|
||||
return new AuthenticationHeaderValue("Basic", base64EncodingToken);
|
||||
}
|
||||
|
||||
private async Task DownloadRepositoryArchive(IExecutionContext executionContext, string downloadUrl, string downloadAuthToken, string archiveFile)
|
||||
{
|
||||
Trace.Info($"Save archive '{downloadUrl}' into {archiveFile}.");
|
||||
int retryCount = 0;
|
||||
|
||||
// Allow up to 20 * 60s for any action to be downloaded from github graph.
|
||||
int timeoutSeconds = 20 * 60;
|
||||
while (retryCount < 3)
|
||||
{
|
||||
using (var actionDownloadTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(timeoutSeconds)))
|
||||
using (var actionDownloadCancellation = CancellationTokenSource.CreateLinkedTokenSource(actionDownloadTimeout.Token, executionContext.CancellationToken))
|
||||
{
|
||||
try
|
||||
{
|
||||
//open zip stream in async mode
|
||||
using (FileStream fs = new(archiveFile, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: _defaultFileStreamBufferSize, useAsync: true))
|
||||
using (var httpClientHandler = HostContext.CreateHttpClientHandler())
|
||||
using (var httpClient = new HttpClient(httpClientHandler))
|
||||
{
|
||||
httpClient.DefaultRequestHeaders.Authorization = CreateAuthHeader(downloadAuthToken);
|
||||
|
||||
httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents);
|
||||
using (var response = await httpClient.GetAsync(downloadUrl))
|
||||
{
|
||||
var requestId = UrlUtil.GetGitHubRequestId(response.Headers);
|
||||
if (!string.IsNullOrEmpty(requestId))
|
||||
{
|
||||
Trace.Info($"Request URL: {downloadUrl} X-GitHub-Request-Id: {requestId} Http Status: {response.StatusCode}");
|
||||
}
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
using (var result = await response.Content.ReadAsStreamAsync())
|
||||
{
|
||||
await result.CopyToAsync(fs, _defaultCopyBufferSize, actionDownloadCancellation.Token);
|
||||
await fs.FlushAsync(actionDownloadCancellation.Token);
|
||||
|
||||
// download succeed, break out the retry loop.
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (response.StatusCode == HttpStatusCode.NotFound)
|
||||
{
|
||||
// It doesn't make sense to retry in this case, so just stop
|
||||
throw new ActionNotFoundException(new Uri(downloadUrl), requestId);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Something else bad happened, let's go to our retry logic
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException) when (executionContext.CancellationToken.IsCancellationRequested)
|
||||
{
|
||||
Trace.Info("Action download has been cancelled.");
|
||||
throw;
|
||||
}
|
||||
catch (OperationCanceledException ex) when (!executionContext.CancellationToken.IsCancellationRequested && retryCount >= 2)
|
||||
{
|
||||
Trace.Info($"Action download final retry timeout after {timeoutSeconds} seconds.");
|
||||
throw new TimeoutException($"Action '{downloadUrl}' download has timed out. Error: {ex.Message}");
|
||||
}
|
||||
catch (ActionNotFoundException)
|
||||
{
|
||||
Trace.Info($"The action at '{downloadUrl}' does not exist");
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex) when (retryCount < 2)
|
||||
{
|
||||
retryCount++;
|
||||
Trace.Error($"Fail to download archive '{downloadUrl}' -- Attempt: {retryCount}");
|
||||
Trace.Error(ex);
|
||||
if (actionDownloadTimeout.Token.IsCancellationRequested)
|
||||
{
|
||||
// action download didn't finish within timeout
|
||||
executionContext.Warning($"Action '{downloadUrl}' didn't finish download within {timeoutSeconds} seconds.");
|
||||
}
|
||||
else
|
||||
{
|
||||
executionContext.Warning($"Failed to download action '{downloadUrl}'. Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (String.IsNullOrEmpty(Environment.GetEnvironmentVariable("_GITHUB_ACTION_DOWNLOAD_NO_BACKOFF")))
|
||||
{
|
||||
var backOff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(30));
|
||||
executionContext.Warning($"Back off {backOff.TotalSeconds} seconds before retry.");
|
||||
await Task.Delay(backOff);
|
||||
}
|
||||
}
|
||||
|
||||
ArgUtil.NotNullOrEmpty(archiveFile, nameof(archiveFile));
|
||||
executionContext.Debug($"Download '{downloadUrl}' to '{archiveFile}'");
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class Definition
|
||||
|
||||
@@ -449,8 +449,7 @@ namespace GitHub.Runner.Worker
|
||||
}
|
||||
}
|
||||
else if (string.Equals(usingToken.Value, "node12", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(usingToken.Value, "node16", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(usingToken.Value, "node20", StringComparison.OrdinalIgnoreCase))
|
||||
string.Equals(usingToken.Value, "node16", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (string.IsNullOrEmpty(mainToken?.Value))
|
||||
{
|
||||
@@ -490,7 +489,7 @@ namespace GitHub.Runner.Worker
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentOutOfRangeException($"'using: {usingToken.Value}' is not supported, use 'docker', 'node12', 'node16' or 'node20' instead.");
|
||||
throw new ArgumentOutOfRangeException($"'using: {usingToken.Value}' is not supported, use 'docker', 'node12' or 'node16' instead.");
|
||||
}
|
||||
}
|
||||
else if (pluginToken != null)
|
||||
@@ -501,7 +500,7 @@ namespace GitHub.Runner.Worker
|
||||
};
|
||||
}
|
||||
|
||||
throw new NotSupportedException("Missing 'using' value. 'using' requires 'composite', 'docker', 'node12', 'node16' or 'node20'.");
|
||||
throw new NotSupportedException("Missing 'using' value. 'using' requires 'composite', 'docker', 'node12' or 'node16'.");
|
||||
}
|
||||
|
||||
private void ConvertInputs(
|
||||
|
||||
@@ -5,8 +5,8 @@ namespace GitHub.Runner.Worker
|
||||
{
|
||||
public class ActionNotFoundException : Exception
|
||||
{
|
||||
public ActionNotFoundException(Uri actionUri, string requestId)
|
||||
: base(FormatMessage(actionUri, requestId))
|
||||
public ActionNotFoundException(Uri actionUri)
|
||||
: base(FormatMessage(actionUri))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -25,13 +25,8 @@ namespace GitHub.Runner.Worker
|
||||
{
|
||||
}
|
||||
|
||||
private static string FormatMessage(Uri actionUri, string requestId)
|
||||
private static string FormatMessage(Uri actionUri)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(requestId))
|
||||
{
|
||||
return $"An action could not be found at the URI '{actionUri}' ({requestId})";
|
||||
}
|
||||
|
||||
return $"An action could not be found at the URI '{actionUri}'";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Channels;
|
||||
@@ -47,9 +46,7 @@ namespace GitHub.Runner.Worker.Container
|
||||
{
|
||||
base.Initialize(hostContext);
|
||||
DockerPath = WhichUtil.Which("docker", true, Trace);
|
||||
string path = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), ".runner");
|
||||
string json = File.ReadAllText(path, Encoding.UTF8);
|
||||
DockerInstanceLabel = IOUtil.GetSha256Hash(json).Substring(0, 6);
|
||||
DockerInstanceLabel = IOUtil.GetSha256Hash(hostContext.GetDirectory(WellKnownDirectory.Root)).Substring(0, 6);
|
||||
}
|
||||
|
||||
public async Task<DockerVersion> DockerVersion(IExecutionContext context)
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace GitHub.Runner.Worker
|
||||
// \_layout\_work\_temp\[jobname-support]\files\environment.txt
|
||||
var configurationStore = HostContext.GetService<IConfigurationStore>();
|
||||
RunnerSettings settings = configurationStore.GetSettings();
|
||||
ulong runnerId = settings.AgentId;
|
||||
int runnerId = settings.AgentId;
|
||||
string runnerName = settings.AgentName;
|
||||
int poolId = settings.PoolId;
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ namespace GitHub.Runner.Worker
|
||||
// Initialize
|
||||
void InitializeJob(Pipelines.AgentJobRequestMessage message, CancellationToken token);
|
||||
void CancelToken();
|
||||
IExecutionContext CreateChild(Guid recordId, string displayName, string refName, string scopeName, string contextName, ActionRunStage stage, Dictionary<string, string> intraActionState = null, int? recordOrder = null, IPagingLogger logger = null, bool isEmbedded = false, CancellationTokenSource cancellationTokenSource = null, Guid embeddedId = default(Guid), string siblingScopeName = null, TimeSpan? timeout = null);
|
||||
IExecutionContext CreateChild(Guid recordId, string displayName, string refName, string scopeName, string contextName, ActionRunStage stage, Dictionary<string, string> intraActionState = null, int? recordOrder = null, IPagingLogger logger = null, bool isEmbedded = false, CancellationTokenSource cancellationTokenSource = null, Guid embeddedId = default(Guid), string siblingScopeName = null);
|
||||
IExecutionContext CreateEmbeddedChild(string scopeName, string contextName, Guid embeddedId, ActionRunStage stage, Dictionary<string, string> intraActionState = null, string siblingScopeName = null);
|
||||
|
||||
// logging
|
||||
@@ -357,8 +357,7 @@ namespace GitHub.Runner.Worker
|
||||
bool isEmbedded = false,
|
||||
CancellationTokenSource cancellationTokenSource = null,
|
||||
Guid embeddedId = default(Guid),
|
||||
string siblingScopeName = null,
|
||||
TimeSpan? timeout = null)
|
||||
string siblingScopeName = null)
|
||||
{
|
||||
Trace.Entering();
|
||||
|
||||
@@ -387,12 +386,6 @@ namespace GitHub.Runner.Worker
|
||||
child.ExpressionFunctions.Add(item);
|
||||
}
|
||||
child._cancellationTokenSource = cancellationTokenSource ?? new CancellationTokenSource();
|
||||
if (timeout != null)
|
||||
{
|
||||
// composite steps inherit the timeout from the parent, set by https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepstimeout-minutes
|
||||
child.SetTimeout(timeout);
|
||||
}
|
||||
|
||||
child.EchoOnActionCommand = EchoOnActionCommand;
|
||||
|
||||
if (recordOrder != null)
|
||||
@@ -432,7 +425,7 @@ namespace GitHub.Runner.Worker
|
||||
Dictionary<string, string> intraActionState = null,
|
||||
string siblingScopeName = null)
|
||||
{
|
||||
return Root.CreateChild(_record.Id, _record.Name, _record.Id.ToString("N"), scopeName, contextName, stage, logger: _logger, isEmbedded: true, cancellationTokenSource: null, intraActionState: intraActionState, embeddedId: embeddedId, siblingScopeName: siblingScopeName, timeout: GetRemainingTimeout());
|
||||
return Root.CreateChild(_record.Id, _record.Name, _record.Id.ToString("N"), scopeName, contextName, stage, logger: _logger, isEmbedded: true, cancellationTokenSource: null, intraActionState: intraActionState, embeddedId: embeddedId, siblingScopeName: siblingScopeName);
|
||||
}
|
||||
|
||||
public void Start(string currentOperation = null)
|
||||
@@ -807,9 +800,9 @@ namespace GitHub.Runner.Worker
|
||||
|
||||
Global.Variables = new Variables(HostContext, variables);
|
||||
|
||||
if (Global.Variables.GetBoolean("DistributedTask.ForceInternalNodeVersionOnRunnerTo16") ?? false)
|
||||
if (Global.Variables.GetBoolean("DistributedTask.ForceInternalNodeVersionOnRunnerTo12") ?? false)
|
||||
{
|
||||
Environment.SetEnvironmentVariable(Constants.Variables.Agent.ForcedInternalNodeVersion, "node16");
|
||||
Environment.SetEnvironmentVariable(Constants.Variables.Agent.ForcedInternalNodeVersion, "node12");
|
||||
}
|
||||
|
||||
// Environment variables shared across all actions
|
||||
@@ -836,6 +829,7 @@ namespace GitHub.Runner.Worker
|
||||
// Actions environment
|
||||
ActionsEnvironment = message.ActionsEnvironment;
|
||||
|
||||
|
||||
// Service container info
|
||||
Global.ServiceContainers = new List<ContainerInfo>();
|
||||
|
||||
|
||||
@@ -141,28 +141,6 @@ namespace GitHub.Runner.Worker
|
||||
var pairs = new EnvFileKeyValuePairs(context, filePath);
|
||||
foreach (var pair in pairs)
|
||||
{
|
||||
var isBlocked = false;
|
||||
foreach (var blocked in _setEnvBlockList)
|
||||
{
|
||||
if (string.Equals(blocked, pair.Key, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Log Telemetry and let user know they shouldn't do this
|
||||
var issue = new Issue()
|
||||
{
|
||||
Type = IssueType.Error,
|
||||
Message = $"Can't store {blocked} output parameter using '$GITHUB_ENV' command."
|
||||
};
|
||||
issue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = $"{Constants.Runner.UnsupportedCommand}_{pair.Key}";
|
||||
context.AddIssue(issue, ExecutionContextLogOptions.Default);
|
||||
|
||||
isBlocked = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isBlocked)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
SetEnvironmentVariable(context, pair.Key, pair.Value);
|
||||
}
|
||||
}
|
||||
@@ -176,11 +154,6 @@ namespace GitHub.Runner.Worker
|
||||
context.SetEnvContext(name, value);
|
||||
context.Debug($"{name}='{value}'");
|
||||
}
|
||||
|
||||
private string[] _setEnvBlockList =
|
||||
{
|
||||
"NODE_OPTIONS"
|
||||
};
|
||||
}
|
||||
|
||||
public sealed class CreateStepSummaryCommand : RunnerService, IFileCommandExtension
|
||||
@@ -349,28 +322,28 @@ namespace GitHub.Runner.Worker
|
||||
var equalsIndex = line.IndexOf("=", StringComparison.Ordinal);
|
||||
var heredocIndex = line.IndexOf("<<", StringComparison.Ordinal);
|
||||
|
||||
// Normal style NAME=VALUE
|
||||
if (equalsIndex >= 0 && (heredocIndex < 0 || equalsIndex < heredocIndex))
|
||||
{
|
||||
var split = line.Split(new[] { '=' }, 2, StringSplitOptions.None);
|
||||
if (string.IsNullOrEmpty(line))
|
||||
{
|
||||
throw new Exception($"Invalid format '{line}'. Name must not be empty");
|
||||
}
|
||||
|
||||
key = split[0];
|
||||
output = split[1];
|
||||
}
|
||||
|
||||
// Heredoc style NAME<<EOF
|
||||
else if (heredocIndex >= 0 && (equalsIndex < 0 || heredocIndex < equalsIndex))
|
||||
// Heredoc style NAME=<<EOF (where EOF is typically randomly-generated Base64 and may include an '=' character)
|
||||
// see https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#multiline-strings
|
||||
// "key=<<EOF is also considered heredoc where the key contains an =.
|
||||
// Also we detect heredoc if whitespace chars exist between = and <<. (ex: "key <<EOF")
|
||||
var isHeredoc = heredocIndex >= 0 &&
|
||||
(
|
||||
equalsIndex < 0 ||
|
||||
heredocIndex < equalsIndex ||
|
||||
(
|
||||
heredocIndex > equalsIndex &&
|
||||
OnlyContainsWhiteSpaceBetweenPositions(line, equalsIndex, heredocIndex)
|
||||
)
|
||||
);
|
||||
|
||||
if (isHeredoc)
|
||||
{
|
||||
var split = line.Split(new[] { "<<" }, 2, StringSplitOptions.None);
|
||||
if (string.IsNullOrEmpty(split[0]) || string.IsNullOrEmpty(split[1]))
|
||||
{
|
||||
throw new Exception($"Invalid format '{line}'. Name must not be empty and delimiter must not be empty");
|
||||
}
|
||||
key = split[0];
|
||||
key = split[0].Trim().TrimEnd('=');
|
||||
var delimiter = split[1];
|
||||
var startIndex = index; // Start index of the value (inclusive)
|
||||
var endIndex = index; // End index of the value (exclusive)
|
||||
@@ -391,6 +364,18 @@ namespace GitHub.Runner.Worker
|
||||
|
||||
output = endIndex > startIndex ? text.Substring(startIndex, endIndex - startIndex) : string.Empty;
|
||||
}
|
||||
// Normal style NAME=VALUE, can have << in the value.
|
||||
else if (equalsIndex >= 0)
|
||||
{
|
||||
var split = line.Split(new[] { '=' }, 2, StringSplitOptions.None);
|
||||
if (string.IsNullOrEmpty(line))
|
||||
{
|
||||
throw new Exception($"Invalid format '{line}'. Name must not be empty");
|
||||
}
|
||||
|
||||
key = split[0];
|
||||
output = split[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Invalid format '{line}'");
|
||||
@@ -410,6 +395,32 @@ namespace GitHub.Runner.Worker
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
// accepts a string, two indexes, and returns true if only whitespace chars exist between those two indexes (non-inclusive)
|
||||
private static bool OnlyContainsWhiteSpaceBetweenPositions(string str, int pos1, int pos2)
|
||||
{
|
||||
// Check if the provided positions are valid
|
||||
if (pos1 < 0 || pos2 < 0 || pos1 >= str.Length || pos2 >= str.Length)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("OnlyContainsWhiteSpaceBetweenPositions: Positions should be within the string length.");
|
||||
}
|
||||
|
||||
// Ensure pos1 is always the smaller position
|
||||
if (pos2 < pos1)
|
||||
{
|
||||
throw new ArgumentException("OnlyContainsWhiteSpaceBetweenPositions: pos1 must be less than or equal to pos2.");
|
||||
}
|
||||
|
||||
for (int i = pos1 + 1; i < pos2; i++)
|
||||
{
|
||||
if (!char.IsWhiteSpace(str[i]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string ReadLine(
|
||||
string text,
|
||||
ref int index)
|
||||
|
||||
@@ -421,6 +421,8 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
{
|
||||
Trace.Info($"Starting: {step.DisplayName}");
|
||||
step.ExecutionContext.Debug($"Starting: {step.DisplayName}");
|
||||
// composite steps inherit the timeout from the parent, set by https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepstimeout-minutes
|
||||
step.ExecutionContext.SetTimeout(step.ExecutionContext.Parent.GetRemainingTimeout());
|
||||
|
||||
await Common.Util.EncodingUtil.SetEncoding(HostContext, Trace, step.ExecutionContext.CancellationToken);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -228,10 +228,6 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
Environment["ACTIONS_ID_TOKEN_REQUEST_URL"] = generateIdTokenUrl;
|
||||
Environment["ACTIONS_ID_TOKEN_REQUEST_TOKEN"] = systemConnection.Authorization.Parameters[EndpointAuthorizationParameters.AccessToken];
|
||||
}
|
||||
if (systemConnection.Data.TryGetValue("ResultsServiceUrl", out var resultsUrl) && !string.IsNullOrEmpty(resultsUrl))
|
||||
{
|
||||
Environment["ACTIONS_RESULTS_URL"] = resultsUrl;
|
||||
}
|
||||
|
||||
foreach (var variable in this.Environment)
|
||||
{
|
||||
|
||||
@@ -58,31 +58,40 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
var nodeData = data as NodeJSActionExecutionData;
|
||||
|
||||
// With node12 EoL in 04/2022, we want to be able to uniformly upgrade all JS actions to node16 from the server
|
||||
if (string.Equals(nodeData.NodeVersion, "node12", StringComparison.InvariantCultureIgnoreCase))
|
||||
if (string.Equals(nodeData.NodeVersion, "node12", StringComparison.InvariantCultureIgnoreCase) &&
|
||||
(executionContext.Global.Variables.GetBoolean("DistributedTask.ForceGithubJavascriptActionsToNode16") ?? false))
|
||||
{
|
||||
var repoAction = action as Pipelines.RepositoryPathReference;
|
||||
if (repoAction != null)
|
||||
// The user can opt out of this behaviour by setting this variable to true, either setting 'env' in their workflow or as an environment variable on their machine
|
||||
executionContext.Global.EnvironmentVariables.TryGetValue(Constants.Variables.Actions.AllowActionsUseUnsecureNodeVersion, out var workflowOptOut);
|
||||
var isWorkflowOptOutSet = !string.IsNullOrEmpty(workflowOptOut);
|
||||
var isLocalOptOut = StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable(Constants.Variables.Actions.AllowActionsUseUnsecureNodeVersion));
|
||||
bool isOptOut = isWorkflowOptOutSet ? StringUtil.ConvertToBoolean(workflowOptOut) : isLocalOptOut;
|
||||
if (!isOptOut)
|
||||
{
|
||||
var warningActions = new HashSet<string>();
|
||||
if (executionContext.Global.Variables.TryGetValue(Constants.Runner.EnforcedNode12DetectedAfterEndOfLifeEnvVariable, out var node16ForceWarnings))
|
||||
var repoAction = action as Pipelines.RepositoryPathReference;
|
||||
if (repoAction != null)
|
||||
{
|
||||
warningActions = StringUtil.ConvertFromJson<HashSet<string>>(node16ForceWarnings);
|
||||
}
|
||||
var warningActions = new HashSet<string>();
|
||||
if (executionContext.Global.Variables.TryGetValue(Constants.Runner.EnforcedNode12DetectedAfterEndOfLifeEnvVariable, out var node16ForceWarnings))
|
||||
{
|
||||
warningActions = StringUtil.ConvertFromJson<HashSet<string>>(node16ForceWarnings);
|
||||
}
|
||||
|
||||
string repoActionFullName;
|
||||
if (string.IsNullOrEmpty(repoAction.Name))
|
||||
{
|
||||
repoActionFullName = repoAction.Path; // local actions don't have a 'Name'
|
||||
}
|
||||
else
|
||||
{
|
||||
repoActionFullName = $"{repoAction.Name}/{repoAction.Path ?? string.Empty}".TrimEnd('/') + $"@{repoAction.Ref}";
|
||||
}
|
||||
var repoActionFullName = "";
|
||||
if (string.IsNullOrEmpty(repoAction.Name))
|
||||
{
|
||||
repoActionFullName = repoAction.Path; // local actions don't have a 'Name'
|
||||
}
|
||||
else
|
||||
{
|
||||
repoActionFullName = $"{repoAction.Name}/{repoAction.Path ?? string.Empty}".TrimEnd('/') + $"@{repoAction.Ref}";
|
||||
}
|
||||
|
||||
warningActions.Add(repoActionFullName);
|
||||
executionContext.Global.Variables.Set("Node16ForceActionsWarnings", StringUtil.ConvertToJson(warningActions));
|
||||
warningActions.Add(repoActionFullName);
|
||||
executionContext.Global.Variables.Set("Node16ForceActionsWarnings", StringUtil.ConvertToJson(warningActions));
|
||||
}
|
||||
nodeData.NodeVersion = "node16";
|
||||
}
|
||||
nodeData.NodeVersion = "node16";
|
||||
}
|
||||
(handler as INodeScriptActionHandler).Data = nodeData;
|
||||
}
|
||||
|
||||
@@ -63,10 +63,6 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
Environment["ACTIONS_ID_TOKEN_REQUEST_URL"] = generateIdTokenUrl;
|
||||
Environment["ACTIONS_ID_TOKEN_REQUEST_TOKEN"] = systemConnection.Authorization.Parameters[EndpointAuthorizationParameters.AccessToken];
|
||||
}
|
||||
if (systemConnection.Data.TryGetValue("ResultsServiceUrl", out var resultsUrl) && !string.IsNullOrEmpty(resultsUrl))
|
||||
{
|
||||
Environment["ACTIONS_RESULTS_URL"] = resultsUrl;
|
||||
}
|
||||
|
||||
// Resolve the target script.
|
||||
string target = null;
|
||||
@@ -102,14 +98,20 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
workingDirectory = HostContext.GetDirectory(WellKnownDirectory.Work);
|
||||
}
|
||||
|
||||
#if OS_OSX || OS_WINDOWS
|
||||
if (string.Equals(Data.NodeVersion, "node12", StringComparison.OrdinalIgnoreCase) &&
|
||||
Constants.Runner.PlatformArchitecture.Equals(Constants.Architecture.Arm64))
|
||||
{
|
||||
ExecutionContext.Output($"The node12 is not supported. Use node16 instead.");
|
||||
#if OS_OSX
|
||||
ExecutionContext.Output($"The node12 is not supported on macOS ARM64 platform. Use node16 instead.");
|
||||
#elif OS_WINDOWS
|
||||
ExecutionContext.Output($"The node12 is not supported on windows ARM64 platform. Use node16 instead.");
|
||||
#endif
|
||||
Data.NodeVersion = "node16";
|
||||
}
|
||||
|
||||
#endif
|
||||
string forcedNodeVersion = System.Environment.GetEnvironmentVariable(Constants.Variables.Agent.ForcedActionsNodeVersion);
|
||||
|
||||
if (forcedNodeVersion == "node16" && Data.NodeVersion != "node16")
|
||||
{
|
||||
Data.NodeVersion = "node16";
|
||||
@@ -134,26 +136,27 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
// Remove environment variable that may cause conflicts with the node within the runner.
|
||||
Environment.Remove("NODE_ICU_DATA"); // https://github.com/actions/runner/issues/795
|
||||
|
||||
if (string.Equals(Data.NodeVersion, Constants.Runner.DeprecatedNodeVersion, StringComparison.OrdinalIgnoreCase) && (ExecutionContext.Global.Variables.GetBoolean(Constants.Runner.Features.Node16Warning) ?? false))
|
||||
if (Data.NodeVersion == "node12" && (ExecutionContext.Global.Variables.GetBoolean(Constants.Runner.Features.Node12Warning) ?? false))
|
||||
{
|
||||
var repoAction = Action as RepositoryPathReference;
|
||||
var warningActions = new HashSet<string>();
|
||||
if (ExecutionContext.Global.Variables.TryGetValue(Constants.Runner.DeprecatedNodeDetectedAfterEndOfLifeActions, out var deprecatedNodeWarnings))
|
||||
if (ExecutionContext.Global.Variables.TryGetValue("Node12ActionsWarnings", out var node12Warnings))
|
||||
{
|
||||
warningActions = StringUtil.ConvertFromJson<HashSet<string>>(deprecatedNodeWarnings);
|
||||
warningActions = StringUtil.ConvertFromJson<HashSet<string>>(node12Warnings);
|
||||
}
|
||||
|
||||
var repoActionFullName = "";
|
||||
if (string.IsNullOrEmpty(repoAction.Name))
|
||||
{
|
||||
// local actions don't have a 'Name'
|
||||
warningActions.Add(repoAction.Path);
|
||||
repoActionFullName = repoAction.Path; // local actions don't have a 'Name'
|
||||
}
|
||||
else
|
||||
{
|
||||
warningActions.Add($"{repoAction.Name}/{repoAction.Path ?? string.Empty}".TrimEnd('/') + $"@{repoAction.Ref}");
|
||||
repoActionFullName = $"{repoAction.Name}/{repoAction.Path ?? string.Empty}".TrimEnd('/') + $"@{repoAction.Ref}";
|
||||
}
|
||||
|
||||
ExecutionContext.Global.Variables.Set(Constants.Runner.DeprecatedNodeDetectedAfterEndOfLifeActions, StringUtil.ConvertToJson(warningActions));
|
||||
warningActions.Add(repoActionFullName);
|
||||
ExecutionContext.Global.Variables.Set("Node12ActionsWarnings", StringUtil.ConvertToJson(warningActions));
|
||||
}
|
||||
|
||||
using (var stdoutManager = new OutputManager(ExecutionContext, ActionCommandManager))
|
||||
|
||||
@@ -83,40 +83,19 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
shellCommand = "pwsh";
|
||||
if (validateShellOnHost)
|
||||
{
|
||||
if (ExecutionContext.Global.Variables.GetBoolean("DistributedTask.UseWhich2") == true)
|
||||
{
|
||||
shellCommandPath = WhichUtil.Which2(shellCommand, require: false, Trace, prependPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
shellCommandPath = WhichUtil.Which(shellCommand, require: false, Trace, prependPath);
|
||||
}
|
||||
shellCommandPath = WhichUtil.Which(shellCommand, require: false, Trace, prependPath);
|
||||
if (string.IsNullOrEmpty(shellCommandPath))
|
||||
{
|
||||
shellCommand = "powershell";
|
||||
Trace.Info($"Defaulting to {shellCommand}");
|
||||
if (ExecutionContext.Global.Variables.GetBoolean("DistributedTask.UseWhich2") == true)
|
||||
{
|
||||
shellCommandPath = WhichUtil.Which2(shellCommand, require: true, Trace, prependPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
shellCommandPath = WhichUtil.Which(shellCommand, require: true, Trace, prependPath);
|
||||
}
|
||||
shellCommandPath = WhichUtil.Which(shellCommand, require: true, Trace, prependPath);
|
||||
}
|
||||
}
|
||||
#else
|
||||
shellCommand = "sh";
|
||||
if (validateShellOnHost)
|
||||
{
|
||||
if (ExecutionContext.Global.Variables.GetBoolean("DistributedTask.UseWhich2") == true)
|
||||
{
|
||||
shellCommandPath = WhichUtil.Which2("bash", false, Trace, prependPath) ?? WhichUtil.Which2("sh", true, Trace, prependPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
shellCommandPath = WhichUtil.Which("bash", false, Trace, prependPath) ?? WhichUtil.Which("sh", true, Trace, prependPath);
|
||||
}
|
||||
shellCommandPath = WhichUtil.Which("bash", false, Trace, prependPath) ?? WhichUtil.Which("sh", true, Trace, prependPath);
|
||||
}
|
||||
#endif
|
||||
argFormat = ScriptHandlerHelpers.GetScriptArgumentsFormat(shellCommand);
|
||||
@@ -127,14 +106,7 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
shellCommand = parsed.shellCommand;
|
||||
if (validateShellOnHost)
|
||||
{
|
||||
if (ExecutionContext.Global.Variables.GetBoolean("DistributedTask.UseWhich2") == true)
|
||||
{
|
||||
shellCommandPath = WhichUtil.Which2(parsed.shellCommand, true, Trace, prependPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
shellCommandPath = WhichUtil.Which(parsed.shellCommand, true, Trace, prependPath);
|
||||
}
|
||||
shellCommandPath = WhichUtil.Which(parsed.shellCommand, true, Trace, prependPath);
|
||||
}
|
||||
|
||||
argFormat = $"{parsed.shellArgs}".TrimStart();
|
||||
@@ -216,38 +188,17 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
{
|
||||
#if OS_WINDOWS
|
||||
shellCommand = "pwsh";
|
||||
if (ExecutionContext.Global.Variables.GetBoolean("DistributedTask.UseWhich2") == true)
|
||||
{
|
||||
commandPath = WhichUtil.Which2(shellCommand, require: false, Trace, prependPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
commandPath = WhichUtil.Which(shellCommand, require: false, Trace, prependPath);
|
||||
}
|
||||
commandPath = WhichUtil.Which(shellCommand, require: false, Trace, prependPath);
|
||||
if (string.IsNullOrEmpty(commandPath))
|
||||
{
|
||||
shellCommand = "powershell";
|
||||
Trace.Info($"Defaulting to {shellCommand}");
|
||||
if (ExecutionContext.Global.Variables.GetBoolean("DistributedTask.UseWhich2") == true)
|
||||
{
|
||||
commandPath = WhichUtil.Which2(shellCommand, require: true, Trace, prependPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
commandPath = WhichUtil.Which(shellCommand, require: true, Trace, prependPath);
|
||||
}
|
||||
commandPath = WhichUtil.Which(shellCommand, require: true, Trace, prependPath);
|
||||
}
|
||||
ArgUtil.NotNullOrEmpty(commandPath, "Default Shell");
|
||||
#else
|
||||
shellCommand = "sh";
|
||||
if (ExecutionContext.Global.Variables.GetBoolean("DistributedTask.UseWhich2") == true)
|
||||
{
|
||||
commandPath = WhichUtil.Which2("bash", false, Trace, prependPath) ?? WhichUtil.Which2("sh", true, Trace, prependPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
commandPath = WhichUtil.Which("bash", false, Trace, prependPath) ?? WhichUtil.Which("sh", true, Trace, prependPath);
|
||||
}
|
||||
commandPath = WhichUtil.Which("bash", false, Trace, prependPath) ?? WhichUtil.Which("sh", true, Trace, prependPath);
|
||||
#endif
|
||||
argFormat = ScriptHandlerHelpers.GetScriptArgumentsFormat(shellCommand);
|
||||
}
|
||||
@@ -258,14 +209,7 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
if (!IsActionStep && systemShells.Contains(shell))
|
||||
{
|
||||
shellCommand = shell;
|
||||
if (ExecutionContext.Global.Variables.GetBoolean("DistributedTask.UseWhich2") == true)
|
||||
{
|
||||
commandPath = WhichUtil.Which2(shell, !isContainerStepHost, Trace, prependPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
commandPath = WhichUtil.Which(shell, !isContainerStepHost, Trace, prependPath);
|
||||
}
|
||||
commandPath = WhichUtil.Which(shell, !isContainerStepHost, Trace, prependPath);
|
||||
if (shell == "bash")
|
||||
{
|
||||
argFormat = ScriptHandlerHelpers.GetScriptArgumentsFormat("sh");
|
||||
@@ -280,14 +224,7 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
var parsed = ScriptHandlerHelpers.ParseShellOptionString(shell);
|
||||
shellCommand = parsed.shellCommand;
|
||||
// For non-ContainerStepHost, the command must be located on the host by Which
|
||||
if (ExecutionContext.Global.Variables.GetBoolean("DistributedTask.UseWhich2") == true)
|
||||
{
|
||||
commandPath = WhichUtil.Which2(parsed.shellCommand, !isContainerStepHost, Trace, prependPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
commandPath = WhichUtil.Which(parsed.shellCommand, !isContainerStepHost, Trace, prependPath);
|
||||
}
|
||||
commandPath = WhichUtil.Which(parsed.shellCommand, !isContainerStepHost, Trace, prependPath);
|
||||
argFormat = $"{parsed.shellArgs}".TrimStart();
|
||||
if (string.IsNullOrEmpty(argFormat))
|
||||
{
|
||||
|
||||
@@ -51,13 +51,6 @@ namespace GitHub.Runner.Worker
|
||||
HostContext.UserAgents.Add(new ProductInfoHeaderValue("OrchestrationId", orchestrationId.Value));
|
||||
}
|
||||
|
||||
var jobServerQueueTelemetry = false;
|
||||
if (message.Variables.TryGetValue("DistributedTask.EnableJobServerQueueTelemetry", out VariableValue enableJobServerQueueTelemetry) &&
|
||||
!string.IsNullOrEmpty(enableJobServerQueueTelemetry?.Value))
|
||||
{
|
||||
jobServerQueueTelemetry = StringUtil.ConvertToBoolean(enableJobServerQueueTelemetry.Value);
|
||||
}
|
||||
|
||||
ServiceEndpoint systemConnection = message.Resources.Endpoints.Single(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
|
||||
if (MessageUtil.IsRunServiceJob(message.MessageType))
|
||||
{
|
||||
@@ -79,7 +72,7 @@ namespace GitHub.Runner.Worker
|
||||
launchServer.InitializeLaunchClient(new Uri(launchReceiverEndpoint), accessToken);
|
||||
}
|
||||
_jobServerQueue = HostContext.GetService<IJobServerQueue>();
|
||||
_jobServerQueue.Start(message, resultsServiceOnly: true, enableTelemetry: jobServerQueueTelemetry);
|
||||
_jobServerQueue.Start(message, resultServiceOnly: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -91,17 +84,10 @@ namespace GitHub.Runner.Worker
|
||||
Trace.Info($"Creating job server with URL: {jobServerUrl}");
|
||||
// jobServerQueue is the throttling reporter.
|
||||
_jobServerQueue = HostContext.GetService<IJobServerQueue>();
|
||||
var delegatingHandlers = new List<DelegatingHandler>() { new ThrottlingReportHandler(_jobServerQueue) };
|
||||
message.Variables.TryGetValue("Actions.EnableHttpRedirects", out VariableValue enableHttpRedirects);
|
||||
if (StringUtil.ConvertToBoolean(enableHttpRedirects?.Value) &&
|
||||
!StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_NO_HTTP_REDIRECTS")))
|
||||
{
|
||||
delegatingHandlers.Add(new RedirectMessageHandler(Trace));
|
||||
}
|
||||
VssConnection jobConnection = VssUtil.CreateConnection(jobServerUrl, jobServerCredential, delegatingHandlers);
|
||||
VssConnection jobConnection = VssUtil.CreateConnection(jobServerUrl, jobServerCredential, new DelegatingHandler[] { new ThrottlingReportHandler(_jobServerQueue) });
|
||||
await jobServer.ConnectAsync(jobConnection);
|
||||
|
||||
_jobServerQueue.Start(message, enableTelemetry: jobServerQueueTelemetry);
|
||||
_jobServerQueue.Start(message);
|
||||
server = jobServer;
|
||||
}
|
||||
|
||||
@@ -283,16 +269,10 @@ namespace GitHub.Runner.Worker
|
||||
{
|
||||
jobContext.Debug($"Finishing: {message.JobDisplayName}");
|
||||
TaskResult result = jobContext.Complete(taskResult);
|
||||
if (jobContext.Global.Variables.TryGetValue(Constants.Runner.DeprecatedNodeDetectedAfterEndOfLifeActions, out var deprecatedNodeWarnings))
|
||||
if (jobContext.Global.Variables.TryGetValue("Node12ActionsWarnings", out var node12Warnings))
|
||||
{
|
||||
var actions = string.Join(", ", StringUtil.ConvertFromJson<HashSet<string>>(deprecatedNodeWarnings));
|
||||
jobContext.Warning(string.Format(Constants.Runner.DetectedNodeAfterEndOfLifeMessage, actions));
|
||||
}
|
||||
|
||||
if (jobContext.Global.Variables.TryGetValue(Constants.Runner.EnforcedNode12DetectedAfterEndOfLifeEnvVariable, out var node16ForceWarnings))
|
||||
{
|
||||
var actions = string.Join(", ", StringUtil.ConvertFromJson<HashSet<string>>(node16ForceWarnings));
|
||||
jobContext.Warning(string.Format(Constants.Runner.EnforcedNode12DetectedAfterEndOfLife, actions));
|
||||
var actions = string.Join(", ", StringUtil.ConvertFromJson<HashSet<string>>(node12Warnings));
|
||||
jobContext.Warning(string.Format(Constants.Runner.Node12DetectedAfterEndOfLife, actions));
|
||||
}
|
||||
|
||||
// Make sure to clean temp after file upload since they may be pending fileupload still use the TEMP dir.
|
||||
@@ -388,10 +368,10 @@ namespace GitHub.Runner.Worker
|
||||
}
|
||||
}
|
||||
|
||||
if (jobContext.Global.Variables.TryGetValue(Constants.Runner.DeprecatedNodeDetectedAfterEndOfLifeActions, out var deprecatedNodeWarnings))
|
||||
if (jobContext.Global.Variables.TryGetValue("Node12ActionsWarnings", out var node12Warnings))
|
||||
{
|
||||
var actions = string.Join(", ", StringUtil.ConvertFromJson<HashSet<string>>(deprecatedNodeWarnings));
|
||||
jobContext.Warning(string.Format(Constants.Runner.DetectedNodeAfterEndOfLifeMessage, actions));
|
||||
var actions = string.Join(", ", StringUtil.ConvertFromJson<HashSet<string>>(node12Warnings));
|
||||
jobContext.Warning(string.Format(Constants.Runner.Node12DetectedAfterEndOfLife, actions));
|
||||
}
|
||||
|
||||
if (jobContext.Global.Variables.TryGetValue(Constants.Runner.EnforcedNode12DetectedAfterEndOfLifeEnvVariable, out var node16ForceWarnings))
|
||||
@@ -412,13 +392,6 @@ namespace GitHub.Runner.Worker
|
||||
result = TaskResultUtil.MergeTaskResults(result, TaskResult.Failed);
|
||||
}
|
||||
|
||||
// include any job telemetry from the background upload process.
|
||||
if (_jobServerQueue != null &&
|
||||
_jobServerQueue.JobTelemetries.Count > 0)
|
||||
{
|
||||
jobContext.Global.JobTelemetry.AddRange(_jobServerQueue.JobTelemetries);
|
||||
}
|
||||
|
||||
// Clean TEMP after finish process jobserverqueue, since there might be a pending fileupload still use the TEMP dir.
|
||||
_tempDirectoryManager?.CleanupTempDirectory();
|
||||
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
// Set of classes used to bypass token operations
|
||||
// Results Service and External services follow a different auth model but
|
||||
// we are required to pass in a credentials object to create a RawHttpMessageHandler
|
||||
public class NoOpCredentials : FederatedCredential
|
||||
{
|
||||
public NoOpCredentials(IssuedToken initialToken) : base(initialToken)
|
||||
{
|
||||
}
|
||||
|
||||
public override VssCredentialsType CredentialType { get; }
|
||||
protected override IssuedTokenProvider OnCreateTokenProvider(Uri serverUrl, IHttpResponse response)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,7 +109,7 @@ namespace GitHub.Services.Common
|
||||
lock (m_thisLock)
|
||||
{
|
||||
// Ensure that we attempt to use the most appropriate authentication mechanism by default.
|
||||
if (m_tokenProvider == null && !(this.Credentials is NoOpCredentials))
|
||||
if (m_tokenProvider == null)
|
||||
{
|
||||
m_tokenProvider = this.Credentials.CreateTokenProvider(request.RequestUri, null, null);
|
||||
}
|
||||
@@ -121,8 +121,7 @@ namespace GitHub.Services.Common
|
||||
HttpResponseMessageWrapper responseWrapper;
|
||||
|
||||
Boolean lastResponseDemandedProxyAuth = false;
|
||||
// do not retry if we cannot recreate tokens
|
||||
Int32 retries = this.Credentials is NoOpCredentials ? 0 : m_maxAuthRetries;
|
||||
Int32 retries = m_maxAuthRetries;
|
||||
try
|
||||
{
|
||||
tokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
@@ -139,12 +138,8 @@ namespace GitHub.Services.Common
|
||||
}
|
||||
|
||||
// Let's start with sending a token
|
||||
IssuedToken token = null;
|
||||
if (m_tokenProvider != null)
|
||||
{
|
||||
token = await m_tokenProvider.GetTokenAsync(null, tokenSource.Token).ConfigureAwait(false);
|
||||
ApplyToken(request, token, applyICredentialsToWebProxy: lastResponseDemandedProxyAuth);
|
||||
}
|
||||
IssuedToken token = await m_tokenProvider.GetTokenAsync(null, tokenSource.Token).ConfigureAwait(false);
|
||||
ApplyToken(request, token, applyICredentialsToWebProxy: lastResponseDemandedProxyAuth);
|
||||
|
||||
// The WinHttpHandler will chunk any content that does not have a computed length which is
|
||||
// not what we want. By loading into a buffer up-front we bypass this behavior and there is
|
||||
|
||||
@@ -97,7 +97,7 @@ namespace GitHub.DistributedTask.WebApi
|
||||
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
|
||||
public virtual async Task DeleteAgentAsync(
|
||||
int poolId,
|
||||
ulong agentId,
|
||||
int agentId,
|
||||
object userState = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -243,7 +243,7 @@ namespace GitHub.DistributedTask.WebApi
|
||||
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
|
||||
public virtual Task<TaskAgent> ReplaceAgentAsync(
|
||||
int poolId,
|
||||
ulong agentId,
|
||||
int agentId,
|
||||
TaskAgent agent,
|
||||
object userState = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
@@ -786,7 +786,7 @@ namespace GitHub.DistributedTask.WebApi
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public virtual Task<TaskAgent> UpdateAgentUpdateStateAsync(
|
||||
int poolId,
|
||||
ulong agentId,
|
||||
int agentId,
|
||||
string currentState,
|
||||
string updateTrace,
|
||||
object userState = null,
|
||||
|
||||
@@ -123,11 +123,8 @@ namespace GitHub.DistributedTask.Logging
|
||||
var secretSection = string.Empty;
|
||||
if (value.Contains("&+"))
|
||||
{
|
||||
if (value.Length > value.IndexOf("&+") + "&+".Length + 1)
|
||||
{
|
||||
// +1 to skip the letter that got colored
|
||||
secretSection = value.Substring(value.IndexOf("&+") + "&+".Length + 1);
|
||||
}
|
||||
// +1 to skip the letter that got colored
|
||||
secretSection = value.Substring(value.IndexOf("&+") + "&+".Length + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace GitHub.DistributedTask.WebApi
|
||||
}
|
||||
|
||||
public AgentRefreshMessage(
|
||||
ulong agentId,
|
||||
Int32 agentId,
|
||||
String targetVersion,
|
||||
TimeSpan? timeout = null)
|
||||
{
|
||||
@@ -26,7 +26,7 @@ namespace GitHub.DistributedTask.WebApi
|
||||
}
|
||||
|
||||
[DataMember]
|
||||
public ulong AgentId
|
||||
public Int32 AgentId
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace GitHub.DistributedTask.WebApi
|
||||
[DataContract]
|
||||
public sealed class DiagnosticLogMetadata
|
||||
{
|
||||
public DiagnosticLogMetadata(string agentName, ulong agentId, int poolId, string phaseName, string fileName, string phaseResult)
|
||||
public DiagnosticLogMetadata(string agentName, int agentId, int poolId, string phaseName, string fileName, string phaseResult)
|
||||
{
|
||||
AgentName = agentName;
|
||||
AgentId = agentId;
|
||||
@@ -19,7 +19,7 @@ namespace GitHub.DistributedTask.WebApi
|
||||
public string AgentName { get; set; }
|
||||
|
||||
[DataMember]
|
||||
public ulong AgentId { get; set; }
|
||||
public int AgentId { get; set; }
|
||||
|
||||
[DataMember]
|
||||
public int PoolId { get; set; }
|
||||
|
||||
@@ -2516,23 +2516,4 @@ namespace GitHub.DistributedTask.WebApi
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class InvalidActionArchiveException : DistributedTaskException
|
||||
{
|
||||
public InvalidActionArchiveException(String message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public InvalidActionArchiveException(String message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
private InvalidActionArchiveException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace GitHub.DistributedTask.WebApi
|
||||
{
|
||||
// do NOT add new enum since it will break backward compatibility with GHES
|
||||
public enum JobTelemetryType
|
||||
{
|
||||
[EnumMember]
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace GitHub.DistributedTask.WebApi
|
||||
|
||||
public List<TaskAgent> ToTaskAgents()
|
||||
{
|
||||
return Runners.Select(runner => new TaskAgent() { Id = runner.Id, Name = runner.Name }).ToList();
|
||||
return Runners.Select(runner => new TaskAgent() { Name = runner.Name }).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace GitHub.DistributedTask.WebApi
|
||||
}
|
||||
|
||||
[JsonProperty("id")]
|
||||
public ulong Id
|
||||
public Int32 Id
|
||||
{
|
||||
get;
|
||||
internal set;
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace GitHub.DistributedTask.WebApi
|
||||
}
|
||||
|
||||
public RunnerRefreshMessage(
|
||||
ulong runnerId,
|
||||
Int32 runnerId,
|
||||
String targetVersion,
|
||||
int? timeoutInSeconds = null)
|
||||
{
|
||||
@@ -26,7 +26,7 @@ namespace GitHub.DistributedTask.WebApi
|
||||
}
|
||||
|
||||
[DataMember]
|
||||
public ulong RunnerId
|
||||
public Int32 RunnerId
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace GitHub.DistributedTask.WebApi
|
||||
/// Identifier of the agent.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public ulong Id
|
||||
public Int32 Id
|
||||
{
|
||||
get;
|
||||
set;
|
||||
|
||||
@@ -14,16 +14,14 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="4.4.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.9" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.4" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="5.2.1" />
|
||||
<PackageReference Include="System.Security.Cryptography.Cng" Version="4.4.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.Pkcs" Version="4.4.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="4.4.0" />
|
||||
<PackageReference Include="Minimatch" Version="2.0.0" />
|
||||
<PackageReference Include="YamlDotNet.Signed" Version="5.3.0" />
|
||||
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -86,11 +86,6 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
return result.Value;
|
||||
}
|
||||
|
||||
if (result.StatusCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
throw new AccessDeniedException(result.Error);
|
||||
}
|
||||
|
||||
throw new Exception($"Failed to get job message: {result.Error}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,7 +120,6 @@ namespace GitHub.Runner.Common.Tests
|
||||
[InlineData("secret&+secret&secret", "secret&+\x0033[96ms\x0033[0mecret&secret", "***\x0033[96ms\x0033[0m***")]
|
||||
[InlineData("secret&+secret&+secret", "secret&+\x0033[96ms\x0033[0mecret&+secret", "***\x0033[96ms\x0033[0m***")]
|
||||
[InlineData("secret&+secret&secret&+secret", "secret&+\x0033[96ms\x0033[0mecret&secret&+secret", "***\x0033[96ms\x0033[0m***")]
|
||||
[InlineData("secret&secret&+", "secret&secret&+\x0033[96m\x0033[0m", "***\x0033[96m\x0033[0m")]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Common")]
|
||||
public void SecretSectionMasking(string secret, string rawOutput, string maskedOutput)
|
||||
|
||||
@@ -110,12 +110,12 @@ namespace GitHub.Runner.Common.Tests.Listener.Configuration
|
||||
_runnerServer.Setup(x => x.GetAgentPoolsAsync(It.IsAny<string>(), It.IsAny<TaskAgentPoolType>())).Returns(Task.FromResult(expectedPools));
|
||||
|
||||
var expectedAgents = new List<TaskAgent>();
|
||||
_runnerServer.Setup(x => x.GetAgentsAsync(It.IsAny<string>())).Returns(Task.FromResult(expectedAgents));
|
||||
_runnerServer.Setup(x => x.GetAgentsAsync(It.IsAny<int>(), It.IsAny<string>())).Returns(Task.FromResult(expectedAgents));
|
||||
|
||||
_runnerServer.Setup(x => x.AddAgentAsync(It.IsAny<int>(), It.IsAny<TaskAgent>())).Returns(Task.FromResult(expectedAgent));
|
||||
_runnerServer.Setup(x => x.ReplaceAgentAsync(It.IsAny<int>(), It.IsAny<TaskAgent>())).Returns(Task.FromResult(expectedAgent));
|
||||
|
||||
_dotcomServer.Setup(x => x.GetRunnerByNameAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>())).Returns(Task.FromResult(expectedAgents));
|
||||
_dotcomServer.Setup(x => x.GetRunnersAsync(It.IsAny<int>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>())).Returns(Task.FromResult(expectedAgents));
|
||||
_dotcomServer.Setup(x => x.GetRunnerGroupsAsync(It.IsAny<string>(), It.IsAny<string>())).Returns(Task.FromResult(expectedPools));
|
||||
_dotcomServer.Setup(x => x.AddRunnerAsync(It.IsAny<int>(), It.IsAny<TaskAgent>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>())).Returns(Task.FromResult(expectedRunner));
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
updater.Initialize(hc);
|
||||
|
||||
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
|
||||
.Callback((int p, ulong a, string s, string t) =>
|
||||
.Callback((int p, int a, string s, string t) =>
|
||||
{
|
||||
hc.GetTrace().Info(t);
|
||||
})
|
||||
@@ -180,7 +180,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
.Returns(Task.FromResult(new PackageMetadata() { Platform = BuildConstants.RunnerPackage.PackageName, Version = new PackageVersion("2.200.0"), DownloadUrl = _packageUrl }));
|
||||
|
||||
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
|
||||
.Callback((int p, ulong a, string s, string t) =>
|
||||
.Callback((int p, int a, string s, string t) =>
|
||||
{
|
||||
hc.GetTrace().Info(t);
|
||||
})
|
||||
@@ -234,7 +234,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
updater.Initialize(hc);
|
||||
|
||||
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
|
||||
.Callback((int p, ulong a, string s, string t) =>
|
||||
.Callback((int p, int a, string s, string t) =>
|
||||
{
|
||||
hc.GetTrace().Info(t);
|
||||
})
|
||||
@@ -289,7 +289,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
updater.Initialize(hc);
|
||||
|
||||
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
|
||||
.Callback((int p, ulong a, string s, string t) =>
|
||||
.Callback((int p, int a, string s, string t) =>
|
||||
{
|
||||
hc.GetTrace().Info(t);
|
||||
})
|
||||
@@ -344,7 +344,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
.Returns(Task.FromResult(new PackageMetadata() { Platform = BuildConstants.RunnerPackage.PackageName, Version = new PackageVersion("2.999.0"), DownloadUrl = _packageUrl, TrimmedPackages = new List<TrimmedPackageMetadata>() { new TrimmedPackageMetadata() } }));
|
||||
|
||||
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
|
||||
.Callback((int p, ulong a, string s, string t) =>
|
||||
.Callback((int p, int a, string s, string t) =>
|
||||
{
|
||||
hc.GetTrace().Info(t);
|
||||
})
|
||||
@@ -416,7 +416,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
updater.Initialize(hc);
|
||||
|
||||
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
|
||||
.Callback((int p, ulong a, string s, string t) =>
|
||||
.Callback((int p, int a, string s, string t) =>
|
||||
{
|
||||
hc.GetTrace().Info(t);
|
||||
})
|
||||
@@ -490,7 +490,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
.Returns(Task.FromResult(new PackageMetadata() { Platform = BuildConstants.RunnerPackage.PackageName, Version = new PackageVersion("2.999.0"), DownloadUrl = _packageUrl, TrimmedPackages = trim }));
|
||||
|
||||
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
|
||||
.Callback((int p, ulong a, string s, string t) =>
|
||||
.Callback((int p, int a, string s, string t) =>
|
||||
{
|
||||
hc.GetTrace().Info(t);
|
||||
})
|
||||
@@ -579,7 +579,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
.Returns(Task.FromResult(new PackageMetadata() { Platform = BuildConstants.RunnerPackage.PackageName, Version = new PackageVersion("2.999.0"), DownloadUrl = _packageUrl, TrimmedPackages = trim }));
|
||||
|
||||
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
|
||||
.Callback((int p, ulong a, string s, string t) =>
|
||||
.Callback((int p, int a, string s, string t) =>
|
||||
{
|
||||
hc.GetTrace().Info(t);
|
||||
})
|
||||
@@ -681,7 +681,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
|
||||
|
||||
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
|
||||
.Callback((int p, ulong a, string s, string t) =>
|
||||
.Callback((int p, int a, string s, string t) =>
|
||||
{
|
||||
hc.GetTrace().Info(t);
|
||||
})
|
||||
@@ -758,7 +758,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
.Returns(Task.FromResult(new PackageMetadata() { Platform = BuildConstants.RunnerPackage.PackageName, Version = new PackageVersion("2.999.0"), DownloadUrl = _packageUrl, TrimmedPackages = trim }));
|
||||
|
||||
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
|
||||
.Callback((int p, ulong a, string s, string t) =>
|
||||
.Callback((int p, int a, string s, string t) =>
|
||||
{
|
||||
hc.GetTrace().Info(t);
|
||||
})
|
||||
|
||||
@@ -1,10 +1,21 @@
|
||||
using System.IO;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using Xunit;
|
||||
using GitHub.Runner.Sdk;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Linq;
|
||||
|
||||
namespace GitHub.Runner.Common.Tests
|
||||
{
|
||||
public enum LineEndingType
|
||||
{
|
||||
Native,
|
||||
Linux = 0x__0A,
|
||||
Windows = 0x0D0A
|
||||
}
|
||||
|
||||
public static class TestUtil
|
||||
{
|
||||
private const string Src = "src";
|
||||
@@ -41,5 +52,24 @@ namespace GitHub.Runner.Common.Tests
|
||||
Assert.True(Directory.Exists(testDataDir));
|
||||
return testDataDir;
|
||||
}
|
||||
|
||||
public static void WriteContent(string path, string content, LineEndingType lineEnding = LineEndingType.Native)
|
||||
{
|
||||
WriteContent(path, Enumerable.Repeat(content, 1), lineEnding);
|
||||
}
|
||||
|
||||
public static void WriteContent(string path, IEnumerable<string> content, LineEndingType lineEnding = LineEndingType.Native)
|
||||
{
|
||||
string newline = lineEnding switch
|
||||
{
|
||||
LineEndingType.Linux => "\n",
|
||||
LineEndingType.Windows => "\r\n",
|
||||
_ => Environment.NewLine,
|
||||
};
|
||||
var encoding = new UTF8Encoding(true); // Emit BOM
|
||||
var contentStr = string.Join(newline, content);
|
||||
File.WriteAllText(path, contentStr, encoding);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,210 +212,5 @@ namespace GitHub.Runner.Common.Tests.Util
|
||||
File.Delete(brokenSymlink);
|
||||
Environment.SetEnvironmentVariable(PathUtil.PathVariable, oldValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Common")]
|
||||
public void UseWhich2FindGit()
|
||||
{
|
||||
using (TestHostContext hc = new(this))
|
||||
{
|
||||
//Arrange
|
||||
Tracing trace = hc.GetTrace();
|
||||
|
||||
// Act.
|
||||
string gitPath = WhichUtil.Which2("git", trace: trace);
|
||||
|
||||
trace.Info($"Which(\"git\") returns: {gitPath ?? string.Empty}");
|
||||
|
||||
// Assert.
|
||||
Assert.True(!string.IsNullOrEmpty(gitPath) && File.Exists(gitPath), $"Unable to find Git through: {nameof(WhichUtil.Which)}");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Common")]
|
||||
public void Which2ReturnsNullWhenNotFound()
|
||||
{
|
||||
using (TestHostContext hc = new(this))
|
||||
{
|
||||
//Arrange
|
||||
Tracing trace = hc.GetTrace();
|
||||
|
||||
// Act.
|
||||
string nosuch = WhichUtil.Which2("no-such-file-cf7e351f", trace: trace);
|
||||
|
||||
trace.Info($"result: {nosuch ?? string.Empty}");
|
||||
|
||||
// Assert.
|
||||
Assert.True(string.IsNullOrEmpty(nosuch), "Path should not be resolved");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Common")]
|
||||
public void Which2ThrowsWhenRequireAndNotFound()
|
||||
{
|
||||
using (TestHostContext hc = new(this))
|
||||
{
|
||||
//Arrange
|
||||
Tracing trace = hc.GetTrace();
|
||||
|
||||
// Act.
|
||||
try
|
||||
{
|
||||
WhichUtil.Which2("no-such-file-cf7e351f", require: true, trace: trace);
|
||||
throw new Exception("which should have thrown");
|
||||
}
|
||||
catch (FileNotFoundException ex)
|
||||
{
|
||||
Assert.Equal("no-such-file-cf7e351f", ex.FileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Common")]
|
||||
public void Which2HandleFullyQualifiedPath()
|
||||
{
|
||||
using (TestHostContext hc = new(this))
|
||||
{
|
||||
//Arrange
|
||||
Tracing trace = hc.GetTrace();
|
||||
|
||||
// Act.
|
||||
var gitPath = WhichUtil.Which2("git", require: true, trace: trace);
|
||||
var gitPath2 = WhichUtil.Which2(gitPath, require: true, trace: trace);
|
||||
|
||||
// Assert.
|
||||
Assert.Equal(gitPath, gitPath2);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Common")]
|
||||
public void Which2HandlesSymlinkToTargetFullPath()
|
||||
{
|
||||
// Arrange
|
||||
using TestHostContext hc = new TestHostContext(this);
|
||||
Tracing trace = hc.GetTrace();
|
||||
string oldValue = Environment.GetEnvironmentVariable(PathUtil.PathVariable);
|
||||
#if OS_WINDOWS
|
||||
string newValue = oldValue + @$";{Path.GetTempPath()}";
|
||||
string symlinkName = $"symlink-{Guid.NewGuid()}";
|
||||
string symlink = Path.GetTempPath() + $"{symlinkName}.exe";
|
||||
string target = Path.GetTempPath() + $"target-{Guid.NewGuid()}.exe";
|
||||
#else
|
||||
string newValue = oldValue + @$":{Path.GetTempPath()}";
|
||||
string symlinkName = $"symlink-{Guid.NewGuid()}";
|
||||
string symlink = Path.GetTempPath() + $"{symlinkName}";
|
||||
string target = Path.GetTempPath() + $"target-{Guid.NewGuid()}";
|
||||
#endif
|
||||
|
||||
Environment.SetEnvironmentVariable(PathUtil.PathVariable, newValue);
|
||||
|
||||
|
||||
using (File.Create(target))
|
||||
{
|
||||
File.CreateSymbolicLink(symlink, target);
|
||||
|
||||
// Act.
|
||||
var result = WhichUtil.Which2(symlinkName, require: true, trace: trace);
|
||||
|
||||
// Assert
|
||||
Assert.True(!string.IsNullOrEmpty(result) && File.Exists(result), $"Unable to find symlink through: {nameof(WhichUtil.Which)}");
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Cleanup
|
||||
File.Delete(symlink);
|
||||
File.Delete(target);
|
||||
Environment.SetEnvironmentVariable(PathUtil.PathVariable, oldValue);
|
||||
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Common")]
|
||||
public void Which2HandlesSymlinkToTargetRelativePath()
|
||||
{
|
||||
// Arrange
|
||||
using TestHostContext hc = new TestHostContext(this);
|
||||
Tracing trace = hc.GetTrace();
|
||||
string oldValue = Environment.GetEnvironmentVariable(PathUtil.PathVariable);
|
||||
#if OS_WINDOWS
|
||||
string newValue = oldValue + @$";{Path.GetTempPath()}";
|
||||
string symlinkName = $"symlink-{Guid.NewGuid()}";
|
||||
string symlink = Path.GetTempPath() + $"{symlinkName}.exe";
|
||||
string targetName = $"target-{Guid.NewGuid()}.exe";
|
||||
string target = Path.GetTempPath() + targetName;
|
||||
#else
|
||||
string newValue = oldValue + @$":{Path.GetTempPath()}";
|
||||
string symlinkName = $"symlink-{Guid.NewGuid()}";
|
||||
string symlink = Path.GetTempPath() + $"{symlinkName}";
|
||||
string targetName = $"target-{Guid.NewGuid()}";
|
||||
string target = Path.GetTempPath() + targetName;
|
||||
#endif
|
||||
Environment.SetEnvironmentVariable(PathUtil.PathVariable, newValue);
|
||||
|
||||
|
||||
using (File.Create(target))
|
||||
{
|
||||
File.CreateSymbolicLink(symlink, targetName);
|
||||
|
||||
// Act.
|
||||
var result = WhichUtil.Which2(symlinkName, require: true, trace: trace);
|
||||
|
||||
// Assert
|
||||
Assert.True(!string.IsNullOrEmpty(result) && File.Exists(result), $"Unable to find {symlinkName} through: {nameof(WhichUtil.Which)}");
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
File.Delete(symlink);
|
||||
File.Delete(target);
|
||||
Environment.SetEnvironmentVariable(PathUtil.PathVariable, oldValue);
|
||||
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Common")]
|
||||
public void Which2ThrowsWhenSymlinkBroken()
|
||||
{
|
||||
// Arrange
|
||||
using TestHostContext hc = new TestHostContext(this);
|
||||
Tracing trace = hc.GetTrace();
|
||||
string oldValue = Environment.GetEnvironmentVariable(PathUtil.PathVariable);
|
||||
|
||||
#if OS_WINDOWS
|
||||
string newValue = oldValue + @$";{Path.GetTempPath()}";
|
||||
string brokenSymlinkName = $"broken-symlink-{Guid.NewGuid()}";
|
||||
string brokenSymlink = Path.GetTempPath() + $"{brokenSymlinkName}.exe";
|
||||
#else
|
||||
string newValue = oldValue + @$":{Path.GetTempPath()}";
|
||||
string brokenSymlinkName = $"broken-symlink-{Guid.NewGuid()}";
|
||||
string brokenSymlink = Path.GetTempPath() + $"{brokenSymlinkName}";
|
||||
#endif
|
||||
|
||||
|
||||
string target = "no-such-file-cf7e351f";
|
||||
Environment.SetEnvironmentVariable(PathUtil.PathVariable, newValue);
|
||||
|
||||
File.CreateSymbolicLink(brokenSymlink, target);
|
||||
|
||||
// Act.
|
||||
var exception = Assert.Throws<FileNotFoundException>(() => WhichUtil.Which2(brokenSymlinkName, require: true, trace: trace));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(brokenSymlinkName, exception.FileName);
|
||||
|
||||
// Cleanup
|
||||
File.Delete(brokenSymlink);
|
||||
Environment.SetEnvironmentVariable(PathUtil.PathVariable, oldValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,63 +96,6 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public async void PrepareActions_DownloadActionFromDotCom_ZipFileError()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Arrange
|
||||
Setup();
|
||||
const string ActionName = "ownerName/sample-action";
|
||||
var actions = new List<Pipelines.ActionStep>
|
||||
{
|
||||
new Pipelines.ActionStep()
|
||||
{
|
||||
Name = "action",
|
||||
Id = Guid.NewGuid(),
|
||||
Reference = new Pipelines.RepositoryPathReference()
|
||||
{
|
||||
Name = ActionName,
|
||||
Ref = "main",
|
||||
RepositoryType = "GitHub"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create a corrupted ZIP file for testing
|
||||
var tempDir = _hc.GetDirectory(WellKnownDirectory.Temp);
|
||||
Directory.CreateDirectory(tempDir);
|
||||
var archiveFile = Path.Combine(tempDir, Path.GetRandomFileName());
|
||||
using (var fileStream = new FileStream(archiveFile, FileMode.Create))
|
||||
{
|
||||
// Used Co-Pilot for magic bytes here. They represent the tar header and just need to be invalid for the CLI to break.
|
||||
var buffer = new byte[] { 0x50, 0x4B, 0x03, 0x04, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00 };
|
||||
fileStream.Write(buffer, 0, buffer.Length);
|
||||
}
|
||||
using var stream = File.OpenRead(archiveFile);
|
||||
|
||||
string dotcomArchiveLink = GetLinkToActionArchive("https://api.github.com", ActionName, "main");
|
||||
var mockClientHandler = new Mock<HttpClientHandler>();
|
||||
mockClientHandler.Protected().Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.Is<HttpRequestMessage>(m => m.RequestUri == new Uri(dotcomArchiveLink)), ItExpr.IsAny<CancellationToken>())
|
||||
.ReturnsAsync(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StreamContent(stream) });
|
||||
|
||||
var mockHandlerFactory = new Mock<IHttpClientHandlerFactory>();
|
||||
mockHandlerFactory.Setup(p => p.CreateClientHandler(It.IsAny<RunnerWebProxy>())).Returns(mockClientHandler.Object);
|
||||
_hc.SetSingleton(mockHandlerFactory.Object);
|
||||
|
||||
_configurationStore.Object.GetSettings().IsHostedServer = true;
|
||||
|
||||
// Act + Assert
|
||||
await Assert.ThrowsAsync<InvalidActionArchiveException>(async () => await _actionManager.PrepareActionsAsync(_ec.Object, actions));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Teardown();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
@@ -293,118 +236,6 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public async void PrepareActions_DownloadActionFromGraph_UseCache()
|
||||
{
|
||||
try
|
||||
{
|
||||
//Arrange
|
||||
Setup();
|
||||
Directory.CreateDirectory(Path.Combine(_hc.GetDirectory(WellKnownDirectory.Temp), "action_cache"));
|
||||
Directory.CreateDirectory(Path.Combine(_hc.GetDirectory(WellKnownDirectory.Temp), "action_cache", "actions_download-artifact"));
|
||||
Directory.CreateDirectory(Path.Combine(_hc.GetDirectory(WellKnownDirectory.Temp), "actions-download-artifact"));
|
||||
Environment.SetEnvironmentVariable(Constants.Variables.Agent.ActionArchiveCacheDirectory, Path.Combine(_hc.GetDirectory(WellKnownDirectory.Temp), "action_cache"));
|
||||
|
||||
const string Content = @"
|
||||
# Container action
|
||||
name: '1ae80bcb-c1df-4362-bdaa-54f729c60281'
|
||||
description: 'Greet the world and record the time'
|
||||
author: 'GitHub'
|
||||
inputs:
|
||||
greeting: # id of input
|
||||
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
|
||||
required: true
|
||||
default: 'Hello'
|
||||
entryPoint: # id of input
|
||||
description: 'optional docker entrypoint overwrite.'
|
||||
required: false
|
||||
outputs:
|
||||
time: # id of output
|
||||
description: 'The time we did the greeting'
|
||||
icon: 'hello.svg' # vector art to display in the GitHub Marketplace
|
||||
color: 'green' # optional, decorates the entry in the GitHub Marketplace
|
||||
runs:
|
||||
using: 'node12'
|
||||
main: 'task.js'
|
||||
";
|
||||
await File.WriteAllTextAsync(Path.Combine(_hc.GetDirectory(WellKnownDirectory.Temp), "actions-download-artifact", "action.yml"), Content);
|
||||
|
||||
#if OS_WINDOWS
|
||||
ZipFile.CreateFromDirectory(Path.Combine(_hc.GetDirectory(WellKnownDirectory.Temp), "actions-download-artifact"), Path.Combine(_hc.GetDirectory(WellKnownDirectory.Temp), "action_cache", "actions_download-artifact", "master-sha.zip"), CompressionLevel.Fastest, true);
|
||||
#else
|
||||
string tar = WhichUtil.Which("tar", require: true, trace: _hc.GetTrace());
|
||||
|
||||
// tar -xzf
|
||||
using (var processInvoker = new ProcessInvokerWrapper())
|
||||
{
|
||||
processInvoker.Initialize(_hc);
|
||||
processInvoker.OutputDataReceived += new EventHandler<ProcessDataReceivedEventArgs>((sender, args) =>
|
||||
{
|
||||
if (!string.IsNullOrEmpty(args.Data))
|
||||
{
|
||||
_hc.GetTrace().Info(args.Data);
|
||||
}
|
||||
});
|
||||
|
||||
processInvoker.ErrorDataReceived += new EventHandler<ProcessDataReceivedEventArgs>((sender, args) =>
|
||||
{
|
||||
if (!string.IsNullOrEmpty(args.Data))
|
||||
{
|
||||
_hc.GetTrace().Error(args.Data);
|
||||
}
|
||||
});
|
||||
|
||||
string cwd = Path.GetDirectoryName(Path.Combine(_hc.GetDirectory(WellKnownDirectory.Temp), "actions-download-artifact"));
|
||||
string inputDirectory = Path.GetFileName(Path.Combine(_hc.GetDirectory(WellKnownDirectory.Temp), "actions-download-artifact"));
|
||||
string archiveFile = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Temp), "action_cache", "actions_download-artifact", "master-sha.tar.gz");
|
||||
int exitCode = await processInvoker.ExecuteAsync(_hc.GetDirectory(WellKnownDirectory.Bin), tar, $"-czf \"{archiveFile}\" -C \"{cwd}\" \"{inputDirectory}\"", null, CancellationToken.None);
|
||||
if (exitCode != 0)
|
||||
{
|
||||
throw new NotSupportedException($"Can't use 'tar -czf' to create archive file: {archiveFile}. return code: {exitCode}.");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
var actionId = Guid.NewGuid();
|
||||
var actions = new List<Pipelines.ActionStep>
|
||||
{
|
||||
new Pipelines.ActionStep()
|
||||
{
|
||||
Name = "action",
|
||||
Id = actionId,
|
||||
Reference = new Pipelines.RepositoryPathReference()
|
||||
{
|
||||
Name = "actions/download-artifact",
|
||||
Ref = "master",
|
||||
RepositoryType = "GitHub"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_ec.Object.Global.Variables.Set("DistributedTask.UseActionArchiveCache", bool.TrueString);
|
||||
|
||||
//Act
|
||||
await _actionManager.PrepareActionsAsync(_ec.Object, actions);
|
||||
|
||||
//Assert
|
||||
var watermarkFile = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), "actions/download-artifact", "master.completed");
|
||||
Assert.True(File.Exists(watermarkFile));
|
||||
|
||||
var actionYamlFile = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), "actions/download-artifact", "master", "action.yml");
|
||||
Assert.True(File.Exists(actionYamlFile));
|
||||
|
||||
_hc.GetTrace().Info(File.ReadAllText(actionYamlFile));
|
||||
|
||||
Assert.Contains("1ae80bcb-c1df-4362-bdaa-54f729c60281", File.ReadAllText(actionYamlFile));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Environment.SetEnvironmentVariable(Constants.Variables.Agent.ActionArchiveCacheDirectory, null);
|
||||
Teardown();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
@@ -1593,74 +1424,6 @@ runs:
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void LoadsNode20ActionDefinition()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Arrange.
|
||||
Setup();
|
||||
const string Content = @"
|
||||
# Container action
|
||||
name: 'Hello World'
|
||||
description: 'Greet the world and record the time'
|
||||
author: 'GitHub'
|
||||
inputs:
|
||||
greeting: # id of input
|
||||
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
|
||||
required: true
|
||||
default: 'Hello'
|
||||
entryPoint: # id of input
|
||||
description: 'optional docker entrypoint overwrite.'
|
||||
required: false
|
||||
outputs:
|
||||
time: # id of output
|
||||
description: 'The time we did the greeting'
|
||||
icon: 'hello.svg' # vector art to display in the GitHub Marketplace
|
||||
color: 'green' # optional, decorates the entry in the GitHub Marketplace
|
||||
runs:
|
||||
using: 'node20'
|
||||
main: 'task.js'
|
||||
";
|
||||
Pipelines.ActionStep instance;
|
||||
string directory;
|
||||
CreateAction(yamlContent: Content, instance: out instance, directory: out directory);
|
||||
|
||||
// Act.
|
||||
Definition definition = _actionManager.LoadAction(_ec.Object, instance);
|
||||
|
||||
// Assert.
|
||||
Assert.NotNull(definition);
|
||||
Assert.Equal(directory, definition.Directory);
|
||||
Assert.NotNull(definition.Data);
|
||||
Assert.NotNull(definition.Data.Inputs); // inputs
|
||||
Dictionary<string, string> inputDefaults = new(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var input in definition.Data.Inputs)
|
||||
{
|
||||
var name = input.Key.AssertString("key").Value;
|
||||
var value = input.Value.AssertScalar("value").ToString();
|
||||
|
||||
_hc.GetTrace().Info($"Default: {name} = {value}");
|
||||
inputDefaults[name] = value;
|
||||
}
|
||||
|
||||
Assert.Equal(2, inputDefaults.Count);
|
||||
Assert.True(inputDefaults.ContainsKey("greeting"));
|
||||
Assert.Equal("Hello", inputDefaults["greeting"]);
|
||||
Assert.True(string.IsNullOrEmpty(inputDefaults["entryPoint"]));
|
||||
Assert.NotNull(definition.Data.Execution); // execution
|
||||
|
||||
Assert.NotNull(definition.Data.Execution as NodeJSActionExecutionData);
|
||||
Assert.Equal("task.js", (definition.Data.Execution as NodeJSActionExecutionData).Script);
|
||||
Assert.Equal("node20", (definition.Data.Execution as NodeJSActionExecutionData).NodeVersion);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Teardown();
|
||||
}
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
@@ -2384,7 +2147,6 @@ runs:
|
||||
_ec.Setup(x => x.ExpressionFunctions).Returns(new List<IFunctionInfo>());
|
||||
_ec.Object.Global.FileTable = new List<String>();
|
||||
_ec.Object.Global.Plan = new TaskOrchestrationPlanReference();
|
||||
_ec.Object.Global.JobTelemetry = new List<JobTelemetry>();
|
||||
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { _hc.GetTrace().Info($"[{tag}]{message}"); });
|
||||
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<ExecutionContextLogOptions>())).Callback((Issue issue, ExecutionContextLogOptions logOptions) => { _hc.GetTrace().Info($"[{issue.Type}]{logOptions.LogMessageOverride ?? issue.Message}"); });
|
||||
_ec.Setup(x => x.GetGitHubContext("workspace")).Returns(Path.Combine(_workFolder, "actions", "actions"));
|
||||
@@ -2407,8 +2169,6 @@ runs:
|
||||
{
|
||||
NameWithOwner = action.NameWithOwner,
|
||||
Ref = action.Ref,
|
||||
ResolvedNameWithOwner = action.NameWithOwner,
|
||||
ResolvedSha = $"{action.Ref}-sha",
|
||||
TarballUrl = $"https://api.github.com/repos/{action.NameWithOwner}/tarball/{action.Ref}",
|
||||
ZipballUrl = $"https://api.github.com/repos/{action.NameWithOwner}/zipball/{action.Ref}",
|
||||
};
|
||||
@@ -2428,8 +2188,6 @@ runs:
|
||||
{
|
||||
NameWithOwner = action.NameWithOwner,
|
||||
Ref = action.Ref,
|
||||
ResolvedNameWithOwner = action.NameWithOwner,
|
||||
ResolvedSha = $"{action.Ref}-sha",
|
||||
TarballUrl = $"https://api.github.com/repos/{action.NameWithOwner}/tarball/{action.Ref}",
|
||||
ZipballUrl = $"https://api.github.com/repos/{action.NameWithOwner}/zipball/{action.Ref}",
|
||||
};
|
||||
|
||||
@@ -459,49 +459,6 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void Load_Node20Action()
|
||||
{
|
||||
try
|
||||
{
|
||||
//Arrange
|
||||
Setup();
|
||||
|
||||
var actionManifest = new ActionManifestManager();
|
||||
actionManifest.Initialize(_hc);
|
||||
|
||||
//Act
|
||||
var result = actionManifest.Load(_ec.Object, Path.Combine(TestUtil.GetTestDataPath(), "node20action.yml"));
|
||||
|
||||
//Assert
|
||||
Assert.Equal("Hello World", result.Name);
|
||||
Assert.Equal("Greet the world and record the time", result.Description);
|
||||
Assert.Equal(2, result.Inputs.Count);
|
||||
Assert.Equal("greeting", result.Inputs[0].Key.AssertString("key").Value);
|
||||
Assert.Equal("Hello", result.Inputs[0].Value.AssertString("value").Value);
|
||||
Assert.Equal("entryPoint", result.Inputs[1].Key.AssertString("key").Value);
|
||||
Assert.Equal("", result.Inputs[1].Value.AssertString("value").Value);
|
||||
Assert.Equal(1, result.Deprecated.Count);
|
||||
|
||||
Assert.True(result.Deprecated.ContainsKey("greeting"));
|
||||
result.Deprecated.TryGetValue("greeting", out string value);
|
||||
Assert.Equal("This property has been deprecated", value);
|
||||
|
||||
Assert.Equal(ActionExecutionType.NodeJS, result.Execution.ExecutionType);
|
||||
|
||||
var nodeAction = result.Execution as NodeJSActionExecutionData;
|
||||
|
||||
Assert.Equal("main.js", nodeAction.Script);
|
||||
Assert.Equal("node20", nodeAction.NodeVersion);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Teardown();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
@@ -758,7 +715,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
//Assert
|
||||
var err = Assert.Throws<ArgumentException>(() => actionManifest.Load(_ec.Object, action_path));
|
||||
Assert.Contains($"Fail to load {action_path}", err.Message);
|
||||
_ec.Verify(x => x.AddIssue(It.Is<Issue>(s => s.Message.Contains("Missing 'using' value. 'using' requires 'composite', 'docker', 'node12', 'node16' or 'node20'.")), It.IsAny<ExecutionContextLogOptions>()), Times.Once);
|
||||
_ec.Verify(x => x.AddIssue(It.Is<Issue>(s => s.Message.Contains("Missing 'using' value. 'using' requires 'composite', 'docker', 'node12' or 'node16'.")), It.IsAny<ExecutionContextLogOptions>()), Times.Once);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -124,7 +124,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
"",
|
||||
"## This is more markdown content",
|
||||
};
|
||||
WriteContent(stepSummaryFile, content);
|
||||
TestUtil.WriteContent(stepSummaryFile, content);
|
||||
|
||||
_createStepCommand.ProcessCommand(_executionContext.Object, stepSummaryFile, null);
|
||||
_jobExecutionContext.Complete();
|
||||
@@ -153,7 +153,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
"",
|
||||
"# GITHUB_TOKEN ghs_verysecuretoken",
|
||||
};
|
||||
WriteContent(stepSummaryFile, content);
|
||||
TestUtil.WriteContent(stepSummaryFile, content);
|
||||
|
||||
_createStepCommand.ProcessCommand(_executionContext.Object, stepSummaryFile, null);
|
||||
|
||||
@@ -167,21 +167,6 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteContent(
|
||||
string path,
|
||||
List<string> content,
|
||||
string newline = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(newline))
|
||||
{
|
||||
newline = Environment.NewLine;
|
||||
}
|
||||
|
||||
var encoding = new UTF8Encoding(true); // Emit BOM
|
||||
var contentStr = string.Join(newline, content);
|
||||
File.WriteAllText(path, contentStr, encoding);
|
||||
}
|
||||
|
||||
private TestHostContext Setup([CallerMemberName] string name = "")
|
||||
{
|
||||
var hostContext = new TestHostContext(this, name);
|
||||
|
||||
431
src/Test/L0/Worker/FileCommandTestBase.cs
Normal file
431
src/Test/L0/Worker/FileCommandTestBase.cs
Normal file
@@ -0,0 +1,431 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using GitHub.Runner.Common.Util;
|
||||
using GitHub.Runner.Sdk;
|
||||
using GitHub.Runner.Worker;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
using DTWebApi = GitHub.DistributedTask.WebApi;
|
||||
|
||||
namespace GitHub.Runner.Common.Tests.Worker
|
||||
{
|
||||
public abstract class FileCommandTestBase<T>
|
||||
where T : IFileCommandExtension, new()
|
||||
{
|
||||
|
||||
protected void TestDirectoryNotFound()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "directory-not-found", "env");
|
||||
_fileCmdExtension.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(0, _store.Count);
|
||||
}
|
||||
}
|
||||
|
||||
protected void TestNotFound()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "file-not-found");
|
||||
_fileCmdExtension.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(0, _store.Count);
|
||||
}
|
||||
}
|
||||
|
||||
protected void TestEmptyFile()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "empty-file");
|
||||
var content = new List<string>();
|
||||
TestUtil.WriteContent(stateFile, content);
|
||||
_fileCmdExtension.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(0, _store.Count);
|
||||
}
|
||||
}
|
||||
|
||||
protected void TestSimple()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "simple");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_KEY=MY VALUE",
|
||||
};
|
||||
TestUtil.WriteContent(stateFile, content);
|
||||
_fileCmdExtension.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(1, _store.Count);
|
||||
Assert.Equal("MY VALUE", _store["MY_KEY"]);
|
||||
}
|
||||
}
|
||||
|
||||
protected void TestSimple_SkipEmptyLines()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "simple");
|
||||
var content = new List<string>
|
||||
{
|
||||
string.Empty,
|
||||
"MY_KEY=my value",
|
||||
string.Empty,
|
||||
"MY_KEY_2=my second value",
|
||||
string.Empty,
|
||||
};
|
||||
TestUtil.WriteContent(stateFile, content);
|
||||
_fileCmdExtension.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(2, _store.Count);
|
||||
Assert.Equal("my value", _store["MY_KEY"]);
|
||||
Assert.Equal("my second value", _store["MY_KEY_2"]);
|
||||
}
|
||||
}
|
||||
|
||||
protected void TestSimple_EmptyValue()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "simple-empty-value");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_KEY=",
|
||||
};
|
||||
TestUtil.WriteContent(stateFile, content);
|
||||
_fileCmdExtension.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(1, _store.Count);
|
||||
Assert.Equal(string.Empty, _store["MY_KEY"]);
|
||||
}
|
||||
}
|
||||
|
||||
protected void TestSimple_MultipleValues()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "simple");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_KEY=my value",
|
||||
"MY_KEY_2=",
|
||||
"MY_KEY_3=my third value",
|
||||
};
|
||||
TestUtil.WriteContent(stateFile, content);
|
||||
_fileCmdExtension.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(3, _store.Count);
|
||||
Assert.Equal("my value", _store["MY_KEY"]);
|
||||
Assert.Equal(string.Empty, _store["MY_KEY_2"]);
|
||||
Assert.Equal("my third value", _store["MY_KEY_3"]);
|
||||
}
|
||||
}
|
||||
|
||||
protected void TestSimple_SpecialCharacters()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "simple");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_KEY==abc",
|
||||
"MY_KEY_2=def=ghi",
|
||||
"MY_KEY_3=jkl=",
|
||||
};
|
||||
TestUtil.WriteContent(stateFile, content);
|
||||
_fileCmdExtension.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(3, _store.Count);
|
||||
Assert.Equal("=abc", _store["MY_KEY"]);
|
||||
Assert.Equal("def=ghi", _store["MY_KEY_2"]);
|
||||
Assert.Equal("jkl=", _store["MY_KEY_3"]);
|
||||
}
|
||||
}
|
||||
|
||||
protected void TestHeredoc()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_KEY<<EOF",
|
||||
"line one",
|
||||
"line two",
|
||||
"line three",
|
||||
"EOF",
|
||||
};
|
||||
TestUtil.WriteContent(stateFile, content);
|
||||
_fileCmdExtension.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(1, _store.Count);
|
||||
Assert.Equal($"line one{BREAK}line two{BREAK}line three", _store["MY_KEY"]);
|
||||
}
|
||||
}
|
||||
|
||||
protected void TestHeredoc_EmptyValue()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_KEY<<EOF",
|
||||
"EOF",
|
||||
};
|
||||
TestUtil.WriteContent(stateFile, content);
|
||||
_fileCmdExtension.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(1, _store.Count);
|
||||
Assert.Equal(string.Empty, _store["MY_KEY"]);
|
||||
}
|
||||
}
|
||||
|
||||
protected void TestHeredoc_SkipEmptyLines()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
string.Empty,
|
||||
"MY_KEY<<EOF",
|
||||
"hello",
|
||||
"world",
|
||||
"EOF",
|
||||
string.Empty,
|
||||
"MY_KEY_2<<EOF",
|
||||
"HELLO",
|
||||
"AGAIN",
|
||||
"EOF",
|
||||
string.Empty,
|
||||
};
|
||||
TestUtil.WriteContent(stateFile, content);
|
||||
_fileCmdExtension.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(2, _store.Count);
|
||||
Assert.Equal($"hello{BREAK}world", _store["MY_KEY"]);
|
||||
Assert.Equal($"HELLO{BREAK}AGAIN", _store["MY_KEY_2"]);
|
||||
}
|
||||
}
|
||||
|
||||
protected void TestHeredoc_EdgeCases()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_KEY_1<<EOF",
|
||||
"hello",
|
||||
string.Empty,
|
||||
"three",
|
||||
string.Empty,
|
||||
"EOF",
|
||||
"MY_KEY_2<<EOF",
|
||||
"hello=two",
|
||||
"EOF",
|
||||
"MY_KEY_3<<EOF",
|
||||
" EOF",
|
||||
"EOF",
|
||||
"MY_KEY_4<<EOF",
|
||||
"EOF EOF",
|
||||
"EOF",
|
||||
"MY_KEY_5=abc << def",
|
||||
"MY_KEY_6= <<EOF",
|
||||
"white space test",
|
||||
"EOF",
|
||||
"MY_KEY_7 <<=EOF=",
|
||||
"abc",
|
||||
"=EOF=",
|
||||
string.Empty
|
||||
};
|
||||
TestUtil.WriteContent(stateFile, content);
|
||||
_fileCmdExtension.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(7, _store.Count);
|
||||
Assert.Equal($"hello{BREAK}{BREAK}three{BREAK}", _store["MY_KEY_1"]);
|
||||
Assert.Equal($"hello=two", _store["MY_KEY_2"]);
|
||||
Assert.Equal($" EOF", _store["MY_KEY_3"]);
|
||||
Assert.Equal($"EOF EOF", _store["MY_KEY_4"]);
|
||||
Assert.Equal($"abc << def", _store["MY_KEY_5"]);
|
||||
Assert.Equal($"white space test", _store["MY_KEY_6"]);
|
||||
Assert.Equal($"abc", _store["MY_KEY_7"]);
|
||||
}
|
||||
}
|
||||
|
||||
protected void TestHeredoc_EndMarkerVariations(string validEndMarker)
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
string eof = validEndMarker;
|
||||
var content = new List<string>
|
||||
{
|
||||
$"MY_KEY_1<<{eof}",
|
||||
$"hello",
|
||||
$"one",
|
||||
$"{eof}",
|
||||
$"MY_KEY_2<<{eof}",
|
||||
$"hello=two",
|
||||
$"{eof}",
|
||||
$"MY_KEY_3<<{eof}",
|
||||
$" {eof}",
|
||||
$"{eof}",
|
||||
$"MY_KEY_4<<{eof}",
|
||||
$"{eof} {eof}",
|
||||
$"{eof}",
|
||||
};
|
||||
TestUtil.WriteContent(stateFile, content);
|
||||
_fileCmdExtension.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(4, _store.Count);
|
||||
Assert.Equal($"hello{BREAK}one", _store["MY_KEY_1"]);
|
||||
Assert.Equal($"hello=two", _store["MY_KEY_2"]);
|
||||
Assert.Equal($" {eof}", _store["MY_KEY_3"]);
|
||||
Assert.Equal($"{eof} {eof}", _store["MY_KEY_4"]);
|
||||
}
|
||||
}
|
||||
|
||||
protected void TestHeredoc_EqualBeforeMultilineIndicator()
|
||||
{
|
||||
using var hostContext = Setup();
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
|
||||
// Define a hypothetical injectable payload that just happens to contain the '=' character.
|
||||
string contrivedGitHubIssueTitle = "Issue 999: Better handling for the `=` character";
|
||||
|
||||
// The docs recommend using randomly-generated EOF markers.
|
||||
// Here's a randomly-generated base64 EOF marker that just happens to contain an '=' character. ('=' is a padding character in base64.)
|
||||
// see https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#multiline-strings
|
||||
string randomizedEOF = "khkIhPxsVA==";
|
||||
var content = new List<string>
|
||||
{
|
||||
// In a real world scenario, "%INJECT%" might instead be something like "${{ github.event.issue.title }}"
|
||||
$"PREFIX_%INJECT%<<{randomizedEOF}".Replace("%INJECT%", contrivedGitHubIssueTitle),
|
||||
"RandomDataThatJustHappensToContainAnEquals=Character",
|
||||
randomizedEOF,
|
||||
};
|
||||
TestUtil.WriteContent(stateFile, content);
|
||||
var ex = Assert.Throws<Exception>(() => _fileCmdExtension.ProcessCommand(_executionContext.Object, stateFile, null));
|
||||
Assert.StartsWith("Invalid format", ex.Message);
|
||||
}
|
||||
|
||||
protected void TestHeredoc_MissingNewLine()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
string content = "MY_KEY<<EOF line one line two line three EOF";
|
||||
TestUtil.WriteContent(stateFile, content);
|
||||
var ex = Assert.Throws<Exception>(() => _fileCmdExtension.ProcessCommand(_executionContext.Object, stateFile, null));
|
||||
Assert.Contains("Matching delimiter not found", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
protected void TestHeredoc_MissingNewLineMultipleLines()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
string multilineFragment = @"line one
|
||||
line two
|
||||
line three";
|
||||
|
||||
// Note that the final EOF does not appear on it's own line.
|
||||
string content = $"MY_KEY<<EOF {multilineFragment} EOF";
|
||||
TestUtil.WriteContent(stateFile, content);
|
||||
var ex = Assert.Throws<Exception>(() => _fileCmdExtension.ProcessCommand(_executionContext.Object, stateFile, null));
|
||||
Assert.Contains("EOF marker missing new line", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
protected void TestHeredoc_PreservesNewline()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var newline = "\n";
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_KEY<<EOF",
|
||||
"hello",
|
||||
"world",
|
||||
"EOF",
|
||||
};
|
||||
TestUtil.WriteContent(stateFile, content, LineEndingType.Linux);
|
||||
_fileCmdExtension.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(1, _store.Count);
|
||||
Assert.Equal($"hello{newline}world", _store["MY_KEY"]);
|
||||
}
|
||||
}
|
||||
|
||||
protected TestHostContext Setup([CallerMemberName] string name = "")
|
||||
{
|
||||
_issues = new List<Tuple<DTWebApi.Issue, string>>();
|
||||
|
||||
var hostContext = new TestHostContext(this, name);
|
||||
|
||||
// Trace
|
||||
_trace = hostContext.GetTrace();
|
||||
|
||||
// Directory for test data
|
||||
var workDirectory = hostContext.GetDirectory(WellKnownDirectory.Work);
|
||||
ArgUtil.NotNullOrEmpty(workDirectory, nameof(workDirectory));
|
||||
Directory.CreateDirectory(workDirectory);
|
||||
_rootDirectory = Path.Combine(workDirectory, nameof(T));
|
||||
Directory.CreateDirectory(_rootDirectory);
|
||||
|
||||
// Execution context
|
||||
_executionContext = new Mock<IExecutionContext>();
|
||||
_executionContext.Setup(x => x.Global)
|
||||
.Returns(new GlobalContext
|
||||
{
|
||||
EnvironmentVariables = new Dictionary<string, string>(VarUtil.EnvironmentVariableKeyComparer),
|
||||
WriteDebug = true,
|
||||
});
|
||||
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.Issue>(), It.IsAny<ExecutionContextLogOptions>()))
|
||||
.Callback((DTWebApi.Issue issue, ExecutionContextLogOptions logOptions) =>
|
||||
{
|
||||
var resolvedMessage = issue.Message;
|
||||
if (logOptions.WriteToLog && !string.IsNullOrEmpty(logOptions.LogMessageOverride))
|
||||
{
|
||||
resolvedMessage = logOptions.LogMessageOverride;
|
||||
}
|
||||
_issues.Add(new(issue, resolvedMessage));
|
||||
_trace.Info($"Issue '{issue.Type}': {resolvedMessage}");
|
||||
});
|
||||
_executionContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()))
|
||||
.Callback((string tag, string message) =>
|
||||
{
|
||||
_trace.Info($"{tag}{message}");
|
||||
});
|
||||
|
||||
_store = PostSetup();
|
||||
|
||||
_fileCmdExtension = new T();
|
||||
_fileCmdExtension.Initialize(hostContext);
|
||||
|
||||
return hostContext;
|
||||
}
|
||||
|
||||
protected abstract IDictionary<string, string> PostSetup();
|
||||
|
||||
protected static readonly string BREAK = Environment.NewLine;
|
||||
|
||||
protected IFileCommandExtension _fileCmdExtension { get; private set; }
|
||||
protected Mock<IExecutionContext> _executionContext { get; private set; }
|
||||
protected List<Tuple<DTWebApi.Issue, string>> _issues { get; private set; }
|
||||
protected IDictionary<string, string> _store { get; private set; }
|
||||
protected string _rootDirectory { get; private set; }
|
||||
protected ITraceWriter _trace { get; private set; }
|
||||
}
|
||||
}
|
||||
@@ -30,10 +30,19 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Theory]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
[InlineData("node12", "node16")]
|
||||
[InlineData("node16", "node16")]
|
||||
[InlineData("node20", "node20")]
|
||||
public void IsNodeVersionUpgraded(string inputVersion, string expectedVersion)
|
||||
[InlineData("node12", "", "", "", "node12")]
|
||||
[InlineData("node12", "true", "", "", "node16")]
|
||||
[InlineData("node12", "true", "", "true", "node12")]
|
||||
[InlineData("node12", "true", "true", "", "node12")]
|
||||
[InlineData("node12", "true", "true", "true", "node12")]
|
||||
[InlineData("node12", "true", "false", "true", "node16")] // workflow overrides env
|
||||
[InlineData("node16", "", "", "", "node16")]
|
||||
[InlineData("node16", "true", "", "", "node16")]
|
||||
[InlineData("node16", "true", "", "true", "node16")]
|
||||
[InlineData("node16", "true", "true", "", "node16")]
|
||||
[InlineData("node16", "true", "true", "true", "node16")]
|
||||
[InlineData("node16", "true", "false", "true", "node16")]
|
||||
public void IsNodeVersionUpgraded(string inputVersion, string serverFeatureFlag, string workflowOptOut, string machineOptOut, string expectedVersion)
|
||||
{
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
@@ -43,10 +52,24 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
|
||||
// Server Feature Flag
|
||||
var variables = new Dictionary<string, VariableValue>();
|
||||
if (!string.IsNullOrEmpty(serverFeatureFlag))
|
||||
{
|
||||
variables["DistributedTask.ForceGithubJavascriptActionsToNode16"] = serverFeatureFlag;
|
||||
}
|
||||
Variables serverVariables = new(hc, variables);
|
||||
|
||||
// Workflow opt-out
|
||||
var workflowVariables = new Dictionary<string, string>();
|
||||
if (!string.IsNullOrEmpty(workflowOptOut))
|
||||
{
|
||||
workflowVariables[Constants.Variables.Actions.AllowActionsUseUnsecureNodeVersion] = workflowOptOut;
|
||||
}
|
||||
|
||||
// Machine opt-out
|
||||
if (!string.IsNullOrEmpty(machineOptOut))
|
||||
{
|
||||
Environment.SetEnvironmentVariable(Constants.Variables.Actions.AllowActionsUseUnsecureNodeVersion, machineOptOut);
|
||||
}
|
||||
|
||||
_ec.Setup(x => x.Global).Returns(new GlobalContext()
|
||||
{
|
||||
|
||||
@@ -1,44 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Runtime.CompilerServices;
|
||||
using GitHub.Runner.Common.Util;
|
||||
using GitHub.Runner.Sdk;
|
||||
using GitHub.Runner.Worker;
|
||||
using GitHub.Runner.Worker.Container;
|
||||
using GitHub.Runner.Worker.Handlers;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
using DTWebApi = GitHub.DistributedTask.WebApi;
|
||||
|
||||
namespace GitHub.Runner.Common.Tests.Worker
|
||||
{
|
||||
public sealed class SaveStateFileCommandL0
|
||||
public sealed class SaveStateFileCommandL0 : FileCommandTestBase<SaveStateFileCommand>
|
||||
{
|
||||
private Mock<IExecutionContext> _executionContext;
|
||||
private List<Tuple<DTWebApi.Issue, string>> _issues;
|
||||
private string _rootDirectory;
|
||||
private SaveStateFileCommand _saveStateFileCommand;
|
||||
private Dictionary<string, string> _intraActionState;
|
||||
private ITraceWriter _trace;
|
||||
|
||||
protected override IDictionary<string, string> PostSetup()
|
||||
{
|
||||
var intraActionState = new Dictionary<string, string>();
|
||||
_executionContext.Setup(x => x.IntraActionState).Returns(intraActionState);
|
||||
return intraActionState;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void SaveStateFileCommand_DirectoryNotFound()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "directory-not-found", "env");
|
||||
_saveStateFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(0, _intraActionState.Count);
|
||||
}
|
||||
base.TestDirectoryNotFound();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -46,13 +29,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SaveStateFileCommand_NotFound()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "file-not-found");
|
||||
_saveStateFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(0, _intraActionState.Count);
|
||||
}
|
||||
base.TestNotFound();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -60,15 +37,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SaveStateFileCommand_EmptyFile()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "empty-file");
|
||||
var content = new List<string>();
|
||||
WriteContent(stateFile, content);
|
||||
_saveStateFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(0, _intraActionState.Count);
|
||||
}
|
||||
base.TestEmptyFile();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -76,19 +45,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SaveStateFileCommand_Simple()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "simple");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_STATE=MY VALUE",
|
||||
};
|
||||
WriteContent(stateFile, content);
|
||||
_saveStateFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(1, _intraActionState.Count);
|
||||
Assert.Equal("MY VALUE", _intraActionState["MY_STATE"]);
|
||||
}
|
||||
base.TestSimple();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -96,24 +53,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SaveStateFileCommand_Simple_SkipEmptyLines()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "simple");
|
||||
var content = new List<string>
|
||||
{
|
||||
string.Empty,
|
||||
"MY_STATE=my value",
|
||||
string.Empty,
|
||||
"MY_STATE_2=my second value",
|
||||
string.Empty,
|
||||
};
|
||||
WriteContent(stateFile, content);
|
||||
_saveStateFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(2, _intraActionState.Count);
|
||||
Assert.Equal("my value", _intraActionState["MY_STATE"]);
|
||||
Assert.Equal("my second value", _intraActionState["MY_STATE_2"]);
|
||||
}
|
||||
base.TestSimple_SkipEmptyLines();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -121,19 +61,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SaveStateFileCommand_Simple_EmptyValue()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "simple-empty-value");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_STATE=",
|
||||
};
|
||||
WriteContent(stateFile, content);
|
||||
_saveStateFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(1, _intraActionState.Count);
|
||||
Assert.Equal(string.Empty, _intraActionState["MY_STATE"]);
|
||||
}
|
||||
base.TestSimple_EmptyValue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -141,23 +69,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SaveStateFileCommand_Simple_MultipleValues()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "simple");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_STATE=my value",
|
||||
"MY_STATE_2=",
|
||||
"MY_STATE_3=my third value",
|
||||
};
|
||||
WriteContent(stateFile, content);
|
||||
_saveStateFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(3, _intraActionState.Count);
|
||||
Assert.Equal("my value", _intraActionState["MY_STATE"]);
|
||||
Assert.Equal(string.Empty, _intraActionState["MY_STATE_2"]);
|
||||
Assert.Equal("my third value", _intraActionState["MY_STATE_3"]);
|
||||
}
|
||||
base.TestSimple_MultipleValues();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -165,23 +77,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SaveStateFileCommand_Simple_SpecialCharacters()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "simple");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_STATE==abc",
|
||||
"MY_STATE_2=def=ghi",
|
||||
"MY_STATE_3=jkl=",
|
||||
};
|
||||
WriteContent(stateFile, content);
|
||||
_saveStateFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(3, _intraActionState.Count);
|
||||
Assert.Equal("=abc", _intraActionState["MY_STATE"]);
|
||||
Assert.Equal("def=ghi", _intraActionState["MY_STATE_2"]);
|
||||
Assert.Equal("jkl=", _intraActionState["MY_STATE_3"]);
|
||||
}
|
||||
base.TestSimple_SpecialCharacters();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -189,23 +85,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SaveStateFileCommand_Heredoc()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_STATE<<EOF",
|
||||
"line one",
|
||||
"line two",
|
||||
"line three",
|
||||
"EOF",
|
||||
};
|
||||
WriteContent(stateFile, content);
|
||||
_saveStateFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(1, _intraActionState.Count);
|
||||
Assert.Equal($"line one{Environment.NewLine}line two{Environment.NewLine}line three", _intraActionState["MY_STATE"]);
|
||||
}
|
||||
base.TestHeredoc();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -213,20 +93,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SaveStateFileCommand_Heredoc_EmptyValue()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_STATE<<EOF",
|
||||
"EOF",
|
||||
};
|
||||
WriteContent(stateFile, content);
|
||||
_saveStateFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(1, _intraActionState.Count);
|
||||
Assert.Equal(string.Empty, _intraActionState["MY_STATE"]);
|
||||
}
|
||||
base.TestHeredoc_EmptyValue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -234,73 +101,52 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SaveStateFileCommand_Heredoc_SkipEmptyLines()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
string.Empty,
|
||||
"MY_STATE<<EOF",
|
||||
"hello",
|
||||
"world",
|
||||
"EOF",
|
||||
string.Empty,
|
||||
"MY_STATE_2<<EOF",
|
||||
"HELLO",
|
||||
"AGAIN",
|
||||
"EOF",
|
||||
string.Empty,
|
||||
};
|
||||
WriteContent(stateFile, content);
|
||||
_saveStateFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(2, _intraActionState.Count);
|
||||
Assert.Equal($"hello{Environment.NewLine}world", _intraActionState["MY_STATE"]);
|
||||
Assert.Equal($"HELLO{Environment.NewLine}AGAIN", _intraActionState["MY_STATE_2"]);
|
||||
}
|
||||
base.TestHeredoc_SkipEmptyLines();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void SaveStateFileCommand_Heredoc_SpecialCharacters()
|
||||
public void SaveStateFileCommand_Heredoc_EdgeCases()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_STATE<<=EOF",
|
||||
"hello",
|
||||
"one",
|
||||
"=EOF",
|
||||
"MY_STATE_2<<<EOF",
|
||||
"hello",
|
||||
"two",
|
||||
"<EOF",
|
||||
"MY_STATE_3<<EOF",
|
||||
"hello",
|
||||
string.Empty,
|
||||
"three",
|
||||
string.Empty,
|
||||
"EOF",
|
||||
"MY_STATE_4<<EOF",
|
||||
"hello=four",
|
||||
"EOF",
|
||||
"MY_STATE_5<<EOF",
|
||||
" EOF",
|
||||
"EOF",
|
||||
};
|
||||
WriteContent(stateFile, content);
|
||||
_saveStateFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(5, _intraActionState.Count);
|
||||
Assert.Equal($"hello{Environment.NewLine}one", _intraActionState["MY_STATE"]);
|
||||
Assert.Equal($"hello{Environment.NewLine}two", _intraActionState["MY_STATE_2"]);
|
||||
Assert.Equal($"hello{Environment.NewLine}{Environment.NewLine}three{Environment.NewLine}", _intraActionState["MY_STATE_3"]);
|
||||
Assert.Equal($"hello=four", _intraActionState["MY_STATE_4"]);
|
||||
Assert.Equal($" EOF", _intraActionState["MY_STATE_5"]);
|
||||
}
|
||||
base.TestHeredoc_EdgeCases();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
// All of the following are not only valid, but quite plausible end markers.
|
||||
// Most are derived straight from the example at https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#multiline-strings
|
||||
#pragma warning disable format
|
||||
[InlineData("=EOF")][InlineData("==EOF")][InlineData("EO=F")][InlineData("EO==F")][InlineData("EOF=")][InlineData("EOF==")]
|
||||
[InlineData("<EOF")][InlineData("<<EOF")][InlineData("EO<F")][InlineData("EO<<F")][InlineData("EOF<")][InlineData("EOF<<")]
|
||||
[InlineData("+EOF")][InlineData("++EOF")][InlineData("EO+F")][InlineData("EO++F")][InlineData("EOF+")][InlineData("EOF++")]
|
||||
[InlineData("/EOF")][InlineData("//EOF")][InlineData("EO/F")][InlineData("EO//F")][InlineData("EOF/")][InlineData("EOF//")]
|
||||
#pragma warning restore format
|
||||
[InlineData("<<//++==")]
|
||||
[InlineData("contrivedBase64==")]
|
||||
[InlineData("khkIhPxsVA==")]
|
||||
[InlineData("D+Y8zE/EOw==")]
|
||||
[InlineData("wuOWG4S6FQ==")]
|
||||
[InlineData("7wigCJ//iw==")]
|
||||
[InlineData("uifTuYTs8K4=")]
|
||||
[InlineData("M7N2ITg/04c=")]
|
||||
[InlineData("Xhh+qp+Y6iM=")]
|
||||
[InlineData("5tdblQajc/b+EGBZXo0w")]
|
||||
[InlineData("jk/UMjIx/N0eVcQYOUfw")]
|
||||
[InlineData("/n5lsw73Cwl35Hfuscdz")]
|
||||
[InlineData("ZvnAEW+9O0tXp3Fmb3Oh")]
|
||||
public void SaveStateFileCommand_Heredoc_EndMarkerVariations(string validEndMarker)
|
||||
{
|
||||
base.TestHeredoc_EndMarkerVariations(validEndMarker);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void SaveStateFileCommand_Heredoc_EqualBeforeMultilineIndicator()
|
||||
{
|
||||
base.TestHeredoc_EqualBeforeMultilineIndicator();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -308,21 +154,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SaveStateFileCommand_Heredoc_MissingNewLine()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_STATE<<EOF",
|
||||
"line one",
|
||||
"line two",
|
||||
"line three",
|
||||
"EOF",
|
||||
};
|
||||
WriteContent(stateFile, content, " ");
|
||||
var ex = Assert.Throws<Exception>(() => _saveStateFileCommand.ProcessCommand(_executionContext.Object, stateFile, null));
|
||||
Assert.Contains("Matching delimiter not found", ex.Message);
|
||||
}
|
||||
base.TestHeredoc_MissingNewLine();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -330,21 +162,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SaveStateFileCommand_Heredoc_MissingNewLineMultipleLines()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_STATE<<EOF",
|
||||
@"line one
|
||||
line two
|
||||
line three",
|
||||
"EOF",
|
||||
};
|
||||
WriteContent(stateFile, content, " ");
|
||||
var ex = Assert.Throws<Exception>(() => _saveStateFileCommand.ProcessCommand(_executionContext.Object, stateFile, null));
|
||||
Assert.Contains("EOF marker missing new line", ex.Message);
|
||||
}
|
||||
base.TestHeredoc_MissingNewLineMultipleLines();
|
||||
}
|
||||
|
||||
#if OS_WINDOWS
|
||||
@@ -353,90 +171,9 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SaveStateFileCommand_Heredoc_PreservesNewline()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var newline = "\n";
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_STATE<<EOF",
|
||||
"hello",
|
||||
"world",
|
||||
"EOF",
|
||||
};
|
||||
WriteContent(stateFile, content, newline: newline);
|
||||
_saveStateFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(1, _intraActionState.Count);
|
||||
Assert.Equal($"hello{newline}world", _intraActionState["MY_STATE"]);
|
||||
}
|
||||
base.TestHeredoc_PreservesNewline();
|
||||
}
|
||||
#endif
|
||||
|
||||
private void WriteContent(
|
||||
string path,
|
||||
List<string> content,
|
||||
string newline = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(newline))
|
||||
{
|
||||
newline = Environment.NewLine;
|
||||
}
|
||||
|
||||
var encoding = new UTF8Encoding(true); // Emit BOM
|
||||
var contentStr = string.Join(newline, content);
|
||||
File.WriteAllText(path, contentStr, encoding);
|
||||
}
|
||||
|
||||
private TestHostContext Setup([CallerMemberName] string name = "")
|
||||
{
|
||||
_issues = new List<Tuple<DTWebApi.Issue, string>>();
|
||||
_intraActionState = new Dictionary<string, string>();
|
||||
|
||||
var hostContext = new TestHostContext(this, name);
|
||||
|
||||
// Trace
|
||||
_trace = hostContext.GetTrace();
|
||||
|
||||
// Directory for test data
|
||||
var workDirectory = hostContext.GetDirectory(WellKnownDirectory.Work);
|
||||
ArgUtil.NotNullOrEmpty(workDirectory, nameof(workDirectory));
|
||||
Directory.CreateDirectory(workDirectory);
|
||||
_rootDirectory = Path.Combine(workDirectory, nameof(SaveStateFileCommandL0));
|
||||
Directory.CreateDirectory(_rootDirectory);
|
||||
|
||||
// Execution context
|
||||
_executionContext = new Mock<IExecutionContext>();
|
||||
_executionContext.Setup(x => x.Global)
|
||||
.Returns(new GlobalContext
|
||||
{
|
||||
EnvironmentVariables = new Dictionary<string, string>(VarUtil.EnvironmentVariableKeyComparer),
|
||||
WriteDebug = true,
|
||||
});
|
||||
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.Issue>(), It.IsAny<ExecutionContextLogOptions>()))
|
||||
.Callback((DTWebApi.Issue issue, ExecutionContextLogOptions logOptions) =>
|
||||
{
|
||||
var resolvedMessage = issue.Message;
|
||||
if (logOptions.WriteToLog && !string.IsNullOrEmpty(logOptions.LogMessageOverride))
|
||||
{
|
||||
resolvedMessage = logOptions.LogMessageOverride;
|
||||
}
|
||||
_issues.Add(new(issue, resolvedMessage));
|
||||
_trace.Info($"Issue '{issue.Type}': {resolvedMessage}");
|
||||
});
|
||||
_executionContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()))
|
||||
.Callback((string tag, string message) =>
|
||||
{
|
||||
_trace.Info($"{tag}{message}");
|
||||
});
|
||||
_executionContext.Setup(x => x.IntraActionState)
|
||||
.Returns(_intraActionState);
|
||||
|
||||
// SaveStateFileCommand
|
||||
_saveStateFileCommand = new SaveStateFileCommand();
|
||||
_saveStateFileCommand.Initialize(hostContext);
|
||||
|
||||
return hostContext;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Runtime.CompilerServices;
|
||||
using GitHub.Runner.Common.Util;
|
||||
using GitHub.Runner.Sdk;
|
||||
using GitHub.Runner.Worker;
|
||||
using GitHub.Runner.Worker.Container;
|
||||
using GitHub.Runner.Worker.Handlers;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
using DTWebApi = GitHub.DistributedTask.WebApi;
|
||||
|
||||
namespace GitHub.Runner.Common.Tests.Worker
|
||||
{
|
||||
public sealed class SetEnvFileCommandL0
|
||||
public sealed class SetEnvFileCommandL0 : FileCommandTestBase<SetEnvFileCommand>
|
||||
{
|
||||
private Mock<IExecutionContext> _executionContext;
|
||||
private List<Tuple<DTWebApi.Issue, string>> _issues;
|
||||
private string _rootDirectory;
|
||||
private SetEnvFileCommand _setEnvFileCommand;
|
||||
private ITraceWriter _trace;
|
||||
|
||||
protected override IDictionary<string, string> PostSetup()
|
||||
{
|
||||
return _executionContext.Object.Global.EnvironmentVariables;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetEnvFileCommand_DirectoryNotFound()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var envFile = Path.Combine(_rootDirectory, "directory-not-found", "env");
|
||||
_setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(0, _executionContext.Object.Global.EnvironmentVariables.Count);
|
||||
}
|
||||
base.TestDirectoryNotFound();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -45,13 +27,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetEnvFileCommand_NotFound()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var envFile = Path.Combine(_rootDirectory, "file-not-found");
|
||||
_setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(0, _executionContext.Object.Global.EnvironmentVariables.Count);
|
||||
}
|
||||
base.TestNotFound();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -59,15 +35,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetEnvFileCommand_EmptyFile()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var envFile = Path.Combine(_rootDirectory, "empty-file");
|
||||
var content = new List<string>();
|
||||
WriteContent(envFile, content);
|
||||
_setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(0, _executionContext.Object.Global.EnvironmentVariables.Count);
|
||||
}
|
||||
base.TestEmptyFile();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -75,19 +43,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetEnvFileCommand_Simple()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var envFile = Path.Combine(_rootDirectory, "simple");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_ENV=MY VALUE",
|
||||
};
|
||||
WriteContent(envFile, content);
|
||||
_setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(1, _executionContext.Object.Global.EnvironmentVariables.Count);
|
||||
Assert.Equal("MY VALUE", _executionContext.Object.Global.EnvironmentVariables["MY_ENV"]);
|
||||
}
|
||||
base.TestSimple();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -95,24 +51,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetEnvFileCommand_Simple_SkipEmptyLines()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var envFile = Path.Combine(_rootDirectory, "simple");
|
||||
var content = new List<string>
|
||||
{
|
||||
string.Empty,
|
||||
"MY_ENV=my value",
|
||||
string.Empty,
|
||||
"MY_ENV_2=my second value",
|
||||
string.Empty,
|
||||
};
|
||||
WriteContent(envFile, content);
|
||||
_setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(2, _executionContext.Object.Global.EnvironmentVariables.Count);
|
||||
Assert.Equal("my value", _executionContext.Object.Global.EnvironmentVariables["MY_ENV"]);
|
||||
Assert.Equal("my second value", _executionContext.Object.Global.EnvironmentVariables["MY_ENV_2"]);
|
||||
}
|
||||
base.TestSimple_SkipEmptyLines();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -120,19 +59,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetEnvFileCommand_Simple_EmptyValue()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var envFile = Path.Combine(_rootDirectory, "simple-empty-value");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_ENV=",
|
||||
};
|
||||
WriteContent(envFile, content);
|
||||
_setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(1, _executionContext.Object.Global.EnvironmentVariables.Count);
|
||||
Assert.Equal(string.Empty, _executionContext.Object.Global.EnvironmentVariables["MY_ENV"]);
|
||||
}
|
||||
base.TestSimple_EmptyValue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -140,23 +67,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetEnvFileCommand_Simple_MultipleValues()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var envFile = Path.Combine(_rootDirectory, "simple");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_ENV=my value",
|
||||
"MY_ENV_2=",
|
||||
"MY_ENV_3=my third value",
|
||||
};
|
||||
WriteContent(envFile, content);
|
||||
_setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(3, _executionContext.Object.Global.EnvironmentVariables.Count);
|
||||
Assert.Equal("my value", _executionContext.Object.Global.EnvironmentVariables["MY_ENV"]);
|
||||
Assert.Equal(string.Empty, _executionContext.Object.Global.EnvironmentVariables["MY_ENV_2"]);
|
||||
Assert.Equal("my third value", _executionContext.Object.Global.EnvironmentVariables["MY_ENV_3"]);
|
||||
}
|
||||
base.TestSimple_MultipleValues();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -164,64 +75,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetEnvFileCommand_Simple_SpecialCharacters()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var envFile = Path.Combine(_rootDirectory, "simple");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_ENV==abc",
|
||||
"MY_ENV_2=def=ghi",
|
||||
"MY_ENV_3=jkl=",
|
||||
};
|
||||
WriteContent(envFile, content);
|
||||
_setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(3, _executionContext.Object.Global.EnvironmentVariables.Count);
|
||||
Assert.Equal("=abc", _executionContext.Object.Global.EnvironmentVariables["MY_ENV"]);
|
||||
Assert.Equal("def=ghi", _executionContext.Object.Global.EnvironmentVariables["MY_ENV_2"]);
|
||||
Assert.Equal("jkl=", _executionContext.Object.Global.EnvironmentVariables["MY_ENV_3"]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetEnvFileCommand_BlockListItemsFiltered()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "simple");
|
||||
var content = new List<string>
|
||||
{
|
||||
"NODE_OPTIONS=asdf",
|
||||
};
|
||||
WriteContent(stateFile, content);
|
||||
_setEnvFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(1, _issues.Count);
|
||||
Assert.Equal(0, _executionContext.Object.Global.EnvironmentVariables.Count);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetEnvFileCommand_BlockListItemsFiltered_Heredoc()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "simple");
|
||||
var content = new List<string>
|
||||
{
|
||||
"NODE_OPTIONS<<EOF",
|
||||
"asdf",
|
||||
"EOF",
|
||||
};
|
||||
WriteContent(stateFile, content);
|
||||
_setEnvFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(1, _issues.Count);
|
||||
Assert.Equal(0, _executionContext.Object.Global.EnvironmentVariables.Count);
|
||||
}
|
||||
base.TestSimple_SpecialCharacters();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -229,23 +83,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetEnvFileCommand_Heredoc()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var envFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_ENV<<EOF",
|
||||
"line one",
|
||||
"line two",
|
||||
"line three",
|
||||
"EOF",
|
||||
};
|
||||
WriteContent(envFile, content);
|
||||
_setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(1, _executionContext.Object.Global.EnvironmentVariables.Count);
|
||||
Assert.Equal($"line one{Environment.NewLine}line two{Environment.NewLine}line three", _executionContext.Object.Global.EnvironmentVariables["MY_ENV"]);
|
||||
}
|
||||
base.TestHeredoc();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -253,20 +91,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetEnvFileCommand_Heredoc_EmptyValue()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var envFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_ENV<<EOF",
|
||||
"EOF",
|
||||
};
|
||||
WriteContent(envFile, content);
|
||||
_setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(1, _executionContext.Object.Global.EnvironmentVariables.Count);
|
||||
Assert.Equal(string.Empty, _executionContext.Object.Global.EnvironmentVariables["MY_ENV"]);
|
||||
}
|
||||
base.TestHeredoc_EmptyValue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -274,73 +99,52 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetEnvFileCommand_Heredoc_SkipEmptyLines()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var envFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
string.Empty,
|
||||
"MY_ENV<<EOF",
|
||||
"hello",
|
||||
"world",
|
||||
"EOF",
|
||||
string.Empty,
|
||||
"MY_ENV_2<<EOF",
|
||||
"HELLO",
|
||||
"AGAIN",
|
||||
"EOF",
|
||||
string.Empty,
|
||||
};
|
||||
WriteContent(envFile, content);
|
||||
_setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(2, _executionContext.Object.Global.EnvironmentVariables.Count);
|
||||
Assert.Equal($"hello{Environment.NewLine}world", _executionContext.Object.Global.EnvironmentVariables["MY_ENV"]);
|
||||
Assert.Equal($"HELLO{Environment.NewLine}AGAIN", _executionContext.Object.Global.EnvironmentVariables["MY_ENV_2"]);
|
||||
}
|
||||
base.TestHeredoc_SkipEmptyLines();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetEnvFileCommand_Heredoc_SpecialCharacters()
|
||||
public void SetEnvFileCommand_Heredoc_EdgeCases()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var envFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_ENV<<=EOF",
|
||||
"hello",
|
||||
"one",
|
||||
"=EOF",
|
||||
"MY_ENV_2<<<EOF",
|
||||
"hello",
|
||||
"two",
|
||||
"<EOF",
|
||||
"MY_ENV_3<<EOF",
|
||||
"hello",
|
||||
string.Empty,
|
||||
"three",
|
||||
string.Empty,
|
||||
"EOF",
|
||||
"MY_ENV_4<<EOF",
|
||||
"hello=four",
|
||||
"EOF",
|
||||
"MY_ENV_5<<EOF",
|
||||
" EOF",
|
||||
"EOF",
|
||||
};
|
||||
WriteContent(envFile, content);
|
||||
_setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(5, _executionContext.Object.Global.EnvironmentVariables.Count);
|
||||
Assert.Equal($"hello{Environment.NewLine}one", _executionContext.Object.Global.EnvironmentVariables["MY_ENV"]);
|
||||
Assert.Equal($"hello{Environment.NewLine}two", _executionContext.Object.Global.EnvironmentVariables["MY_ENV_2"]);
|
||||
Assert.Equal($"hello{Environment.NewLine}{Environment.NewLine}three{Environment.NewLine}", _executionContext.Object.Global.EnvironmentVariables["MY_ENV_3"]);
|
||||
Assert.Equal($"hello=four", _executionContext.Object.Global.EnvironmentVariables["MY_ENV_4"]);
|
||||
Assert.Equal($" EOF", _executionContext.Object.Global.EnvironmentVariables["MY_ENV_5"]);
|
||||
}
|
||||
base.TestHeredoc_EdgeCases();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
// All of the following are not only valid, but quite plausible end markers.
|
||||
// Most are derived straight from the example at https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#multiline-strings
|
||||
#pragma warning disable format
|
||||
[InlineData("=EOF")][InlineData("==EOF")][InlineData("EO=F")][InlineData("EO==F")][InlineData("EOF=")][InlineData("EOF==")]
|
||||
[InlineData("<EOF")][InlineData("<<EOF")][InlineData("EO<F")][InlineData("EO<<F")][InlineData("EOF<")][InlineData("EOF<<")]
|
||||
[InlineData("+EOF")][InlineData("++EOF")][InlineData("EO+F")][InlineData("EO++F")][InlineData("EOF+")][InlineData("EOF++")]
|
||||
[InlineData("/EOF")][InlineData("//EOF")][InlineData("EO/F")][InlineData("EO//F")][InlineData("EOF/")][InlineData("EOF//")]
|
||||
#pragma warning restore format
|
||||
[InlineData("<<//++==")]
|
||||
[InlineData("contrivedBase64==")]
|
||||
[InlineData("khkIhPxsVA==")]
|
||||
[InlineData("D+Y8zE/EOw==")]
|
||||
[InlineData("wuOWG4S6FQ==")]
|
||||
[InlineData("7wigCJ//iw==")]
|
||||
[InlineData("uifTuYTs8K4=")]
|
||||
[InlineData("M7N2ITg/04c=")]
|
||||
[InlineData("Xhh+qp+Y6iM=")]
|
||||
[InlineData("5tdblQajc/b+EGBZXo0w")]
|
||||
[InlineData("jk/UMjIx/N0eVcQYOUfw")]
|
||||
[InlineData("/n5lsw73Cwl35Hfuscdz")]
|
||||
[InlineData("ZvnAEW+9O0tXp3Fmb3Oh")]
|
||||
public void SetEnvFileCommand_Heredoc_EndMarkerVariations(string validEndMarker)
|
||||
{
|
||||
base.TestHeredoc_EndMarkerVariations(validEndMarker);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetEnvFileCommand_Heredoc_EqualBeforeMultilineIndicator()
|
||||
{
|
||||
base.TestHeredoc_EqualBeforeMultilineIndicator();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -348,43 +152,15 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetEnvFileCommand_Heredoc_MissingNewLine()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var envFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_ENV<<EOF",
|
||||
"line one",
|
||||
"line two",
|
||||
"line three",
|
||||
"EOF",
|
||||
};
|
||||
WriteContent(envFile, content, " ");
|
||||
var ex = Assert.Throws<Exception>(() => _setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null));
|
||||
Assert.Contains("Matching delimiter not found", ex.Message);
|
||||
}
|
||||
base.TestHeredoc_MissingNewLine();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetEnvFileCommand_Heredoc_MissingNewLineMultipleLinesEnv()
|
||||
public void SetEnvFileCommand_Heredoc_MissingNewLineMultipleLines()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var envFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_ENV<<EOF",
|
||||
@"line one
|
||||
line two
|
||||
line three",
|
||||
"EOF",
|
||||
};
|
||||
WriteContent(envFile, content, " ");
|
||||
var ex = Assert.Throws<Exception>(() => _setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null));
|
||||
Assert.Contains("EOF marker missing new line", ex.Message);
|
||||
}
|
||||
base.TestHeredoc_MissingNewLineMultipleLines();
|
||||
}
|
||||
|
||||
#if OS_WINDOWS
|
||||
@@ -393,87 +169,9 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetEnvFileCommand_Heredoc_PreservesNewline()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var newline = "\n";
|
||||
var envFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_ENV<<EOF",
|
||||
"hello",
|
||||
"world",
|
||||
"EOF",
|
||||
};
|
||||
WriteContent(envFile, content, newline: newline);
|
||||
_setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(1, _executionContext.Object.Global.EnvironmentVariables.Count);
|
||||
Assert.Equal($"hello{newline}world", _executionContext.Object.Global.EnvironmentVariables["MY_ENV"]);
|
||||
}
|
||||
base.TestHeredoc_PreservesNewline();
|
||||
}
|
||||
#endif
|
||||
|
||||
private void WriteContent(
|
||||
string path,
|
||||
List<string> content,
|
||||
string newline = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(newline))
|
||||
{
|
||||
newline = Environment.NewLine;
|
||||
}
|
||||
|
||||
var encoding = new UTF8Encoding(true); // Emit BOM
|
||||
var contentStr = string.Join(newline, content);
|
||||
File.WriteAllText(path, contentStr, encoding);
|
||||
}
|
||||
|
||||
private TestHostContext Setup([CallerMemberName] string name = "")
|
||||
{
|
||||
_issues = new List<Tuple<DTWebApi.Issue, string>>();
|
||||
|
||||
var hostContext = new TestHostContext(this, name);
|
||||
|
||||
// Trace
|
||||
_trace = hostContext.GetTrace();
|
||||
|
||||
// Directory for test data
|
||||
var workDirectory = hostContext.GetDirectory(WellKnownDirectory.Work);
|
||||
ArgUtil.NotNullOrEmpty(workDirectory, nameof(workDirectory));
|
||||
Directory.CreateDirectory(workDirectory);
|
||||
_rootDirectory = Path.Combine(workDirectory, nameof(SetEnvFileCommandL0));
|
||||
Directory.CreateDirectory(_rootDirectory);
|
||||
|
||||
// Execution context
|
||||
_executionContext = new Mock<IExecutionContext>();
|
||||
_executionContext.Setup(x => x.Global)
|
||||
.Returns(new GlobalContext
|
||||
{
|
||||
EnvironmentVariables = new Dictionary<string, string>(VarUtil.EnvironmentVariableKeyComparer),
|
||||
WriteDebug = true,
|
||||
});
|
||||
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.Issue>(), It.IsAny<ExecutionContextLogOptions>()))
|
||||
.Callback((DTWebApi.Issue issue, ExecutionContextLogOptions logOptions) =>
|
||||
{
|
||||
var resolvedMessage = issue.Message;
|
||||
if (logOptions.WriteToLog && !string.IsNullOrEmpty(logOptions.LogMessageOverride))
|
||||
{
|
||||
resolvedMessage = logOptions.LogMessageOverride;
|
||||
}
|
||||
_issues.Add(new(issue, resolvedMessage));
|
||||
_trace.Info($"Issue '{issue.Type}': {resolvedMessage}");
|
||||
});
|
||||
_executionContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()))
|
||||
.Callback((string tag, string message) =>
|
||||
{
|
||||
_trace.Info($"{tag}{message}");
|
||||
});
|
||||
|
||||
// SetEnvFileCommand
|
||||
_setEnvFileCommand = new SetEnvFileCommand();
|
||||
_setEnvFileCommand.Initialize(hostContext);
|
||||
|
||||
return hostContext;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Runtime.CompilerServices;
|
||||
using GitHub.Runner.Common.Util;
|
||||
using GitHub.Runner.Sdk;
|
||||
using GitHub.Runner.Worker;
|
||||
using GitHub.Runner.Worker.Container;
|
||||
using GitHub.Runner.Worker.Handlers;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
using DTWebApi = GitHub.DistributedTask.WebApi;
|
||||
|
||||
namespace GitHub.Runner.Common.Tests.Worker
|
||||
{
|
||||
public sealed class SetOutputFileCommandL0
|
||||
public sealed class SetOutputFileCommandL0 : FileCommandTestBase<SetOutputFileCommand>
|
||||
{
|
||||
private Mock<IExecutionContext> _executionContext;
|
||||
private List<Tuple<DTWebApi.Issue, string>> _issues;
|
||||
private Dictionary<string, string> _outputs;
|
||||
private string _rootDirectory;
|
||||
private SetOutputFileCommand _setOutputFileCommand;
|
||||
private ITraceWriter _trace;
|
||||
|
||||
protected override IDictionary<string, string> PostSetup()
|
||||
{
|
||||
var outputs = new Dictionary<string, string>();
|
||||
var reference = string.Empty;
|
||||
_executionContext.Setup(x => x.SetOutput(It.IsAny<string>(), It.IsAny<string>(), out reference))
|
||||
.Callback((string name, string value, out string reference) =>
|
||||
{
|
||||
reference = value;
|
||||
outputs[name] = value;
|
||||
});
|
||||
|
||||
return outputs;
|
||||
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetOutputFileCommand_DirectoryNotFound()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "directory-not-found", "env");
|
||||
_setOutputFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(0, _outputs.Count);
|
||||
}
|
||||
base.TestDirectoryNotFound();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -46,13 +38,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetOutputFileCommand_NotFound()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "file-not-found");
|
||||
_setOutputFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(0, _outputs.Count);
|
||||
}
|
||||
base.TestNotFound();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -60,15 +46,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetOutputFileCommand_EmptyFile()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "empty-file");
|
||||
var content = new List<string>();
|
||||
WriteContent(stateFile, content);
|
||||
_setOutputFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(0, _outputs.Count);
|
||||
}
|
||||
base.TestEmptyFile();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -76,19 +54,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetOutputFileCommand_Simple()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "simple");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_OUTPUT=MY VALUE",
|
||||
};
|
||||
WriteContent(stateFile, content);
|
||||
_setOutputFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(1, _outputs.Count);
|
||||
Assert.Equal("MY VALUE", _outputs["MY_OUTPUT"]);
|
||||
}
|
||||
base.TestSimple();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -96,24 +62,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetOutputFileCommand_Simple_SkipEmptyLines()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "simple");
|
||||
var content = new List<string>
|
||||
{
|
||||
string.Empty,
|
||||
"MY_OUTPUT=my value",
|
||||
string.Empty,
|
||||
"MY_OUTPUT_2=my second value",
|
||||
string.Empty,
|
||||
};
|
||||
WriteContent(stateFile, content);
|
||||
_setOutputFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(2, _outputs.Count);
|
||||
Assert.Equal("my value", _outputs["MY_OUTPUT"]);
|
||||
Assert.Equal("my second value", _outputs["MY_OUTPUT_2"]);
|
||||
}
|
||||
base.TestSimple_SkipEmptyLines();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -121,19 +70,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetOutputFileCommand_Simple_EmptyValue()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "simple-empty-value");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_OUTPUT=",
|
||||
};
|
||||
WriteContent(stateFile, content);
|
||||
_setOutputFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(1, _outputs.Count);
|
||||
Assert.Equal(string.Empty, _outputs["MY_OUTPUT"]);
|
||||
}
|
||||
base.TestSimple_EmptyValue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -141,23 +78,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetOutputFileCommand_Simple_MultipleValues()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "simple");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_OUTPUT=my value",
|
||||
"MY_OUTPUT_2=",
|
||||
"MY_OUTPUT_3=my third value",
|
||||
};
|
||||
WriteContent(stateFile, content);
|
||||
_setOutputFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(3, _outputs.Count);
|
||||
Assert.Equal("my value", _outputs["MY_OUTPUT"]);
|
||||
Assert.Equal(string.Empty, _outputs["MY_OUTPUT_2"]);
|
||||
Assert.Equal("my third value", _outputs["MY_OUTPUT_3"]);
|
||||
}
|
||||
base.TestSimple_MultipleValues();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -165,23 +86,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetOutputFileCommand_Simple_SpecialCharacters()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "simple");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_OUTPUT==abc",
|
||||
"MY_OUTPUT_2=def=ghi",
|
||||
"MY_OUTPUT_3=jkl=",
|
||||
};
|
||||
WriteContent(stateFile, content);
|
||||
_setOutputFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(3, _outputs.Count);
|
||||
Assert.Equal("=abc", _outputs["MY_OUTPUT"]);
|
||||
Assert.Equal("def=ghi", _outputs["MY_OUTPUT_2"]);
|
||||
Assert.Equal("jkl=", _outputs["MY_OUTPUT_3"]);
|
||||
}
|
||||
base.TestSimple_SpecialCharacters();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -189,23 +94,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetOutputFileCommand_Heredoc()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_OUTPUT<<EOF",
|
||||
"line one",
|
||||
"line two",
|
||||
"line three",
|
||||
"EOF",
|
||||
};
|
||||
WriteContent(stateFile, content);
|
||||
_setOutputFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(1, _outputs.Count);
|
||||
Assert.Equal($"line one{Environment.NewLine}line two{Environment.NewLine}line three", _outputs["MY_OUTPUT"]);
|
||||
}
|
||||
base.TestHeredoc();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -213,20 +102,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetOutputFileCommand_Heredoc_EmptyValue()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_OUTPUT<<EOF",
|
||||
"EOF",
|
||||
};
|
||||
WriteContent(stateFile, content);
|
||||
_setOutputFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(1, _outputs.Count);
|
||||
Assert.Equal(string.Empty, _outputs["MY_OUTPUT"]);
|
||||
}
|
||||
base.TestHeredoc_EmptyValue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -234,73 +110,52 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetOutputFileCommand_Heredoc_SkipEmptyLines()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
string.Empty,
|
||||
"MY_OUTPUT<<EOF",
|
||||
"hello",
|
||||
"world",
|
||||
"EOF",
|
||||
string.Empty,
|
||||
"MY_OUTPUT_2<<EOF",
|
||||
"HELLO",
|
||||
"AGAIN",
|
||||
"EOF",
|
||||
string.Empty,
|
||||
};
|
||||
WriteContent(stateFile, content);
|
||||
_setOutputFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(2, _outputs.Count);
|
||||
Assert.Equal($"hello{Environment.NewLine}world", _outputs["MY_OUTPUT"]);
|
||||
Assert.Equal($"HELLO{Environment.NewLine}AGAIN", _outputs["MY_OUTPUT_2"]);
|
||||
}
|
||||
base.TestHeredoc_SkipEmptyLines();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetOutputFileCommand_Heredoc_SpecialCharacters()
|
||||
public void SetOutputFileCommand_Heredoc_EdgeCases()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_OUTPUT<<=EOF",
|
||||
"hello",
|
||||
"one",
|
||||
"=EOF",
|
||||
"MY_OUTPUT_2<<<EOF",
|
||||
"hello",
|
||||
"two",
|
||||
"<EOF",
|
||||
"MY_OUTPUT_3<<EOF",
|
||||
"hello",
|
||||
string.Empty,
|
||||
"three",
|
||||
string.Empty,
|
||||
"EOF",
|
||||
"MY_OUTPUT_4<<EOF",
|
||||
"hello=four",
|
||||
"EOF",
|
||||
"MY_OUTPUT_5<<EOF",
|
||||
" EOF",
|
||||
"EOF",
|
||||
};
|
||||
WriteContent(stateFile, content);
|
||||
_setOutputFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(5, _outputs.Count);
|
||||
Assert.Equal($"hello{Environment.NewLine}one", _outputs["MY_OUTPUT"]);
|
||||
Assert.Equal($"hello{Environment.NewLine}two", _outputs["MY_OUTPUT_2"]);
|
||||
Assert.Equal($"hello{Environment.NewLine}{Environment.NewLine}three{Environment.NewLine}", _outputs["MY_OUTPUT_3"]);
|
||||
Assert.Equal($"hello=four", _outputs["MY_OUTPUT_4"]);
|
||||
Assert.Equal($" EOF", _outputs["MY_OUTPUT_5"]);
|
||||
}
|
||||
base.TestHeredoc_EdgeCases();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
// All of the following are not only valid, but quite plausible end markers.
|
||||
// Most are derived straight from the example at https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#multiline-strings
|
||||
#pragma warning disable format
|
||||
[InlineData("=EOF")][InlineData("==EOF")][InlineData("EO=F")][InlineData("EO==F")][InlineData("EOF=")][InlineData("EOF==")]
|
||||
[InlineData("<EOF")][InlineData("<<EOF")][InlineData("EO<F")][InlineData("EO<<F")][InlineData("EOF<")][InlineData("EOF<<")]
|
||||
[InlineData("+EOF")][InlineData("++EOF")][InlineData("EO+F")][InlineData("EO++F")][InlineData("EOF+")][InlineData("EOF++")]
|
||||
[InlineData("/EOF")][InlineData("//EOF")][InlineData("EO/F")][InlineData("EO//F")][InlineData("EOF/")][InlineData("EOF//")]
|
||||
#pragma warning restore format
|
||||
[InlineData("<<//++==")]
|
||||
[InlineData("contrivedBase64==")]
|
||||
[InlineData("khkIhPxsVA==")]
|
||||
[InlineData("D+Y8zE/EOw==")]
|
||||
[InlineData("wuOWG4S6FQ==")]
|
||||
[InlineData("7wigCJ//iw==")]
|
||||
[InlineData("uifTuYTs8K4=")]
|
||||
[InlineData("M7N2ITg/04c=")]
|
||||
[InlineData("Xhh+qp+Y6iM=")]
|
||||
[InlineData("5tdblQajc/b+EGBZXo0w")]
|
||||
[InlineData("jk/UMjIx/N0eVcQYOUfw")]
|
||||
[InlineData("/n5lsw73Cwl35Hfuscdz")]
|
||||
[InlineData("ZvnAEW+9O0tXp3Fmb3Oh")]
|
||||
public void SetOutputFileCommand_Heredoc_EndMarkerVariations(string validEndMarker)
|
||||
{
|
||||
base.TestHeredoc_EndMarkerVariations(validEndMarker);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetOutputFileCommand_Heredoc_EqualBeforeMultilineIndicator()
|
||||
{
|
||||
base.TestHeredoc_EqualBeforeMultilineIndicator();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -308,21 +163,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetOutputFileCommand_Heredoc_MissingNewLine()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_OUTPUT<<EOF",
|
||||
"line one",
|
||||
"line two",
|
||||
"line three",
|
||||
"EOF",
|
||||
};
|
||||
WriteContent(stateFile, content, " ");
|
||||
var ex = Assert.Throws<Exception>(() => _setOutputFileCommand.ProcessCommand(_executionContext.Object, stateFile, null));
|
||||
Assert.Contains("Matching delimiter not found", ex.Message);
|
||||
}
|
||||
base.TestHeredoc_MissingNewLine();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -330,21 +171,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetOutputFileCommand_Heredoc_MissingNewLineMultipleLines()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_OUTPUT<<EOF",
|
||||
@"line one
|
||||
line two
|
||||
line three",
|
||||
"EOF",
|
||||
};
|
||||
WriteContent(stateFile, content, " ");
|
||||
var ex = Assert.Throws<Exception>(() => _setOutputFileCommand.ProcessCommand(_executionContext.Object, stateFile, null));
|
||||
Assert.Contains("EOF marker missing new line", ex.Message);
|
||||
}
|
||||
base.TestHeredoc_MissingNewLineMultipleLines();
|
||||
}
|
||||
|
||||
#if OS_WINDOWS
|
||||
@@ -353,96 +180,9 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetOutputFileCommand_Heredoc_PreservesNewline()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var newline = "\n";
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_OUTPUT<<EOF",
|
||||
"hello",
|
||||
"world",
|
||||
"EOF",
|
||||
};
|
||||
WriteContent(stateFile, content, newline: newline);
|
||||
_setOutputFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(1, _outputs.Count);
|
||||
Assert.Equal($"hello{newline}world", _outputs["MY_OUTPUT"]);
|
||||
}
|
||||
base.TestHeredoc_PreservesNewline();
|
||||
}
|
||||
#endif
|
||||
|
||||
private void WriteContent(
|
||||
string path,
|
||||
List<string> content,
|
||||
string newline = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(newline))
|
||||
{
|
||||
newline = Environment.NewLine;
|
||||
}
|
||||
|
||||
var encoding = new UTF8Encoding(true); // Emit BOM
|
||||
var contentStr = string.Join(newline, content);
|
||||
File.WriteAllText(path, contentStr, encoding);
|
||||
}
|
||||
|
||||
private TestHostContext Setup([CallerMemberName] string name = "")
|
||||
{
|
||||
_issues = new List<Tuple<DTWebApi.Issue, string>>();
|
||||
_outputs = new Dictionary<string, string>();
|
||||
|
||||
var hostContext = new TestHostContext(this, name);
|
||||
|
||||
// Trace
|
||||
_trace = hostContext.GetTrace();
|
||||
|
||||
// Directory for test data
|
||||
var workDirectory = hostContext.GetDirectory(WellKnownDirectory.Work);
|
||||
ArgUtil.NotNullOrEmpty(workDirectory, nameof(workDirectory));
|
||||
Directory.CreateDirectory(workDirectory);
|
||||
_rootDirectory = Path.Combine(workDirectory, nameof(SetOutputFileCommandL0));
|
||||
Directory.CreateDirectory(_rootDirectory);
|
||||
|
||||
// Execution context
|
||||
_executionContext = new Mock<IExecutionContext>();
|
||||
_executionContext.Setup(x => x.Global)
|
||||
.Returns(new GlobalContext
|
||||
{
|
||||
EnvironmentVariables = new Dictionary<string, string>(VarUtil.EnvironmentVariableKeyComparer),
|
||||
WriteDebug = true,
|
||||
});
|
||||
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.Issue>(), It.IsAny<ExecutionContextLogOptions>()))
|
||||
.Callback((DTWebApi.Issue issue, ExecutionContextLogOptions logOptions) =>
|
||||
{
|
||||
var resolvedMessage = issue.Message;
|
||||
if (logOptions.WriteToLog && !string.IsNullOrEmpty(logOptions.LogMessageOverride))
|
||||
{
|
||||
resolvedMessage = logOptions.LogMessageOverride;
|
||||
}
|
||||
_issues.Add(new(issue, resolvedMessage));
|
||||
_trace.Info($"Issue '{issue.Type}': {resolvedMessage}");
|
||||
});
|
||||
_executionContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()))
|
||||
.Callback((string tag, string message) =>
|
||||
{
|
||||
_trace.Info($"{tag}{message}");
|
||||
});
|
||||
|
||||
var reference = string.Empty;
|
||||
_executionContext.Setup(x => x.SetOutput(It.IsAny<string>(), It.IsAny<string>(), out reference))
|
||||
.Callback((string name, string value, out string reference) =>
|
||||
{
|
||||
reference = value;
|
||||
_outputs[name] = value;
|
||||
});
|
||||
|
||||
// SetOutputFileCommand
|
||||
_setOutputFileCommand = new SetOutputFileCommand();
|
||||
_setOutputFileCommand.Initialize(hostContext);
|
||||
|
||||
return hostContext;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user