Compare commits

..

2 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
23ea13be96 Complete documentation and tests for action execution model
Co-authored-by: salmanmkc <32169182+salmanmkc@users.noreply.github.com>
2025-08-14 09:26:12 +00:00
copilot-swe-agent[bot]
e4511c02ad Initial plan 2025-08-14 09:09:22 +00:00
22 changed files with 736 additions and 1091 deletions

View File

@@ -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"

View File

@@ -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 }}

View File

@@ -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)

View 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.

View File

@@ -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.

View 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.

View File

@@ -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

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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"

View File

@@ -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'"

View File

@@ -1,6 +1,6 @@
#!/bin/bash
SECONDS=0
while [[ $SECONDS -lt $1 ]]; do
while [[ $SECONDS != $1 ]]; do
:
done

View File

@@ -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();

View File

@@ -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);
}
}

View File

@@ -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 ||

View File

@@ -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 ||

View File

@@ -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.");

View File

@@ -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; }

View File

@@ -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))

View 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();
}
}
}

View File

@@ -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)

View File

@@ -1,5 +1,5 @@
{
"sdk": {
"version": "8.0.413"
"version": "8.0.412"
}
}