mirror of
https://github.com/actions/runner.git
synced 2025-12-10 20:36:49 +00:00
Compare commits
2 Commits
copilot/fi
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
23ea13be96 | ||
|
|
e4511c02ad |
@@ -4,7 +4,7 @@
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/docker-in-docker:1": {},
|
||||
"ghcr.io/devcontainers/features/dotnet": {
|
||||
"version": "8.0.413"
|
||||
"version": "8.0.412"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/node:1": {
|
||||
"version": "20"
|
||||
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -338,7 +338,7 @@ jobs:
|
||||
org.opencontainers.image.licenses=MIT
|
||||
|
||||
- name: Generate attestation
|
||||
uses: actions/attest-build-provenance@v3
|
||||
uses: actions/attest-build-provenance@v2
|
||||
with:
|
||||
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
subject-digest: ${{ steps.build-and-push.outputs.digest }}
|
||||
|
||||
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.
|
||||
@@ -20,7 +20,6 @@ Execute ./bin/installdependencies.sh to install any missing Dotnet Core 6.0 depe
|
||||
You can easily correct the problem by executing `./bin/installdependencies.sh`.
|
||||
The `installdependencies.sh` script should install all required dependencies on all supported Linux versions
|
||||
> Note: The `installdependencies.sh` script will try to use the default package management mechanism on your Linux flavor (ex. `yum`/`apt-get`/`apt`).
|
||||
> For Fedora-based systems, the script automatically handles lttng-ust version compatibility by creating symlinks when needed (e.g., Fedora 41 ships with liblttng-ust.so.1 but the runner needs liblttng-ust.so.0).
|
||||
|
||||
### Full dependencies list
|
||||
|
||||
@@ -34,7 +33,7 @@ Debian based OS (Debian, Ubuntu, Linux Mint)
|
||||
|
||||
Fedora based OS (Fedora, Red Hat Enterprise Linux, CentOS, Oracle Linux 7)
|
||||
|
||||
- lttng-ust (the installdependencies.sh script will automatically handle version compatibility for newer Fedora versions)
|
||||
- lttng-ust
|
||||
- openssl-libs
|
||||
- krb5-libs
|
||||
- zlib
|
||||
|
||||
@@ -5,8 +5,8 @@ ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
ARG RUNNER_VERSION
|
||||
ARG RUNNER_CONTAINER_HOOKS_VERSION=0.7.0
|
||||
ARG DOCKER_VERSION=28.3.3
|
||||
ARG BUILDX_VERSION=0.27.0
|
||||
ARG DOCKER_VERSION=28.3.2
|
||||
ARG BUILDX_VERSION=0.26.1
|
||||
|
||||
RUN apt update -y && apt install curl unzip -y
|
||||
|
||||
|
||||
1070
src/Misc/expressionFunc/hashFiles/package-lock.json
generated
1070
src/Misc/expressionFunc/hashFiles/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -36,13 +36,13 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.6.2",
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.7.2",
|
||||
"@typescript-eslint/parser": "^6.7.2",
|
||||
"@vercel/ncc": "^0.38.3",
|
||||
"@vercel/ncc": "^0.38.0",
|
||||
"eslint": "^8.47.0",
|
||||
"eslint-plugin-github": "^4.10.2",
|
||||
"eslint-plugin-github": "^4.10.0",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"husky": "^9.1.7",
|
||||
"husky": "^8.0.3",
|
||||
"lint-staged": "^15.5.0",
|
||||
"prettier": "^3.0.3",
|
||||
"typescript": "^5.2.2"
|
||||
|
||||
@@ -131,63 +131,12 @@ then
|
||||
command -v dnf
|
||||
if [ $? -eq 0 ]
|
||||
then
|
||||
# Install basic dependencies first
|
||||
dnf install -y openssl-libs krb5-libs zlib libicu
|
||||
dnf install -y lttng-ust openssl-libs krb5-libs zlib libicu
|
||||
if [ $? -ne 0 ]
|
||||
then
|
||||
echo "'dnf' failed with exit code '$?'"
|
||||
print_errormessage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Handle lttng-ust with fallback logic for version compatibility
|
||||
dnf_with_lttng_fallbacks() {
|
||||
# Try to install the current lttng-ust package
|
||||
dnf install -y lttng-ust
|
||||
if [ $? -eq 0 ]
|
||||
then
|
||||
# Check if it provides the required liblttng-ust.so.0
|
||||
if ldconfig -p | grep -q "liblttng-ust.so.0"
|
||||
then
|
||||
echo "Found liblttng-ust.so.0"
|
||||
return 0
|
||||
else
|
||||
echo "Warning: lttng-ust installed but liblttng-ust.so.0 not found"
|
||||
echo "Attempting to create compatibility symlink..."
|
||||
|
||||
# Find the actual liblttng-ust library
|
||||
lttng_lib=$(ldconfig -p | grep "liblttng-ust.so" | head -1 | awk '{print $NF}')
|
||||
if [ -n "$lttng_lib" ] && [ -f "$lttng_lib" ]
|
||||
then
|
||||
# Create symlink in the same directory
|
||||
lib_dir=$(dirname "$lttng_lib")
|
||||
if [ -w "$lib_dir" ]
|
||||
then
|
||||
ln -sf "$(basename "$lttng_lib")" "$lib_dir/liblttng-ust.so.0"
|
||||
echo "Created compatibility symlink: $lib_dir/liblttng-ust.so.0 -> $(basename "$lttng_lib")"
|
||||
ldconfig
|
||||
return 0
|
||||
else
|
||||
echo "Cannot create symlink in $lib_dir (permission denied)"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
echo "Could not find lttng-ust library file"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo "Failed to install lttng-ust package"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
dnf_with_lttng_fallbacks
|
||||
if [ $? -ne 0 ]
|
||||
then
|
||||
echo "Failed to install lttng-ust with compatibility"
|
||||
print_errormessage
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "Can not find 'dnf'"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
SECONDS=0
|
||||
while [[ $SECONDS -lt $1 ]]; do
|
||||
while [[ $SECONDS != $1 ]]; do
|
||||
:
|
||||
done
|
||||
|
||||
@@ -23,8 +23,6 @@ namespace GitHub.Runner.Common
|
||||
|
||||
Task<TaskAgentMessage> GetRunnerMessageAsync(Guid? sessionId, TaskAgentStatus status, string version, string os, string architecture, bool disableUpdate, CancellationToken token);
|
||||
|
||||
Task AcknowledgeRunnerRequestAsync(string runnerRequestId, Guid? sessionId, TaskAgentStatus status, string version, string os, string architecture, CancellationToken token);
|
||||
|
||||
Task UpdateConnectionIfNeeded(Uri serverUri, VssCredentials credentials);
|
||||
|
||||
Task ForceRefreshConnection(VssCredentials credentials);
|
||||
@@ -69,17 +67,10 @@ namespace GitHub.Runner.Common
|
||||
var brokerSession = RetryRequest<TaskAgentMessage>(
|
||||
async () => await _brokerHttpClient.GetRunnerMessageAsync(sessionId, version, status, os, architecture, disableUpdate, cancellationToken), cancellationToken, shouldRetry: ShouldRetryException);
|
||||
|
||||
|
||||
return brokerSession;
|
||||
}
|
||||
|
||||
public async Task AcknowledgeRunnerRequestAsync(string runnerRequestId, Guid? sessionId, TaskAgentStatus status, string version, string os, string architecture, CancellationToken cancellationToken)
|
||||
{
|
||||
CheckConnection();
|
||||
|
||||
// No retries
|
||||
await _brokerHttpClient.AcknowledgeRunnerRequestAsync(runnerRequestId, sessionId, version, status, os, architecture, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task DeleteSessionAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
CheckConnection();
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace GitHub.Runner.Common
|
||||
|
||||
protected async Task RetryRequest(Func<Task> func,
|
||||
CancellationToken cancellationToken,
|
||||
int maxAttempts = 5,
|
||||
int maxRetryAttemptsCount = 5,
|
||||
Func<Exception, bool> shouldRetry = null
|
||||
)
|
||||
{
|
||||
@@ -79,31 +79,31 @@ namespace GitHub.Runner.Common
|
||||
await func();
|
||||
return Unit.Value;
|
||||
}
|
||||
await RetryRequest<Unit>(wrappedFunc, cancellationToken, maxAttempts, shouldRetry);
|
||||
await RetryRequest<Unit>(wrappedFunc, cancellationToken, maxRetryAttemptsCount, shouldRetry);
|
||||
}
|
||||
|
||||
protected async Task<T> RetryRequest<T>(Func<Task<T>> func,
|
||||
CancellationToken cancellationToken,
|
||||
int maxAttempts = 5,
|
||||
int maxRetryAttemptsCount = 5,
|
||||
Func<Exception, bool> shouldRetry = null
|
||||
)
|
||||
{
|
||||
var attempt = 0;
|
||||
var retryCount = 0;
|
||||
while (true)
|
||||
{
|
||||
attempt++;
|
||||
retryCount++;
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
try
|
||||
{
|
||||
return await func();
|
||||
}
|
||||
// TODO: Add handling of non-retriable exceptions: https://github.com/github/actions-broker/issues/122
|
||||
catch (Exception ex) when (attempt < maxAttempts && (shouldRetry == null || shouldRetry(ex)))
|
||||
catch (Exception ex) when (retryCount < maxRetryAttemptsCount && (shouldRetry == null || shouldRetry(ex)))
|
||||
{
|
||||
Trace.Error("Catch exception during request");
|
||||
Trace.Error(ex);
|
||||
var backOff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(15));
|
||||
Trace.Warning($"Back off {backOff.TotalSeconds} seconds before next retry. {maxAttempts - attempt} attempt left.");
|
||||
Trace.Warning($"Back off {backOff.TotalSeconds} seconds before next retry. {maxRetryAttemptsCount - retryCount} attempt left.");
|
||||
await Task.Delay(backOff, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace GitHub.Runner.Listener
|
||||
private RunnerSettings _settings;
|
||||
private ITerminal _term;
|
||||
private TimeSpan _getNextMessageRetryInterval;
|
||||
private TaskAgentStatus _runnerStatus = TaskAgentStatus.Online;
|
||||
private TaskAgentStatus runnerStatus = TaskAgentStatus.Online;
|
||||
private CancellationTokenSource _getMessagesTokenSource;
|
||||
private VssCredentials _creds;
|
||||
private VssCredentials _credsV2;
|
||||
@@ -258,7 +258,7 @@ namespace GitHub.Runner.Listener
|
||||
public void OnJobStatus(object sender, JobStatusEventArgs e)
|
||||
{
|
||||
Trace.Info("Received job status event. JobState: {0}", e.Status);
|
||||
_runnerStatus = e.Status;
|
||||
runnerStatus = e.Status;
|
||||
try
|
||||
{
|
||||
_getMessagesTokenSource?.Cancel();
|
||||
@@ -291,7 +291,7 @@ namespace GitHub.Runner.Listener
|
||||
}
|
||||
|
||||
message = await _brokerServer.GetRunnerMessageAsync(_session.SessionId,
|
||||
_runnerStatus,
|
||||
runnerStatus,
|
||||
BuildConstants.RunnerPackage.Version,
|
||||
VarUtil.OS,
|
||||
VarUtil.OSArchitecture,
|
||||
@@ -417,21 +417,6 @@ namespace GitHub.Runner.Listener
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task AcknowledgeMessageAsync(string runnerRequestId, CancellationToken cancellationToken)
|
||||
{
|
||||
using var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); // Short timeout
|
||||
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token);
|
||||
Trace.Info($"Acknowledging runner request '{runnerRequestId}'.");
|
||||
await _brokerServer.AcknowledgeRunnerRequestAsync(
|
||||
runnerRequestId,
|
||||
_session.SessionId,
|
||||
_runnerStatus,
|
||||
BuildConstants.RunnerPackage.Version,
|
||||
VarUtil.OS,
|
||||
VarUtil.OSArchitecture,
|
||||
linkedCts.Token);
|
||||
}
|
||||
|
||||
private bool IsGetNextMessageExceptionRetriable(Exception ex)
|
||||
{
|
||||
if (ex is TaskAgentNotFoundException ||
|
||||
|
||||
@@ -32,7 +32,6 @@ namespace GitHub.Runner.Listener
|
||||
Task DeleteSessionAsync();
|
||||
Task<TaskAgentMessage> GetNextMessageAsync(CancellationToken token);
|
||||
Task DeleteMessageAsync(TaskAgentMessage message);
|
||||
Task AcknowledgeMessageAsync(string runnerRequestId, CancellationToken cancellationToken);
|
||||
|
||||
Task RefreshListenerTokenAsync();
|
||||
void OnJobStatus(object sender, JobStatusEventArgs e);
|
||||
@@ -53,7 +52,7 @@ namespace GitHub.Runner.Listener
|
||||
private readonly TimeSpan _sessionConflictRetryLimit = TimeSpan.FromMinutes(4);
|
||||
private readonly TimeSpan _clockSkewRetryLimit = TimeSpan.FromMinutes(30);
|
||||
private readonly Dictionary<string, int> _sessionCreationExceptionTracker = new();
|
||||
private TaskAgentStatus _runnerStatus = TaskAgentStatus.Online;
|
||||
private TaskAgentStatus runnerStatus = TaskAgentStatus.Online;
|
||||
private CancellationTokenSource _getMessagesTokenSource;
|
||||
private VssCredentials _creds;
|
||||
private VssCredentials _credsV2;
|
||||
@@ -218,7 +217,7 @@ namespace GitHub.Runner.Listener
|
||||
public void OnJobStatus(object sender, JobStatusEventArgs e)
|
||||
{
|
||||
Trace.Info("Received job status event. JobState: {0}", e.Status);
|
||||
_runnerStatus = e.Status;
|
||||
runnerStatus = e.Status;
|
||||
try
|
||||
{
|
||||
_getMessagesTokenSource?.Cancel();
|
||||
@@ -251,7 +250,7 @@ namespace GitHub.Runner.Listener
|
||||
message = await _runnerServer.GetAgentMessageAsync(_settings.PoolId,
|
||||
_session.SessionId,
|
||||
_lastMessageId,
|
||||
_runnerStatus,
|
||||
runnerStatus,
|
||||
BuildConstants.RunnerPackage.Version,
|
||||
VarUtil.OS,
|
||||
VarUtil.OSArchitecture,
|
||||
@@ -275,7 +274,7 @@ namespace GitHub.Runner.Listener
|
||||
}
|
||||
|
||||
message = await _brokerServer.GetRunnerMessageAsync(_session.SessionId,
|
||||
_runnerStatus,
|
||||
runnerStatus,
|
||||
BuildConstants.RunnerPackage.Version,
|
||||
VarUtil.OS,
|
||||
VarUtil.OSArchitecture,
|
||||
@@ -438,21 +437,6 @@ namespace GitHub.Runner.Listener
|
||||
await _brokerServer.ForceRefreshConnection(_credsV2);
|
||||
}
|
||||
|
||||
public async Task AcknowledgeMessageAsync(string runnerRequestId, CancellationToken cancellationToken)
|
||||
{
|
||||
using var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); // Short timeout
|
||||
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token);
|
||||
Trace.Info($"Acknowledging runner request '{runnerRequestId}'.");
|
||||
await _brokerServer.AcknowledgeRunnerRequestAsync(
|
||||
runnerRequestId,
|
||||
_session.SessionId,
|
||||
_runnerStatus,
|
||||
BuildConstants.RunnerPackage.Version,
|
||||
VarUtil.OS,
|
||||
VarUtil.OSArchitecture,
|
||||
linkedCts.Token);
|
||||
}
|
||||
|
||||
private TaskAgentMessage DecryptMessage(TaskAgentMessage message)
|
||||
{
|
||||
if (_session.EncryptionKey == null ||
|
||||
|
||||
@@ -654,42 +654,22 @@ namespace GitHub.Runner.Listener
|
||||
else
|
||||
{
|
||||
var messageRef = StringUtil.ConvertFromJson<RunnerJobRequestRef>(message.Body);
|
||||
|
||||
// Acknowledge (best-effort)
|
||||
if (messageRef.ShouldAcknowledge) // Temporary feature flag
|
||||
{
|
||||
try
|
||||
{
|
||||
await _listener.AcknowledgeMessageAsync(messageRef.RunnerRequestId, messageQueueLoopTokenSource.Token);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Error($"Best-effort acknowledge failed for request '{messageRef.RunnerRequestId}'");
|
||||
Trace.Error(ex);
|
||||
}
|
||||
}
|
||||
|
||||
Pipelines.AgentJobRequestMessage jobRequestMessage = null;
|
||||
|
||||
// Create connection
|
||||
var credMgr = HostContext.GetService<ICredentialManager>();
|
||||
if (string.IsNullOrEmpty(messageRef.RunServiceUrl))
|
||||
{
|
||||
// Connect
|
||||
var credMgr = HostContext.GetService<ICredentialManager>();
|
||||
var creds = credMgr.LoadCredentials(allowAuthUrlV2: false);
|
||||
var actionsRunServer = HostContext.CreateService<IActionsRunServer>();
|
||||
await actionsRunServer.ConnectAsync(new Uri(settings.ServerUrl), creds);
|
||||
|
||||
// Get job message
|
||||
jobRequestMessage = await actionsRunServer.GetJobMessageAsync(messageRef.RunnerRequestId, messageQueueLoopTokenSource.Token);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Connect
|
||||
var credMgr = HostContext.GetService<ICredentialManager>();
|
||||
var credsV2 = credMgr.LoadCredentials(allowAuthUrlV2: true);
|
||||
var runServer = HostContext.CreateService<IRunServer>();
|
||||
await runServer.ConnectAsync(new Uri(messageRef.RunServiceUrl), credsV2);
|
||||
|
||||
// Get job message
|
||||
try
|
||||
{
|
||||
jobRequestMessage = await runServer.GetJobMessageAsync(messageRef.RunnerRequestId, messageRef.BillingOwnerId, messageQueueLoopTokenSource.Token);
|
||||
@@ -718,10 +698,7 @@ namespace GitHub.Runner.Listener
|
||||
}
|
||||
}
|
||||
|
||||
// Dispatch
|
||||
jobDispatcher.Run(jobRequestMessage, runOnce);
|
||||
|
||||
// Run once?
|
||||
if (runOnce)
|
||||
{
|
||||
Trace.Info("One time used runner received job message.");
|
||||
|
||||
@@ -10,9 +10,6 @@ namespace GitHub.Runner.Listener
|
||||
|
||||
[DataMember(Name = "runner_request_id")]
|
||||
public string RunnerRequestId { get; set; }
|
||||
|
||||
[DataMember(Name = "should_acknowledge")]
|
||||
public bool ShouldAcknowledge { get; set; }
|
||||
|
||||
[DataMember(Name = "run_service_url")]
|
||||
public string RunServiceUrl { get; set; }
|
||||
|
||||
@@ -79,7 +79,6 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
{
|
||||
queryParams.Add("status", status.Value.ToString());
|
||||
}
|
||||
|
||||
if (runnerVersion != null)
|
||||
{
|
||||
queryParams.Add("runnerVersion", runnerVersion);
|
||||
@@ -143,6 +142,7 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
}
|
||||
|
||||
public async Task<TaskAgentSession> CreateSessionAsync(
|
||||
|
||||
TaskAgentSession session,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -191,76 +191,6 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
throw new Exception($"Failed to delete broker session: {result.Error}");
|
||||
}
|
||||
|
||||
public async Task AcknowledgeRunnerRequestAsync(
|
||||
string runnerRequestId,
|
||||
Guid? sessionId,
|
||||
string runnerVersion,
|
||||
TaskAgentStatus? status,
|
||||
string os = null,
|
||||
string architecture = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
// URL
|
||||
var requestUri = new Uri(Client.BaseAddress, "acknowledge");
|
||||
|
||||
// Query parameters
|
||||
List<KeyValuePair<string, string>> queryParams = new List<KeyValuePair<string, string>>();
|
||||
if (sessionId != null)
|
||||
{
|
||||
queryParams.Add("sessionId", sessionId.Value.ToString());
|
||||
}
|
||||
if (status != null)
|
||||
{
|
||||
queryParams.Add("status", status.Value.ToString());
|
||||
}
|
||||
if (runnerVersion != null)
|
||||
{
|
||||
queryParams.Add("runnerVersion", runnerVersion);
|
||||
}
|
||||
if (os != null)
|
||||
{
|
||||
queryParams.Add("os", os);
|
||||
}
|
||||
if (architecture != null)
|
||||
{
|
||||
queryParams.Add("architecture", architecture);
|
||||
}
|
||||
|
||||
// Body
|
||||
var payload = new Dictionary<string, string>
|
||||
{
|
||||
["runnerRequestId"] = runnerRequestId,
|
||||
};
|
||||
var requestContent = new ObjectContent<Dictionary<string, string>>(payload, new VssJsonMediaTypeFormatter(true));
|
||||
|
||||
// POST
|
||||
var result = await SendAsync<object>(
|
||||
new HttpMethod("POST"),
|
||||
requestUri: requestUri,
|
||||
queryParameters: queryParams,
|
||||
content: requestContent,
|
||||
readErrorBody: true,
|
||||
cancellationToken: cancellationToken);
|
||||
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (TryParseErrorBody(result.ErrorBody, out BrokerError brokerError))
|
||||
{
|
||||
switch (brokerError.ErrorKind)
|
||||
{
|
||||
case BrokerErrorKind.RunnerNotFound:
|
||||
throw new RunnerNotFoundException(brokerError.Message);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception($"Failed to acknowledge runner request. Request to {requestUri} failed with status: {result.StatusCode}. Error message {result.Error}");
|
||||
}
|
||||
|
||||
private static bool TryParseErrorBody(string errorBody, out BrokerError error)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(errorBody))
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ LAYOUT_DIR="$SCRIPT_DIR/../_layout"
|
||||
DOWNLOAD_DIR="$SCRIPT_DIR/../_downloads/netcore2x"
|
||||
PACKAGE_DIR="$SCRIPT_DIR/../_package"
|
||||
DOTNETSDK_ROOT="$SCRIPT_DIR/../_dotnetsdk"
|
||||
DOTNETSDK_VERSION="8.0.413"
|
||||
DOTNETSDK_VERSION="8.0.412"
|
||||
DOTNETSDK_INSTALLDIR="$DOTNETSDK_ROOT/$DOTNETSDK_VERSION"
|
||||
RUNNER_VERSION=$(cat runnerversion)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "8.0.413"
|
||||
"version": "8.0.412"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user