mirror of
https://github.com/actions/runner.git
synced 2025-12-10 12:36:23 +00:00
Compare commits
33 Commits
v2.324.0
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
23ea13be96 | ||
|
|
e4511c02ad | ||
|
|
0ebdf9e83d | ||
|
|
6543bf206b | ||
|
|
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": {
|
"features": {
|
||||||
"ghcr.io/devcontainers/features/docker-in-docker:1": {},
|
"ghcr.io/devcontainers/features/docker-in-docker:1": {},
|
||||||
"ghcr.io/devcontainers/features/dotnet": {
|
"ghcr.io/devcontainers/features/dotnet": {
|
||||||
"version": "8.0.408"
|
"version": "8.0.412"
|
||||||
},
|
},
|
||||||
"ghcr.io/devcontainers/features/node:1": {
|
"ghcr.io/devcontainers/features/node:1": {
|
||||||
"version": "20"
|
"version": "20"
|
||||||
|
|||||||
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@@ -41,7 +41,7 @@ jobs:
|
|||||||
devScript: ./dev.sh
|
devScript: ./dev.sh
|
||||||
|
|
||||||
- runtime: win-x64
|
- runtime: win-x64
|
||||||
os: windows-2019
|
os: windows-latest
|
||||||
devScript: ./dev
|
devScript: ./dev
|
||||||
|
|
||||||
- runtime: win-arm64
|
- runtime: win-arm64
|
||||||
@@ -50,7 +50,7 @@ jobs:
|
|||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
# Build runner layout
|
# Build runner layout
|
||||||
- name: Build & Layout Release
|
- name: Build & Layout Release
|
||||||
|
|||||||
2
.github/workflows/codeql.yml
vendored
2
.github/workflows/codeql.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
|
|||||||
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@v5
|
||||||
|
|
||||||
|
- 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@v5
|
||||||
|
|
||||||
|
- 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"
|
||||||
4
.github/workflows/dotnet-upgrade.yml
vendored
4
.github/workflows/dotnet-upgrade.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
|||||||
DOTNET_CURRENT_MAJOR_MINOR_VERSION: ${{ steps.fetch_current_version.outputs.DOTNET_CURRENT_MAJOR_MINOR_VERSION }}
|
DOTNET_CURRENT_MAJOR_MINOR_VERSION: ${{ steps.fetch_current_version.outputs.DOTNET_CURRENT_MAJOR_MINOR_VERSION }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
- name: Get current major minor version
|
- name: Get current major minor version
|
||||||
id: fetch_current_version
|
id: fetch_current_version
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -89,7 +89,7 @@ jobs:
|
|||||||
if: ${{ needs.dotnet-update.outputs.SHOULD_UPDATE == 1 && needs.dotnet-update.outputs.BRANCH_EXISTS == 0 }}
|
if: ${{ needs.dotnet-update.outputs.SHOULD_UPDATE == 1 && needs.dotnet-update.outputs.BRANCH_EXISTS == 0 }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: feature/dotnetsdk-upgrade/${{ needs.dotnet-update.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }}
|
ref: feature/dotnetsdk-upgrade/${{ needs.dotnet-update.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }}
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
|
|||||||
24
.github/workflows/release.yml
vendored
24
.github/workflows/release.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
|||||||
if: startsWith(github.ref, 'refs/heads/releases/') || github.ref == 'refs/heads/main'
|
if: startsWith(github.ref, 'refs/heads/releases/') || github.ref == 'refs/heads/main'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
# Make sure ./releaseVersion match ./src/runnerversion
|
# Make sure ./releaseVersion match ./src/runnerversion
|
||||||
# Query GitHub release ensure version is not used
|
# Query GitHub release ensure version is not used
|
||||||
@@ -77,7 +77,7 @@ jobs:
|
|||||||
devScript: ./dev.sh
|
devScript: ./dev.sh
|
||||||
|
|
||||||
- runtime: win-x64
|
- runtime: win-x64
|
||||||
os: windows-2019
|
os: windows-latest
|
||||||
devScript: ./dev
|
devScript: ./dev
|
||||||
|
|
||||||
- runtime: win-arm64
|
- runtime: win-arm64
|
||||||
@@ -86,7 +86,7 @@ jobs:
|
|||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
# Build runner layout
|
# Build runner layout
|
||||||
- name: Build & Layout Release
|
- name: Build & Layout Release
|
||||||
@@ -129,41 +129,41 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
# Download runner package tar.gz/zip produced by 'build' job
|
# Download runner package tar.gz/zip produced by 'build' job
|
||||||
- name: Download Artifact (win-x64)
|
- name: Download Artifact (win-x64)
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: runner-packages-win-x64
|
name: runner-packages-win-x64
|
||||||
path: ./
|
path: ./
|
||||||
- name: Download Artifact (win-arm64)
|
- name: Download Artifact (win-arm64)
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: runner-packages-win-arm64
|
name: runner-packages-win-arm64
|
||||||
path: ./
|
path: ./
|
||||||
- name: Download Artifact (osx-x64)
|
- name: Download Artifact (osx-x64)
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: runner-packages-osx-x64
|
name: runner-packages-osx-x64
|
||||||
path: ./
|
path: ./
|
||||||
- name: Download Artifact (osx-arm64)
|
- name: Download Artifact (osx-arm64)
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: runner-packages-osx-arm64
|
name: runner-packages-osx-arm64
|
||||||
path: ./
|
path: ./
|
||||||
- name: Download Artifact (linux-x64)
|
- name: Download Artifact (linux-x64)
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: runner-packages-linux-x64
|
name: runner-packages-linux-x64
|
||||||
path: ./
|
path: ./
|
||||||
- name: Download Artifact (linux-arm)
|
- name: Download Artifact (linux-arm)
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: runner-packages-linux-arm
|
name: runner-packages-linux-arm
|
||||||
path: ./
|
path: ./
|
||||||
- name: Download Artifact (linux-arm64)
|
- name: Download Artifact (linux-arm64)
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: runner-packages-linux-arm64
|
name: runner-packages-linux-arm64
|
||||||
path: ./
|
path: ./
|
||||||
@@ -296,7 +296,7 @@ jobs:
|
|||||||
IMAGE_NAME: ${{ github.repository_owner }}/actions-runner
|
IMAGE_NAME: ${{ github.repository_owner }}/actions-runner
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Compute image version
|
- name: Compute image version
|
||||||
id: image
|
id: image
|
||||||
|
|||||||
28
README.md
28
README.md
@@ -8,6 +8,16 @@
|
|||||||
|
|
||||||
The runner is the application that runs a job from a GitHub Actions workflow. It is used by GitHub Actions in the [hosted virtual environments](https://github.com/actions/virtual-environments), or you can [self-host the runner](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/about-self-hosted-runners) in your own environment.
|
The runner is the application that runs a job from a GitHub Actions workflow. It is used by GitHub Actions in the [hosted virtual environments](https://github.com/actions/virtual-environments), or you can [self-host the runner](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/about-self-hosted-runners) in your own environment.
|
||||||
|
|
||||||
|
## Understanding How Actions Work
|
||||||
|
|
||||||
|
**New to GitHub Actions development?** The runner (this repository) is compiled C# code that executes actions. Actions themselves typically do NOT require compilation:
|
||||||
|
|
||||||
|
- **JavaScript Actions** run source `.js` files directly
|
||||||
|
- **Container Actions** use Docker images (pre-built or built from Dockerfile)
|
||||||
|
- **Composite Actions** are YAML step definitions
|
||||||
|
|
||||||
|
📖 See [docs/action-execution-model.md](docs/action-execution-model.md) for detailed information and [examples](docs/examples/action-execution-examples.md).
|
||||||
|
|
||||||
## Get Started
|
## Get Started
|
||||||
|
|
||||||
For more information about installing and using self-hosted runners, see [Adding self-hosted runners](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/adding-self-hosted-runners) and [Using self-hosted runners in a workflow](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/using-self-hosted-runners-in-a-workflow)
|
For more information about installing and using self-hosted runners, see [Adding self-hosted runners](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/adding-self-hosted-runners) and [Using self-hosted runners in a workflow](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/using-self-hosted-runners-in-a-workflow)
|
||||||
@@ -20,6 +30,20 @@ Runner releases:
|
|||||||
|
|
||||||
 [Pre-reqs](docs/start/envlinux.md) | [Download](https://github.com/actions/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.
|
||||||
|
|||||||
144
docs/action-execution-model.md
Normal file
144
docs/action-execution-model.md
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
# GitHub Actions Execution Model
|
||||||
|
|
||||||
|
## Question: Do Actions Need to be Compiled?
|
||||||
|
|
||||||
|
**Short Answer**: No, GitHub Actions themselves do **NOT** need to be compiled from source code. They run directly as interpreted code, container images, or step definitions.
|
||||||
|
|
||||||
|
## How Different Action Types Are Executed
|
||||||
|
|
||||||
|
### 1. JavaScript Actions (`using: node12/16/20/24`)
|
||||||
|
|
||||||
|
JavaScript actions execute source code directly without compilation:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# action.yml
|
||||||
|
runs:
|
||||||
|
using: 'node20'
|
||||||
|
main: 'index.js'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Execution Process**:
|
||||||
|
1. Runner downloads the action repository
|
||||||
|
2. Locates the `main` JavaScript file (e.g., `index.js`)
|
||||||
|
3. Executes it directly using Node.js runtime: `node index.js`
|
||||||
|
4. No compilation or build step required
|
||||||
|
|
||||||
|
**Code Reference**: `src/Runner.Worker/Handlers/NodeScriptActionHandler.cs`
|
||||||
|
- Resolves the target script file
|
||||||
|
- Executes using Node.js: `StepHost.ExecuteAsync()` with node executable
|
||||||
|
|
||||||
|
### 2. Container Actions (`using: docker`)
|
||||||
|
|
||||||
|
Container actions run pre-built images or build from Dockerfile:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# action.yml - Pre-built image
|
||||||
|
runs:
|
||||||
|
using: 'docker'
|
||||||
|
image: 'docker://alpine:3.10'
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# action.yml - Build from Dockerfile
|
||||||
|
runs:
|
||||||
|
using: 'docker'
|
||||||
|
image: 'Dockerfile'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Execution Process**:
|
||||||
|
1. If using pre-built image: Pull and run the container
|
||||||
|
2. If using Dockerfile: Build the container image, then run it
|
||||||
|
3. No compilation of action source code - Docker handles image building
|
||||||
|
|
||||||
|
**Code Reference**: `src/Runner.Worker/Handlers/ContainerActionHandler.cs`
|
||||||
|
- Handles both pre-built images and Dockerfile builds
|
||||||
|
- Uses Docker commands to run containers
|
||||||
|
|
||||||
|
### 3. Composite Actions (`using: composite`)
|
||||||
|
|
||||||
|
Composite actions are collections of steps defined in YAML:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# action.yml
|
||||||
|
runs:
|
||||||
|
using: 'composite'
|
||||||
|
steps:
|
||||||
|
- run: echo "Hello"
|
||||||
|
shell: bash
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
```
|
||||||
|
|
||||||
|
**Execution Process**:
|
||||||
|
1. Parse the YAML step definitions
|
||||||
|
2. Execute each step in sequence
|
||||||
|
3. No compilation - just step orchestration
|
||||||
|
|
||||||
|
**Code Reference**: `src/Runner.Worker/Handlers/CompositeActionHandler.cs`
|
||||||
|
- Iterates through defined steps
|
||||||
|
- Executes each step using appropriate handlers
|
||||||
|
|
||||||
|
## What Does Get Compiled?
|
||||||
|
|
||||||
|
### The GitHub Actions Runner (This Repository)
|
||||||
|
|
||||||
|
The runner itself is compiled from C# source code:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd src
|
||||||
|
./dev.sh build # Compiles the runner binaries
|
||||||
|
```
|
||||||
|
|
||||||
|
**What gets compiled**:
|
||||||
|
- `Runner.Listener` - Registers with GitHub and receives jobs
|
||||||
|
- `Runner.Worker` - Executes individual jobs and steps
|
||||||
|
- `Runner.PluginHost` - Handles plugin execution
|
||||||
|
- Supporting libraries
|
||||||
|
|
||||||
|
**Build Output**: Compiled binaries in `_layout/bin/`
|
||||||
|
|
||||||
|
## Key Distinctions
|
||||||
|
|
||||||
|
| Component | Compilation Required | Execution Method |
|
||||||
|
|-----------|---------------------|------------------|
|
||||||
|
| **Runner** (this repo) | ✅ Yes - C# → binaries | Compiled executable |
|
||||||
|
| **JavaScript Actions** | ❌ No | Direct interpretation |
|
||||||
|
| **Container Actions** | ❌ No* | Container runtime |
|
||||||
|
| **Composite Actions** | ❌ No | YAML interpretation |
|
||||||
|
|
||||||
|
*Container actions may involve building Docker images, but not compiling action source code.
|
||||||
|
|
||||||
|
## Implementation Details
|
||||||
|
|
||||||
|
### Action Loading Process
|
||||||
|
|
||||||
|
1. **Action Discovery** (`ActionManager.LoadAction()`)
|
||||||
|
- Parses `action.yml` manifest
|
||||||
|
- Determines action type from `using` field
|
||||||
|
- Creates appropriate execution data object
|
||||||
|
|
||||||
|
2. **Handler Selection** (`HandlerFactory.Create()`)
|
||||||
|
- Routes to appropriate handler based on action type
|
||||||
|
- `NodeScriptActionHandler` for JavaScript
|
||||||
|
- `ContainerActionHandler` for Docker
|
||||||
|
- `CompositeActionHandler` for composite
|
||||||
|
|
||||||
|
3. **Execution** (Handler-specific `RunAsync()`)
|
||||||
|
- Each handler implements execution logic
|
||||||
|
- No compilation step - direct execution
|
||||||
|
|
||||||
|
### Source Code References
|
||||||
|
|
||||||
|
- **Action Type Detection**: `src/Runner.Worker/ActionManifestManager.cs:428-495`
|
||||||
|
- **Handler Factory**: `src/Runner.Worker/Handlers/HandlerFactory.cs`
|
||||||
|
- **JavaScript Execution**: `src/Runner.Worker/Handlers/NodeScriptActionHandler.cs:143-153`
|
||||||
|
- **Container Execution**: `src/Runner.Worker/Handlers/ContainerActionHandler.cs:247-261`
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
GitHub Actions are designed for **runtime interpretation**, not compilation:
|
||||||
|
|
||||||
|
- **JavaScript actions** run source `.js` files directly
|
||||||
|
- **Container actions** use existing images or build from Dockerfile
|
||||||
|
- **Composite actions** are YAML step definitions
|
||||||
|
|
||||||
|
The only compilation involved is building the **runner infrastructure** (this repository) that interprets and executes the actions.
|
||||||
@@ -4,9 +4,9 @@
|
|||||||
|
|
||||||
Make sure the built-in node.js has access to GitHub.com or GitHub Enterprise Server.
|
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`
|
> Not the `node` from `$PATH`
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,14 @@ We welcome contributions in the form of issues and pull requests. We view the co
|
|||||||
|
|
||||||
> IMPORTANT: Building your own runner is critical for the dev inner loop process when contributing changes. However, only runners built and distributed by GitHub (releases) are supported in production. Be aware that workflows and orchestrations run service side with the runner being a remote process to run steps. For that reason, the service can pull the runner forward so customizations can be lost.
|
> IMPORTANT: Building your own runner is critical for the dev inner loop process when contributing changes. However, only runners built and distributed by GitHub (releases) are supported in production. Be aware that workflows and orchestrations run service side with the runner being a remote process to run steps. For that reason, the service can pull the runner forward so customizations can be lost.
|
||||||
|
|
||||||
|
## Understanding Actions vs Runner
|
||||||
|
|
||||||
|
**New to GitHub Actions development?** See [Action Execution Model](action-execution-model.md) to understand the difference between:
|
||||||
|
- **Actions** (JavaScript, containers, composite) - Run without compilation
|
||||||
|
- **Runner** (this repository) - Compiled C# application that executes actions
|
||||||
|
|
||||||
|
For examples of how different action types work, see [Action Execution Examples](examples/action-execution-examples.md).
|
||||||
|
|
||||||
## Issues
|
## Issues
|
||||||
|
|
||||||
Log issues for both bugs and enhancement requests. Logging issues are important for the open community.
|
Log issues for both bugs and enhancement requests. Logging issues are important for the open community.
|
||||||
|
|||||||
117
docs/examples/action-execution-examples.md
Normal file
117
docs/examples/action-execution-examples.md
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
# Action Execution Examples
|
||||||
|
|
||||||
|
This directory contains examples demonstrating how different types of GitHub Actions are executed without compilation.
|
||||||
|
|
||||||
|
## JavaScript Action Example
|
||||||
|
|
||||||
|
A simple JavaScript action that runs source code directly:
|
||||||
|
|
||||||
|
### action.yml
|
||||||
|
```yaml
|
||||||
|
name: 'JavaScript Example'
|
||||||
|
description: 'Demonstrates direct JavaScript execution'
|
||||||
|
runs:
|
||||||
|
using: 'node20'
|
||||||
|
main: 'index.js'
|
||||||
|
```
|
||||||
|
|
||||||
|
### index.js
|
||||||
|
```javascript
|
||||||
|
// This file runs directly - no compilation needed
|
||||||
|
console.log('Hello from JavaScript action!');
|
||||||
|
console.log('Process args:', process.argv);
|
||||||
|
console.log('Environment:', process.env.INPUT_MESSAGE || 'No input provided');
|
||||||
|
```
|
||||||
|
|
||||||
|
**Execution**: The runner directly executes `node index.js` - no build step.
|
||||||
|
|
||||||
|
## Container Action Example
|
||||||
|
|
||||||
|
### action.yml (Pre-built image)
|
||||||
|
```yaml
|
||||||
|
name: 'Container Example'
|
||||||
|
description: 'Demonstrates container execution'
|
||||||
|
runs:
|
||||||
|
using: 'docker'
|
||||||
|
image: 'docker://alpine:latest'
|
||||||
|
entrypoint: '/bin/sh'
|
||||||
|
args:
|
||||||
|
- '-c'
|
||||||
|
- 'echo "Hello from container!" && env | grep INPUT_'
|
||||||
|
```
|
||||||
|
|
||||||
|
### action.yml (Build from source)
|
||||||
|
```yaml
|
||||||
|
name: 'Container Build Example'
|
||||||
|
description: 'Demonstrates building from Dockerfile'
|
||||||
|
runs:
|
||||||
|
using: 'docker'
|
||||||
|
image: 'Dockerfile'
|
||||||
|
args:
|
||||||
|
- 'Hello from built container!'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dockerfile
|
||||||
|
```dockerfile
|
||||||
|
FROM alpine:latest
|
||||||
|
COPY entrypoint.sh /entrypoint.sh
|
||||||
|
RUN chmod +x /entrypoint.sh
|
||||||
|
ENTRYPOINT ["/entrypoint.sh"]
|
||||||
|
```
|
||||||
|
|
||||||
|
### entrypoint.sh
|
||||||
|
```bash
|
||||||
|
#!/bin/sh
|
||||||
|
echo "Container built and running: $1"
|
||||||
|
echo "Environment variables:"
|
||||||
|
env | grep INPUT_ || echo "No INPUT_ variables found"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Execution**: Docker builds the image (if needed) and runs the container - action source isn't compiled.
|
||||||
|
|
||||||
|
## Composite Action Example
|
||||||
|
|
||||||
|
### action.yml
|
||||||
|
```yaml
|
||||||
|
name: 'Composite Example'
|
||||||
|
description: 'Demonstrates composite action execution'
|
||||||
|
runs:
|
||||||
|
using: 'composite'
|
||||||
|
steps:
|
||||||
|
- name: Run shell command
|
||||||
|
run: echo "Step 1: Hello from composite action!"
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Use another action
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
path: 'checked-out-code'
|
||||||
|
|
||||||
|
- name: Run another shell command
|
||||||
|
run: |
|
||||||
|
echo "Step 3: Files in workspace:"
|
||||||
|
ls -la
|
||||||
|
shell: bash
|
||||||
|
```
|
||||||
|
|
||||||
|
**Execution**: The runner interprets the YAML and executes each step - no compilation.
|
||||||
|
|
||||||
|
## Comparison with Runner Compilation
|
||||||
|
|
||||||
|
The **runner itself** (this repository) must be compiled:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# This compiles the runner from C# source code
|
||||||
|
cd src
|
||||||
|
./dev.sh build
|
||||||
|
|
||||||
|
# The compiled runner then executes actions WITHOUT compiling them
|
||||||
|
./_layout/bin/Runner.Worker
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Takeaway
|
||||||
|
|
||||||
|
- **Actions** = Interpreted at runtime (JavaScript, containers, YAML)
|
||||||
|
- **Runner** = Compiled from source (C# → binaries)
|
||||||
|
|
||||||
|
The runner compiles once and then executes many different actions without compiling them.
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
## Supported Distributions and Versions
|
## 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
|
## Install .Net Core 3.x Linux Dependencies
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,6 @@
|
|||||||
|
|
||||||
## Supported Versions
|
## 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)
|
## [More .Net Core Prerequisites Information](https://docs.microsoft.com/en-us/dotnet/core/macos-prerequisites?tabs=netcore30)
|
||||||
|
|||||||
@@ -2,6 +2,6 @@
|
|||||||
|
|
||||||
## Supported Versions
|
## 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)
|
## [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 TARGETARCH
|
||||||
ARG RUNNER_VERSION
|
ARG RUNNER_VERSION
|
||||||
ARG RUNNER_CONTAINER_HOOKS_VERSION=0.7.0
|
ARG RUNNER_CONTAINER_HOOKS_VERSION=0.7.0
|
||||||
ARG DOCKER_VERSION=28.1.1
|
ARG DOCKER_VERSION=28.3.2
|
||||||
ARG BUILDX_VERSION=0.23.0
|
ARG BUILDX_VERSION=0.26.1
|
||||||
|
|
||||||
RUN apt update -y && apt install curl unzip -y
|
RUN apt update -y && apt install curl unzip -y
|
||||||
|
|
||||||
|
|||||||
@@ -1,37 +1,20 @@
|
|||||||
## What's Changed
|
## What's Changed
|
||||||
* Increase error body max length before truncation by @ericsciple in https://github.com/actions/runner/pull/3762
|
* Update Docker to v28.3.2 and Buildx to v0.26.1 by @github-actions[bot] in https://github.com/actions/runner/pull/3953
|
||||||
* Fix release.yml break by upgrading actions/github-script by @TingluoHuang in https://github.com/actions/runner/pull/3772
|
* Fix if statement structure in update script and variable reference by @salmanmkc in https://github.com/actions/runner/pull/3956
|
||||||
* Small runner code cleanup. by @TingluoHuang in https://github.com/actions/runner/pull/3773
|
* Add V2 flow for runner deletion by @Samirat in https://github.com/actions/runner/pull/3954
|
||||||
* Enable hostcontext to track auth migration. by @TingluoHuang in https://github.com/actions/runner/pull/3776
|
* Node 20 -> Node 24 migration feature flagging, opt-in and opt-out environment variables by @salmanmkc in https://github.com/actions/runner/pull/3948
|
||||||
* Add option in OAuthCred to load authUrlV2. by @TingluoHuang in https://github.com/actions/runner/pull/3777
|
* Update Node20 and Node24 to latest by @djs-intel in https://github.com/actions/runner/pull/3972
|
||||||
* Remove create session with broker in MessageListener. by @TingluoHuang in https://github.com/actions/runner/pull/3782
|
* Redirect supported OS doc section to current public Docs location by @corycalahan in https://github.com/actions/runner/pull/3979
|
||||||
* Enable auth migration based on config refresh. by @TingluoHuang in https://github.com/actions/runner/pull/3786
|
* Bump Microsoft.NET.Test.Sdk from 17.13.0 to 17.14.1 by @dependabot[bot] in https://github.com/actions/runner/pull/3975
|
||||||
* Set JWT.alg to PS256 with PssPadding. by @TingluoHuang in https://github.com/actions/runner/pull/3789
|
* Bump Azure.Storage.Blobs from 12.24.0 to 12.25.0 by @dependabot[bot] in https://github.com/actions/runner/pull/3974
|
||||||
* Enable FIPS by default. by @TingluoHuang in https://github.com/actions/runner/pull/3793
|
* Bump actions/download-artifact from 4 to 5 by @dependabot[bot] in https://github.com/actions/runner/pull/3973
|
||||||
* Support auth migration using authUrlV2 in Runner/MessageListener. by @TingluoHuang in https://github.com/actions/runner/pull/3787
|
* Bump actions/checkout from 4 to 5 by @dependabot[bot] in https://github.com/actions/runner/pull/3982
|
||||||
* 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
|
|
||||||
|
|
||||||
## New Contributors
|
## New Contributors
|
||||||
* @dsanders11 made their first contribution in https://github.com/actions/runner/pull/3802
|
* @Samirat made their first contribution in https://github.com/actions/runner/pull/3954
|
||||||
|
* @djs-intel made their first contribution in https://github.com/actions/runner/pull/3972
|
||||||
|
|
||||||
**Full Changelog**: https://github.com/actions/runner/compare/v2.323.0...v2.324.0
|
**Full Changelog**: https://github.com/actions/runner/compare/v2.327.1...v2.328.0
|
||||||
|
|
||||||
_Note: Actions Runner follows a progressive release policy, so the latest release might not be available to your enterprise, organization, or repository yet.
|
_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.
|
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": {
|
"node_modules/brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"balanced-match": "^1.0.0",
|
"balanced-match": "^1.0.0",
|
||||||
"concat-map": "0.0.1"
|
"concat-map": "0.0.1"
|
||||||
@@ -4751,9 +4752,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"brace-expansion": {
|
"brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"balanced-match": "^1.0.0",
|
"balanced-match": "^1.0.0",
|
||||||
"concat-map": "0.0.1"
|
"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
|
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.
|
# 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
|
# 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() {
|
get_abs_path() {
|
||||||
# exploits the fact that pwd will print abs path when no args
|
# 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
|
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.exe" node20/bin
|
||||||
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/$PACKAGERUNTIME/node.lib" 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
|
if [[ "$PRECACHE" != "" ]]; then
|
||||||
acquireExternalTool "https://github.com/microsoft/vswhere/releases/download/2.6.7/vswhere.exe" vswhere
|
acquireExternalTool "https://github.com/microsoft/vswhere/releases/download/2.6.7/vswhere.exe" vswhere
|
||||||
fi
|
fi
|
||||||
@@ -149,6 +152,8 @@ if [[ "$PACKAGERUNTIME" == "win-arm64" ]]; then
|
|||||||
# todo: replace these with official release when available
|
# 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.exe" node20/bin
|
||||||
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/$PACKAGERUNTIME/node.lib" 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
|
if [[ "$PRECACHE" != "" ]]; then
|
||||||
acquireExternalTool "https://github.com/microsoft/vswhere/releases/download/2.6.7/vswhere.exe" vswhere
|
acquireExternalTool "https://github.com/microsoft/vswhere/releases/download/2.6.7/vswhere.exe" vswhere
|
||||||
fi
|
fi
|
||||||
@@ -157,21 +162,26 @@ fi
|
|||||||
# Download the external tools only for OSX.
|
# Download the external tools only for OSX.
|
||||||
if [[ "$PACKAGERUNTIME" == "osx-x64" ]]; then
|
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${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
|
fi
|
||||||
|
|
||||||
if [[ "$PACKAGERUNTIME" == "osx-arm64" ]]; then
|
if [[ "$PACKAGERUNTIME" == "osx-arm64" ]]; then
|
||||||
# node.js v12 doesn't support macOS on arm64.
|
# 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${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
|
fi
|
||||||
|
|
||||||
# Download the external tools for Linux PACKAGERUNTIMEs.
|
# Download the external tools for Linux PACKAGERUNTIMEs.
|
||||||
if [[ "$PACKAGERUNTIME" == "linux-x64" ]]; then
|
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_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_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
|
fi
|
||||||
|
|
||||||
if [[ "$PACKAGERUNTIME" == "linux-arm64" ]]; then
|
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${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
|
fi
|
||||||
|
|
||||||
if [[ "$PACKAGERUNTIME" == "linux-arm" ]]; then
|
if [[ "$PACKAGERUNTIME" == "linux-arm" ]]; then
|
||||||
|
|||||||
@@ -3299,7 +3299,7 @@ function expand(str, isTop) {
|
|||||||
var isOptions = m.body.indexOf(',') >= 0;
|
var isOptions = m.body.indexOf(',') >= 0;
|
||||||
if (!isSequence && !isOptions) {
|
if (!isSequence && !isOptions) {
|
||||||
// {a},b}
|
// {a},b}
|
||||||
if (m.post.match(/,.*\}/)) {
|
if (m.post.match(/,(?!,).*\}/)) {
|
||||||
str = m.pre + '{' + m.body + escClose + m.post;
|
str = m.pre + '{' + m.body + escClose + m.post;
|
||||||
return expand(str);
|
return expand(str);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ fi
|
|||||||
# fix upgrade issue with macOS when running as a service
|
# fix upgrade issue with macOS when running as a service
|
||||||
attemptedtargetedfix=0
|
attemptedtargetedfix=0
|
||||||
currentplatform=$(uname | awk '{print tolower($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 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
|
# 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
|
# Otherwise mac gatekeeper kills the processes we spawn on creation as we are running a process with no backing file
|
||||||
@@ -135,16 +135,22 @@ if [[ "$currentplatform" == 'darwin' && restartinteractiverunner -eq 0 ]]; then
|
|||||||
then
|
then
|
||||||
# inspect the open file handles to find the node process
|
# 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
|
# we can't actually inspect the process using ps because it uses relative paths and doesn't follow symlinks
|
||||||
nodever="node20"
|
# 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-)
|
path=$(lsof -a -g "$procgroup" -F n | grep $nodever/bin/node | grep externals | tail -1 | cut -c2-)
|
||||||
if [[ $? -ne 0 || -z "$path" ]] # Fallback if RunnerService.js was started with node16
|
if [[ $? -ne 0 || -z "$path" ]] # Fallback if RunnerService.js was started with node20
|
||||||
then
|
then
|
||||||
nodever="node16"
|
nodever="node20"
|
||||||
path=$(lsof -a -g "$procgroup" -F n | grep $nodever/bin/node | grep externals | tail -1 | cut -c2-)
|
path=$(lsof -a -g "$procgroup" -F n | grep $nodever/bin/node | grep externals | tail -1 | cut -c2-)
|
||||||
if [[ $? -ne 0 || -z "$path" ]] # Fallback if RunnerService.js was started with node12
|
if [[ $? -ne 0 || -z "$path" ]] # Fallback if RunnerService.js was started with node16
|
||||||
then
|
then
|
||||||
nodever="node12"
|
nodever="node16"
|
||||||
path=$(lsof -a -g "$procgroup" -F n | grep $nodever/bin/node | grep externals | tail -1 | cut -c2-)
|
path=$(lsof -a -g "$procgroup" -F n | grep $nodever/bin/node | grep externals | tail -1 | cut -c2-)
|
||||||
|
if [[ $? -ne 0 || -z "$path" ]] # Fallback if RunnerService.js was started with node12
|
||||||
|
then
|
||||||
|
nodever="node12"
|
||||||
|
path=$(lsof -a -g "$procgroup" -F n | grep $nodever/bin/node | grep externals | tail -1 | cut -c2-)
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
if [[ $? -eq 0 && -n "$path" ]]
|
if [[ $? -eq 0 && -n "$path" ]]
|
||||||
|
|||||||
@@ -168,6 +168,23 @@ namespace GitHub.Runner.Common
|
|||||||
public static readonly string UseContainerPathForTemplate = "DistributedTask.UseContainerPathForTemplate";
|
public static readonly string UseContainerPathForTemplate = "DistributedTask.UseContainerPathForTemplate";
|
||||||
public static readonly string AllowRunnerContainerHooks = "DistributedTask.AllowRunnerContainerHooks";
|
public static readonly string AllowRunnerContainerHooks = "DistributedTask.AllowRunnerContainerHooks";
|
||||||
public static readonly string AddCheckRunIdToJobContext = "actions_add_check_run_id_to_job_context";
|
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";
|
public static readonly string InternalTelemetryIssueDataKey = "_internal_telemetry";
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ using System.Threading.Tasks;
|
|||||||
using GitHub.DistributedTask.Logging;
|
using GitHub.DistributedTask.Logging;
|
||||||
using GitHub.Runner.Common.Util;
|
using GitHub.Runner.Common.Util;
|
||||||
using GitHub.Runner.Sdk;
|
using GitHub.Runner.Sdk;
|
||||||
|
using GitHub.Services.WebApi.Jwt;
|
||||||
|
|
||||||
namespace GitHub.Runner.Common
|
namespace GitHub.Runner.Common
|
||||||
{
|
{
|
||||||
@@ -306,6 +307,36 @@ namespace GitHub.Runner.Common
|
|||||||
{
|
{
|
||||||
_userAgents.Add(new ProductInfoHeaderValue("ClientId", clientId));
|
_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);
|
var runnerFile = GetConfigFile(WellKnownConfigFile.Runner);
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace GitHub.Runner.Common
|
|||||||
{
|
{
|
||||||
void InitializeLaunchClient(Uri uri, string token);
|
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
|
public sealed class LaunchServer : RunnerService, ILaunchServer
|
||||||
@@ -42,12 +42,16 @@ namespace GitHub.Runner.Common
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Task<ActionDownloadInfoCollection> ResolveActionsDownloadInfoAsync(Guid planId, Guid jobId, ActionReferenceList actionReferenceList,
|
public Task<ActionDownloadInfoCollection> ResolveActionsDownloadInfoAsync(Guid planId, Guid jobId, ActionReferenceList actionReferenceList,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken, bool displayHelpfulActionsDownloadErrors)
|
||||||
{
|
{
|
||||||
if (_launchClient != null)
|
if (_launchClient != null)
|
||||||
{
|
{
|
||||||
return _launchClient.GetResolveActionsDownloadInfoAsync(planId, jobId, actionReferenceList,
|
if (!displayHelpfulActionsDownloadErrors)
|
||||||
cancellationToken: cancellationToken);
|
{
|
||||||
|
return _launchClient.GetResolveActionsDownloadInfoAsync(planId, jobId, actionReferenceList,
|
||||||
|
cancellationToken: cancellationToken);
|
||||||
|
}
|
||||||
|
return _launchClient.GetResolveActionsDownloadInfoAsyncV2(planId, jobId, actionReferenceList, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new InvalidOperationException("Launch client is not initialized.");
|
throw new InvalidOperationException("Launch client is not initialized.");
|
||||||
|
|||||||
@@ -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> 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<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);
|
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)
|
public async Task<List<TaskAgent>> GetRunnerByNameAsync(string githubUrl, string githubToken, string agentName)
|
||||||
{
|
{
|
||||||
var githubApiUrl = "";
|
var githubApiUrl = $"{GetEntityUrl(githubUrl)}/runners?name={Uri.EscapeDataString(agentName)}";
|
||||||
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 runnersList = await RetryRequest<ListRunnersResponse>(githubApiUrl, githubToken, RequestType.Get, 3, "Failed to get agents pools");
|
var runnersList = await RetryRequest<ListRunnersResponse>(githubApiUrl, githubToken, RequestType.Get, 3, "Failed to get agents pools");
|
||||||
|
|
||||||
return runnersList.ToTaskAgents();
|
return runnersList.ToTaskAgents();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<TaskAgentPool>> GetRunnerGroupsAsync(string githubUrl, string githubToken)
|
public async Task<List<TaskAgentPool>> GetRunnerGroupsAsync(string githubUrl, string githubToken)
|
||||||
{
|
{
|
||||||
var githubApiUrl = "";
|
var githubApiUrl = $"{GetEntityUrl(githubUrl)}/runner-groups";
|
||||||
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 agentPools = await RetryRequest<RunnerGroupList>(githubApiUrl, githubToken, RequestType.Get, 3, "Failed to get agents pools");
|
var agentPools = await RetryRequest<RunnerGroupList>(githubApiUrl, githubToken, RequestType.Get, 3, "Failed to get agents pools");
|
||||||
|
|
||||||
return agentPools?.ToAgentPoolList();
|
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);
|
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)
|
private async Task<T> RetryRequest<T>(string githubApiUrl, string githubToken, RequestType requestType, int maxRetryAttemptsCount = 5, string errorMessage = null, StringContent body = null)
|
||||||
{
|
{
|
||||||
int retry = 0;
|
int retry = 0;
|
||||||
@@ -220,13 +125,22 @@ namespace GitHub.Runner.Common
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
HttpResponseMessage response = null;
|
HttpResponseMessage response = null;
|
||||||
if (requestType == RequestType.Get)
|
switch (requestType)
|
||||||
{
|
{
|
||||||
response = await httpClient.GetAsync(githubApiUrl);
|
case RequestType.Get:
|
||||||
}
|
response = await httpClient.GetAsync(githubApiUrl);
|
||||||
else
|
break;
|
||||||
{
|
case RequestType.Post:
|
||||||
response = await httpClient.PostAsync(githubApiUrl, body);
|
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)
|
if (response != null)
|
||||||
@@ -261,5 +175,61 @@ namespace GitHub.Runner.Common
|
|||||||
await Task.Delay(backOff);
|
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;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
using GitHub.Runner.Sdk;
|
||||||
|
|
||||||
namespace GitHub.Runner.Common.Util
|
namespace GitHub.Runner.Common.Util
|
||||||
{
|
{
|
||||||
public static class NodeUtil
|
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";
|
private const string _defaultNodeVersion = "node20";
|
||||||
public static readonly ReadOnlyCollection<string> BuiltInNodeVersions = new(new[] { "node20" });
|
public static readonly ReadOnlyCollection<string> BuiltInNodeVersions = new(new[] { "node20" });
|
||||||
public static string GetInternalNodeVersion()
|
public static string GetInternalNodeVersion()
|
||||||
@@ -18,5 +41,122 @@ namespace GitHub.Runner.Common.Util
|
|||||||
}
|
}
|
||||||
return _defaultNodeVersion;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
public RunnerSettings LoadMigratedSettings()
|
public RunnerSettings LoadMigratedSettings()
|
||||||
{
|
{
|
||||||
Trace.Info(nameof(LoadMigratedSettings));
|
Trace.Info(nameof(LoadMigratedSettings));
|
||||||
|
|
||||||
// Check if migrated settings file exists
|
// Check if migrated settings file exists
|
||||||
if (!_store.IsMigratedConfigured())
|
if (!_store.IsMigratedConfigured())
|
||||||
{
|
{
|
||||||
@@ -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
|
// Save the negotiated OAuth credential data
|
||||||
_store.SaveCredential(credentialData);
|
_store.SaveCredential(credentialData);
|
||||||
}
|
}
|
||||||
@@ -529,41 +537,50 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
if (isConfigured && hasCredentials)
|
if (isConfigured && hasCredentials)
|
||||||
{
|
{
|
||||||
RunnerSettings settings = _store.GetSettings();
|
RunnerSettings settings = _store.GetSettings();
|
||||||
var credentialManager = HostContext.GetService<ICredentialManager>();
|
|
||||||
|
|
||||||
// Get the credentials
|
if (settings.UseV2Flow)
|
||||||
VssCredentials creds = null;
|
|
||||||
if (string.IsNullOrEmpty(settings.GitHubUrl))
|
|
||||||
{
|
|
||||||
var credProvider = GetCredentialProvider(command, settings.ServerUrl);
|
|
||||||
creds = credProvider.GetVssCredentials(HostContext, allowAuthUrlV2: false);
|
|
||||||
Trace.Info("legacy vss cred retrieved");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
var deletionToken = await GetRunnerTokenAsync(command, settings.GitHubUrl, "remove");
|
var deletionToken = await GetRunnerTokenAsync(command, settings.GitHubUrl, "remove");
|
||||||
GitHubAuthResult authResult = await GetTenantCredential(settings.GitHubUrl, deletionToken, Constants.RunnerEvent.Remove);
|
await _dotcomServer.DeleteRunnerAsync(settings.GitHubUrl, deletionToken, settings.AgentId);
|
||||||
creds = authResult.ToVssCredentials();
|
|
||||||
Trace.Info("cred retrieved via GitHub auth");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine the service deployment type based on connection data. (Hosted/OnPremises)
|
|
||||||
await _runnerServer.ConnectAsync(new Uri(settings.ServerUrl), creds);
|
|
||||||
|
|
||||||
var agents = await _runnerServer.GetAgentsAsync(settings.AgentName);
|
|
||||||
Trace.Verbose("Returns {0} agents", agents.Count);
|
|
||||||
TaskAgent agent = agents.FirstOrDefault();
|
|
||||||
if (agent == null)
|
|
||||||
{
|
|
||||||
_term.WriteLine("Does not exist. Skipping " + currentAction);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await _runnerServer.DeleteAgentAsync(settings.AgentId);
|
var credentialManager = HostContext.GetService<ICredentialManager>();
|
||||||
|
|
||||||
_term.WriteLine();
|
// Get the credentials
|
||||||
_term.WriteSuccessMessage("Runner removed successfully");
|
VssCredentials creds = null;
|
||||||
|
if (string.IsNullOrEmpty(settings.GitHubUrl))
|
||||||
|
{
|
||||||
|
var credProvider = GetCredentialProvider(command, settings.ServerUrl);
|
||||||
|
creds = credProvider.GetVssCredentials(HostContext, allowAuthUrlV2: false);
|
||||||
|
Trace.Info("legacy vss cred retrieved");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var deletionToken = await GetRunnerTokenAsync(command, settings.GitHubUrl, "remove");
|
||||||
|
GitHubAuthResult authResult = await GetTenantCredential(settings.GitHubUrl, deletionToken, Constants.RunnerEvent.Remove);
|
||||||
|
creds = authResult.ToVssCredentials();
|
||||||
|
Trace.Info("cred retrieved via GitHub auth");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the service deployment type based on connection data. (Hosted/OnPremises)
|
||||||
|
await _runnerServer.ConnectAsync(new Uri(settings.ServerUrl), creds);
|
||||||
|
|
||||||
|
var agents = await _runnerServer.GetAgentsAsync(settings.AgentName);
|
||||||
|
Trace.Verbose("Returns {0} agents", agents.Count);
|
||||||
|
TaskAgent agent = agents.FirstOrDefault();
|
||||||
|
if (agent == null)
|
||||||
|
{
|
||||||
|
_term.WriteLine("Does not exist. Skipping " + currentAction);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await _runnerServer.DeleteAgentAsync(settings.AgentId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_term.WriteLine();
|
||||||
|
_term.WriteSuccessMessage("Runner removed successfully");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -110,7 +110,12 @@ namespace GitHub.Runner.Listener
|
|||||||
{
|
{
|
||||||
var jwt = JsonWebToken.Create(accessToken);
|
var jwt = JsonWebToken.Create(accessToken);
|
||||||
var claims = jwt.ExtractClaims();
|
var claims = jwt.ExtractClaims();
|
||||||
orchestrationId = claims.FirstOrDefault(x => string.Equals(x.Type, "orchid", StringComparison.OrdinalIgnoreCase))?.Value;
|
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))
|
if (!string.IsNullOrEmpty(orchestrationId))
|
||||||
{
|
{
|
||||||
Trace.Info($"Pull OrchestrationId {orchestrationId} from JWT claims");
|
Trace.Info($"Pull OrchestrationId {orchestrationId} from JWT claims");
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ namespace GitHub.Runner.Sdk
|
|||||||
if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_TLS_NO_VERIFY")))
|
if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_TLS_NO_VERIFY")))
|
||||||
{
|
{
|
||||||
VssClientHttpRequestSettings.Default.ServerCertificateValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
|
VssClientHttpRequestSettings.Default.ServerCertificateValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
|
||||||
|
RawClientHttpRequestSettings.Default.ServerCertificateValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
|
||||||
}
|
}
|
||||||
|
|
||||||
var rawHeaderValues = new List<ProductInfoHeaderValue>();
|
var rawHeaderValues = new List<ProductInfoHeaderValue>();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
<configuration>
|
<configuration>
|
||||||
<startup>
|
<startup>
|
||||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
|
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7" />
|
||||||
</startup>
|
</startup>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(PackageRuntime)' != 'win-arm64' ">
|
<PropertyGroup Condition=" '$(PackageRuntime)' != 'win-arm64' ">
|
||||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
<TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
|
|||||||
@@ -688,7 +688,8 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
if (MessageUtil.IsRunServiceJob(executionContext.Global.Variables.Get(Constants.Variables.System.JobRequestType)))
|
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
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -450,7 +450,8 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
else if (string.Equals(usingToken.Value, "node12", StringComparison.OrdinalIgnoreCase) ||
|
else if (string.Equals(usingToken.Value, "node12", StringComparison.OrdinalIgnoreCase) ||
|
||||||
string.Equals(usingToken.Value, "node16", 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))
|
if (string.IsNullOrEmpty(mainToken?.Value))
|
||||||
{
|
{
|
||||||
@@ -490,7 +491,7 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
else
|
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)
|
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(
|
private void ConvertInputs(
|
||||||
|
|||||||
@@ -58,10 +58,41 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
var nodeData = data as NodeJSActionExecutionData;
|
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 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) ||
|
if (string.Equals(nodeData.NodeVersion, "node12", StringComparison.InvariantCultureIgnoreCase) ||
|
||||||
string.Equals(nodeData.NodeVersion, "node16", 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;
|
(handler as INodeScriptActionHandler).Data = nodeData;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using GitHub.DistributedTask.Pipelines.ContextData;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -9,7 +8,6 @@ using GitHub.Runner.Common;
|
|||||||
using GitHub.Runner.Sdk;
|
using GitHub.Runner.Sdk;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using GitHub.Runner.Worker.Container.ContainerHooks;
|
using GitHub.Runner.Worker.Container.ContainerHooks;
|
||||||
using System.IO;
|
|
||||||
using System.Threading.Channels;
|
using System.Threading.Channels;
|
||||||
|
|
||||||
namespace GitHub.Runner.Worker.Handlers
|
namespace GitHub.Runner.Worker.Handlers
|
||||||
@@ -60,7 +58,14 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
|
|
||||||
public Task<string> DetermineNodeRuntimeVersion(IExecutionContext executionContext, string preferredVersion)
|
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,
|
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)
|
public async Task<string> DetermineNodeRuntimeVersion(IExecutionContext executionContext, string preferredVersion)
|
||||||
{
|
{
|
||||||
// Optimistically use the default
|
// Use NodeUtil to check if Node24 is requested but we're on ARM32 Linux
|
||||||
string nodeExternal = preferredVersion;
|
var (nodeExternal, warningMessage) = Common.Util.NodeUtil.CheckNodeVersionForLinuxArm32(preferredVersion);
|
||||||
|
if (!string.IsNullOrEmpty(warningMessage))
|
||||||
|
{
|
||||||
|
executionContext.Warning(warningMessage);
|
||||||
|
}
|
||||||
|
|
||||||
if (FeatureManager.IsContainerHooksEnabled(executionContext.Global.Variables))
|
if (FeatureManager.IsContainerHooksEnabled(executionContext.Global.Variables))
|
||||||
{
|
{
|
||||||
@@ -264,7 +273,14 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
|
|
||||||
private string CheckPlatformForAlpineContainer(IExecutionContext executionContext, string preferredVersion)
|
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))
|
if (!Constants.Runner.PlatformArchitecture.Equals(Constants.Architecture.X64))
|
||||||
{
|
{
|
||||||
var os = Constants.Runner.Platform.ToString();
|
var os = Constants.Runner.Platform.ToString();
|
||||||
|
|||||||
@@ -50,8 +50,11 @@ namespace GitHub.Runner.Worker
|
|||||||
if (message.Variables.TryGetValue(Constants.Variables.System.OrchestrationId, out VariableValue orchestrationId) &&
|
if (message.Variables.TryGetValue(Constants.Variables.System.OrchestrationId, out VariableValue orchestrationId) &&
|
||||||
!string.IsNullOrEmpty(orchestrationId.Value))
|
!string.IsNullOrEmpty(orchestrationId.Value))
|
||||||
{
|
{
|
||||||
// make the orchestration id the first item in the user-agent header to avoid get truncated in server log.
|
if (!HostContext.UserAgents.Any(x => string.Equals(x.Product?.Name, "OrchestrationId", StringComparison.OrdinalIgnoreCase)))
|
||||||
HostContext.UserAgents.Insert(0, new ProductInfoHeaderValue("OrchestrationId", orchestrationId.Value));
|
{
|
||||||
|
// 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.
|
// make sure orchestration id is in the user-agent header.
|
||||||
VssUtil.InitializeVssClientSettings(HostContext.UserAgents, HostContext.WebProxy);
|
VssUtil.InitializeVssClientSettings(HostContext.UserAgents, HostContext.WebProxy);
|
||||||
|
|||||||
@@ -106,6 +106,18 @@ namespace GitHub.Services.Common
|
|||||||
{
|
{
|
||||||
VssTraceActivity traceActivity = VssTraceActivity.Current;
|
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)
|
lock (m_thisLock)
|
||||||
{
|
{
|
||||||
// Ensure that we attempt to use the most appropriate authentication mechanism by default.
|
// 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 readonly HttpMessageHandler m_transportHandler;
|
||||||
private HttpMessageInvoker m_messageInvoker;
|
private HttpMessageInvoker m_messageInvoker;
|
||||||
private CredentialWrapper m_credentialWrapper;
|
private CredentialWrapper m_credentialWrapper;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<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="Microsoft.Win32.Registry" Version="5.0.0" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="6.0.0" />
|
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="6.0.0" />
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ namespace GitHub.Services.Launch.Contracts
|
|||||||
{
|
{
|
||||||
[DataMember(EmitDefaultValue = false, Name = "authentication")]
|
[DataMember(EmitDefaultValue = false, Name = "authentication")]
|
||||||
public ActionDownloadAuthenticationResponse Authentication { get; set; }
|
public ActionDownloadAuthenticationResponse Authentication { get; set; }
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false, Name = "package_details")]
|
[DataMember(EmitDefaultValue = false, Name = "package_details")]
|
||||||
public ActionDownloadPackageDetailsResponse PackageDetails { get; set; }
|
public ActionDownloadPackageDetailsResponse PackageDetails { get; set; }
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@ namespace GitHub.Services.Launch.Contracts
|
|||||||
|
|
||||||
|
|
||||||
[DataContract]
|
[DataContract]
|
||||||
public class ActionDownloadPackageDetailsResponse
|
public class ActionDownloadPackageDetailsResponse
|
||||||
{
|
{
|
||||||
[DataMember(EmitDefaultValue = false, Name = "version")]
|
[DataMember(EmitDefaultValue = false, Name = "version")]
|
||||||
public string Version { get; set; }
|
public string Version { get; set; }
|
||||||
@@ -81,4 +81,25 @@ namespace GitHub.Services.Launch.Contracts
|
|||||||
[DataMember(EmitDefaultValue = false, Name = "actions")]
|
[DataMember(EmitDefaultValue = false, Name = "actions")]
|
||||||
public IDictionary<string, ActionDownloadInfoResponse> Actions { get; set; }
|
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;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Formatting;
|
using System.Net.Http.Formatting;
|
||||||
using System.Net.Http.Headers;
|
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)
|
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");
|
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
|
public async Task<ActionDownloadInfoCollection> GetResolveActionsDownloadInfoAsyncV2(Guid planId, Guid jobId, ActionReferenceList actionReferenceList, CancellationToken cancellationToken)
|
||||||
private async Task<T> GetLaunchSignedURLResponse<R, T>(Uri uri, R request, 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))
|
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))
|
using (HttpContent content = new ObjectContent<R>(request, m_formatter))
|
||||||
{
|
{
|
||||||
requestMessage.Content = content;
|
requestMessage.Content = content;
|
||||||
using (var response = await SendAsync(requestMessage, HttpCompletionOption.ResponseContentRead, cancellationToken: cancellationToken))
|
return await SendAsync(requestMessage, HttpCompletionOption.ResponseContentRead, cancellationToken: cancellationToken);
|
||||||
{
|
|
||||||
return await ReadJsonContentAsync<T>(response, cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -520,8 +520,8 @@ namespace GitHub.Services.Results.Client
|
|||||||
Number = r.Order.GetValueOrDefault(),
|
Number = r.Order.GetValueOrDefault(),
|
||||||
Name = r.Name,
|
Name = r.Name,
|
||||||
Status = ConvertStateToStatus(r.State.GetValueOrDefault()),
|
Status = ConvertStateToStatus(r.State.GetValueOrDefault()),
|
||||||
StartedAt = r.StartTime?.ToString(Constants.TimestampFormat),
|
StartedAt = r.StartTime?.ToString(Constants.TimestampFormat, CultureInfo.InvariantCulture),
|
||||||
CompletedAt = r.FinishTime?.ToString(Constants.TimestampFormat),
|
CompletedAt = r.FinishTime?.ToString(Constants.TimestampFormat, CultureInfo.InvariantCulture),
|
||||||
Conclusion = ConvertResultToConclusion(r.Result)
|
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.GetNextMessageAsync(It.IsAny<CancellationToken>()), Times.AtLeast(2));
|
||||||
_messageListener.Verify(x => x.DeleteMessageAsync(It.IsAny<TaskAgentMessage>()), Times.AtLeast(2));
|
_messageListener.Verify(x => x.DeleteMessageAsync(It.IsAny<TaskAgentMessage>()), Times.AtLeast(2));
|
||||||
_messageListener.Verify(x => x.DeleteSessionAsync(), Times.Once());
|
_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);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
226
src/Test/L0/Worker/ActionExecutionModelL0.cs
Normal file
226
src/Test/L0/Worker/ActionExecutionModelL0.cs
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Threading;
|
||||||
|
using GitHub.DistributedTask.WebApi;
|
||||||
|
using GitHub.Runner.Worker;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace GitHub.Runner.Common.Tests.Worker
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Tests to verify that actions are executed without compilation
|
||||||
|
/// </summary>
|
||||||
|
public sealed class ActionExecutionModelL0
|
||||||
|
{
|
||||||
|
private CancellationTokenSource _ecTokenSource;
|
||||||
|
private Mock<IExecutionContext> _ec;
|
||||||
|
private TestHostContext _hc;
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void JavaScriptActions_UseSourceFiles_NoCompilation()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Setup();
|
||||||
|
var actionManifest = new ActionManifestManager();
|
||||||
|
actionManifest.Initialize(_hc);
|
||||||
|
|
||||||
|
// Create a temporary action.yml for a JavaScript action
|
||||||
|
string actionYml = @"
|
||||||
|
name: 'Test JS Action'
|
||||||
|
description: 'Test JavaScript action execution'
|
||||||
|
runs:
|
||||||
|
using: 'node20'
|
||||||
|
main: 'index.js'
|
||||||
|
";
|
||||||
|
string tempFile = Path.GetTempFileName();
|
||||||
|
File.WriteAllText(tempFile, actionYml);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = actionManifest.Load(_ec.Object, tempFile);
|
||||||
|
|
||||||
|
// Assert - JavaScript actions should use direct script execution
|
||||||
|
Assert.Equal(ActionExecutionType.NodeJS, result.Execution.ExecutionType);
|
||||||
|
|
||||||
|
var nodeAction = result.Execution as NodeJSActionExecutionData;
|
||||||
|
Assert.NotNull(nodeAction);
|
||||||
|
Assert.Equal("node20", nodeAction.NodeVersion);
|
||||||
|
Assert.Equal("index.js", nodeAction.Script); // Points to source file, not compiled binary
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
File.Delete(tempFile);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void ContainerActions_UseImages_NoSourceCompilation()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Setup();
|
||||||
|
var actionManifest = new ActionManifestManager();
|
||||||
|
actionManifest.Initialize(_hc);
|
||||||
|
|
||||||
|
// Create a temporary action.yml for a container action
|
||||||
|
string actionYml = @"
|
||||||
|
name: 'Test Container Action'
|
||||||
|
description: 'Test container action execution'
|
||||||
|
runs:
|
||||||
|
using: 'docker'
|
||||||
|
image: 'alpine:latest'
|
||||||
|
entrypoint: '/bin/sh'
|
||||||
|
args:
|
||||||
|
- '-c'
|
||||||
|
- 'echo Hello World'
|
||||||
|
";
|
||||||
|
string tempFile = Path.GetTempFileName();
|
||||||
|
File.WriteAllText(tempFile, actionYml);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = actionManifest.Load(_ec.Object, tempFile);
|
||||||
|
|
||||||
|
// Assert - Container actions should use images, not compiled source
|
||||||
|
Assert.Equal(ActionExecutionType.Container, result.Execution.ExecutionType);
|
||||||
|
|
||||||
|
var containerAction = result.Execution as ContainerActionExecutionData;
|
||||||
|
Assert.NotNull(containerAction);
|
||||||
|
Assert.Equal("alpine:latest", containerAction.Image); // Uses pre-built image
|
||||||
|
Assert.Equal("/bin/sh", containerAction.EntryPoint);
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
File.Delete(tempFile);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void CompositeActions_UseStepDefinitions_NoCompilation()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Setup();
|
||||||
|
var actionManifest = new ActionManifestManager();
|
||||||
|
actionManifest.Initialize(_hc);
|
||||||
|
|
||||||
|
// Create a temporary action.yml for a composite action
|
||||||
|
string actionYml = @"
|
||||||
|
name: 'Test Composite Action'
|
||||||
|
description: 'Test composite action execution'
|
||||||
|
runs:
|
||||||
|
using: 'composite'
|
||||||
|
steps:
|
||||||
|
- run: echo 'Hello from step 1'
|
||||||
|
shell: bash
|
||||||
|
- run: echo 'Hello from step 2'
|
||||||
|
shell: bash
|
||||||
|
";
|
||||||
|
string tempFile = Path.GetTempFileName();
|
||||||
|
File.WriteAllText(tempFile, actionYml);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = actionManifest.Load(_ec.Object, tempFile);
|
||||||
|
|
||||||
|
// Assert - Composite actions should use step definitions, not compiled code
|
||||||
|
Assert.Equal(ActionExecutionType.Composite, result.Execution.ExecutionType);
|
||||||
|
|
||||||
|
var compositeAction = result.Execution as CompositeActionExecutionData;
|
||||||
|
Assert.NotNull(compositeAction);
|
||||||
|
Assert.Equal(2, compositeAction.Steps.Count); // Contains step definitions, not binaries
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
File.Delete(tempFile);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void ActionTypes_DoNotRequireCompilation_OnlyInterpretation()
|
||||||
|
{
|
||||||
|
// This test documents that actions are interpreted, not compiled
|
||||||
|
|
||||||
|
// JavaScript actions: Node.js interprets .js files directly
|
||||||
|
// Container actions: Docker runs images or builds from Dockerfile
|
||||||
|
// Composite actions: Runner interprets YAML step definitions
|
||||||
|
|
||||||
|
// The runner itself (this C# code) is compiled, but actions are not
|
||||||
|
Assert.True(true, "Actions use interpretation model, not compilation model");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void ActionExecutionTypes_ShowNoCompilationRequired()
|
||||||
|
{
|
||||||
|
// Test that all action execution types are designed for interpretation
|
||||||
|
|
||||||
|
// NodeJS actions execute source JavaScript files directly
|
||||||
|
var nodeAction = new NodeJSActionExecutionData
|
||||||
|
{
|
||||||
|
NodeVersion = "node20",
|
||||||
|
Script = "index.js" // Points to source file, not compiled binary
|
||||||
|
};
|
||||||
|
Assert.Equal(ActionExecutionType.NodeJS, nodeAction.ExecutionType);
|
||||||
|
Assert.Equal("index.js", nodeAction.Script);
|
||||||
|
|
||||||
|
// Container actions use images, not compiled source
|
||||||
|
var containerAction = new ContainerActionExecutionData
|
||||||
|
{
|
||||||
|
Image = "alpine:latest" // Pre-built image, not compiled from this action's source
|
||||||
|
};
|
||||||
|
Assert.Equal(ActionExecutionType.Container, containerAction.ExecutionType);
|
||||||
|
Assert.Equal("alpine:latest", containerAction.Image);
|
||||||
|
|
||||||
|
// Composite actions contain step definitions
|
||||||
|
var compositeAction = new CompositeActionExecutionData
|
||||||
|
{
|
||||||
|
Steps = new List<GitHub.DistributedTask.Pipelines.ActionStep>()
|
||||||
|
};
|
||||||
|
Assert.Equal(ActionExecutionType.Composite, compositeAction.ExecutionType);
|
||||||
|
Assert.NotNull(compositeAction.Steps); // Contains YAML-defined steps, not compiled code
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Setup([CallerMemberName] string name = "")
|
||||||
|
{
|
||||||
|
_ecTokenSource = new CancellationTokenSource();
|
||||||
|
_hc = new TestHostContext(this, name);
|
||||||
|
|
||||||
|
_ec = new Mock<IExecutionContext>();
|
||||||
|
_ec.Setup(x => x.CancellationToken).Returns(_ecTokenSource.Token);
|
||||||
|
_ec.Setup(x => x.Global).Returns(new GlobalContext
|
||||||
|
{
|
||||||
|
Variables = new Variables(_hc, new Dictionary<string, VariableValue>()),
|
||||||
|
FileTable = new List<string>()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Teardown()
|
||||||
|
{
|
||||||
|
_hc?.Dispose();
|
||||||
|
_ecTokenSource?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1659,6 +1659,76 @@ runs:
|
|||||||
Teardown();
|
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]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Worker")]
|
[Trait("Category", "Worker")]
|
||||||
@@ -2411,8 +2481,8 @@ runs:
|
|||||||
});
|
});
|
||||||
|
|
||||||
_launchServer = new Mock<ILaunchServer>();
|
_launchServer = new Mock<ILaunchServer>();
|
||||||
_launchServer.Setup(x => x.ResolveActionsDownloadInfoAsync(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<ActionReferenceList>(), It.IsAny<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) =>
|
.Returns((Guid planId, Guid jobId, ActionReferenceList actions, CancellationToken cancellationToken, bool displayHelpfulActionsDownloadErrors) =>
|
||||||
{
|
{
|
||||||
var result = new ActionDownloadInfoCollection { Actions = new Dictionary<string, ActionDownloadInfo>() };
|
var result = new ActionDownloadInfoCollection { Actions = new Dictionary<string, ActionDownloadInfo>() };
|
||||||
foreach (var action in actions.Actions)
|
foreach (var action in actions.Actions)
|
||||||
|
|||||||
@@ -502,6 +502,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]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Worker")]
|
[Trait("Category", "Worker")]
|
||||||
@@ -758,7 +801,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
//Assert
|
//Assert
|
||||||
var err = Assert.Throws<ArgumentException>(() => actionManifest.Load(_ec.Object, action_path));
|
var err = Assert.Throws<ArgumentException>(() => actionManifest.Load(_ec.Object, action_path));
|
||||||
Assert.Contains($"Failed to load {action_path}", err.Message);
|
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
|
finally
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
[InlineData("node12", "node20")]
|
[InlineData("node12", "node20")]
|
||||||
[InlineData("node16", "node20")]
|
[InlineData("node16", "node20")]
|
||||||
[InlineData("node20", "node20")]
|
[InlineData("node20", "node20")]
|
||||||
|
[InlineData("node24", "node24")]
|
||||||
public void IsNodeVersionUpgraded(string inputVersion, string expectedVersion)
|
public void IsNodeVersionUpgraded(string inputVersion, string expectedVersion)
|
||||||
{
|
{
|
||||||
using (TestHostContext hc = CreateTestContext())
|
using (TestHostContext hc = CreateTestContext())
|
||||||
@@ -41,7 +42,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
var hf = new HandlerFactory();
|
var hf = new HandlerFactory();
|
||||||
hf.Initialize(hc);
|
hf.Initialize(hc);
|
||||||
|
|
||||||
// Server Feature Flag
|
// Setup variables
|
||||||
var variables = new Dictionary<string, VariableValue>();
|
var variables = new Dictionary<string, VariableValue>();
|
||||||
Variables serverVariables = new(hc, variables);
|
Variables serverVariables = new(hc, variables);
|
||||||
|
|
||||||
@@ -72,5 +73,48 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
Assert.Equal(expectedVersion, handler.Data.NodeVersion);
|
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);
|
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
|
#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>
|
||||||
|
|
||||||
<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" Version="2.7.1" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||||
<PackageReference Include="System.Reflection.TypeExtensions" Version="4.7.0" />
|
<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"
|
DOWNLOAD_DIR="$SCRIPT_DIR/../_downloads/netcore2x"
|
||||||
PACKAGE_DIR="$SCRIPT_DIR/../_package"
|
PACKAGE_DIR="$SCRIPT_DIR/../_package"
|
||||||
DOTNETSDK_ROOT="$SCRIPT_DIR/../_dotnetsdk"
|
DOTNETSDK_ROOT="$SCRIPT_DIR/../_dotnetsdk"
|
||||||
DOTNETSDK_VERSION="8.0.408"
|
DOTNETSDK_VERSION="8.0.412"
|
||||||
DOTNETSDK_INSTALLDIR="$DOTNETSDK_ROOT/$DOTNETSDK_VERSION"
|
DOTNETSDK_INSTALLDIR="$DOTNETSDK_ROOT/$DOTNETSDK_VERSION"
|
||||||
RUNNER_VERSION=$(cat runnerversion)
|
RUNNER_VERSION=$(cat runnerversion)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"sdk": {
|
"sdk": {
|
||||||
"version": "8.0.408"
|
"version": "8.0.412"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
2.324.0
|
2.328.0
|
||||||
|
|||||||
Reference in New Issue
Block a user