Compare commits

..

14 Commits

Author SHA1 Message Date
Tingluo Huang
fb21f0da05 Bump docker image to use ubuntu 24.04 2025-09-05 18:43:33 -04:00
Tingluo Huang
97b2254146 Break UseV2Flow into UseV2Flow and UseRunnerAdminFlow. (#4013) 2025-09-03 17:09:17 -04:00
eric sciple
7f72ba9e48 Map RUNNER_TEMP for container action (#4011) 2025-09-03 11:45:43 -05: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
28 changed files with 1142 additions and 515 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

@@ -14,6 +14,9 @@ on:
paths-ignore:
- '**.md'
permissions:
contents: read
jobs:
build:
strategy:
@@ -80,3 +83,48 @@ jobs:
name: runner-package-${{ matrix.runtime }}
path: |
_package
docker:
strategy:
matrix:
os: [ ubuntu-latest, ubuntu-24.04-arm ]
include:
- os: ubuntu-latest
docker_platform: linux/amd64
- os: ubuntu-24.04-arm
docker_platform: linux/arm64
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v5
- name: Get latest runner version
id: latest_runner
uses: actions/github-script@v7
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
const release = await github.rest.repos.getLatestRelease({
owner: 'actions',
repo: 'runner',
});
const version = release.data.tag_name.replace(/^v/, '');
core.setOutput('version', version);
- name: Setup Docker buildx
uses: docker/setup-buildx-action@v3
- name: Build Docker image
uses: docker/build-push-action@v6
with:
context: ./images
load: true
platforms: ${{ matrix.docker_platform }}
tags: |
${{ github.sha }}:latest
build-args: |
RUNNER_VERSION=${{ steps.latest_runner.outputs.version }}
- name: Test Docker image
run: |
docker run --rm ${{ github.sha }}:latest ./run.sh --version

View File

@@ -334,11 +334,12 @@ jobs:
push: true
labels: |
org.opencontainers.image.source=${{github.server_url}}/${{github.repository}}
org.opencontainers.image.description=https://github.com/actions/runner/releases/tag/v${{ steps.image.outputs.version }}
org.opencontainers.image.licenses=MIT
annotations: |
org.opencontainers.image.description=https://github.com/actions/runner/releases/tag/v${{ steps.image.outputs.version }}
- 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 }}

2
.gitignore vendored
View File

@@ -27,4 +27,4 @@ TestResults
TestLogs
.DS_Store
.mono
**/*.DotSettings.user/tmp/
**/*.DotSettings.user

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

@@ -1,12 +1,12 @@
# Source: https://github.com/dotnet/dotnet-docker
FROM mcr.microsoft.com/dotnet/runtime-deps:8.0-jammy AS build
FROM mcr.microsoft.com/dotnet/runtime-deps:8.0-noble AS build
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
@@ -32,12 +32,12 @@ RUN export RUNNER_ARCH=${TARGETARCH} \
"https://github.com/docker/buildx/releases/download/v${BUILDX_VERSION}/buildx-v${BUILDX_VERSION}.linux-${TARGETARCH}" \
&& chmod +x /usr/local/lib/docker/cli-plugins/docker-buildx
FROM mcr.microsoft.com/dotnet/runtime-deps:8.0-jammy
FROM mcr.microsoft.com/dotnet/runtime-deps:8.0-noble
ENV DEBIAN_FRONTEND=noninteractive
ENV RUNNER_MANUALLY_TRAP_SIG=1
ENV ACTIONS_RUNNER_PRINT_LOG_TO_STDOUT=1
ENV ImageOS=ubuntu22
ENV ImageOS=ubuntu24
# 'gpg-agent' and 'software-properties-common' are needed for the 'add-apt-repository' command that follows
RUN apt update -y \

View File

