mirror of
https://github.com/actions/runner.git
synced 2025-12-10 12:36:23 +00:00
Compare commits
30 Commits
v2.324.0
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d559d016df | ||
|
|
a942627965 | ||
|
|
83539166c9 | ||
|
|
1c1e8bfd18 | ||
|
|
59177fa379 | ||
|
|
2d7635a7f0 | ||
|
|
0203cf24d3 | ||
|
|
5e74a4d8e4 | ||
|
|
6ca97eeb88 | ||
|
|
8a9b96806d | ||
|
|
dc9cf684c9 | ||
|
|
c765c990b9 | ||
|
|
ed48ddd08c | ||
|
|
a1e6ad8d2e | ||
|
|
14856e63bc | ||
|
|
0d24afa114 | ||
|
|
20912234a5 | ||
|
|
5969cbe208 | ||
|
|
9f57d37642 | ||
|
|
60563d82d1 | ||
|
|
097ada9374 | ||
|
|
9b457781d6 | ||
|
|
9709b69571 | ||
|
|
acf3f2ba12 | ||
|
|
f03fcc8a01 | ||
|
|
e4e103c5ed | ||
|
|
a906ec302b | ||
|
|
d9e714496d | ||
|
|
df189ba6e3 | ||
|
|
4c1de69e1c |
@@ -4,7 +4,7 @@
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/docker-in-docker:1": {},
|
||||
"ghcr.io/devcontainers/features/dotnet": {
|
||||
"version": "8.0.408"
|
||||
"version": "8.0.412"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/node:1": {
|
||||
"version": "20"
|
||||
|
||||
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -41,7 +41,7 @@ jobs:
|
||||
devScript: ./dev.sh
|
||||
|
||||
- runtime: win-x64
|
||||
os: windows-2019
|
||||
os: windows-latest
|
||||
devScript: ./dev
|
||||
|
||||
- runtime: win-arm64
|
||||
|
||||
144
.github/workflows/docker-buildx-upgrade.yml
vendored
Normal file
144
.github/workflows/docker-buildx-upgrade.yml
vendored
Normal file
@@ -0,0 +1,144 @@
|
||||
name: "Docker/Buildx Version Upgrade"
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * 1' # Run every Monday at midnight
|
||||
workflow_dispatch: # Allow manual triggering
|
||||
|
||||
jobs:
|
||||
check-versions:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
DOCKER_SHOULD_UPDATE: ${{ steps.check_docker_version.outputs.SHOULD_UPDATE }}
|
||||
DOCKER_LATEST_VERSION: ${{ steps.check_docker_version.outputs.LATEST_VERSION }}
|
||||
DOCKER_CURRENT_VERSION: ${{ steps.check_docker_version.outputs.CURRENT_VERSION }}
|
||||
BUILDX_SHOULD_UPDATE: ${{ steps.check_buildx_version.outputs.SHOULD_UPDATE }}
|
||||
BUILDX_LATEST_VERSION: ${{ steps.check_buildx_version.outputs.LATEST_VERSION }}
|
||||
BUILDX_CURRENT_VERSION: ${{ steps.check_buildx_version.outputs.CURRENT_VERSION }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Check Docker version
|
||||
id: check_docker_version
|
||||
shell: bash
|
||||
run: |
|
||||
# Extract current Docker version from Dockerfile
|
||||
current_version=$(grep "ARG DOCKER_VERSION=" ./images/Dockerfile | cut -d'=' -f2)
|
||||
|
||||
# Fetch latest Docker Engine version from Docker's download site
|
||||
# This gets the latest Linux static binary version which matches what's used in the Dockerfile
|
||||
latest_version=$(curl -s https://download.docker.com/linux/static/stable/x86_64/ | grep -o 'docker-[0-9]*\.[0-9]*\.[0-9]*\.tgz' | sort -V | tail -n 1 | sed 's/docker-\(.*\)\.tgz/\1/')
|
||||
|
||||
# Extra check to ensure we got a valid version
|
||||
if [[ ! $latest_version =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "Failed to retrieve a valid Docker version"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
should_update=0
|
||||
[ "$current_version" != "$latest_version" ] && should_update=1
|
||||
|
||||
echo "CURRENT_VERSION=${current_version}" >> $GITHUB_OUTPUT
|
||||
echo "LATEST_VERSION=${latest_version}" >> $GITHUB_OUTPUT
|
||||
echo "SHOULD_UPDATE=${should_update}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Check Buildx version
|
||||
id: check_buildx_version
|
||||
shell: bash
|
||||
run: |
|
||||
# Extract current Buildx version from Dockerfile
|
||||
current_version=$(grep "ARG BUILDX_VERSION=" ./images/Dockerfile | cut -d'=' -f2)
|
||||
|
||||
# Fetch latest Buildx version
|
||||
latest_version=$(curl -s https://api.github.com/repos/docker/buildx/releases/latest | jq -r '.tag_name' | sed 's/^v//')
|
||||
|
||||
should_update=0
|
||||
[ "$current_version" != "$latest_version" ] && should_update=1
|
||||
|
||||
echo "CURRENT_VERSION=${current_version}" >> $GITHUB_OUTPUT
|
||||
echo "LATEST_VERSION=${latest_version}" >> $GITHUB_OUTPUT
|
||||
echo "SHOULD_UPDATE=${should_update}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create annotations for versions
|
||||
run: |
|
||||
docker_should_update="${{ steps.check_docker_version.outputs.SHOULD_UPDATE }}"
|
||||
buildx_should_update="${{ steps.check_buildx_version.outputs.SHOULD_UPDATE }}"
|
||||
|
||||
# Show annotation if only Docker needs update
|
||||
if [[ "$docker_should_update" == "1" && "$buildx_should_update" == "0" ]]; then
|
||||
echo "::warning ::Docker version (${{ steps.check_docker_version.outputs.LATEST_VERSION }}) needs update but Buildx is current. Only updating when both need updates."
|
||||
fi
|
||||
|
||||
# Show annotation if only Buildx needs update
|
||||
if [[ "$docker_should_update" == "0" && "$buildx_should_update" == "1" ]]; then
|
||||
echo "::warning ::Buildx version (${{ steps.check_buildx_version.outputs.LATEST_VERSION }}) needs update but Docker is current. Only updating when both need updates."
|
||||
fi
|
||||
|
||||
# Show annotation when both are current
|
||||
if [[ "$docker_should_update" == "0" && "$buildx_should_update" == "0" ]]; then
|
||||
echo "::warning ::Latest Docker version is ${{ steps.check_docker_version.outputs.LATEST_VERSION }} and Buildx version is ${{ steps.check_buildx_version.outputs.LATEST_VERSION }}. No updates needed."
|
||||
fi
|
||||
|
||||
update-versions:
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: write
|
||||
needs: [check-versions]
|
||||
if: ${{ needs.check-versions.outputs.DOCKER_SHOULD_UPDATE == 1 && needs.check-versions.outputs.BUILDX_SHOULD_UPDATE == 1 }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Update Docker version
|
||||
shell: bash
|
||||
run: |
|
||||
latest_version="${{ needs.check-versions.outputs.DOCKER_LATEST_VERSION }}"
|
||||
current_version="${{ needs.check-versions.outputs.DOCKER_CURRENT_VERSION }}"
|
||||
|
||||
# Update version in Dockerfile
|
||||
sed -i "s/ARG DOCKER_VERSION=$current_version/ARG DOCKER_VERSION=$latest_version/g" ./images/Dockerfile
|
||||
|
||||
- name: Update Buildx version
|
||||
shell: bash
|
||||
run: |
|
||||
latest_version="${{ needs.check-versions.outputs.BUILDX_LATEST_VERSION }}"
|
||||
current_version="${{ needs.check-versions.outputs.BUILDX_CURRENT_VERSION }}"
|
||||
|
||||
# Update version in Dockerfile
|
||||
sed -i "s/ARG BUILDX_VERSION=$current_version/ARG BUILDX_VERSION=$latest_version/g" ./images/Dockerfile
|
||||
|
||||
- name: Commit changes and create Pull Request
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
# Setup branch and commit information
|
||||
branch_name="feature/docker-buildx-upgrade"
|
||||
commit_message="Upgrade Docker to v${{ needs.check-versions.outputs.DOCKER_LATEST_VERSION }} and Buildx to v${{ needs.check-versions.outputs.BUILDX_LATEST_VERSION }}"
|
||||
pr_title="Update Docker to v${{ needs.check-versions.outputs.DOCKER_LATEST_VERSION }} and Buildx to v${{ needs.check-versions.outputs.BUILDX_LATEST_VERSION }}"
|
||||
|
||||
# Configure git
|
||||
git config --global user.name "github-actions[bot]"
|
||||
git config --global user.email "<41898282+github-actions[bot]@users.noreply.github.com>"
|
||||
|
||||
# Create branch or switch to it if it exists
|
||||
if git show-ref --quiet refs/remotes/origin/$branch_name; then
|
||||
git fetch origin
|
||||
git checkout -B "$branch_name" origin/$branch_name
|
||||
else
|
||||
git checkout -b "$branch_name"
|
||||
fi
|
||||
|
||||
# Commit and push changes
|
||||
git commit -a -m "$commit_message"
|
||||
git push --force origin "$branch_name"
|
||||
|
||||
# Create PR
|
||||
pr_body="Upgrades Docker version from ${{ needs.check-versions.outputs.DOCKER_CURRENT_VERSION }} to ${{ needs.check-versions.outputs.DOCKER_LATEST_VERSION }} and Docker Buildx version from ${{ needs.check-versions.outputs.BUILDX_CURRENT_VERSION }} to ${{ needs.check-versions.outputs.BUILDX_LATEST_VERSION }}.\n\n"
|
||||
pr_body+="Release notes: https://docs.docker.com/engine/release-notes/\n\n"
|
||||
pr_body+="---\n\nAutogenerated by [Docker/Buildx Version Upgrade Workflow](https://github.com/actions/runner/blob/main/.github/workflows/docker-buildx-upgrade.yml)"
|
||||
|
||||
gh pr create -B main -H "$branch_name" \
|
||||
--title "$pr_title" \
|
||||
--body "$pr_body"
|
||||
16
.github/workflows/release.yml
vendored
16
.github/workflows/release.yml
vendored
@@ -77,7 +77,7 @@ jobs:
|
||||
devScript: ./dev.sh
|
||||
|
||||
- runtime: win-x64
|
||||
os: windows-2019
|
||||
os: windows-latest
|
||||
devScript: ./dev
|
||||
|
||||
- runtime: win-arm64
|
||||
@@ -133,37 +133,37 @@ jobs:
|
||||
|
||||
# Download runner package tar.gz/zip produced by 'build' job
|
||||
- name: Download Artifact (win-x64)
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
name: runner-packages-win-x64
|
||||
path: ./
|
||||
- name: Download Artifact (win-arm64)
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
name: runner-packages-win-arm64
|
||||
path: ./
|
||||
- name: Download Artifact (osx-x64)
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
name: runner-packages-osx-x64
|
||||
path: ./
|
||||
- name: Download Artifact (osx-arm64)
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
name: runner-packages-osx-arm64
|
||||
path: ./
|
||||
- name: Download Artifact (linux-x64)
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
name: runner-packages-linux-x64
|
||||
path: ./
|
||||
- name: Download Artifact (linux-arm)
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
name: runner-packages-linux-arm
|
||||
path: ./
|
||||
- name: Download Artifact (linux-arm64)
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
name: runner-packages-linux-arm64
|
||||
path: ./
|
||||
|
||||
18
README.md
18
README.md
@@ -20,6 +20,20 @@ Runner releases:
|
||||
|
||||
 [Pre-reqs](docs/start/envlinux.md) | [Download](https://github.com/actions/runner/releases)
|
||||
|
||||
## Contribute
|
||||
### Note
|
||||
|
||||
We accept contributions in the form of issues and pull requests. The runner typically requires changes across the entire system and we aim for issues in the runner to be entirely self contained and fixable here. Therefore, we will primarily handle bug issues opened in this repo and we kindly request you to create all feature and enhancement requests on the [GitHub Feedback](https://github.com/community/community/discussions/categories/actions-and-packages) page. [Read more about our guidelines here](docs/contribute.md) before contributing.
|
||||
Thank you for your interest in this GitHub repo, however, right now we are not taking contributions.
|
||||
|
||||
We continue to focus our resources on strategic areas that help our customers be successful while making developers' lives easier. While GitHub Actions remains a key part of this vision, we are allocating resources towards other areas of Actions and are not taking contributions to this repository at this time. The GitHub public roadmap is the best place to follow along for any updates on features we’re working on and what stage they’re in.
|
||||
|
||||
We are taking the following steps to better direct requests related to GitHub Actions, including:
|
||||
|
||||
1. We will be directing questions and support requests to our [Community Discussions area](https://github.com/orgs/community/discussions/categories/actions)
|
||||
|
||||
2. High Priority bugs can be reported through Community Discussions or you can report these to our support team https://support.github.com/contact/bug-report.
|
||||
|
||||
3. Security Issues should be handled as per our [security.md](security.md)
|
||||
|
||||
We will still provide security updates for this project and fix major breaking changes during this time.
|
||||
|
||||
You are welcome to still raise bugs in this repo.
|
||||
|
||||
@@ -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 copies of node.js executables under `<runner_root>/externals/node20/` and `<runner_root>/externals/node24/`.
|
||||
|
||||
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 either `<runner_root>/externals/node20/` or `<runner_root>/externals/node24/` depending on the version specified in the action's metadata.
|
||||
|
||||
> Not the `node` from `$PATH`
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
## Supported Distributions and Versions
|
||||
|
||||
Please see "[Supported architectures and operating systems for self-hosted runners](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners#linux)."
|
||||
Please see "[Supported architectures and operating systems for self-hosted runners](https://docs.github.com/en/actions/reference/runners/self-hosted-runners#linux)."
|
||||
|
||||
## Install .Net Core 3.x Linux Dependencies
|
||||
|
||||
|
||||
@@ -4,6 +4,6 @@
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Please see "[Supported architectures and operating systems for self-hosted runners](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners#macos)."
|
||||
Please see "[Supported architectures and operating systems for self-hosted runners](https://docs.github.com/en/actions/reference/runners/self-hosted-runners#macos)."
|
||||
|
||||
## [More .Net Core Prerequisites Information](https://docs.microsoft.com/en-us/dotnet/core/macos-prerequisites?tabs=netcore30)
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Please see "[Supported architectures and operating systems for self-hosted runners](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners#windows)."
|
||||
Please see "[Supported architectures and operating systems for self-hosted runners](https://docs.github.com/en/actions/reference/runners/self-hosted-runners#windows)."
|
||||
|
||||
## [More .NET Core Prerequisites Information](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=netcore30)
|
||||
|
||||
@@ -5,8 +5,8 @@ ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
ARG RUNNER_VERSION
|
||||
ARG RUNNER_CONTAINER_HOOKS_VERSION=0.7.0
|
||||
ARG DOCKER_VERSION=28.1.1
|
||||
ARG BUILDX_VERSION=0.23.0
|
||||
ARG DOCKER_VERSION=28.3.2
|
||||
ARG BUILDX_VERSION=0.26.1
|
||||
|
||||
RUN apt update -y && apt install curl unzip -y
|
||||
|
||||
|
||||
@@ -1,37 +1,13 @@
|
||||
## What's Changed
|
||||
* Increase error body max length before truncation by @ericsciple in https://github.com/actions/runner/pull/3762
|
||||
* Fix release.yml break by upgrading actions/github-script by @TingluoHuang in https://github.com/actions/runner/pull/3772
|
||||
* Small runner code cleanup. by @TingluoHuang in https://github.com/actions/runner/pull/3773
|
||||
* Enable hostcontext to track auth migration. by @TingluoHuang in https://github.com/actions/runner/pull/3776
|
||||
* Add option in OAuthCred to load authUrlV2. by @TingluoHuang in https://github.com/actions/runner/pull/3777
|
||||
* Remove create session with broker in MessageListener. by @TingluoHuang in https://github.com/actions/runner/pull/3782
|
||||
* Enable auth migration based on config refresh. by @TingluoHuang in https://github.com/actions/runner/pull/3786
|
||||
* Set JWT.alg to PS256 with PssPadding. by @TingluoHuang in https://github.com/actions/runner/pull/3789
|
||||
* Enable FIPS by default. by @TingluoHuang in https://github.com/actions/runner/pull/3793
|
||||
* Support auth migration using authUrlV2 in Runner/MessageListener. by @TingluoHuang in https://github.com/actions/runner/pull/3787
|
||||
* Cleanup feature flag actions_skip_retry_complete_job_upon_known_errors by @ericsciple in https://github.com/actions/runner/pull/3806
|
||||
* Update dotnet sdk to latest version @8.0.408 by @github-actions in https://github.com/actions/runner/pull/3808
|
||||
* Bump hook to 0.7.0 by @nikola-jokic in https://github.com/actions/runner/pull/3813
|
||||
* Allow enable auth migration by default. by @TingluoHuang in https://github.com/actions/runner/pull/3804
|
||||
* Do not retry /renewjob on 404 by @ericsciple in https://github.com/actions/runner/pull/3828
|
||||
* Bump Microsoft.NET.Test.Sdk from 17.12.0 to 17.13.0 in /src by @dependabot in https://github.com/actions/runner/pull/3719
|
||||
* Add copilot-instructions.md by @pje in https://github.com/actions/runner/pull/3810
|
||||
* Bump actions/upload-release-asset from 1.0.1 to 1.0.2 by @dependabot in https://github.com/actions/runner/pull/3553
|
||||
* Ignore exception during auth migration. by @TingluoHuang in https://github.com/actions/runner/pull/3835
|
||||
* feat: default fromPath for problem matchers by @dsanders11 in https://github.com/actions/runner/pull/3802
|
||||
* Bump Azure.Storage.Blobs from 12.23.0 to 12.24.0 in /src by @dependabot in https://github.com/actions/runner/pull/3837
|
||||
* Bump nodejs version. by @TingluoHuang in https://github.com/actions/runner/pull/3840
|
||||
* Feature-flagged support for `JobContext.CheckRunID` by @pje in https://github.com/actions/runner/pull/3811
|
||||
* Bump System.ServiceProcess.ServiceController from 8.0.0 to 8.0.1 in /src by @dependabot in https://github.com/actions/runner/pull/3844
|
||||
* Bump xunit.runner.visualstudio from 2.5.8 to 2.8.2 in /src by @dependabot in https://github.com/actions/runner/pull/3845
|
||||
* Make sure the token's claims are match as expected. by @TingluoHuang in https://github.com/actions/runner/pull/3846
|
||||
* Prefer _migrated config on startup by @lokesh755 in https://github.com/actions/runner/pull/3853
|
||||
* Update docker and buildx by @TingluoHuang in https://github.com/actions/runner/pull/3854
|
||||
* Try add orchestrationid into user-agent using token claim. by @TingluoHuang in https://github.com/actions/runner/pull/3945
|
||||
* Fix null reference exception in user agent handling by @salmanmkc in https://github.com/actions/runner/pull/3946
|
||||
* Runner Support for executing Node24 Actions by @salmanmkc in https://github.com/actions/runner/pull/3940
|
||||
* Update dotnet sdk to latest version @8.0.412 by @github-actions[bot] in https://github.com/actions/runner/pull/3941
|
||||
|
||||
## New Contributors
|
||||
* @dsanders11 made their first contribution in https://github.com/actions/runner/pull/3802
|
||||
* @salmanmkc made their first contribution in https://github.com/actions/runner/pull/3946
|
||||
|
||||
**Full Changelog**: https://github.com/actions/runner/compare/v2.323.0...v2.324.0
|
||||
**Full Changelog**: https://github.com/actions/runner/compare/v2.326.0...v2.327.0
|
||||
|
||||
_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.
|
||||
|
||||
13
src/Misc/expressionFunc/hashFiles/package-lock.json
generated
13
src/Misc/expressionFunc/hashFiles/package-lock.json
generated
@@ -716,9 +716,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
@@ -4751,9 +4752,9 @@
|
||||
}
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
|
||||
@@ -6,7 +6,8 @@ NODE_URL=https://nodejs.org/dist
|
||||
NODE_ALPINE_URL=https://github.com/actions/alpine_nodejs/releases/download
|
||||
# When you update Node versions you must also create a new release of alpine_nodejs at that updated version.
|
||||
# Follow the instructions here: https://github.com/actions/alpine_nodejs?tab=readme-ov-file#getting-started
|
||||
NODE20_VERSION="20.19.1"
|
||||
NODE20_VERSION="20.19.4"
|
||||
NODE24_VERSION="24.5.0"
|
||||
|
||||
get_abs_path() {
|
||||
# exploits the fact that pwd will print abs path when no args
|
||||
@@ -139,6 +140,8 @@ function acquireExternalTool() {
|
||||
if [[ "$PACKAGERUNTIME" == "win-x64" || "$PACKAGERUNTIME" == "win-x86" ]]; then
|
||||
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/$PACKAGERUNTIME/node.exe" node20/bin
|
||||
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/$PACKAGERUNTIME/node.lib" node20/bin
|
||||
acquireExternalTool "$NODE_URL/v${NODE24_VERSION}/$PACKAGERUNTIME/node.exe" node24/bin
|
||||
acquireExternalTool "$NODE_URL/v${NODE24_VERSION}/$PACKAGERUNTIME/node.lib" node24/bin
|
||||
if [[ "$PRECACHE" != "" ]]; then
|
||||
acquireExternalTool "https://github.com/microsoft/vswhere/releases/download/2.6.7/vswhere.exe" vswhere
|
||||
fi
|
||||
@@ -149,6 +152,8 @@ if [[ "$PACKAGERUNTIME" == "win-arm64" ]]; then
|
||||
# todo: replace these with official release when available
|
||||
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/$PACKAGERUNTIME/node.exe" node20/bin
|
||||
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/$PACKAGERUNTIME/node.lib" node20/bin
|
||||
acquireExternalTool "$NODE_URL/v${NODE24_VERSION}/$PACKAGERUNTIME/node.exe" node24/bin
|
||||
acquireExternalTool "$NODE_URL/v${NODE24_VERSION}/$PACKAGERUNTIME/node.lib" node24/bin
|
||||
if [[ "$PRECACHE" != "" ]]; then
|
||||
acquireExternalTool "https://github.com/microsoft/vswhere/releases/download/2.6.7/vswhere.exe" vswhere
|
||||
fi
|
||||
@@ -157,21 +162,26 @@ fi
|
||||
# Download the external tools only for OSX.
|
||||
if [[ "$PACKAGERUNTIME" == "osx-x64" ]]; then
|
||||
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-darwin-x64.tar.gz" node20 fix_nested_dir
|
||||
acquireExternalTool "$NODE_URL/v${NODE24_VERSION}/node-v${NODE24_VERSION}-darwin-x64.tar.gz" node24 fix_nested_dir
|
||||
fi
|
||||
|
||||
if [[ "$PACKAGERUNTIME" == "osx-arm64" ]]; then
|
||||
# node.js v12 doesn't support macOS on arm64.
|
||||
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-darwin-arm64.tar.gz" node20 fix_nested_dir
|
||||
acquireExternalTool "$NODE_URL/v${NODE24_VERSION}/node-v${NODE24_VERSION}-darwin-arm64.tar.gz" node24 fix_nested_dir
|
||||
fi
|
||||
|
||||
# Download the external tools for Linux PACKAGERUNTIMEs.
|
||||
if [[ "$PACKAGERUNTIME" == "linux-x64" ]]; then
|
||||
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 "$NODE_URL/v${NODE24_VERSION}/node-v${NODE24_VERSION}-linux-x64.tar.gz" node24 fix_nested_dir
|
||||
acquireExternalTool "$NODE_ALPINE_URL/v${NODE24_VERSION}/node-v${NODE24_VERSION}-alpine-x64.tar.gz" node24_alpine
|
||||
fi
|
||||
|
||||
if [[ "$PACKAGERUNTIME" == "linux-arm64" ]]; then
|
||||
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-linux-arm64.tar.gz" node20 fix_nested_dir
|
||||
acquireExternalTool "$NODE_URL/v${NODE24_VERSION}/node-v${NODE24_VERSION}-linux-arm64.tar.gz" node24 fix_nested_dir
|
||||
fi
|
||||
|
||||
if [[ "$PACKAGERUNTIME" == "linux-arm" ]]; then
|
||||
|
||||
@@ -3299,7 +3299,7 @@ function expand(str, isTop) {
|
||||
var isOptions = m.body.indexOf(',') >= 0;
|
||||
if (!isSequence && !isOptions) {
|
||||
// {a},b}
|
||||
if (m.post.match(/,.*\}/)) {
|
||||
if (m.post.match(/,(?!,).*\}/)) {
|
||||
str = m.pre + '{' + m.body + escClose + m.post;
|
||||
return expand(str);
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ fi
|
||||
# fix upgrade issue with macOS when running as a service
|
||||
attemptedtargetedfix=0
|
||||
currentplatform=$(uname | awk '{print tolower($0)}')
|
||||
if [[ "$currentplatform" == 'darwin' && restartinteractiverunner -eq 0 ]]; then
|
||||
if [[ "$currentplatform" == 'darwin' && $restartinteractiverunner -eq 0 ]]; then
|
||||
# We needed a fix for https://github.com/actions/runner/issues/743
|
||||
# We will recreate the ./externals/nodeXY/bin/node of the past runner version that launched the runnerlistener service
|
||||
# Otherwise mac gatekeeper kills the processes we spawn on creation as we are running a process with no backing file
|
||||
@@ -135,6 +135,11 @@ 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
|
||||
# Try finding node24 first, then fallback to earlier versions if needed
|
||||
nodever="node24"
|
||||
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 node20
|
||||
then
|
||||
nodever="node20"
|
||||
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
|
||||
@@ -147,6 +152,7 @@ if [[ "$currentplatform" == 'darwin' && restartinteractiverunner -eq 0 ]]; then
|
||||
path=$(lsof -a -g "$procgroup" -F n | grep $nodever/bin/node | grep externals | tail -1 | cut -c2-)
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
if [[ $? -eq 0 && -n "$path" ]]
|
||||
then
|
||||
# trim the last 5 characters of the path '/node'
|
||||
|
||||
@@ -168,6 +168,23 @@ namespace GitHub.Runner.Common
|
||||
public static readonly string UseContainerPathForTemplate = "DistributedTask.UseContainerPathForTemplate";
|
||||
public static readonly string AllowRunnerContainerHooks = "DistributedTask.AllowRunnerContainerHooks";
|
||||
public static readonly string AddCheckRunIdToJobContext = "actions_add_check_run_id_to_job_context";
|
||||
public static readonly string DisplayHelpfulActionsDownloadErrors = "actions_display_helpful_actions_download_errors";
|
||||
}
|
||||
|
||||
// Node version migration related constants
|
||||
public static class NodeMigration
|
||||
{
|
||||
// Node versions
|
||||
public static readonly string Node20 = "node20";
|
||||
public static readonly string Node24 = "node24";
|
||||
|
||||
// Environment variables for controlling node version selection
|
||||
public static readonly string ForceNode24Variable = "FORCE_JAVASCRIPT_ACTIONS_TO_NODE24";
|
||||
public static readonly string AllowUnsecureNodeVersionVariable = "ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION";
|
||||
|
||||
// Feature flags for controlling the migration phases
|
||||
public static readonly string UseNode24ByDefaultFlag = "actions.runner.usenode24bydefault";
|
||||
public static readonly string RequireNode24Flag = "actions.runner.requirenode24";
|
||||
}
|
||||
|
||||
public static readonly string InternalTelemetryIssueDataKey = "_internal_telemetry";
|
||||
|
||||
@@ -15,6 +15,7 @@ using System.Threading.Tasks;
|
||||
using GitHub.DistributedTask.Logging;
|
||||
using GitHub.Runner.Common.Util;
|
||||
using GitHub.Runner.Sdk;
|
||||
using GitHub.Services.WebApi.Jwt;
|
||||
|
||||
namespace GitHub.Runner.Common
|
||||
{
|
||||
@@ -306,6 +307,36 @@ namespace GitHub.Runner.Common
|
||||
{
|
||||
_userAgents.Add(new ProductInfoHeaderValue("ClientId", clientId));
|
||||
}
|
||||
|
||||
// for Hosted runner, we can pull orchestrationId from JWT claims of the runner listening token.
|
||||
if (credData != null &&
|
||||
credData.Scheme == Constants.Configuration.OAuthAccessToken &&
|
||||
credData.Data.TryGetValue(Constants.Runner.CommandLine.Args.Token, out var accessToken) &&
|
||||
!string.IsNullOrEmpty(accessToken))
|
||||
{
|
||||
try
|
||||
{
|
||||
var jwt = JsonWebToken.Create(accessToken);
|
||||
var claims = jwt.ExtractClaims();
|
||||
var orchestrationId = claims.FirstOrDefault(x => string.Equals(x.Type, "orch_id", StringComparison.OrdinalIgnoreCase))?.Value;
|
||||
if (string.IsNullOrEmpty(orchestrationId))
|
||||
{
|
||||
// fallback to orchid for C# actions-service
|
||||
orchestrationId = claims.FirstOrDefault(x => string.Equals(x.Type, "orchid", StringComparison.OrdinalIgnoreCase))?.Value;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(orchestrationId))
|
||||
{
|
||||
_trace.Info($"Pull OrchestrationId {orchestrationId} from runner JWT claims");
|
||||
_userAgents.Insert(0, new ProductInfoHeaderValue("OrchestrationId", orchestrationId));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_trace.Error("Fail to extract OrchestrationId from runner JWT claims");
|
||||
_trace.Error(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var runnerFile = GetConfigFile(WellKnownConfigFile.Runner);
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace GitHub.Runner.Common
|
||||
{
|
||||
void InitializeLaunchClient(Uri uri, string token);
|
||||
|
||||
Task<ActionDownloadInfoCollection> ResolveActionsDownloadInfoAsync(Guid planId, Guid jobId, ActionReferenceList actionReferenceList, CancellationToken cancellationToken);
|
||||
Task<ActionDownloadInfoCollection> ResolveActionsDownloadInfoAsync(Guid planId, Guid jobId, ActionReferenceList actionReferenceList, CancellationToken cancellationToken, bool displayHelpfulActionsDownloadErrors);
|
||||
}
|
||||
|
||||
public sealed class LaunchServer : RunnerService, ILaunchServer
|
||||
@@ -42,13 +42,17 @@ namespace GitHub.Runner.Common
|
||||
}
|
||||
|
||||
public Task<ActionDownloadInfoCollection> ResolveActionsDownloadInfoAsync(Guid planId, Guid jobId, ActionReferenceList actionReferenceList,
|
||||
CancellationToken cancellationToken)
|
||||
CancellationToken cancellationToken, bool displayHelpfulActionsDownloadErrors)
|
||||
{
|
||||
if (_launchClient != null)
|
||||
{
|
||||
if (!displayHelpfulActionsDownloadErrors)
|
||||
{
|
||||
return _launchClient.GetResolveActionsDownloadInfoAsync(planId, jobId, actionReferenceList,
|
||||
cancellationToken: cancellationToken);
|
||||
}
|
||||
return _launchClient.GetResolveActionsDownloadInfoAsyncV2(planId, jobId, actionReferenceList, cancellationToken);
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Launch client is not initialized.");
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="8.0.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="9.0.8" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="8.0.0" />
|
||||
<PackageReference Include="System.Threading.Channels" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace GitHub.Runner.Common
|
||||
|
||||
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 DeleteRunnerAsync(string githubUrl, string githubToken, ulong runnerId);
|
||||
Task<List<TaskAgentPool>> GetRunnerGroupsAsync(string githubUrl, string githubToken);
|
||||
}
|
||||
|
||||
@@ -43,117 +44,15 @@ namespace GitHub.Runner.Common
|
||||
|
||||
public async Task<List<TaskAgent>> GetRunnerByNameAsync(string githubUrl, string githubToken, string agentName)
|
||||
{
|
||||
var githubApiUrl = "";
|
||||
var gitHubUrlBuilder = new UriBuilder(githubUrl);
|
||||
var path = gitHubUrlBuilder.Path.Split('/', '\\', StringSplitOptions.RemoveEmptyEntries);
|
||||
var isOrgRunner = path.Length == 1;
|
||||
var isRepoOrEnterpriseRunner = path.Length == 2;
|
||||
var isRepoRunner = isRepoOrEnterpriseRunner && !string.Equals(path[0], "enterprises", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (isOrgRunner)
|
||||
{
|
||||
// org runner
|
||||
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/orgs/{path[0]}/actions/runners?name={Uri.EscapeDataString(agentName)}";
|
||||
}
|
||||
else
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/orgs/{path[0]}/actions/runners?name={Uri.EscapeDataString(agentName)}";
|
||||
}
|
||||
}
|
||||
else if (isRepoOrEnterpriseRunner)
|
||||
{
|
||||
// Repository runner
|
||||
if (isRepoRunner)
|
||||
{
|
||||
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/repos/{path[0]}/{path[1]}/actions/runners?name={Uri.EscapeDataString(agentName)}";
|
||||
}
|
||||
else
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/repos/{path[0]}/{path[1]}/actions/runners?name={Uri.EscapeDataString(agentName)}";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Enterprise runner
|
||||
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/{path[0]}/{path[1]}/actions/runners?name={Uri.EscapeDataString(agentName)}";
|
||||
}
|
||||
else
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/{path[0]}/{path[1]}/actions/runners?name={Uri.EscapeDataString(agentName)}";
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"'{githubUrl}' should point to an org or enterprise.");
|
||||
}
|
||||
|
||||
var githubApiUrl = $"{GetEntityUrl(githubUrl)}/runners?name={Uri.EscapeDataString(agentName)}";
|
||||
var runnersList = await RetryRequest<ListRunnersResponse>(githubApiUrl, githubToken, RequestType.Get, 3, "Failed to get agents pools");
|
||||
|
||||
return runnersList.ToTaskAgents();
|
||||
}
|
||||
|
||||
public async Task<List<TaskAgentPool>> GetRunnerGroupsAsync(string githubUrl, string githubToken)
|
||||
{
|
||||
var githubApiUrl = "";
|
||||
var gitHubUrlBuilder = new UriBuilder(githubUrl);
|
||||
var path = gitHubUrlBuilder.Path.Split('/', '\\', StringSplitOptions.RemoveEmptyEntries);
|
||||
var isOrgRunner = path.Length == 1;
|
||||
var isRepoOrEnterpriseRunner = path.Length == 2;
|
||||
var isRepoRunner = isRepoOrEnterpriseRunner && !string.Equals(path[0], "enterprises", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (isOrgRunner)
|
||||
{
|
||||
// org runner
|
||||
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/orgs/{path[0]}/actions/runner-groups";
|
||||
}
|
||||
else
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/orgs/{path[0]}/actions/runner-groups";
|
||||
}
|
||||
}
|
||||
else if (isRepoOrEnterpriseRunner)
|
||||
{
|
||||
// Repository Runner
|
||||
if (isRepoRunner)
|
||||
{
|
||||
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/repos/{path[0]}/{path[1]}/actions/runner-groups";
|
||||
}
|
||||
else
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/repos/{path[0]}/{path[1]}/actions/runner-groups";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Enterprise Runner
|
||||
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/{path[0]}/{path[1]}/actions/runner-groups";
|
||||
}
|
||||
else
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/{path[0]}/{path[1]}/actions/runner-groups";
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"'{githubUrl}' should point to an org or enterprise.");
|
||||
}
|
||||
|
||||
var githubApiUrl = $"{GetEntityUrl(githubUrl)}/runner-groups";
|
||||
var agentPools = await RetryRequest<RunnerGroupList>(githubApiUrl, githubToken, RequestType.Get, 3, "Failed to get agents pools");
|
||||
|
||||
return agentPools?.ToAgentPoolList();
|
||||
}
|
||||
|
||||
@@ -204,6 +103,12 @@ namespace GitHub.Runner.Common
|
||||
return await RetryRequest<DistributedTask.WebApi.Runner>(githubApiUrl, githubToken, RequestType.Post, 3, "Failed to add agent", body);
|
||||
}
|
||||
|
||||
public async Task DeleteRunnerAsync(string githubUrl, string githubToken, ulong runnerId)
|
||||
{
|
||||
var githubApiUrl = $"{GetEntityUrl(githubUrl)}/runners/{runnerId}";
|
||||
await RetryRequest<DistributedTask.WebApi.Runner>(githubApiUrl, githubToken, RequestType.Delete, 3, "Failed to delete agent");
|
||||
}
|
||||
|
||||
private async Task<T> RetryRequest<T>(string githubApiUrl, string githubToken, RequestType requestType, int maxRetryAttemptsCount = 5, string errorMessage = null, StringContent body = null)
|
||||
{
|
||||
int retry = 0;
|
||||
@@ -220,13 +125,22 @@ namespace GitHub.Runner.Common
|
||||
try
|
||||
{
|
||||
HttpResponseMessage response = null;
|
||||
if (requestType == RequestType.Get)
|
||||
switch (requestType)
|
||||
{
|
||||
case RequestType.Get:
|
||||
response = await httpClient.GetAsync(githubApiUrl);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
case RequestType.Post:
|
||||
response = await httpClient.PostAsync(githubApiUrl, body);
|
||||
break;
|
||||
case RequestType.Patch:
|
||||
response = await httpClient.PatchAsync(githubApiUrl, body);
|
||||
break;
|
||||
case RequestType.Delete:
|
||||
response = await httpClient.DeleteAsync(githubApiUrl);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(requestType), requestType, null);
|
||||
}
|
||||
|
||||
if (response != null)
|
||||
@@ -261,5 +175,61 @@ namespace GitHub.Runner.Common
|
||||
await Task.Delay(backOff);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetEntityUrl(string githubUrl)
|
||||
{
|
||||
var githubApiUrl = "";
|
||||
var gitHubUrlBuilder = new UriBuilder(githubUrl);
|
||||
var path = gitHubUrlBuilder.Path.Split('/', '\\', StringSplitOptions.RemoveEmptyEntries);
|
||||
var isOrgRunner = path.Length == 1;
|
||||
var isRepoOrEnterpriseRunner = path.Length == 2;
|
||||
var isRepoRunner = isRepoOrEnterpriseRunner && !string.Equals(path[0], "enterprises", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (isOrgRunner)
|
||||
{
|
||||
// org runner
|
||||
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/orgs/{path[0]}/actions";
|
||||
}
|
||||
else
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/orgs/{path[0]}/actions";
|
||||
}
|
||||
}
|
||||
else if (isRepoOrEnterpriseRunner)
|
||||
{
|
||||
// Repository Runner
|
||||
if (isRepoRunner)
|
||||
{
|
||||
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/repos/{path[0]}/{path[1]}/actions";
|
||||
}
|
||||
else
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/repos/{path[0]}/{path[1]}/actions";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Enterprise Runner
|
||||
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/{path[0]}/{path[1]}/actions";
|
||||
}
|
||||
else
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/{path[0]}/{path[1]}/actions";
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"'{githubUrl}' should point to an org or enterprise.");
|
||||
}
|
||||
|
||||
return githubApiUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,33 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using GitHub.Runner.Sdk;
|
||||
|
||||
namespace GitHub.Runner.Common.Util
|
||||
{
|
||||
public static class NodeUtil
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents details about an environment variable, including its value and source
|
||||
/// </summary>
|
||||
private class EnvironmentVariableInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets whether the value evaluates to true
|
||||
/// </summary>
|
||||
public bool IsTrue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the value came from the workflow environment
|
||||
/// </summary>
|
||||
public bool FromWorkflow { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the value came from the system environment
|
||||
/// </summary>
|
||||
public bool FromSystem { get; set; }
|
||||
}
|
||||
|
||||
private const string _defaultNodeVersion = "node20";
|
||||
public static readonly ReadOnlyCollection<string> BuiltInNodeVersions = new(new[] { "node20" });
|
||||
public static string GetInternalNodeVersion()
|
||||
@@ -18,5 +41,122 @@ namespace GitHub.Runner.Common.Util
|
||||
}
|
||||
return _defaultNodeVersion;
|
||||
}
|
||||
/// <summary>
|
||||
/// Determines the appropriate Node version for Actions to use
|
||||
/// </summary>
|
||||
/// <param name="workflowEnvironment">Optional dictionary containing workflow-level environment variables</param>
|
||||
/// <param name="useNode24ByDefault">Feature flag indicating if Node 24 should be the default</param>
|
||||
/// <param name="requireNode24">Feature flag indicating if Node 24 is required</param>
|
||||
/// <returns>The Node version to use (node20 or node24) and warning message if both env vars are set</returns>
|
||||
public static (string nodeVersion, string warningMessage) DetermineActionsNodeVersion(
|
||||
IDictionary<string, string> workflowEnvironment = null,
|
||||
bool useNode24ByDefault = false,
|
||||
bool requireNode24 = false)
|
||||
{
|
||||
// Phase 3: Always use Node 24 regardless of environment variables
|
||||
if (requireNode24)
|
||||
{
|
||||
return (Constants.Runner.NodeMigration.Node24, null);
|
||||
}
|
||||
|
||||
// Get environment variable details with source information
|
||||
var forceNode24Details = GetEnvironmentVariableDetails(
|
||||
Constants.Runner.NodeMigration.ForceNode24Variable, workflowEnvironment);
|
||||
|
||||
var allowUnsecureNodeDetails = GetEnvironmentVariableDetails(
|
||||
Constants.Runner.NodeMigration.AllowUnsecureNodeVersionVariable, workflowEnvironment);
|
||||
|
||||
bool forceNode24 = forceNode24Details.IsTrue;
|
||||
bool allowUnsecureNode = allowUnsecureNodeDetails.IsTrue;
|
||||
string warningMessage = null;
|
||||
|
||||
// Check if both flags are set from the same source
|
||||
bool bothFromWorkflow = forceNode24Details.IsTrue && allowUnsecureNodeDetails.IsTrue &&
|
||||
forceNode24Details.FromWorkflow && allowUnsecureNodeDetails.FromWorkflow;
|
||||
|
||||
bool bothFromSystem = forceNode24Details.IsTrue && allowUnsecureNodeDetails.IsTrue &&
|
||||
forceNode24Details.FromSystem && allowUnsecureNodeDetails.FromSystem;
|
||||
|
||||
// Handle the case when both are set in the same source
|
||||
if (bothFromWorkflow || bothFromSystem)
|
||||
{
|
||||
string source = bothFromWorkflow ? "workflow" : "system";
|
||||
string defaultVersion = useNode24ByDefault ? Constants.Runner.NodeMigration.Node24 : Constants.Runner.NodeMigration.Node20;
|
||||
warningMessage = $"Both {Constants.Runner.NodeMigration.ForceNode24Variable} and {Constants.Runner.NodeMigration.AllowUnsecureNodeVersionVariable} environment variables are set to true in the {source} environment. This is likely a configuration error. Using the default Node version: {defaultVersion}.";
|
||||
return (defaultVersion, warningMessage);
|
||||
}
|
||||
|
||||
// Phase 2: Node 24 is the default
|
||||
if (useNode24ByDefault)
|
||||
{
|
||||
if (allowUnsecureNode)
|
||||
{
|
||||
return (Constants.Runner.NodeMigration.Node20, null);
|
||||
}
|
||||
|
||||
return (Constants.Runner.NodeMigration.Node24, null);
|
||||
}
|
||||
|
||||
// Phase 1: Node 20 is the default
|
||||
if (forceNode24)
|
||||
{
|
||||
return (Constants.Runner.NodeMigration.Node24, null);
|
||||
}
|
||||
|
||||
return (Constants.Runner.NodeMigration.Node20, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if Node24 is requested but running on ARM32 Linux, and determines if fallback is needed.
|
||||
/// </summary>
|
||||
/// <param name="preferredVersion">The preferred Node version</param>
|
||||
/// <returns>A tuple containing the adjusted node version and an optional warning message</returns>
|
||||
public static (string nodeVersion, string warningMessage) CheckNodeVersionForLinuxArm32(string preferredVersion)
|
||||
{
|
||||
if (string.Equals(preferredVersion, Constants.Runner.NodeMigration.Node24, StringComparison.OrdinalIgnoreCase) &&
|
||||
Constants.Runner.PlatformArchitecture.Equals(Constants.Architecture.Arm) &&
|
||||
Constants.Runner.Platform.Equals(Constants.OSPlatform.Linux))
|
||||
{
|
||||
return (Constants.Runner.NodeMigration.Node20, "Node 24 is not supported on Linux ARM32 platforms. Falling back to Node 20.");
|
||||
}
|
||||
|
||||
return (preferredVersion, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets detailed information about an environment variable from both workflow and system environments
|
||||
/// </summary>
|
||||
/// <param name="variableName">The name of the environment variable</param>
|
||||
/// <param name="workflowEnvironment">Optional dictionary containing workflow-level environment variables</param>
|
||||
/// <returns>An EnvironmentVariableInfo object containing details about the variable from both sources</returns>
|
||||
private static EnvironmentVariableInfo GetEnvironmentVariableDetails(string variableName, IDictionary<string, string> workflowEnvironment)
|
||||
{
|
||||
var info = new EnvironmentVariableInfo();
|
||||
|
||||
// Check workflow environment
|
||||
bool foundInWorkflow = false;
|
||||
string workflowValue = null;
|
||||
|
||||
if (workflowEnvironment != null && workflowEnvironment.TryGetValue(variableName, out workflowValue))
|
||||
{
|
||||
foundInWorkflow = true;
|
||||
info.FromWorkflow = true;
|
||||
info.IsTrue = StringUtil.ConvertToBoolean(workflowValue); // Workflow value takes precedence for the boolean value
|
||||
}
|
||||
|
||||
// Also check system environment
|
||||
string systemValue = Environment.GetEnvironmentVariable(variableName);
|
||||
bool foundInSystem = !string.IsNullOrEmpty(systemValue);
|
||||
|
||||
info.FromSystem = foundInSystem;
|
||||
|
||||
// If not found in workflow, use system values
|
||||
if (!foundInWorkflow)
|
||||
{
|
||||
info.IsTrue = StringUtil.ConvertToBoolean(systemValue);
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,6 +387,14 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
},
|
||||
};
|
||||
|
||||
if (agent.Properties.GetValue("EnableAuthMigrationByDefault", false) &&
|
||||
agent.Properties.TryGetValue<string>("AuthorizationUrlV2", out var authUrlV2) &&
|
||||
!string.IsNullOrEmpty(authUrlV2))
|
||||
{
|
||||
credentialData.Data["enableAuthMigrationByDefault"] = "true";
|
||||
credentialData.Data["authorizationUrlV2"] = authUrlV2;
|
||||
}
|
||||
|
||||
// Save the negotiated OAuth credential data
|
||||
_store.SaveCredential(credentialData);
|
||||
}
|
||||
@@ -529,6 +537,14 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
if (isConfigured && hasCredentials)
|
||||
{
|
||||
RunnerSettings settings = _store.GetSettings();
|
||||
|
||||
if (settings.UseV2Flow)
|
||||
{
|
||||
var deletionToken = await GetRunnerTokenAsync(command, settings.GitHubUrl, "remove");
|
||||
await _dotcomServer.DeleteRunnerAsync(settings.GitHubUrl, deletionToken, settings.AgentId);
|
||||
}
|
||||
else
|
||||
{
|
||||
var credentialManager = HostContext.GetService<ICredentialManager>();
|
||||
|
||||
// Get the credentials
|
||||
@@ -560,11 +576,12 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
else
|
||||
{
|
||||
await _runnerServer.DeleteAgentAsync(settings.AgentId);
|
||||
}
|
||||
}
|
||||
|
||||
_term.WriteLine();
|
||||
_term.WriteSuccessMessage("Runner removed successfully");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_term.WriteLine("Cannot connect to server, because config files are missing. Skipping removing runner from the server.");
|
||||
|
||||
@@ -110,7 +110,12 @@ namespace GitHub.Runner.Listener
|
||||
{
|
||||
var jwt = JsonWebToken.Create(accessToken);
|
||||
var claims = jwt.ExtractClaims();
|
||||
orchestrationId = claims.FirstOrDefault(x => string.Equals(x.Type, "orch_id", StringComparison.OrdinalIgnoreCase))?.Value;
|
||||
if (string.IsNullOrEmpty(orchestrationId))
|
||||
{
|
||||
orchestrationId = claims.FirstOrDefault(x => string.Equals(x.Type, "orchid", StringComparison.OrdinalIgnoreCase))?.Value;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(orchestrationId))
|
||||
{
|
||||
Trace.Info($"Pull OrchestrationId {orchestrationId} from JWT claims");
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="9.0.8" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
||||
<PackageReference Include="System.Threading.Channels" Version="8.0.0" />
|
||||
|
||||
@@ -38,6 +38,7 @@ namespace GitHub.Runner.Sdk
|
||||
if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_TLS_NO_VERIFY")))
|
||||
{
|
||||
VssClientHttpRequestSettings.Default.ServerCertificateValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
|
||||
RawClientHttpRequestSettings.Default.ServerCertificateValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
|
||||
}
|
||||
|
||||
var rawHeaderValues = new List<ProductInfoHeaderValue>();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7" />
|
||||
</startup>
|
||||
</configuration>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(PackageRuntime)' != 'win-arm64' ">
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
|
||||
@@ -688,7 +688,8 @@ namespace GitHub.Runner.Worker
|
||||
{
|
||||
if (MessageUtil.IsRunServiceJob(executionContext.Global.Variables.Get(Constants.Variables.System.JobRequestType)))
|
||||
{
|
||||
actionDownloadInfos = await launchServer.ResolveActionsDownloadInfoAsync(executionContext.Global.Plan.PlanId, executionContext.Root.Id, new WebApi.ActionReferenceList { Actions = actionReferences }, executionContext.CancellationToken);
|
||||
var displayHelpfulActionsDownloadErrors = executionContext.Global.Variables.GetBoolean(Constants.Runner.Features.DisplayHelpfulActionsDownloadErrors) ?? false;
|
||||
actionDownloadInfos = await launchServer.ResolveActionsDownloadInfoAsync(executionContext.Global.Plan.PlanId, executionContext.Root.Id, new WebApi.ActionReferenceList { Actions = actionReferences }, executionContext.CancellationToken, displayHelpfulActionsDownloadErrors);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -450,7 +450,8 @@ 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, "node20", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(usingToken.Value, "node24", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (string.IsNullOrEmpty(mainToken?.Value))
|
||||
{
|
||||
@@ -490,7 +491,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', 'node16', 'node20' or 'node24' instead.");
|
||||
}
|
||||
}
|
||||
else if (pluginToken != null)
|
||||
@@ -501,7 +502,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', 'node16', 'node20' or 'node24'.");
|
||||
}
|
||||
|
||||
private void ConvertInputs(
|
||||
|
||||
@@ -58,10 +58,41 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
var nodeData = data as NodeJSActionExecutionData;
|
||||
|
||||
// With node12 EoL in 04/2022 and node16 EoL in 09/23, we want to execute all JS actions using node20
|
||||
// With node20 EoL approaching, we're preparing to migrate to node24
|
||||
if (string.Equals(nodeData.NodeVersion, "node12", StringComparison.InvariantCultureIgnoreCase) ||
|
||||
string.Equals(nodeData.NodeVersion, "node16", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
nodeData.NodeVersion = "node20";
|
||||
nodeData.NodeVersion = Common.Constants.Runner.NodeMigration.Node20;
|
||||
}
|
||||
|
||||
// Check if node20 was explicitly specified in the action
|
||||
// We don't modify if node24 was explicitly specified
|
||||
if (string.Equals(nodeData.NodeVersion, Constants.Runner.NodeMigration.Node20, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
bool useNode24ByDefault = executionContext.Global.Variables?.GetBoolean(Constants.Runner.NodeMigration.UseNode24ByDefaultFlag) ?? false;
|
||||
bool requireNode24 = executionContext.Global.Variables?.GetBoolean(Constants.Runner.NodeMigration.RequireNode24Flag) ?? false;
|
||||
|
||||
var (nodeVersion, configWarningMessage) = NodeUtil.DetermineActionsNodeVersion(environment, useNode24ByDefault, requireNode24);
|
||||
var (finalNodeVersion, platformWarningMessage) = NodeUtil.CheckNodeVersionForLinuxArm32(nodeVersion);
|
||||
nodeData.NodeVersion = finalNodeVersion;
|
||||
|
||||
if (!string.IsNullOrEmpty(configWarningMessage))
|
||||
{
|
||||
executionContext.Warning(configWarningMessage);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(platformWarningMessage))
|
||||
{
|
||||
executionContext.Warning(platformWarningMessage);
|
||||
}
|
||||
|
||||
// Show information about Node 24 migration in Phase 2
|
||||
if (useNode24ByDefault && !requireNode24 && string.Equals(finalNodeVersion, Constants.Runner.NodeMigration.Node24, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
string infoMessage = "Node 20 is being deprecated. This workflow is running with Node 24 by default. " +
|
||||
"If you need to temporarily use Node 20, you can set the ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true environment variable.";
|
||||
executionContext.Output(infoMessage);
|
||||
}
|
||||
}
|
||||
|
||||
(handler as INodeScriptActionHandler).Data = nodeData;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -9,7 +8,6 @@ using GitHub.Runner.Common;
|
||||
using GitHub.Runner.Sdk;
|
||||
using System.Linq;
|
||||
using GitHub.Runner.Worker.Container.ContainerHooks;
|
||||
using System.IO;
|
||||
using System.Threading.Channels;
|
||||
|
||||
namespace GitHub.Runner.Worker.Handlers
|
||||
@@ -60,7 +58,14 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
|
||||
public Task<string> DetermineNodeRuntimeVersion(IExecutionContext executionContext, string preferredVersion)
|
||||
{
|
||||
return Task.FromResult<string>(preferredVersion);
|
||||
// Use NodeUtil to check if Node24 is requested but we're on ARM32 Linux
|
||||
var (nodeVersion, warningMessage) = Common.Util.NodeUtil.CheckNodeVersionForLinuxArm32(preferredVersion);
|
||||
if (!string.IsNullOrEmpty(warningMessage))
|
||||
{
|
||||
executionContext.Warning(warningMessage);
|
||||
}
|
||||
|
||||
return Task.FromResult(nodeVersion);
|
||||
}
|
||||
|
||||
public async Task<int> ExecuteAsync(IExecutionContext context,
|
||||
@@ -137,8 +142,12 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
|
||||
public async Task<string> DetermineNodeRuntimeVersion(IExecutionContext executionContext, string preferredVersion)
|
||||
{
|
||||
// Optimistically use the default
|
||||
string nodeExternal = preferredVersion;
|
||||
// Use NodeUtil to check if Node24 is requested but we're on ARM32 Linux
|
||||
var (nodeExternal, warningMessage) = Common.Util.NodeUtil.CheckNodeVersionForLinuxArm32(preferredVersion);
|
||||
if (!string.IsNullOrEmpty(warningMessage))
|
||||
{
|
||||
executionContext.Warning(warningMessage);
|
||||
}
|
||||
|
||||
if (FeatureManager.IsContainerHooksEnabled(executionContext.Global.Variables))
|
||||
{
|
||||
@@ -264,7 +273,14 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
|
||||
private string CheckPlatformForAlpineContainer(IExecutionContext executionContext, string preferredVersion)
|
||||
{
|
||||
string nodeExternal = preferredVersion;
|
||||
// Use NodeUtil to check if Node24 is requested but we're on ARM32 Linux
|
||||
var (nodeExternal, warningMessage) = Common.Util.NodeUtil.CheckNodeVersionForLinuxArm32(preferredVersion);
|
||||
if (!string.IsNullOrEmpty(warningMessage))
|
||||
{
|
||||
executionContext.Warning(warningMessage);
|
||||
}
|
||||
|
||||
// Check for Alpine container compatibility
|
||||
if (!Constants.Runner.PlatformArchitecture.Equals(Constants.Architecture.X64))
|
||||
{
|
||||
var os = Constants.Runner.Platform.ToString();
|
||||
|
||||
@@ -49,9 +49,12 @@ namespace GitHub.Runner.Worker
|
||||
// add orchestration id to useragent for better correlation.
|
||||
if (message.Variables.TryGetValue(Constants.Variables.System.OrchestrationId, out VariableValue orchestrationId) &&
|
||||
!string.IsNullOrEmpty(orchestrationId.Value))
|
||||
{
|
||||
if (!HostContext.UserAgents.Any(x => string.Equals(x.Product?.Name, "OrchestrationId", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
// make the orchestration id the first item in the user-agent header to avoid get truncated in server log.
|
||||
HostContext.UserAgents.Insert(0, new ProductInfoHeaderValue("OrchestrationId", orchestrationId.Value));
|
||||
}
|
||||
|
||||
// make sure orchestration id is in the user-agent header.
|
||||
VssUtil.InitializeVssClientSettings(HostContext.UserAgents, HostContext.WebProxy);
|
||||
|
||||
@@ -106,6 +106,18 @@ namespace GitHub.Services.Common
|
||||
{
|
||||
VssTraceActivity traceActivity = VssTraceActivity.Current;
|
||||
|
||||
if (!m_appliedServerCertificateValidationCallbackToTransportHandler &&
|
||||
request.RequestUri.Scheme == "https")
|
||||
{
|
||||
HttpClientHandler httpClientHandler = m_transportHandler as HttpClientHandler;
|
||||
if (httpClientHandler != null &&
|
||||
this.Settings.ServerCertificateValidationCallback != null)
|
||||
{
|
||||
httpClientHandler.ServerCertificateCustomValidationCallback = this.Settings.ServerCertificateValidationCallback;
|
||||
}
|
||||
m_appliedServerCertificateValidationCallbackToTransportHandler = true;
|
||||
}
|
||||
|
||||
lock (m_thisLock)
|
||||
{
|
||||
// Ensure that we attempt to use the most appropriate authentication mechanism by default.
|
||||
@@ -291,6 +303,7 @@ namespace GitHub.Services.Common
|
||||
}
|
||||
}
|
||||
|
||||
private bool m_appliedServerCertificateValidationCallbackToTransportHandler;
|
||||
private readonly HttpMessageHandler m_transportHandler;
|
||||
private HttpMessageInvoker m_messageInvoker;
|
||||
private CredentialWrapper m_credentialWrapper;
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.Storage.Blobs" Version="12.24.0" />
|
||||
<PackageReference Include="Azure.Storage.Blobs" Version="12.25.0" />
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="6.0.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.Cng" Version="5.0.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.Pkcs" Version="8.0.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="8.0.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="9.0.8" />
|
||||
<PackageReference Include="Minimatch" Version="2.0.0" />
|
||||
<PackageReference Include="YamlDotNet.Signed" Version="5.3.0" />
|
||||
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
||||
|
||||
@@ -81,4 +81,25 @@ namespace GitHub.Services.Launch.Contracts
|
||||
[DataMember(EmitDefaultValue = false, Name = "actions")]
|
||||
public IDictionary<string, ActionDownloadInfoResponse> Actions { get; set; }
|
||||
}
|
||||
|
||||
[DataContract]
|
||||
public class ActionDownloadResolutionError
|
||||
{
|
||||
/// <summary>
|
||||
/// The error message associated with the action download error.
|
||||
/// </summary>
|
||||
[DataMember(EmitDefaultValue = false, Name = "message")]
|
||||
public string Message { get; set; }
|
||||
}
|
||||
|
||||
[DataContract]
|
||||
public class ActionDownloadResolutionErrorCollection
|
||||
{
|
||||
/// <summary>
|
||||
/// A mapping of action specifications to their download errors.
|
||||
/// <remarks>The key is the full name of the action plus version, e.g. "actions/checkout@v2".</remarks>
|
||||
/// </summary>
|
||||
[DataMember(EmitDefaultValue = false, Name = "errors")]
|
||||
public IDictionary<string, ActionDownloadResolutionError> Errors { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Formatting;
|
||||
using System.Net.Http.Headers;
|
||||
@@ -32,11 +33,52 @@ namespace GitHub.Services.Launch.Client
|
||||
public async Task<ActionDownloadInfoCollection> GetResolveActionsDownloadInfoAsync(Guid planId, Guid jobId, ActionReferenceList actionReferenceList, CancellationToken cancellationToken)
|
||||
{
|
||||
var GetResolveActionsDownloadInfoURLEndpoint = new Uri(m_launchServiceUrl, $"/actions/build/{planId.ToString()}/jobs/{jobId.ToString()}/runnerresolve/actions");
|
||||
return ToServerData(await GetLaunchSignedURLResponse<ActionReferenceRequestList, ActionDownloadInfoResponseCollection>(GetResolveActionsDownloadInfoURLEndpoint, ToGitHubData(actionReferenceList), cancellationToken));
|
||||
var response = await GetLaunchSignedURLResponse<ActionReferenceRequestList>(GetResolveActionsDownloadInfoURLEndpoint, ToGitHubData(actionReferenceList), cancellationToken);
|
||||
return ToServerData(await ReadJsonContentAsync<ActionDownloadInfoResponseCollection>(response, cancellationToken));
|
||||
}
|
||||
|
||||
// Resolve Actions
|
||||
private async Task<T> GetLaunchSignedURLResponse<R, T>(Uri uri, R request, CancellationToken cancellationToken)
|
||||
public async Task<ActionDownloadInfoCollection> GetResolveActionsDownloadInfoAsyncV2(Guid planId, Guid jobId, ActionReferenceList actionReferenceList, CancellationToken cancellationToken)
|
||||
{
|
||||
var GetResolveActionsDownloadInfoURLEndpoint = new Uri(m_launchServiceUrl, $"/actions/build/{planId.ToString()}/jobs/{jobId.ToString()}/runnerresolve/actions");
|
||||
var response = await GetLaunchSignedURLResponse<ActionReferenceRequestList>(GetResolveActionsDownloadInfoURLEndpoint, ToGitHubData(actionReferenceList), cancellationToken);
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
// Success response - deserialize the action download info
|
||||
return ToServerData(await ReadJsonContentAsync<ActionDownloadInfoResponseCollection>(response, cancellationToken));
|
||||
}
|
||||
|
||||
var responseError = response.ReasonPhrase ?? "";
|
||||
if (response.StatusCode == HttpStatusCode.UnprocessableEntity)
|
||||
{
|
||||
// 422 response - unresolvable actions, error details are in the body
|
||||
var errors = await ReadJsonContentAsync<ActionDownloadResolutionErrorCollection>(response, cancellationToken);
|
||||
string combinedErrorMessage;
|
||||
if (errors?.Errors != null && errors.Errors.Any())
|
||||
{
|
||||
combinedErrorMessage = String.Join(". ", errors.Errors.Select(kvp => kvp.Value.Message));
|
||||
}
|
||||
else
|
||||
{
|
||||
combinedErrorMessage = responseError;
|
||||
}
|
||||
|
||||
throw new UnresolvableActionDownloadInfoException(combinedErrorMessage);
|
||||
}
|
||||
else if (response.StatusCode == HttpStatusCode.TooManyRequests)
|
||||
{
|
||||
// Here we want to add a message so customers don't think it's a rate limit scoped to them
|
||||
// Ideally this would be 500 but the runner retries 500s, which we don't want to do when we're being rate limited
|
||||
// See: https://github.com/github/ecosystem-api/issues/4084
|
||||
throw new NonRetryableActionDownloadInfoException(responseError + " (GitHub has reached an internal rate limit, please try again later)");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception(responseError);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<HttpResponseMessage> GetLaunchSignedURLResponse<R>(Uri uri, R request, CancellationToken cancellationToken)
|
||||
{
|
||||
using (HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post, uri))
|
||||
{
|
||||
@@ -46,10 +88,7 @@ namespace GitHub.Services.Launch.Client
|
||||
using (HttpContent content = new ObjectContent<R>(request, m_formatter))
|
||||
{
|
||||
requestMessage.Content = content;
|
||||
using (var response = await SendAsync(requestMessage, HttpCompletionOption.ResponseContentRead, cancellationToken: cancellationToken))
|
||||
{
|
||||
return await ReadJsonContentAsync<T>(response, cancellationToken);
|
||||
}
|
||||
return await SendAsync(requestMessage, HttpCompletionOption.ResponseContentRead, cancellationToken: cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -520,8 +520,8 @@ namespace GitHub.Services.Results.Client
|
||||
Number = r.Order.GetValueOrDefault(),
|
||||
Name = r.Name,
|
||||
Status = ConvertStateToStatus(r.State.GetValueOrDefault()),
|
||||
StartedAt = r.StartTime?.ToString(Constants.TimestampFormat),
|
||||
CompletedAt = r.FinishTime?.ToString(Constants.TimestampFormat),
|
||||
StartedAt = r.StartTime?.ToString(Constants.TimestampFormat, CultureInfo.InvariantCulture),
|
||||
CompletedAt = r.FinishTime?.ToString(Constants.TimestampFormat, CultureInfo.InvariantCulture),
|
||||
Conclusion = ConvertResultToConclusion(r.Result)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -978,7 +978,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
_messageListener.Verify(x => x.GetNextMessageAsync(It.IsAny<CancellationToken>()), Times.AtLeast(2));
|
||||
_messageListener.Verify(x => x.DeleteMessageAsync(It.IsAny<TaskAgentMessage>()), Times.AtLeast(2));
|
||||
_messageListener.Verify(x => x.DeleteSessionAsync(), Times.Once());
|
||||
_credentialManager.Verify(x => x.LoadCredentials(true), Times.Exactly(2));
|
||||
_credentialManager.Verify(x => x.LoadCredentials(true), Times.AtLeast(2));
|
||||
|
||||
Assert.False(hc.AllowAuthMigration);
|
||||
}
|
||||
|
||||
126
src/Test/L0/Sdk/LaunchWebApi/LaunchHttpClientL0.cs
Normal file
126
src/Test/L0/Sdk/LaunchWebApi/LaunchHttpClientL0.cs
Normal file
@@ -0,0 +1,126 @@
|
||||
using GitHub.Actions.RunService.WebApi;
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
using GitHub.Services.Launch.Client;
|
||||
using GitHub.Services.Launch.Contracts;
|
||||
using Moq;
|
||||
using Moq.Protected;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace GitHub.Actions.RunService.WebApi.Tests
|
||||
{
|
||||
public sealed class LaunchHttpClientL0
|
||||
{
|
||||
[Fact]
|
||||
public async Task GetResolveActionsDownloadInfoAsync_SuccessResponse()
|
||||
{
|
||||
var baseUrl = new Uri("https://api.github.com/");
|
||||
var planId = Guid.NewGuid();
|
||||
var jobId = Guid.NewGuid();
|
||||
var token = "fake-token";
|
||||
|
||||
var actionReferenceList = new ActionReferenceList
|
||||
{
|
||||
Actions = new List<ActionReference>
|
||||
{
|
||||
new ActionReference
|
||||
{
|
||||
NameWithOwner = "owner1/action1",
|
||||
Ref = "0123456789"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var responseContent = @"{
|
||||
""actions"": {
|
||||
""owner1/action1@0123456789"": {
|
||||
""name"": ""owner1/action1"",
|
||||
""resolved_name"": ""owner1/action1"",
|
||||
""resolved_sha"": ""0123456789"",
|
||||
""version"": ""0123456789"",
|
||||
""zip_url"": ""https://github.com/owner1/action1/zip"",
|
||||
""tar_url"": ""https://github.com/owner1/action1/tar""
|
||||
}
|
||||
}
|
||||
}";
|
||||
|
||||
var httpResponse = new HttpResponseMessage(HttpStatusCode.OK)
|
||||
{
|
||||
Content = new StringContent(responseContent, Encoding.UTF8, "application/json"),
|
||||
RequestMessage = new HttpRequestMessage()
|
||||
{
|
||||
RequestUri = new Uri($"{baseUrl}actions/build/{planId}/jobs/{jobId}/runnerresolve/actions")
|
||||
}
|
||||
};
|
||||
|
||||
var mockHandler = new Mock<HttpMessageHandler>();
|
||||
mockHandler.Protected().Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
|
||||
.ReturnsAsync(httpResponse);
|
||||
|
||||
var client = new LaunchHttpClient(baseUrl, mockHandler.Object, token, false);
|
||||
var result = await client.GetResolveActionsDownloadInfoAsyncV2(planId, jobId, actionReferenceList, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.NotEmpty(result.Actions);
|
||||
Assert.Equal(actionReferenceList.Actions.Count, result.Actions.Count);
|
||||
Assert.True(result.Actions.ContainsKey("owner1/action1@0123456789"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetResolveActionsDownloadInfoAsync_UnprocessableEntityResponse()
|
||||
{
|
||||
var baseUrl = new Uri("https://api.github.com/");
|
||||
var planId = Guid.NewGuid();
|
||||
var jobId = Guid.NewGuid();
|
||||
var token = "fake-token";
|
||||
|
||||
var actionReferenceList = new ActionReferenceList
|
||||
{
|
||||
Actions = new List<ActionReference>
|
||||
{
|
||||
new ActionReference
|
||||
{
|
||||
NameWithOwner = "owner1/action1",
|
||||
Ref = "0123456789"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var responseContent = @"{
|
||||
""errors"": {
|
||||
""owner1/invalid-action@0123456789"": {
|
||||
""message"": ""Unable to resolve action 'owner1/invalid-action@0123456789', repository not found""
|
||||
}
|
||||
}
|
||||
}";
|
||||
|
||||
var httpResponse = new HttpResponseMessage(HttpStatusCode.UnprocessableEntity)
|
||||
{
|
||||
Content = new StringContent(responseContent, Encoding.UTF8, "application/json"),
|
||||
RequestMessage = new HttpRequestMessage()
|
||||
{
|
||||
RequestUri = new Uri($"{baseUrl}actions/build/{planId}/jobs/{jobId}/runnerresolve/actions")
|
||||
}
|
||||
};
|
||||
|
||||
var mockHandler = new Mock<HttpMessageHandler>();
|
||||
mockHandler.Protected().Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
|
||||
.ReturnsAsync(httpResponse);
|
||||
|
||||
var client = new LaunchHttpClient(baseUrl, mockHandler.Object, token, false);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<UnresolvableActionDownloadInfoException>(
|
||||
() => client.GetResolveActionsDownloadInfoAsyncV2(planId, jobId, actionReferenceList, CancellationToken.None));
|
||||
|
||||
Assert.Contains("repository not found", exception.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
120
src/Test/L0/Util/NodeUtilL0.cs
Normal file
120
src/Test/L0/Util/NodeUtilL0.cs
Normal file
@@ -0,0 +1,120 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GitHub.Runner.Common;
|
||||
using GitHub.Runner.Common.Util;
|
||||
using Xunit;
|
||||
|
||||
namespace GitHub.Runner.Common.Tests.Util
|
||||
{
|
||||
public class NodeUtilL0
|
||||
{
|
||||
// We're testing the logic with feature flags
|
||||
[Theory]
|
||||
[InlineData(false, false, false, false, "node20", false)] // Phase 1: No env vars
|
||||
[InlineData(false, false, false, true, "node20", false)] // Phase 1: Allow unsecure (redundant)
|
||||
[InlineData(false, false, true, false, "node24", false)] // Phase 1: Force node24
|
||||
[InlineData(false, false, true, true, "node20", true)] // Phase 1: Both flags (use phase default + warning)
|
||||
[InlineData(false, true, false, false, "node24", false)] // Phase 2: No env vars
|
||||
[InlineData(false, true, false, true, "node20", false)] // Phase 2: Allow unsecure
|
||||
[InlineData(false, true, true, false, "node24", false)] // Phase 2: Force node24 (redundant)
|
||||
[InlineData(false, true, true, true, "node24", true)] // Phase 2: Both flags (use phase default + warning)
|
||||
[InlineData(true, false, false, false, "node24", false)] // Phase 3: Always Node 24 regardless of env vars
|
||||
[InlineData(true, false, false, true, "node24", false)] // Phase 3: Always Node 24 regardless of env vars
|
||||
[InlineData(true, false, true, false, "node24", false)] // Phase 3: Always Node 24 regardless of env vars
|
||||
[InlineData(true, false, true, true, "node24", false)] // Phase 3: Always Node 24 regardless of env vars, no warnings in Phase 3
|
||||
public void TestNodeVersionLogic(bool requireNode24, bool useNode24ByDefault, bool forceNode24, bool allowUnsecureNode, string expectedVersion, bool expectWarning)
|
||||
{
|
||||
try
|
||||
{
|
||||
Environment.SetEnvironmentVariable(Constants.Runner.NodeMigration.ForceNode24Variable, forceNode24 ? "true" : null);
|
||||
Environment.SetEnvironmentVariable(Constants.Runner.NodeMigration.AllowUnsecureNodeVersionVariable, allowUnsecureNode ? "true" : null);
|
||||
|
||||
// Call the actual method
|
||||
var (actualVersion, warningMessage) = NodeUtil.DetermineActionsNodeVersion(null, useNode24ByDefault, requireNode24);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedVersion, actualVersion);
|
||||
|
||||
if (expectWarning)
|
||||
{
|
||||
Assert.NotNull(warningMessage);
|
||||
Assert.Contains("Both", warningMessage);
|
||||
Assert.Contains("are set to true", warningMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Null(warningMessage);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Cleanup
|
||||
Environment.SetEnvironmentVariable(Constants.Runner.NodeMigration.ForceNode24Variable, null);
|
||||
Environment.SetEnvironmentVariable(Constants.Runner.NodeMigration.AllowUnsecureNodeVersionVariable, null);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false, false, false, false, false, true, "node20", false)] // Phase 1: System env: none, Workflow env: allow=true
|
||||
[InlineData(false, false, true, false, false, false, "node24", false)] // Phase 1: System env: force node24, Workflow env: none
|
||||
[InlineData(false, true, false, false, true, false, "node24", false)] // Phase 1: System env: none, Workflow env: force node24
|
||||
[InlineData(false, false, false, true, false, true, "node20", false)] // Phase 1: System env: allow=true, Workflow env: allow=true (workflow takes precedence)
|
||||
[InlineData(false, false, true, true, false, false, "node20", true)] // Phase 1: System env: both true, Workflow env: none (use phase default + warning)
|
||||
[InlineData(false, false, false, false, true, true, "node20", true)] // Phase 1: System env: none, Workflow env: both (use phase default + warning)
|
||||
[InlineData(true, false, false, false, false, false, "node24", false)] // Phase 2: System env: none, Workflow env: none
|
||||
[InlineData(true, false, false, true, false, false, "node20", false)] // Phase 2: System env: allow=true, Workflow env: none
|
||||
[InlineData(true, false, false, false, false, true, "node20", false)] // Phase 2: System env: none, Workflow env: allow unsecure
|
||||
[InlineData(true, false, true, false, false, true, "node20", false)] // Phase 2: System env: force node24, Workflow env: allow unsecure
|
||||
[InlineData(true, false, true, true, false, false, "node24", true)] // Phase 2: System env: both true, Workflow env: none (use phase default + warning)
|
||||
[InlineData(true, false, false, false, true, true, "node24", true)] // Phase 2: System env: none, Workflow env: both (phase default + warning)
|
||||
[InlineData(false, true, false, false, false, true, "node24", false)] // Phase 3: System env: none, Workflow env: allow=true (always Node 24 in Phase 3)
|
||||
[InlineData(false, true, true, true, false, false, "node24", false)] // Phase 3: System env: both true, Workflow env: none (always Node 24 in Phase 3, no warning)
|
||||
[InlineData(false, true, false, false, true, true, "node24", false)] // Phase 3: System env: none, Workflow env: both (always Node 24 in Phase 3, no warning)
|
||||
public void TestNodeVersionLogicWithWorkflowEnvironment(bool useNode24ByDefault, bool requireNode24,
|
||||
bool systemForceNode24, bool systemAllowUnsecure,
|
||||
bool workflowForceNode24, bool workflowAllowUnsecure,
|
||||
string expectedVersion, bool expectWarning)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Set system environment variables
|
||||
Environment.SetEnvironmentVariable(Constants.Runner.NodeMigration.ForceNode24Variable, systemForceNode24 ? "true" : null);
|
||||
Environment.SetEnvironmentVariable(Constants.Runner.NodeMigration.AllowUnsecureNodeVersionVariable, systemAllowUnsecure ? "true" : null);
|
||||
|
||||
// Set workflow environment variables
|
||||
var workflowEnv = new Dictionary<string, string>();
|
||||
if (workflowForceNode24)
|
||||
{
|
||||
workflowEnv[Constants.Runner.NodeMigration.ForceNode24Variable] = "true";
|
||||
}
|
||||
if (workflowAllowUnsecure)
|
||||
{
|
||||
workflowEnv[Constants.Runner.NodeMigration.AllowUnsecureNodeVersionVariable] = "true";
|
||||
}
|
||||
|
||||
// Call the actual method with our test parameters
|
||||
var (actualVersion, warningMessage) = NodeUtil.DetermineActionsNodeVersion(workflowEnv, useNode24ByDefault, requireNode24);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedVersion, actualVersion);
|
||||
|
||||
if (expectWarning)
|
||||
{
|
||||
Assert.NotNull(warningMessage);
|
||||
Assert.Contains("Both", warningMessage);
|
||||
Assert.Contains("are set to true", warningMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Null(warningMessage);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Cleanup
|
||||
Environment.SetEnvironmentVariable(Constants.Runner.NodeMigration.ForceNode24Variable, null);
|
||||
Environment.SetEnvironmentVariable(Constants.Runner.NodeMigration.AllowUnsecureNodeVersionVariable, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1659,6 +1659,76 @@ runs:
|
||||
Teardown();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void LoadsNode24ActionDefinition()
|
||||
{
|
||||
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: 'node24'
|
||||
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("node24", (definition.Data.Execution as NodeJSActionExecutionData).NodeVersion);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Teardown();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
@@ -2411,8 +2481,8 @@ runs:
|
||||
});
|
||||
|
||||
_launchServer = new Mock<ILaunchServer>();
|
||||
_launchServer.Setup(x => x.ResolveActionsDownloadInfoAsync(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<ActionReferenceList>(), It.IsAny<CancellationToken>()))
|
||||
.Returns((Guid planId, Guid jobId, ActionReferenceList actions, CancellationToken cancellationToken) =>
|
||||
_launchServer.Setup(x => x.ResolveActionsDownloadInfoAsync(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<ActionReferenceList>(), It.IsAny<CancellationToken>(), It.IsAny<bool>()))
|
||||
.Returns((Guid planId, Guid jobId, ActionReferenceList actions, CancellationToken cancellationToken, bool displayHelpfulActionsDownloadErrors) =>
|
||||
{
|
||||
var result = new ActionDownloadInfoCollection { Actions = new Dictionary<string, ActionDownloadInfo>() };
|
||||
foreach (var action in actions.Actions)
|
||||
|
||||
@@ -505,6 +505,49 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void Load_Node24Action()
|
||||
{
|
||||
try
|
||||
{
|
||||
//Arrange
|
||||
Setup();
|
||||
|
||||
var actionManifest = new ActionManifestManager();
|
||||
actionManifest.Initialize(_hc);
|
||||
|
||||
//Act
|
||||
var result = actionManifest.Load(_ec.Object, Path.Combine(TestUtil.GetTestDataPath(), "node24action.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("node24", nodeAction.NodeVersion);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Teardown();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void Load_NodeAction_Pre()
|
||||
{
|
||||
try
|
||||
@@ -758,7 +801,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
//Assert
|
||||
var err = Assert.Throws<ArgumentException>(() => actionManifest.Load(_ec.Object, action_path));
|
||||
Assert.Contains($"Failed 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', 'node16', 'node20' or 'node24'.")), It.IsAny<ExecutionContextLogOptions>()), Times.Once);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -33,6 +33,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[InlineData("node12", "node20")]
|
||||
[InlineData("node16", "node20")]
|
||||
[InlineData("node20", "node20")]
|
||||
[InlineData("node24", "node24")]
|
||||
public void IsNodeVersionUpgraded(string inputVersion, string expectedVersion)
|
||||
{
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
@@ -41,7 +42,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
var hf = new HandlerFactory();
|
||||
hf.Initialize(hc);
|
||||
|
||||
// Server Feature Flag
|
||||
// Setup variables
|
||||
var variables = new Dictionary<string, VariableValue>();
|
||||
Variables serverVariables = new(hc, variables);
|
||||
|
||||
@@ -72,5 +73,48 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
Assert.Equal(expectedVersion, handler.Data.NodeVersion);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void Node24ExplicitlyRequested_HonoredByDefault()
|
||||
{
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
// Arrange.
|
||||
var hf = new HandlerFactory();
|
||||
hf.Initialize(hc);
|
||||
|
||||
// Basic variables setup
|
||||
var variables = new Dictionary<string, VariableValue>();
|
||||
Variables serverVariables = new(hc, variables);
|
||||
|
||||
_ec.Setup(x => x.Global).Returns(new GlobalContext()
|
||||
{
|
||||
Variables = serverVariables,
|
||||
EnvironmentVariables = new Dictionary<string, string>()
|
||||
});
|
||||
|
||||
// Act - Node 24 explicitly requested in action.yml
|
||||
var data = new NodeJSActionExecutionData();
|
||||
data.NodeVersion = "node24";
|
||||
var handler = hf.Create(
|
||||
_ec.Object,
|
||||
new ScriptReference(),
|
||||
new Mock<IStepHost>().Object,
|
||||
data,
|
||||
new Dictionary<string, string>(),
|
||||
new Dictionary<string, string>(),
|
||||
new Variables(hc, new Dictionary<string, VariableValue>()),
|
||||
"",
|
||||
new List<JobExtensionRunner>()
|
||||
) as INodeScriptActionHandler;
|
||||
|
||||
// Assert - should be node24 as requested
|
||||
Assert.Equal("node24", handler.Data.NodeVersion);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
35
src/Test/L0/Worker/Handlers/NodeHandlerL0.cs
Normal file
35
src/Test/L0/Worker/Handlers/NodeHandlerL0.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
using GitHub.Runner.Sdk;
|
||||
using GitHub.Runner.Worker;
|
||||
using GitHub.Runner.Worker.Handlers;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace GitHub.Runner.Common.Tests.Worker.Handlers
|
||||
{
|
||||
public sealed class NodeHandlerL0
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void NodeJSActionExecutionDataSupportsNode24()
|
||||
{
|
||||
// Create NodeJSActionExecutionData with node24
|
||||
var nodeJSData = new NodeJSActionExecutionData
|
||||
{
|
||||
NodeVersion = "node24",
|
||||
Script = "test.js"
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
Assert.Equal("node24", nodeJSData.NodeVersion);
|
||||
Assert.Equal(ActionExecutionType.NodeJS, nodeJSData.ExecutionType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,6 +162,60 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
Assert.Equal("node20", nodeVersion);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public async Task DetermineNode24RuntimeVersionInAlpineContainerAsync()
|
||||
{
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
// Arrange.
|
||||
var sh = new ContainerStepHost();
|
||||
sh.Initialize(hc);
|
||||
sh.Container = new ContainerInfo() { ContainerId = "1234abcd" };
|
||||
|
||||
_dc.Setup(d => d.DockerExec(_ec.Object, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<List<string>>()))
|
||||
.Callback((IExecutionContext ec, string id, string options, string command, List<string> output) =>
|
||||
{
|
||||
output.Add("alpine");
|
||||
})
|
||||
.ReturnsAsync(0);
|
||||
|
||||
// Act.
|
||||
var nodeVersion = await sh.DetermineNodeRuntimeVersion(_ec.Object, "node24");
|
||||
|
||||
// Assert.
|
||||
Assert.Equal("node24_alpine", nodeVersion);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public async Task DetermineNode24RuntimeVersionInUnknownContainerAsync()
|
||||
{
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
// Arrange.
|
||||
var sh = new ContainerStepHost();
|
||||
sh.Initialize(hc);
|
||||
sh.Container = new ContainerInfo() { ContainerId = "1234abcd" };
|
||||
|
||||
_dc.Setup(d => d.DockerExec(_ec.Object, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<List<string>>()))
|
||||
.Callback((IExecutionContext ec, string id, string options, string command, List<string> output) =>
|
||||
{
|
||||
output.Add("github");
|
||||
})
|
||||
.ReturnsAsync(0);
|
||||
|
||||
// Act.
|
||||
var nodeVersion = await sh.DetermineNodeRuntimeVersion(_ec.Object, "node24");
|
||||
|
||||
// Assert.
|
||||
Assert.Equal("node24", nodeVersion);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
63
src/Test/L0/Worker/StepHostNodeVersionL0.cs
Normal file
63
src/Test/L0/Worker/StepHostNodeVersionL0.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using GitHub.Runner.Worker;
|
||||
using GitHub.Runner.Worker.Handlers;
|
||||
using Moq;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Xunit;
|
||||
|
||||
namespace GitHub.Runner.Common.Tests.Worker
|
||||
{
|
||||
public sealed class StepHostNodeVersionL0
|
||||
{
|
||||
private Mock<IExecutionContext> _ec;
|
||||
private DefaultStepHost _defaultStepHost;
|
||||
|
||||
public StepHostNodeVersionL0()
|
||||
{
|
||||
_ec = new Mock<IExecutionContext>();
|
||||
_defaultStepHost = new DefaultStepHost();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void CheckNodeVersionForArm32_Node24OnArm32Linux()
|
||||
{
|
||||
// Test via NodeUtil directly
|
||||
string preferredVersion = "node24";
|
||||
var (nodeVersion, warningMessage) = Common.Util.NodeUtil.CheckNodeVersionForLinuxArm32(preferredVersion);
|
||||
|
||||
// On ARM32 Linux, we should fall back to node20
|
||||
bool isArm32 = RuntimeInformation.ProcessArchitecture == Architecture.Arm ||
|
||||
Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE")?.Contains("ARM") == true;
|
||||
bool isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
|
||||
|
||||
if (isArm32 && isLinux)
|
||||
{
|
||||
// Should downgrade to node20 on ARM32 Linux
|
||||
Assert.Equal("node20", nodeVersion);
|
||||
Assert.NotNull(warningMessage);
|
||||
Assert.Contains("Node 24 is not supported on Linux ARM32 platforms", warningMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
// On non-ARM32 platforms, should pass through the version unmodified
|
||||
Assert.Equal("node24", nodeVersion);
|
||||
Assert.Null(warningMessage);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void CheckNodeVersionForArm32_PassThroughNonNode24Versions()
|
||||
{
|
||||
string preferredVersion = "node20";
|
||||
var (nodeVersion, warningMessage) = Common.Util.NodeUtil.CheckNodeVersionForLinuxArm32(preferredVersion);
|
||||
|
||||
// Should never modify the version for non-node24 inputs
|
||||
Assert.Equal("node20", nodeVersion);
|
||||
Assert.Null(warningMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageReference Include="xunit" Version="2.7.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
<PackageReference Include="System.Reflection.TypeExtensions" Version="4.7.0" />
|
||||
|
||||
20
src/Test/TestData/node24action.yml
Normal file
20
src/Test/TestData/node24action.yml
Normal file
@@ -0,0 +1,20 @@
|
||||
name: 'Hello World'
|
||||
description: 'Greet the world and record the time'
|
||||
author: 'Test Corporation'
|
||||
inputs:
|
||||
greeting: # id of input
|
||||
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
|
||||
required: true
|
||||
default: 'Hello'
|
||||
deprecationMessage: 'This property has been deprecated'
|
||||
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: 'node24'
|
||||
main: 'main.js'
|
||||
@@ -17,7 +17,7 @@ LAYOUT_DIR="$SCRIPT_DIR/../_layout"
|
||||
DOWNLOAD_DIR="$SCRIPT_DIR/../_downloads/netcore2x"
|
||||
PACKAGE_DIR="$SCRIPT_DIR/../_package"
|
||||
DOTNETSDK_ROOT="$SCRIPT_DIR/../_dotnetsdk"
|
||||
DOTNETSDK_VERSION="8.0.408"
|
||||
DOTNETSDK_VERSION="8.0.412"
|
||||
DOTNETSDK_INSTALLDIR="$DOTNETSDK_ROOT/$DOTNETSDK_VERSION"
|
||||
RUNNER_VERSION=$(cat runnerversion)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "8.0.408"
|
||||
"version": "8.0.412"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
2.324.0
|
||||
2.327.0
|
||||
|
||||
Reference in New Issue
Block a user