Compare commits

..

12 Commits

Author SHA1 Message Date
dependabot[bot]
b9772a0fb4 Bump eslint-plugin-github in /src/Misc/expressionFunc/hashFiles
Bumps [eslint-plugin-github](https://github.com/github/eslint-plugin-github) from 4.10.2 to 6.0.0.
- [Release notes](https://github.com/github/eslint-plugin-github/releases)
- [Commits](https://github.com/github/eslint-plugin-github/compare/v4.10.2...v6.0.0)

---
updated-dependencies:
- dependency-name: eslint-plugin-github
  dependency-version: 6.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-01 09:17:44 +00:00
Salman Chishti
f8ae5bb1a7 chore: migrate Husky config from v8 to v9 format (#4003) 2025-09-01 09:16:05 +00:00
dependabot[bot]
a5631456a2 Bump typescript from 5.2.2 to 5.9.2 in /src/Misc/expressionFunc/hashFiles (#4007)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-01 09:33:54 +01:00
dependabot[bot]
65dfa460ba Bump eslint-plugin-github in /src/Misc/expressionFunc/hashFiles (#3180)
Bumps [eslint-plugin-github](https://github.com/github/eslint-plugin-github) from 4.10.0 to 4.10.2.
- [Release notes](https://github.com/github/eslint-plugin-github/releases)
- [Commits](https://github.com/github/eslint-plugin-github/compare/v4.10.0...v4.10.2)

---
updated-dependencies:
- dependency-name: eslint-plugin-github
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Patrick Ellis <319655+pje@users.noreply.github.com>
2025-08-30 04:03:46 +00:00
dependabot[bot]
80ee51f164 Bump @vercel/ncc from 0.38.0 to 0.38.3 in /src/Misc/expressionFunc/hashFiles (#3841)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Salman Chishti <salmanmkc@GitHub.com>
2025-08-30 03:24:53 +00:00
dependabot[bot]
c95883f28e Bump husky from 8.0.3 to 9.1.7 in /src/Misc/expressionFunc/hashFiles (#3842)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Salman Chishti <salmanmkc@GitHub.com>
2025-08-30 03:19:49 +00:00
dependabot[bot]
6e940643a9 Bump @typescript-eslint/eslint-plugin from 6.7.2 to 8.35.0 in /src/Misc/expressionFunc/hashFiles (#3920)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Salman Chishti <salmanmkc@GitHub.com>
2025-08-29 20:08:31 +00:00
dependabot[bot]
629f2384a4 Bump actions/attest-build-provenance from 2 to 3 (#4002)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-29 21:01:36 +01:00
github-actions[bot]
c3bf70becb Update dotnet sdk to latest version @8.0.413 (#4000)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-25 02:09:47 +00:00
github-actions[bot]
8b65f5f9df Update Docker to v28.3.3 and Buildx to v0.27.0 (#3999)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-25 00:31:52 +00:00
eric sciple
5f1efec208 Acknowledge runner request (#3996) 2025-08-22 13:52:32 -05:00
Doug Horner
20d82ad357 Update safe_sleep.sh for bug when scheduler is paused for more than 1 second (#3157) 2025-08-20 19:04:48 +00:00
21 changed files with 3766 additions and 2338 deletions

View File

@@ -4,7 +4,7 @@
"features": {
"ghcr.io/devcontainers/features/docker-in-docker:1": {},
"ghcr.io/devcontainers/features/dotnet": {
"version": "8.0.412"
"version": "8.0.413"
},
"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@v2
uses: actions/attest-build-provenance@v3
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
subject-digest: ${{ steps.build-and-push.outputs.digest }}

View File

@@ -1,6 +1 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
cd src/Misc/expressionFunc/hashFiles
npx lint-staged
cd src/Misc/expressionFunc/hashFiles && npx lint-staged

View File

@@ -8,16 +8,6 @@
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

@@ -1,144 +0,0 @@
# 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,14 +4,6 @@ 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

@@ -1,117 +0,0 @@
# 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

@@ -5,8 +5,8 @@ ARG TARGETOS
ARG TARGETARCH
ARG RUNNER_VERSION
ARG RUNNER_CONTAINER_HOOKS_VERSION=0.7.0
ARG DOCKER_VERSION=28.3.2
ARG BUILDX_VERSION=0.26.1
ARG DOCKER_VERSION=28.3.3
ARG BUILDX_VERSION=0.27.0
RUN apt update -y && apt install curl unzip -y

File diff suppressed because it is too large Load Diff

View File

@@ -10,7 +10,7 @@
"lint": "eslint src/**/*.ts",
"pack": "ncc build -o ../../layoutbin/hashFiles",
"all": "npm run format && npm run lint && npm run build && npm run pack",
"prepare": "cd ../../../../ && husky install"
"prepare": "cd ../../../../ && husky"
},
"repository": {
"type": "git",
@@ -36,15 +36,15 @@
},
"devDependencies": {
"@types/node": "^20.6.2",
"@typescript-eslint/eslint-plugin": "^6.7.2",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.7.2",
"@vercel/ncc": "^0.38.0",
"@vercel/ncc": "^0.38.3",
"eslint": "^8.47.0",
"eslint-plugin-github": "^4.10.0",
"eslint-plugin-github": "^6.0.0",
"eslint-plugin-prettier": "^5.0.0",
"husky": "^8.0.3",
"husky": "^9.1.7",
"lint-staged": "^15.5.0",
"prettier": "^3.0.3",
"typescript": "^5.2.2"
"typescript": "^5.9.2"
}
}

View File

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

View File

@@ -23,6 +23,8 @@ 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);
@@ -67,10 +69,17 @@ 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 maxRetryAttemptsCount = 5,
int maxAttempts = 5,
Func<Exception, bool> shouldRetry = null
)
{
@@ -79,31 +79,31 @@ namespace GitHub.Runner.Common
await func();
return Unit.Value;
}
await RetryRequest<Unit>(wrappedFunc, cancellationToken, maxRetryAttemptsCount, shouldRetry);
await RetryRequest<Unit>(wrappedFunc, cancellationToken, maxAttempts, shouldRetry);
}
protected async Task<T> RetryRequest<T>(Func<Task<T>> func,
CancellationToken cancellationToken,
int maxRetryAttemptsCount = 5,
int maxAttempts = 5,
Func<Exception, bool> shouldRetry = null
)
{
var retryCount = 0;
var attempt = 0;
while (true)
{
retryCount++;
attempt++;
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 (retryCount < maxRetryAttemptsCount && (shouldRetry == null || shouldRetry(ex)))
catch (Exception ex) when (attempt < maxAttempts && (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. {maxRetryAttemptsCount - retryCount} attempt left.");
Trace.Warning($"Back off {backOff.TotalSeconds} seconds before next retry. {maxAttempts - attempt} 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,6 +417,21 @@ 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,6 +32,7 @@ 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);
@@ -52,7 +53,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;
@@ -217,7 +218,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();
@@ -250,7 +251,7 @@ namespace GitHub.Runner.Listener
message = await _runnerServer.GetAgentMessageAsync(_settings.PoolId,
_session.SessionId,
_lastMessageId,
runnerStatus,
_runnerStatus,
BuildConstants.RunnerPackage.Version,
VarUtil.OS,
VarUtil.OSArchitecture,
@@ -274,7 +275,7 @@ namespace GitHub.Runner.Listener
}
message = await _brokerServer.GetRunnerMessageAsync(_session.SessionId,
runnerStatus,
_runnerStatus,
BuildConstants.RunnerPackage.Version,
VarUtil.OS,
VarUtil.OSArchitecture,
@@ -437,6 +438,21 @@ 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,22 +654,42 @@ namespace GitHub.Runner.Listener
else
{
var messageRef = StringUtil.ConvertFromJson<RunnerJobRequestRef>(message.Body);
Pipelines.AgentJobRequestMessage jobRequestMessage = null;
// Create connection
var credMgr = HostContext.GetService<ICredentialManager>();
// 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;
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);
@@ -698,7 +718,10 @@ 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,6 +10,9 @@ 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,6 +79,7 @@ namespace GitHub.Actions.RunService.WebApi
{
queryParams.Add("status", status.Value.ToString());
}
if (runnerVersion != null)
{
queryParams.Add("runnerVersion", runnerVersion);
@@ -142,7 +143,6 @@ namespace GitHub.Actions.RunService.WebApi
}
public async Task<TaskAgentSession> CreateSessionAsync(
TaskAgentSession session,
CancellationToken cancellationToken = default)
{
@@ -191,6 +191,76 @@ 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

@@ -1,226 +0,0 @@
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.412"
DOTNETSDK_VERSION="8.0.413"
DOTNETSDK_INSTALLDIR="$DOTNETSDK_ROOT/$DOTNETSDK_VERSION"
RUNNER_VERSION=$(cat runnerversion)

View File

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