@@ -1,73 +0,0 @@
# KillMode Change Implementation Summary
## Problem Addressed
The question "is this a good idea?" regarding "killmode changing?" has been thoroughly analyzed and addressed through a minimal but impactful change to the GitHub Actions Runner systemd service configuration.
## Solution Implemented
**Changed**: `KillMode=process``KillMode=mixed` in `src/Misc/layoutbin/actions.runner.service.template`
## Why This Change Makes Sense
### Evidence from Codebase Analysis
1. **Orphan Process Concerns**: The codebase contains extensive orphan process cleanup mechanisms in:
- `JobExtension.cs`: Tracks and cleans up orphan processes using `RUNNER_TRACKING_ID`
- `JobDispatcher.cs`: Prevents orphan worker processes
- `ProcessInvoker.cs`: Implements process tree termination
2. **Current Signal Flow**:
- systemd → runsvc.sh (SIGTERM) → Node.js process (SIGINT)
- Relies on runsvc.sh successfully forwarding signals
### Benefits of KillMode=mixed
1. **Maintains Graceful Shutdown**: Main process (runsvc.sh) still receives SIGTERM first
2. **Adds Safety Net**: systemd ensures cleanup if signal forwarding fails
3. **Better Process Tree Cleanup**: More robust handling of complex job hierarchies
4. **Reduced Orphan Risk**: Addresses concerns evident throughout the codebase
5. **Container Compatibility**: Better termination of containerized workloads
## Implementation Details
### Files Changed
- `src/Misc/layoutbin/actions.runner.service.template`: Single line change
- Added comprehensive test coverage in `src/Test/L0/Misc/SystemdServiceTemplateL0.cs`
- Created analysis documentation and testing tools
### Testing
- ✅ Build succeeds with no errors
- ✅ New tests validate the change
- ✅ Existing functionality unchanged
- ✅ Layout generation includes the change
## Impact Assessment
### Risk Level: **LOW**
- Only affects service shutdown behavior
- No changes to startup or normal operation
- Backward compatible with existing signal handling
- Testable with standard systemd tools
### Compatibility
- Maintains existing runsvc.sh signal forwarding behavior
- Compatible with all existing process handling code
- No breaking changes to APIs or interfaces
## Testing Tools Provided
Created `/tmp/killmode-test.sh` script that allows administrators to:
- Test different KillMode configurations
- Compare process cleanup behavior
- Validate signal handling works correctly
## Conclusion
This change represents a **good idea** because it:
1. Addresses real orphan process concerns evident in the codebase
2. Provides better reliability with minimal risk
3. Maintains existing graceful shutdown behavior
4. Adds systemd's robust process cleanup as a safety net
5. Requires only a single line change with comprehensive testing
The implementation follows the principle of making the smallest possible change while addressing the underlying concern about process cleanup reliability.

View File

