mirror of
https://github.com/actions/runner.git
synced 2025-12-10 12:36:23 +00:00
Compare commits
11 Commits
salmanmkc/
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
23ea13be96 | ||
|
|
e4511c02ad | ||
|
|
0ebdf9e83d | ||
|
|
6543bf206b | ||
|
|
a942627965 | ||
|
|
83539166c9 | ||
|
|
1c1e8bfd18 | ||
|
|
59177fa379 | ||
|
|
2d7635a7f0 | ||
|
|
0203cf24d3 | ||
|
|
5e74a4d8e4 |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -50,7 +50,7 @@ jobs:
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
# Build runner layout
|
||||
- name: Build & Layout Release
|
||||
|
||||
2
.github/workflows/codeql.yml
vendored
2
.github/workflows/codeql.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
|
||||
4
.github/workflows/docker-buildx-upgrade.yml
vendored
4
.github/workflows/docker-buildx-upgrade.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
BUILDX_CURRENT_VERSION: ${{ steps.check_buildx_version.outputs.CURRENT_VERSION }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Check Docker version
|
||||
id: check_docker_version
|
||||
@@ -89,7 +89,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Update Docker version
|
||||
shell: bash
|
||||
|
||||
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 }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
- name: Get current major minor version
|
||||
id: fetch_current_version
|
||||
shell: bash
|
||||
@@ -89,7 +89,7 @@ jobs:
|
||||
if: ${{ needs.dotnet-update.outputs.SHOULD_UPDATE == 1 && needs.dotnet-update.outputs.BRANCH_EXISTS == 0 }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
ref: feature/dotnetsdk-upgrade/${{ needs.dotnet-update.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }}
|
||||
- name: Create Pull Request
|
||||
|
||||
22
.github/workflows/release.yml
vendored
22
.github/workflows/release.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
if: startsWith(github.ref, 'refs/heads/releases/') || github.ref == 'refs/heads/main'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
# Make sure ./releaseVersion match ./src/runnerversion
|
||||
# Query GitHub release ensure version is not used
|
||||
@@ -86,7 +86,7 @@ jobs:
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
# Build runner layout
|
||||
- name: Build & Layout Release
|
||||
@@ -129,41 +129,41 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
# Download runner package tar.gz/zip produced by 'build' job
|
||||
- name: Download Artifact (win-x64)
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
name: runner-packages-win-x64
|
||||
path: ./
|
||||
- name: Download Artifact (win-arm64)
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
name: runner-packages-win-arm64
|
||||
path: ./
|
||||
- name: Download Artifact (osx-x64)
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
name: runner-packages-osx-x64
|
||||
path: ./
|
||||
- name: Download Artifact (osx-arm64)
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
name: runner-packages-osx-arm64
|
||||
path: ./
|
||||
- name: Download Artifact (linux-x64)
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
name: runner-packages-linux-x64
|
||||
path: ./
|
||||
- name: Download Artifact (linux-arm)
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
name: runner-packages-linux-arm
|
||||
path: ./
|
||||
- name: Download Artifact (linux-arm64)
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
name: runner-packages-linux-arm64
|
||||
path: ./
|
||||
@@ -296,7 +296,7 @@ jobs:
|
||||
IMAGE_NAME: ${{ github.repository_owner }}/actions-runner
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Compute image version
|
||||
id: image
|
||||
|
||||
10
README.md
10
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.
|
||||
|
||||
## 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
|
||||
|
||||
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)
|
||||
|
||||
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,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.
|
||||
|
||||
## 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
|
||||
|
||||
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
|
||||
|
||||
Please see "[Supported architectures and operating systems for self-hosted runners](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners#linux)."
|
||||
Please see "[Supported architectures and operating systems for self-hosted runners](https://docs.github.com/en/actions/reference/runners/self-hosted-runners#linux)."
|
||||
|
||||
## Install .Net Core 3.x Linux Dependencies
|
||||
|
||||
|
||||
@@ -4,6 +4,6 @@
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Please see "[Supported architectures and operating systems for self-hosted runners](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners#macos)."
|
||||
Please see "[Supported architectures and operating systems for self-hosted runners](https://docs.github.com/en/actions/reference/runners/self-hosted-runners#macos)."
|
||||
|
||||
## [More .Net Core Prerequisites Information](https://docs.microsoft.com/en-us/dotnet/core/macos-prerequisites?tabs=netcore30)
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Please see "[Supported architectures and operating systems for self-hosted runners](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners#windows)."
|
||||
Please see "[Supported architectures and operating systems for self-hosted runners](https://docs.github.com/en/actions/reference/runners/self-hosted-runners#windows)."
|
||||
|
||||
## [More .NET Core Prerequisites Information](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=netcore30)
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
## What's Changed
|
||||
* Try add orchestrationid into user-agent using token claim. by @TingluoHuang in https://github.com/actions/runner/pull/3945
|
||||
* Fix null reference exception in user agent handling by @salmanmkc in https://github.com/actions/runner/pull/3946
|
||||
* Runner Support for executing Node24 Actions by @salmanmkc in https://github.com/actions/runner/pull/3940
|
||||
* Update dotnet sdk to latest version @8.0.412 by @github-actions[bot] in https://github.com/actions/runner/pull/3941
|
||||
* 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 if statement structure in update script and variable reference by @salmanmkc in https://github.com/actions/runner/pull/3956
|
||||
* Add V2 flow for runner deletion by @Samirat in https://github.com/actions/runner/pull/3954
|
||||
* Node 20 -> Node 24 migration feature flagging, opt-in and opt-out environment variables by @salmanmkc in https://github.com/actions/runner/pull/3948
|
||||
* Update Node20 and Node24 to latest by @djs-intel in https://github.com/actions/runner/pull/3972
|
||||
* Redirect supported OS doc section to current public Docs location by @corycalahan in https://github.com/actions/runner/pull/3979
|
||||
* Bump Microsoft.NET.Test.Sdk from 17.13.0 to 17.14.1 by @dependabot[bot] in https://github.com/actions/runner/pull/3975
|
||||
* Bump Azure.Storage.Blobs from 12.24.0 to 12.25.0 by @dependabot[bot] in https://github.com/actions/runner/pull/3974
|
||||
* Bump actions/download-artifact from 4 to 5 by @dependabot[bot] in https://github.com/actions/runner/pull/3973
|
||||
* Bump actions/checkout from 4 to 5 by @dependabot[bot] in https://github.com/actions/runner/pull/3982
|
||||
|
||||
## New Contributors
|
||||
* @salmanmkc made their first contribution in https://github.com/actions/runner/pull/3946
|
||||
* @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.326.0...v2.327.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.
|
||||
To confirm which version of the Actions Runner you should expect, please view the download instructions for your enterprise, organization, or repository.
|
||||
|
||||
@@ -6,8 +6,8 @@ NODE_URL=https://nodejs.org/dist
|
||||
NODE_ALPINE_URL=https://github.com/actions/alpine_nodejs/releases/download
|
||||
# When you update Node versions you must also create a new release of alpine_nodejs at that updated version.
|
||||
# Follow the instructions here: https://github.com/actions/alpine_nodejs?tab=readme-ov-file#getting-started
|
||||
NODE20_VERSION="20.19.3"
|
||||
NODE24_VERSION="24.4.0"
|
||||
NODE20_VERSION="20.19.4"
|
||||
NODE24_VERSION="24.5.0"
|
||||
|
||||
get_abs_path() {
|
||||
# exploits the fact that pwd will print abs path when no args
|
||||
|
||||
@@ -123,8 +123,7 @@ fi
|
||||
# fix upgrade issue with macOS when running as a service
|
||||
attemptedtargetedfix=0
|
||||
currentplatform=$(uname | awk '{print tolower($0)}')
|
||||
if [[ "$currentplatform" == 'darwin' && $restartinteractiverunner -eq 0 ]];
|
||||
then
|
||||
if [[ "$currentplatform" == 'darwin' && $restartinteractiverunner -eq 0 ]]; then
|
||||
# We needed a fix for https://github.com/actions/runner/issues/743
|
||||
# We will recreate the ./externals/nodeXY/bin/node of the past runner version that launched the runnerlistener service
|
||||
# Otherwise mac gatekeeper kills the processes we spawn on creation as we are running a process with no backing file
|
||||
|
||||
@@ -170,6 +170,22 @@ namespace GitHub.Runner.Common
|
||||
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 Guid TelemetryRecordId = new Guid("11111111-1111-1111-1111-111111111111");
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace GitHub.Runner.Common
|
||||
|
||||
Task<DistributedTask.WebApi.Runner> AddRunnerAsync(int runnerGroupId, TaskAgent agent, string githubUrl, string githubToken, string publicKey);
|
||||
Task<DistributedTask.WebApi.Runner> ReplaceRunnerAsync(int runnerGroupId, TaskAgent agent, string githubUrl, string githubToken, string publicKey);
|
||||
Task DeleteRunnerAsync(string githubUrl, string githubToken, ulong runnerId);
|
||||
Task<List<TaskAgentPool>> GetRunnerGroupsAsync(string githubUrl, string githubToken);
|
||||
}
|
||||
|
||||
@@ -43,117 +44,15 @@ namespace GitHub.Runner.Common
|
||||
|
||||
public async Task<List<TaskAgent>> GetRunnerByNameAsync(string githubUrl, string githubToken, string agentName)
|
||||
{
|
||||
var githubApiUrl = "";
|
||||
var gitHubUrlBuilder = new UriBuilder(githubUrl);
|
||||
var path = gitHubUrlBuilder.Path.Split('/', '\\', StringSplitOptions.RemoveEmptyEntries);
|
||||
var isOrgRunner = path.Length == 1;
|
||||
var isRepoOrEnterpriseRunner = path.Length == 2;
|
||||
var isRepoRunner = isRepoOrEnterpriseRunner && !string.Equals(path[0], "enterprises", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (isOrgRunner)
|
||||
{
|
||||
// org runner
|
||||
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/orgs/{path[0]}/actions/runners?name={Uri.EscapeDataString(agentName)}";
|
||||
}
|
||||
else
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/orgs/{path[0]}/actions/runners?name={Uri.EscapeDataString(agentName)}";
|
||||
}
|
||||
}
|
||||
else if (isRepoOrEnterpriseRunner)
|
||||
{
|
||||
// Repository runner
|
||||
if (isRepoRunner)
|
||||
{
|
||||
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/repos/{path[0]}/{path[1]}/actions/runners?name={Uri.EscapeDataString(agentName)}";
|
||||
}
|
||||
else
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/repos/{path[0]}/{path[1]}/actions/runners?name={Uri.EscapeDataString(agentName)}";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Enterprise runner
|
||||
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/{path[0]}/{path[1]}/actions/runners?name={Uri.EscapeDataString(agentName)}";
|
||||
}
|
||||
else
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/{path[0]}/{path[1]}/actions/runners?name={Uri.EscapeDataString(agentName)}";
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"'{githubUrl}' should point to an org or enterprise.");
|
||||
}
|
||||
|
||||
var githubApiUrl = $"{GetEntityUrl(githubUrl)}/runners?name={Uri.EscapeDataString(agentName)}";
|
||||
var runnersList = await RetryRequest<ListRunnersResponse>(githubApiUrl, githubToken, RequestType.Get, 3, "Failed to get agents pools");
|
||||
|
||||
return runnersList.ToTaskAgents();
|
||||
}
|
||||
|
||||
public async Task<List<TaskAgentPool>> GetRunnerGroupsAsync(string githubUrl, string githubToken)
|
||||
{
|
||||
var githubApiUrl = "";
|
||||
var gitHubUrlBuilder = new UriBuilder(githubUrl);
|
||||
var path = gitHubUrlBuilder.Path.Split('/', '\\', StringSplitOptions.RemoveEmptyEntries);
|
||||
var isOrgRunner = path.Length == 1;
|
||||
var isRepoOrEnterpriseRunner = path.Length == 2;
|
||||
var isRepoRunner = isRepoOrEnterpriseRunner && !string.Equals(path[0], "enterprises", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (isOrgRunner)
|
||||
{
|
||||
// org runner
|
||||
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/orgs/{path[0]}/actions/runner-groups";
|
||||
}
|
||||
else
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/orgs/{path[0]}/actions/runner-groups";
|
||||
}
|
||||
}
|
||||
else if (isRepoOrEnterpriseRunner)
|
||||
{
|
||||
// Repository Runner
|
||||
if (isRepoRunner)
|
||||
{
|
||||
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/repos/{path[0]}/{path[1]}/actions/runner-groups";
|
||||
}
|
||||
else
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/repos/{path[0]}/{path[1]}/actions/runner-groups";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Enterprise Runner
|
||||
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/{path[0]}/{path[1]}/actions/runner-groups";
|
||||
}
|
||||
else
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/{path[0]}/{path[1]}/actions/runner-groups";
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"'{githubUrl}' should point to an org or enterprise.");
|
||||
}
|
||||
|
||||
var githubApiUrl = $"{GetEntityUrl(githubUrl)}/runner-groups";
|
||||
var agentPools = await RetryRequest<RunnerGroupList>(githubApiUrl, githubToken, RequestType.Get, 3, "Failed to get agents pools");
|
||||
|
||||
return agentPools?.ToAgentPoolList();
|
||||
}
|
||||
|
||||
@@ -204,6 +103,12 @@ namespace GitHub.Runner.Common
|
||||
return await RetryRequest<DistributedTask.WebApi.Runner>(githubApiUrl, githubToken, RequestType.Post, 3, "Failed to add agent", body);
|
||||
}
|
||||
|
||||
public async Task DeleteRunnerAsync(string githubUrl, string githubToken, ulong runnerId)
|
||||
{
|
||||
var githubApiUrl = $"{GetEntityUrl(githubUrl)}/runners/{runnerId}";
|
||||
await RetryRequest<DistributedTask.WebApi.Runner>(githubApiUrl, githubToken, RequestType.Delete, 3, "Failed to delete agent");
|
||||
}
|
||||
|
||||
private async Task<T> RetryRequest<T>(string githubApiUrl, string githubToken, RequestType requestType, int maxRetryAttemptsCount = 5, string errorMessage = null, StringContent body = null)
|
||||
{
|
||||
int retry = 0;
|
||||
@@ -220,13 +125,22 @@ namespace GitHub.Runner.Common
|
||||
try
|
||||
{
|
||||
HttpResponseMessage response = null;
|
||||
if (requestType == RequestType.Get)
|
||||
switch (requestType)
|
||||
{
|
||||
response = await httpClient.GetAsync(githubApiUrl);
|
||||
}
|
||||
else
|
||||
{
|
||||
response = await httpClient.PostAsync(githubApiUrl, body);
|
||||
case RequestType.Get:
|
||||
response = await httpClient.GetAsync(githubApiUrl);
|
||||
break;
|
||||
case RequestType.Post:
|
||||
response = await httpClient.PostAsync(githubApiUrl, body);
|
||||
break;
|
||||
case RequestType.Patch:
|
||||
response = await httpClient.PatchAsync(githubApiUrl, body);
|
||||
break;
|
||||
case RequestType.Delete:
|
||||
response = await httpClient.DeleteAsync(githubApiUrl);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(requestType), requestType, null);
|
||||
}
|
||||
|
||||
if (response != null)
|
||||
@@ -261,5 +175,61 @@ namespace GitHub.Runner.Common
|
||||
await Task.Delay(backOff);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetEntityUrl(string githubUrl)
|
||||
{
|
||||
var githubApiUrl = "";
|
||||
var gitHubUrlBuilder = new UriBuilder(githubUrl);
|
||||
var path = gitHubUrlBuilder.Path.Split('/', '\\', StringSplitOptions.RemoveEmptyEntries);
|
||||
var isOrgRunner = path.Length == 1;
|
||||
var isRepoOrEnterpriseRunner = path.Length == 2;
|
||||
var isRepoRunner = isRepoOrEnterpriseRunner && !string.Equals(path[0], "enterprises", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (isOrgRunner)
|
||||
{
|
||||
// org runner
|
||||
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/orgs/{path[0]}/actions";
|
||||
}
|
||||
else
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/orgs/{path[0]}/actions";
|
||||
}
|
||||
}
|
||||
else if (isRepoOrEnterpriseRunner)
|
||||
{
|
||||
// Repository Runner
|
||||
if (isRepoRunner)
|
||||
{
|
||||
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/repos/{path[0]}/{path[1]}/actions";
|
||||
}
|
||||
else
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/repos/{path[0]}/{path[1]}/actions";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Enterprise Runner
|
||||
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/{path[0]}/{path[1]}/actions";
|
||||
}
|
||||
else
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/{path[0]}/{path[1]}/actions";
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"'{githubUrl}' should point to an org or enterprise.");
|
||||
}
|
||||
|
||||
return githubApiUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,33 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using GitHub.Runner.Sdk;
|
||||
|
||||
namespace GitHub.Runner.Common.Util
|
||||
{
|
||||
public static class NodeUtil
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents details about an environment variable, including its value and source
|
||||
/// </summary>
|
||||
private class EnvironmentVariableInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets whether the value evaluates to true
|
||||
/// </summary>
|
||||
public bool IsTrue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the value came from the workflow environment
|
||||
/// </summary>
|
||||
public bool FromWorkflow { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the value came from the system environment
|
||||
/// </summary>
|
||||
public bool FromSystem { get; set; }
|
||||
}
|
||||
|
||||
private const string _defaultNodeVersion = "node20";
|
||||
public static readonly ReadOnlyCollection<string> BuiltInNodeVersions = new(new[] { "node20" });
|
||||
public static string GetInternalNodeVersion()
|
||||
@@ -18,6 +41,70 @@ namespace GitHub.Runner.Common.Util
|
||||
}
|
||||
return _defaultNodeVersion;
|
||||
}
|
||||
/// <summary>
|
||||
/// Determines the appropriate Node version for Actions to use
|
||||
/// </summary>
|
||||
/// <param name="workflowEnvironment">Optional dictionary containing workflow-level environment variables</param>
|
||||
/// <param name="useNode24ByDefault">Feature flag indicating if Node 24 should be the default</param>
|
||||
/// <param name="requireNode24">Feature flag indicating if Node 24 is required</param>
|
||||
/// <returns>The Node version to use (node20 or node24) and warning message if both env vars are set</returns>
|
||||
public static (string nodeVersion, string warningMessage) DetermineActionsNodeVersion(
|
||||
IDictionary<string, string> workflowEnvironment = null,
|
||||
bool useNode24ByDefault = false,
|
||||
bool requireNode24 = false)
|
||||
{
|
||||
// Phase 3: Always use Node 24 regardless of environment variables
|
||||
if (requireNode24)
|
||||
{
|
||||
return (Constants.Runner.NodeMigration.Node24, null);
|
||||
}
|
||||
|
||||
// Get environment variable details with source information
|
||||
var forceNode24Details = GetEnvironmentVariableDetails(
|
||||
Constants.Runner.NodeMigration.ForceNode24Variable, workflowEnvironment);
|
||||
|
||||
var allowUnsecureNodeDetails = GetEnvironmentVariableDetails(
|
||||
Constants.Runner.NodeMigration.AllowUnsecureNodeVersionVariable, workflowEnvironment);
|
||||
|
||||
bool forceNode24 = forceNode24Details.IsTrue;
|
||||
bool allowUnsecureNode = allowUnsecureNodeDetails.IsTrue;
|
||||
string warningMessage = null;
|
||||
|
||||
// Check if both flags are set from the same source
|
||||
bool bothFromWorkflow = forceNode24Details.IsTrue && allowUnsecureNodeDetails.IsTrue &&
|
||||
forceNode24Details.FromWorkflow && allowUnsecureNodeDetails.FromWorkflow;
|
||||
|
||||
bool bothFromSystem = forceNode24Details.IsTrue && allowUnsecureNodeDetails.IsTrue &&
|
||||
forceNode24Details.FromSystem && allowUnsecureNodeDetails.FromSystem;
|
||||
|
||||
// Handle the case when both are set in the same source
|
||||
if (bothFromWorkflow || bothFromSystem)
|
||||
{
|
||||
string source = bothFromWorkflow ? "workflow" : "system";
|
||||
string defaultVersion = useNode24ByDefault ? Constants.Runner.NodeMigration.Node24 : Constants.Runner.NodeMigration.Node20;
|
||||
warningMessage = $"Both {Constants.Runner.NodeMigration.ForceNode24Variable} and {Constants.Runner.NodeMigration.AllowUnsecureNodeVersionVariable} environment variables are set to true in the {source} environment. This is likely a configuration error. Using the default Node version: {defaultVersion}.";
|
||||
return (defaultVersion, warningMessage);
|
||||
}
|
||||
|
||||
// Phase 2: Node 24 is the default
|
||||
if (useNode24ByDefault)
|
||||
{
|
||||
if (allowUnsecureNode)
|
||||
{
|
||||
return (Constants.Runner.NodeMigration.Node20, null);
|
||||
}
|
||||
|
||||
return (Constants.Runner.NodeMigration.Node24, null);
|
||||
}
|
||||
|
||||
// Phase 1: Node 20 is the default
|
||||
if (forceNode24)
|
||||
{
|
||||
return (Constants.Runner.NodeMigration.Node24, null);
|
||||
}
|
||||
|
||||
return (Constants.Runner.NodeMigration.Node20, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if Node24 is requested but running on ARM32 Linux, and determines if fallback is needed.
|
||||
@@ -26,14 +113,50 @@ namespace GitHub.Runner.Common.Util
|
||||
/// <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, "node24", StringComparison.OrdinalIgnoreCase) &&
|
||||
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 ("node20", "Node 24 is not supported on Linux ARM32 platforms. Falling back to Node 20.");
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -537,41 +537,50 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
if (isConfigured && hasCredentials)
|
||||
{
|
||||
RunnerSettings settings = _store.GetSettings();
|
||||
var credentialManager = HostContext.GetService<ICredentialManager>();
|
||||
|
||||
// Get the credentials
|
||||
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
|
||||
if (settings.UseV2Flow)
|
||||
{
|
||||
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);
|
||||
await _dotcomServer.DeleteRunnerAsync(settings.GitHubUrl, deletionToken, settings.AgentId);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _runnerServer.DeleteAgentAsync(settings.AgentId);
|
||||
var credentialManager = HostContext.GetService<ICredentialManager>();
|
||||
|
||||
_term.WriteLine();
|
||||
_term.WriteSuccessMessage("Runner removed successfully");
|
||||
// Get the credentials
|
||||
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
|
||||
{
|
||||
|
||||
@@ -58,10 +58,41 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
var nodeData = data as NodeJSActionExecutionData;
|
||||
|
||||
// With node12 EoL in 04/2022 and node16 EoL in 09/23, we want to execute all JS actions using node20
|
||||
// With node20 EoL approaching, we're preparing to migrate to node24
|
||||
if (string.Equals(nodeData.NodeVersion, "node12", StringComparison.InvariantCultureIgnoreCase) ||
|
||||
string.Equals(nodeData.NodeVersion, "node16", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
nodeData.NodeVersion = "node20";
|
||||
nodeData.NodeVersion = Common.Constants.Runner.NodeMigration.Node20;
|
||||
}
|
||||
|
||||
// Check if node20 was explicitly specified in the action
|
||||
// We don't modify if node24 was explicitly specified
|
||||
if (string.Equals(nodeData.NodeVersion, Constants.Runner.NodeMigration.Node20, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
bool useNode24ByDefault = executionContext.Global.Variables?.GetBoolean(Constants.Runner.NodeMigration.UseNode24ByDefaultFlag) ?? false;
|
||||
bool requireNode24 = executionContext.Global.Variables?.GetBoolean(Constants.Runner.NodeMigration.RequireNode24Flag) ?? false;
|
||||
|
||||
var (nodeVersion, configWarningMessage) = NodeUtil.DetermineActionsNodeVersion(environment, useNode24ByDefault, requireNode24);
|
||||
var (finalNodeVersion, platformWarningMessage) = NodeUtil.CheckNodeVersionForLinuxArm32(nodeVersion);
|
||||
nodeData.NodeVersion = finalNodeVersion;
|
||||
|
||||
if (!string.IsNullOrEmpty(configWarningMessage))
|
||||
{
|
||||
executionContext.Warning(configWarningMessage);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(platformWarningMessage))
|
||||
{
|
||||
executionContext.Warning(platformWarningMessage);
|
||||
}
|
||||
|
||||
// Show information about Node 24 migration in Phase 2
|
||||
if (useNode24ByDefault && !requireNode24 && string.Equals(finalNodeVersion, Constants.Runner.NodeMigration.Node24, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
string infoMessage = "Node 20 is being deprecated. This workflow is running with Node 24 by default. " +
|
||||
"If you need to temporarily use Node 20, you can set the ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true environment variable.";
|
||||
executionContext.Output(infoMessage);
|
||||
}
|
||||
}
|
||||
|
||||
(handler as INodeScriptActionHandler).Data = nodeData;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.Storage.Blobs" Version="12.24.0" />
|
||||
<PackageReference Include="Azure.Storage.Blobs" Version="12.25.0" />
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="6.0.0" />
|
||||
|
||||
@@ -978,7 +978,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
_messageListener.Verify(x => x.GetNextMessageAsync(It.IsAny<CancellationToken>()), Times.AtLeast(2));
|
||||
_messageListener.Verify(x => x.DeleteMessageAsync(It.IsAny<TaskAgentMessage>()), Times.AtLeast(2));
|
||||
_messageListener.Verify(x => x.DeleteSessionAsync(), Times.Once());
|
||||
_credentialManager.Verify(x => x.LoadCredentials(true), Times.Exactly(2));
|
||||
_credentialManager.Verify(x => x.LoadCredentials(true), Times.AtLeast(2));
|
||||
|
||||
Assert.False(hc.AllowAuthMigration);
|
||||
}
|
||||
|
||||
@@ -1,388 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using GitHub.Runner.Common.Tests;
|
||||
using GitHub.Runner.Sdk;
|
||||
using Xunit;
|
||||
|
||||
namespace GitHub.Runner.Common.Tests.Listener
|
||||
{
|
||||
public sealed class ShellScriptSyntaxL0
|
||||
{
|
||||
// Generic method to test any shell script template for bash syntax errors
|
||||
private void ValidateShellScriptTemplateSyntax(string relativePath, string templateName, bool shouldPass = true, Func<string, string> templateModifier = null)
|
||||
{
|
||||
// Skip on Windows
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using (var hc = new TestHostContext(this))
|
||||
{
|
||||
// Arrange
|
||||
string rootDirectory = Path.GetFullPath(Path.Combine(TestUtil.GetSrcPath(), ".."));
|
||||
string templatePath = Path.Combine(rootDirectory, relativePath, templateName);
|
||||
string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
|
||||
Directory.CreateDirectory(tempDir);
|
||||
string tempScriptPath = Path.Combine(tempDir, Path.GetFileNameWithoutExtension(templateName));
|
||||
|
||||
// Read the template
|
||||
string template = File.ReadAllText(templatePath);
|
||||
|
||||
// Apply template modifier if provided (for injecting errors)
|
||||
if (templateModifier != null)
|
||||
{
|
||||
template = templateModifier(template);
|
||||
}
|
||||
|
||||
// Replace common placeholders with valid test values
|
||||
template = ReplaceCommonPlaceholders(template, rootDirectory, tempDir);
|
||||
|
||||
// Write the processed template to a temporary file
|
||||
File.WriteAllText(tempScriptPath, template);
|
||||
|
||||
// Make the file executable
|
||||
var chmodProcess = new Process();
|
||||
chmodProcess.StartInfo.FileName = "chmod";
|
||||
chmodProcess.StartInfo.Arguments = $"+x {tempScriptPath}";
|
||||
chmodProcess.Start();
|
||||
chmodProcess.WaitForExit();
|
||||
|
||||
// Act - Check syntax using bash -n
|
||||
var process = new Process();
|
||||
process.StartInfo.FileName = "bash";
|
||||
process.StartInfo.Arguments = $"-n {tempScriptPath}";
|
||||
process.StartInfo.RedirectStandardError = true;
|
||||
process.StartInfo.UseShellExecute = false;
|
||||
process.Start();
|
||||
string errors = process.StandardError.ReadToEnd();
|
||||
process.WaitForExit();
|
||||
|
||||
// Assert based on expected outcome
|
||||
if (shouldPass)
|
||||
{
|
||||
Assert.Equal(0, process.ExitCode);
|
||||
Assert.Empty(errors);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.NotEqual(0, process.ExitCode);
|
||||
Assert.NotEmpty(errors);
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
try
|
||||
{
|
||||
Directory.Delete(tempDir, true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Best effort cleanup
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Assert.Fail($"Exception during test for {templateName}: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
// Helper method to replace common placeholders in shell script templates
|
||||
private string ReplaceCommonPlaceholders(string template, string rootDirectory, string tempDir)
|
||||
{
|
||||
// Replace common placeholders
|
||||
template = template.Replace("_PROCESS_ID_", "1234");
|
||||
template = template.Replace("_RUNNER_PROCESS_NAME_", "Runner.Listener");
|
||||
template = template.Replace("_ROOT_FOLDER_", rootDirectory);
|
||||
template = template.Replace("_EXIST_RUNNER_VERSION_", "2.300.0");
|
||||
template = template.Replace("_DOWNLOAD_RUNNER_VERSION_", "2.301.0");
|
||||
template = template.Replace("_UPDATE_LOG_", Path.Combine(tempDir, "update.log"));
|
||||
template = template.Replace("_RESTART_INTERACTIVE_RUNNER_", "0");
|
||||
template = template.Replace("_SERVICEUSERNAME_", "runner");
|
||||
template = template.Replace("_SERVICEPASSWORD_", "password");
|
||||
template = template.Replace("_SERVICEDISPLAYNAME_", "GitHub Actions Runner");
|
||||
template = template.Replace("_SERVICENAME_", "github-runner");
|
||||
template = template.Replace("_SERVICELOGPATH_", Path.Combine(tempDir, "service.log"));
|
||||
template = template.Replace("_RUNNERSERVICEUSERDISPLAYNAME_", "GitHub Actions Runner Service");
|
||||
|
||||
return template;
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Runner")]
|
||||
[Trait("SkipOn", "windows")]
|
||||
public void UpdateShTemplateHasValidSyntax()
|
||||
{
|
||||
ValidateShellScriptTemplateSyntax("src/Misc/layoutbin", "update.sh.template");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Runner")]
|
||||
[Trait("SkipOn", "windows")]
|
||||
public void UpdateShTemplateWithErrorsFailsValidation()
|
||||
{
|
||||
ValidateShellScriptTemplateSyntax(
|
||||
"src/Misc/layoutbin",
|
||||
"update.sh.template",
|
||||
shouldPass: false,
|
||||
templateModifier: template =>
|
||||
{
|
||||
// Introduce syntax errors
|
||||
|
||||
// 1. Missing 'fi' for an 'if' statement
|
||||
template = template.Replace("fi\n", "\n");
|
||||
|
||||
// 2. Unbalanced quotes
|
||||
template = template.Replace("date \"+[%F %T-%4N]", "date \"+[%F %T-%4N");
|
||||
|
||||
// 3. Invalid syntax in if condition
|
||||
template = template.Replace("if [ $? -ne 0 ]", "if [ $? -ne 0");
|
||||
|
||||
return template;
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Runner")]
|
||||
[Trait("SkipOn", "windows")]
|
||||
public void DarwinSvcShTemplateHasValidSyntax()
|
||||
{
|
||||
ValidateShellScriptTemplateSyntax("src/Misc/layoutbin", "darwin.svc.sh.template");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Runner")]
|
||||
[Trait("SkipOn", "windows")]
|
||||
public void SystemdSvcShTemplateHasValidSyntax()
|
||||
{
|
||||
ValidateShellScriptTemplateSyntax("src/Misc/layoutbin", "systemd.svc.sh.template");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Runner")]
|
||||
[Trait("SkipOn", "windows")]
|
||||
public void RunHelperShTemplateHasValidSyntax()
|
||||
{
|
||||
ValidateShellScriptTemplateSyntax("src/Misc/layoutroot", "run-helper.sh.template");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Runner")]
|
||||
[Trait("SkipOn", "windows")]
|
||||
public void UpdateShTemplateHasCorrectVariableReferencesAndIfStructure()
|
||||
{
|
||||
// Skip on Windows
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using (var hc = new TestHostContext(this))
|
||||
{
|
||||
// Arrange
|
||||
string rootDirectory = Path.GetFullPath(Path.Combine(TestUtil.GetSrcPath(), ".."));
|
||||
string templatePath = Path.Combine(rootDirectory, "src", "Misc", "layoutbin", "update.sh.template");
|
||||
|
||||
// Read the template
|
||||
string template = File.ReadAllText(templatePath);
|
||||
|
||||
// Assert
|
||||
// 1. Check that $restartinteractiverunner is correctly referenced with $ in if condition
|
||||
Assert.Contains("if [[ \"$currentplatform\" == 'darwin' && $restartinteractiverunner -eq 0 ]];\nthen", template);
|
||||
|
||||
// 2. Check for proper nesting of if statements for node version checks
|
||||
int nodeVersionCheckLines = 0;
|
||||
bool foundNode24Block = false;
|
||||
bool foundNode16Block = false;
|
||||
bool foundNode12Block = false;
|
||||
bool hasProperIndentation = false;
|
||||
|
||||
string[] lines = template.Split('\n');
|
||||
for (int i = 0; i < lines.Length; i++)
|
||||
{
|
||||
string line = lines[i];
|
||||
if (line.Contains("nodever=\"node24\""))
|
||||
{
|
||||
foundNode24Block = true;
|
||||
}
|
||||
if (line.Contains("nodever=\"node16\""))
|
||||
{
|
||||
foundNode16Block = true;
|
||||
}
|
||||
if (foundNode16Block && line.Contains("nodever=\"node12\""))
|
||||
{
|
||||
foundNode12Block = true;
|
||||
// Check if we have proper indentation for this nested block
|
||||
hasProperIndentation = line.StartsWith(" ");
|
||||
}
|
||||
if (line.Contains("Fallback if RunnerService.js was started with"))
|
||||
{
|
||||
nodeVersionCheckLines++;
|
||||
}
|
||||
}
|
||||
|
||||
// The template has node24 check but there's no "Fallback if RunnerService.js was started with node24" comment for it
|
||||
// Only the node20, node16, and node12 sections have this comment
|
||||
Assert.Equal(3, nodeVersionCheckLines); // node20, node16, node12
|
||||
Assert.True(foundNode24Block, "Could not find node24 block");
|
||||
Assert.True(foundNode16Block, "Could not find node16 block");
|
||||
Assert.True(foundNode12Block, "Could not find node12 block");
|
||||
Assert.True(hasProperIndentation, "node12 block is not properly indented");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Assert.Fail($"Exception during test: {ex.ToString()}");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Runner")]
|
||||
[Trait("SkipOn", "osx,linux")]
|
||||
public void UpdateCmdTemplateHasValidSyntax()
|
||||
{
|
||||
// Skip on non-Windows platforms
|
||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ValidateCmdScriptTemplateSyntax("update.cmd.template", shouldPass: true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Runner")]
|
||||
[Trait("SkipOn", "osx,linux")]
|
||||
public void UpdateCmdTemplateWithErrorsFailsValidation()
|
||||
{
|
||||
// Skip on non-Windows platforms
|
||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ValidateCmdScriptTemplateSyntax("update.cmd.template", shouldPass: false,
|
||||
templateModifier: template =>
|
||||
{
|
||||
// Introduce syntax errors in the template
|
||||
// 1. Unbalanced parentheses
|
||||
template = template.Replace("if exist", "if exist (");
|
||||
|
||||
// 2. Unclosed quotes
|
||||
template = template.Replace("echo", "echo \"Unclosed quote");
|
||||
|
||||
return template;
|
||||
});
|
||||
}
|
||||
|
||||
private void ValidateCmdScriptTemplateSyntax(string templateName, bool shouldPass, Func<string, string> templateModifier = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var hc = new TestHostContext(this))
|
||||
{
|
||||
// Arrange
|
||||
string rootDirectory = Path.GetFullPath(Path.Combine(TestUtil.GetSrcPath(), ".."));
|
||||
string templatePath = Path.Combine(rootDirectory, "src", "Misc", "layoutbin", templateName);
|
||||
string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
|
||||
Directory.CreateDirectory(tempDir);
|
||||
string tempUpdatePath = Path.Combine(tempDir, Path.GetFileName(templateName).Replace(".template", ""));
|
||||
|
||||
// Read the template
|
||||
string template = File.ReadAllText(templatePath);
|
||||
|
||||
// Apply template modifier if provided (for injecting errors)
|
||||
if (templateModifier != null)
|
||||
{
|
||||
template = templateModifier(template);
|
||||
}
|
||||
|
||||
// Replace the placeholders with valid test values
|
||||
template = template.Replace("_PROCESS_ID_", "1234");
|
||||
template = template.Replace("_RUNNER_PROCESS_NAME_", "Runner.Listener.exe");
|
||||
template = template.Replace("_ROOT_FOLDER_", rootDirectory);
|
||||
template = template.Replace("_EXIST_RUNNER_VERSION_", "2.300.0");
|
||||
template = template.Replace("_DOWNLOAD_RUNNER_VERSION_", "2.301.0");
|
||||
template = template.Replace("_UPDATE_LOG_", Path.Combine(tempDir, "update.log"));
|
||||
template = template.Replace("_RESTART_INTERACTIVE_RUNNER_", "0");
|
||||
|
||||
// Write the processed template to a temporary file
|
||||
File.WriteAllText(tempUpdatePath, template);
|
||||
|
||||
// Act - Check syntax using cmd with special flags:
|
||||
// /v:on - Enable delayed environment variable expansion
|
||||
// /f:off - Disable file name completion
|
||||
// /e:on - Enable command extensions
|
||||
// These flags help validate the syntax without fully executing the script
|
||||
var process = new Process();
|
||||
process.StartInfo.FileName = "cmd.exe";
|
||||
process.StartInfo.Arguments = $"/c /v:on /f:off /e:on \"{tempUpdatePath}\" echo SyntaxCheckOnly && exit /b 0";
|
||||
process.StartInfo.RedirectStandardError = true;
|
||||
process.StartInfo.RedirectStandardOutput = true;
|
||||
process.StartInfo.UseShellExecute = false;
|
||||
process.Start();
|
||||
string errors = process.StandardError.ReadToEnd();
|
||||
string output = process.StandardOutput.ReadToEnd();
|
||||
process.WaitForExit();
|
||||
|
||||
// Check for mismatched parentheses in the file content
|
||||
int openParenCount = template.Split('(').Length - 1;
|
||||
int closeParenCount = template.Split(')').Length - 1;
|
||||
bool hasMissingParenthesis = openParenCount != closeParenCount;
|
||||
|
||||
// Check for unclosed quotes (simple check - not perfect but catches obvious errors)
|
||||
int doubleQuoteCount = template.Split('"').Length - 1;
|
||||
bool hasUnclosedQuotes = doubleQuoteCount % 2 != 0;
|
||||
|
||||
// Determine if the validation passed
|
||||
bool validationPassed = process.ExitCode == 0 &&
|
||||
string.IsNullOrEmpty(errors) &&
|
||||
!hasMissingParenthesis &&
|
||||
!hasUnclosedQuotes;
|
||||
|
||||
// Assert based on expected outcome
|
||||
if (shouldPass)
|
||||
{
|
||||
Assert.True(validationPassed,
|
||||
$"Template validation should have passed but failed. Exit code: {process.ExitCode}, " +
|
||||
$"Errors: {errors}, HasMissingParenthesis: {hasMissingParenthesis}, " +
|
||||
$"HasUnclosedQuotes: {hasUnclosedQuotes}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.False(validationPassed,
|
||||
"Template validation should have failed but passed. " +
|
||||
"The intentionally introduced syntax errors were not detected.");
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
try
|
||||
{
|
||||
Directory.Delete(tempDir, true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Best effort cleanup
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Assert.Fail($"Exception during test: {ex.ToString()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageReference Include="xunit" Version="2.7.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
<PackageReference Include="System.Reflection.TypeExtensions" Version="4.7.0" />
|
||||
|
||||
@@ -1 +1 @@
|
||||
2.327.0
|
||||
2.328.0
|
||||
|
||||
Reference in New Issue
Block a user