@@ -1,120 +0,0 @@
# GitHub Actions Runner KillMode Analysis
## Problem Statement
The question "is this a good idea?" regarding "killmode changing?" asks us to evaluate whether the current systemd `KillMode=process` setting should be changed to a different option.
## Current Implementation
### Systemd Service Configuration
- **KillMode**: `process` (only main process gets signal)
- **KillSignal**: `SIGTERM`
- **TimeoutStopSec**: `5min`
### Signal Handling Flow
1. systemd sends SIGTERM to `runsvc.sh` (main process)
2. `runsvc.sh` has trap: `trap 'kill -INT $PID' TERM INT`
3. Converts SIGTERM → SIGINT and sends to Node.js runner process
4. Node.js process handles graceful shutdown
## Analysis of Current Approach
### Strengths
1. **Graceful Shutdown Control**: Manual signal conversion allows proper Node.js shutdown handling
2. **Predictable Behavior**: Only main process receives systemd signals
3. **Custom Logic**: Allows for runner-specific shutdown procedures
4. **Signal Compatibility**: SIGINT is more commonly handled by Node.js applications
### Potential Issues
1. **Single Point of Failure**: If `runsvc.sh` fails to forward signals, child processes orphaned
2. **Complex Chain**: More components in signal propagation path
3. **Process Tree Cleanup**: May not handle deep process hierarchies as robustly
## Orphan Process Context
The codebase reveals significant effort to handle orphan processes:
### Evidence from Code Analysis
1. **JobExtension.cs**: Dedicated orphan process cleanup mechanism
- Tracks processes before/after job execution
- Uses `RUNNER_TRACKING_ID` environment variable
- Terminates orphan processes at job completion
2. **JobDispatcher.cs**: Worker process orphan prevention
- Explicit waits to prevent orphan worker processes
- Handles "zombie worker" scenarios
3. **ProcessInvoker.cs**: Process tree termination
- Implements both Windows and Unix process tree killing
- Signal escalation: SIGINT → SIGTERM → SIGKILL
## Alternative KillMode Options
### KillMode=control-group
**Behavior**: All processes in service's cgroup get SIGTERM, then SIGKILL after timeout
**Pros**:
- Robust cleanup of entire process tree
- Built-in systemd guarantees
- Simpler signal flow
- No dependency on runsvc.sh signal forwarding
**Cons**:
- Less control over shutdown sequence
- All processes get SIGTERM simultaneously
- May interrupt graceful shutdown of worker processes
### KillMode=mixed
**Behavior**: Main process gets SIGTERM, remaining processes get SIGKILL after timeout
**Pros**:
- Combines benefits of both approaches
- Main process can handle graceful shutdown
- Systemd ensures process tree cleanup
- Fallback protection against orphan processes
**Cons**:
- More complex behavior
- Still depends on main process signal handling
## Security and Reliability Considerations
### Current Risks
1. If `runsvc.sh` crashes before forwarding signals, Node.js process continues running
2. Deep process trees from job execution may not be properly cleaned up
3. Container processes might not receive proper termination signals
### Reliability Improvements with control-group/mixed
1. systemd guarantees process cleanup regardless of main process behavior
2. Reduces risk of orphan processes surviving service shutdown
3. More predictable behavior for administrators
## Recommendation
### Recommended Change: KillMode=mixed
**Rationale**:
1. **Maintains Graceful Shutdown**: Main process (runsvc.sh) still receives SIGTERM first
2. **Adds Safety Net**: systemd ensures cleanup if main process fails to handle signals
3. **Reduces Orphan Risk**: Addresses the orphan process concerns evident in the codebase
4. **Better Process Tree Handling**: More robust for complex job process hierarchies
5. **Container Compatibility**: Better handling of containerized workloads
### Implementation Impact
- **Low Risk**: Change only affects service shutdown behavior
- **Backward Compatible**: No changes to startup or normal operation
- **Testable**: Can be validated with process monitoring during service stops
### Alternative Considerations
- **KillMode=control-group** could be considered if graceful shutdown proves problematic
- Current **KillMode=process** could remain if the signal forwarding is deemed reliable enough
## Testing Recommendations
1. Test service shutdown with various job types running
2. Verify process cleanup with nested process trees
3. Test container job termination scenarios
4. Monitor for any regressions in graceful shutdown behavior
## Conclusion
Changing to `KillMode=mixed` would provide a good balance between maintaining the current graceful shutdown behavior while adding systemd's robust process cleanup guarantees. This addresses the orphan process concerns evident throughout the codebase while maintaining compatibility.

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": "^4.10.2",
"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

@@ -6,7 +6,7 @@ After=network.target
ExecStart={{RunnerRoot}}/runsvc.sh
User={{User}}
WorkingDirectory={{RunnerRoot}}
KillMode=mixed
KillMode=process
KillSignal=SIGTERM
TimeoutStopSec=5min

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

@@ -53,6 +53,9 @@ namespace GitHub.Runner.Common
[DataMember(EmitDefaultValue = false)]
public bool UseV2Flow { get; set; }
[DataMember(EmitDefaultValue = false)]
public bool UseRunnerAdminFlow { get; set; }
[DataMember(EmitDefaultValue = false)]
public string ServerUrlV2 { get; set; }

View File

@@ -169,6 +169,7 @@ namespace GitHub.Runner.Common
public static readonly string AllowRunnerContainerHooks = "DistributedTask.AllowRunnerContainerHooks";
public static readonly string AddCheckRunIdToJobContext = "actions_add_check_run_id_to_job_context";
public static readonly string DisplayHelpfulActionsDownloadErrors = "actions_display_helpful_actions_download_errors";
public static readonly string ContainerActionRunnerTemp = "actions_container_action_runner_temp";
}
// Node version migration related constants

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

@@ -153,8 +153,8 @@ namespace GitHub.Runner.Listener.Configuration
registerToken = await GetRunnerTokenAsync(command, inputUrl, "registration");
GitHubAuthResult authResult = await GetTenantCredential(inputUrl, registerToken, Constants.RunnerEvent.Register);
runnerSettings.ServerUrl = authResult.TenantUrl;
runnerSettings.UseV2Flow = authResult.UseV2Flow;
Trace.Info($"Using V2 flow: {runnerSettings.UseV2Flow}");
runnerSettings.UseRunnerAdminFlow = authResult.UseRunnerAdminFlow;
Trace.Info($"Using runner-admin flow: {runnerSettings.UseRunnerAdminFlow}");
creds = authResult.ToVssCredentials();
Trace.Info("cred retrieved via GitHub auth");
}
@@ -211,7 +211,7 @@ namespace GitHub.Runner.Listener.Configuration
string poolName = null;
TaskAgentPool agentPool = null;
List<TaskAgentPool> agentPools;
if (runnerSettings.UseV2Flow)
if (runnerSettings.UseRunnerAdminFlow)
{
agentPools = await _dotcomServer.GetRunnerGroupsAsync(runnerSettings.GitHubUrl, registerToken);
}
@@ -259,7 +259,7 @@ namespace GitHub.Runner.Listener.Configuration
var userLabels = command.GetLabels();
_term.WriteLine();
List<TaskAgent> agents;
if (runnerSettings.UseV2Flow)
if (runnerSettings.UseRunnerAdminFlow)
{
agents = await _dotcomServer.GetRunnerByNameAsync(runnerSettings.GitHubUrl, registerToken, runnerSettings.AgentName);
}
@@ -280,7 +280,7 @@ namespace GitHub.Runner.Listener.Configuration
try
{
if (runnerSettings.UseV2Flow)
if (runnerSettings.UseRunnerAdminFlow)
{
var runner = await _dotcomServer.ReplaceRunnerAsync(runnerSettings.PoolId, agent, runnerSettings.GitHubUrl, registerToken, publicKeyXML);
runnerSettings.ServerUrlV2 = runner.RunnerAuthorization.ServerUrl;
@@ -330,7 +330,7 @@ namespace GitHub.Runner.Listener.Configuration
try
{
if (runnerSettings.UseV2Flow)
if (runnerSettings.UseRunnerAdminFlow)
{
var runner = await _dotcomServer.AddRunnerAsync(runnerSettings.PoolId, agent, runnerSettings.GitHubUrl, registerToken, publicKeyXML);
runnerSettings.ServerUrlV2 = runner.RunnerAuthorization.ServerUrl;
@@ -400,13 +400,26 @@ namespace GitHub.Runner.Listener.Configuration
}
else
{
throw new NotSupportedException("Message queue listen OAuth token.");
}
// allow the server to override the serverUrlV2 and useV2Flow
if (agent.Properties.TryGetValue("ServerUrlV2", out string serverUrlV2) &&
!string.IsNullOrEmpty(serverUrlV2))
{
Trace.Info($"Service enforced serverUrlV2: {serverUrlV2}");
runnerSettings.ServerUrlV2 = serverUrlV2;
}
if (agent.Properties.TryGetValue("UseV2Flow", out bool useV2Flow) && useV2Flow)
{
Trace.Info($"Service enforced useV2Flow: {useV2Flow}");
runnerSettings.UseV2Flow = useV2Flow;
}
// Testing agent connection, detect any potential connection issue, like local clock skew that cause OAuth token expired.
if (!runnerSettings.UseV2Flow)
if (!runnerSettings.UseV2Flow && !runnerSettings.UseRunnerAdminFlow)
{
var credMgr = HostContext.GetService<ICredentialManager>();
VssCredentials credential = credMgr.LoadCredentials(allowAuthUrlV2: false);
@@ -429,20 +442,6 @@ namespace GitHub.Runner.Listener.Configuration
}
}
// allow the server to override the serverUrlV2 and useV2Flow
if (agent.Properties.TryGetValue("ServerUrlV2", out string serverUrlV2) &&
!string.IsNullOrEmpty(serverUrlV2))
{
Trace.Info($"Service enforced serverUrlV2: {serverUrlV2}");
runnerSettings.ServerUrlV2 = serverUrlV2;
}
if (agent.Properties.TryGetValue("UseV2Flow", out bool useV2Flow) && useV2Flow)
{
Trace.Info($"Service enforced useV2Flow: {useV2Flow}");
runnerSettings.UseV2Flow = useV2Flow;
}
_term.WriteSection("Runner settings");
// We will Combine() what's stored with root. Defaults to string a relative path
@@ -538,7 +537,7 @@ namespace GitHub.Runner.Listener.Configuration
{
RunnerSettings settings = _store.GetSettings();
if (settings.UseV2Flow)
if (settings.UseRunnerAdminFlow)
{
var deletionToken = await GetRunnerTokenAsync(command, settings.GitHubUrl, "remove");
await _dotcomServer.DeleteRunnerAsync(settings.GitHubUrl, deletionToken, settings.AgentId);

View File

@@ -89,7 +89,7 @@ namespace GitHub.Runner.Listener.Configuration
public string Token { get; set; }
[DataMember(Name = "use_v2_flow")]
public bool UseV2Flow { get; set; }
public bool UseRunnerAdminFlow { get; set; }
public VssCredentials ToVssCredentials()
{

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

@@ -11,5 +11,10 @@ namespace GitHub.Runner.Worker
var isContainerHooksPathSet = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable(Constants.Hooks.ContainerHooksPath));
return isContainerHookFeatureFlagSet && isContainerHooksPathSet;
}
public static bool IsContainerActionRunnerTempEnabled(Variables variables)
{
return variables?.GetBoolean(Constants.Runner.Features.ContainerActionRunnerTemp) ?? false;
}
}
}

View File

@@ -191,11 +191,19 @@ namespace GitHub.Runner.Worker.Handlers
ArgUtil.Directory(tempWorkflowDirectory, nameof(tempWorkflowDirectory));
container.MountVolumes.Add(new MountVolume("/var/run/docker.sock", "/var/run/docker.sock"));
if (FeatureManager.IsContainerActionRunnerTempEnabled(ExecutionContext.Global.Variables))
{
container.MountVolumes.Add(new MountVolume(tempDirectory, "/github/runner_temp"));
}
container.MountVolumes.Add(new MountVolume(tempHomeDirectory, "/github/home"));
container.MountVolumes.Add(new MountVolume(tempWorkflowDirectory, "/github/workflow"));
container.MountVolumes.Add(new MountVolume(tempFileCommandDirectory, "/github/file_commands"));
container.MountVolumes.Add(new MountVolume(defaultWorkingDirectory, "/github/workspace"));
if (FeatureManager.IsContainerActionRunnerTempEnabled(ExecutionContext.Global.Variables))
{
container.AddPathTranslateMapping(tempDirectory, "/github/runner_temp");
}
container.AddPathTranslateMapping(tempHomeDirectory, "/github/home");
container.AddPathTranslateMapping(tempWorkflowDirectory, "/github/workflow");
container.AddPathTranslateMapping(tempFileCommandDirectory, "/github/file_commands");

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,48 +0,0 @@
using System;
using System.IO;
using Xunit;
namespace GitHub.Runner.Common.Tests.Misc
{
public sealed class SystemdServiceTemplateL0
{
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void ServiceTemplate_ContainsExpectedKillMode()
{
// Arrange
var templatePath = Path.Combine(TestUtil.GetSrcPath(), "Misc", "layoutbin", "actions.runner.service.template");
// Act
var templateContent = File.ReadAllText(templatePath);
// Assert
Assert.Contains("KillMode=mixed", templateContent);
Assert.Contains("KillSignal=SIGTERM", templateContent);
Assert.Contains("TimeoutStopSec=5min", templateContent);
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void ServiceTemplate_HasValidStructure()
{
// Arrange
var templatePath = Path.Combine(TestUtil.GetSrcPath(), "Misc", "layoutbin", "actions.runner.service.template");
// Act
var templateContent = File.ReadAllText(templatePath);
var lines = templateContent.Split('\n', StringSplitOptions.RemoveEmptyEntries);
// Assert
Assert.Contains("[Unit]", lines);
Assert.Contains("[Service]", lines);
Assert.Contains("[Install]", lines);
Assert.Contains("Description={{Description}}", lines);
Assert.Contains("ExecStart={{RunnerRoot}}/runsvc.sh", lines);
Assert.Contains("User={{User}}", lines);
Assert.Contains("WorkingDirectory={{RunnerRoot}}", lines);
}
}
}

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