mirror of
https://github.com/actions/runner.git
synced 2025-12-12 15:13:30 +00:00
Compare commits
15 Commits
legacy-url
...
users/tihu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd127e6a90 | ||
|
|
1c319b4d42 | ||
|
|
fe10d4ae82 | ||
|
|
27d9c886ab | ||
|
|
5106d6578e | ||
|
|
d5ccbd10d1 | ||
|
|
f1b5b5bd5c | ||
|
|
aaf1b92847 | ||
|
|
c1095ae2d1 | ||
|
|
a0a0a76378 | ||
|
|
d47013928b | ||
|
|
cdeec012aa | ||
|
|
2cb1f9431a | ||
|
|
e86c9487ab | ||
|
|
dc9695f123 |
@@ -4,7 +4,7 @@
|
|||||||
"features": {
|
"features": {
|
||||||
"ghcr.io/devcontainers/features/docker-in-docker:1": {},
|
"ghcr.io/devcontainers/features/docker-in-docker:1": {},
|
||||||
"ghcr.io/devcontainers/features/dotnet": {
|
"ghcr.io/devcontainers/features/dotnet": {
|
||||||
"version": "8.0.407"
|
"version": "8.0.408"
|
||||||
},
|
},
|
||||||
"ghcr.io/devcontainers/features/node:1": {
|
"ghcr.io/devcontainers/features/node:1": {
|
||||||
"version": "20"
|
"version": "20"
|
||||||
|
|||||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -20,7 +20,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||||
script: |
|
script: |
|
||||||
const core = require('@actions/core')
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const runnerVersion = fs.readFileSync('${{ github.workspace }}/src/runnerversion', 'utf8').replace(/\n$/g, '')
|
const runnerVersion = fs.readFileSync('${{ github.workspace }}/src/runnerversion', 'utf8').replace(/\n$/g, '')
|
||||||
const releaseVersion = fs.readFileSync('${{ github.workspace }}/releaseVersion', 'utf8').replace(/\n$/g, '')
|
const releaseVersion = fs.readFileSync('${{ github.workspace }}/releaseVersion', 'utf8').replace(/\n$/g, '')
|
||||||
@@ -30,7 +29,7 @@ jobs:
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const release = await github.repos.getReleaseByTag({
|
const release = await github.rest.repos.getReleaseByTag({
|
||||||
owner: '${{ github.event.repository.owner.name }}',
|
owner: '${{ github.event.repository.owner.name }}',
|
||||||
repo: '${{ github.event.repository.name }}',
|
repo: '${{ github.event.repository.name }}',
|
||||||
tag: 'v' + runnerVersion
|
tag: 'v' + runnerVersion
|
||||||
@@ -176,7 +175,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||||
script: |
|
script: |
|
||||||
const core = require('@actions/core')
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const runnerVersion = fs.readFileSync('${{ github.workspace }}/src/runnerversion', 'utf8').replace(/\n$/g, '')
|
const runnerVersion = fs.readFileSync('${{ github.workspace }}/src/runnerversion', 'utf8').replace(/\n$/g, '')
|
||||||
var releaseNote = fs.readFileSync('${{ github.workspace }}/releaseNote.md', 'utf8').replace(/<RUNNER_VERSION>/g, runnerVersion)
|
var releaseNote = fs.readFileSync('${{ github.workspace }}/releaseNote.md', 'utf8').replace(/<RUNNER_VERSION>/g, runnerVersion)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ FROM mcr.microsoft.com/dotnet/runtime-deps:8.0-jammy AS build
|
|||||||
ARG TARGETOS
|
ARG TARGETOS
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
ARG RUNNER_VERSION
|
ARG RUNNER_VERSION
|
||||||
ARG RUNNER_CONTAINER_HOOKS_VERSION=0.6.1
|
ARG RUNNER_CONTAINER_HOOKS_VERSION=0.7.0
|
||||||
ARG DOCKER_VERSION=28.0.1
|
ARG DOCKER_VERSION=28.0.1
|
||||||
ARG BUILDX_VERSION=0.21.2
|
ARG BUILDX_VERSION=0.21.2
|
||||||
|
|
||||||
|
|||||||
13
src/Runner.Common/AuthMigration.cs
Normal file
13
src/Runner.Common/AuthMigration.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace GitHub.Runner.Common
|
||||||
|
{
|
||||||
|
public class AuthMigrationEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public AuthMigrationEventArgs(string trace)
|
||||||
|
{
|
||||||
|
Trace = trace;
|
||||||
|
}
|
||||||
|
public string Trace { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,6 +37,7 @@ namespace GitHub.Runner.Common
|
|||||||
|
|
||||||
public async Task ConnectAsync(Uri serverUri, VssCredentials credentials)
|
public async Task ConnectAsync(Uri serverUri, VssCredentials credentials)
|
||||||
{
|
{
|
||||||
|
Trace.Entering();
|
||||||
_brokerUri = serverUri;
|
_brokerUri = serverUri;
|
||||||
|
|
||||||
_connection = VssUtil.CreateRawConnection(serverUri, credentials);
|
_connection = VssUtil.CreateRawConnection(serverUri, credentials);
|
||||||
@@ -87,10 +88,15 @@ namespace GitHub.Runner.Common
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Task ForceRefreshConnection(VssCredentials credentials)
|
public Task ForceRefreshConnection(VssCredentials credentials)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(_brokerUri?.AbsoluteUri))
|
||||||
{
|
{
|
||||||
return ConnectAsync(_brokerUri, credentials);
|
return ConnectAsync(_brokerUri, credentials);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
public bool ShouldRetryException(Exception ex)
|
public bool ShouldRetryException(Exception ex)
|
||||||
{
|
{
|
||||||
if (ex is AccessDeniedException || ex is RunnerNotFoundException || ex is HostedRunnerDeprovisionedException)
|
if (ex is AccessDeniedException || ex is RunnerNotFoundException || ex is HostedRunnerDeprovisionedException)
|
||||||
|
|||||||
@@ -161,7 +161,6 @@ namespace GitHub.Runner.Common
|
|||||||
{
|
{
|
||||||
public static readonly string DiskSpaceWarning = "runner.diskspace.warning";
|
public static readonly string DiskSpaceWarning = "runner.diskspace.warning";
|
||||||
public static readonly string LogTemplateErrorsAsDebugMessages = "DistributedTask.LogTemplateErrorsAsDebugMessages";
|
public static readonly string LogTemplateErrorsAsDebugMessages = "DistributedTask.LogTemplateErrorsAsDebugMessages";
|
||||||
public static readonly string SkipRetryCompleteJobUponKnownErrors = "actions_skip_retry_complete_job_upon_known_errors";
|
|
||||||
public static readonly string UseContainerPathForTemplate = "DistributedTask.UseContainerPathForTemplate";
|
public static readonly string UseContainerPathForTemplate = "DistributedTask.UseContainerPathForTemplate";
|
||||||
public static readonly string AllowRunnerContainerHooks = "DistributedTask.AllowRunnerContainerHooks";
|
public static readonly string AllowRunnerContainerHooks = "DistributedTask.AllowRunnerContainerHooks";
|
||||||
}
|
}
|
||||||
@@ -258,6 +257,7 @@ namespace GitHub.Runner.Common
|
|||||||
public static readonly string ForcedActionsNodeVersion = "ACTIONS_RUNNER_FORCE_ACTIONS_NODE_VERSION";
|
public static readonly string ForcedActionsNodeVersion = "ACTIONS_RUNNER_FORCE_ACTIONS_NODE_VERSION";
|
||||||
public static readonly string PrintLogToStdout = "ACTIONS_RUNNER_PRINT_LOG_TO_STDOUT";
|
public static readonly string PrintLogToStdout = "ACTIONS_RUNNER_PRINT_LOG_TO_STDOUT";
|
||||||
public static readonly string ActionArchiveCacheDirectory = "ACTIONS_RUNNER_ACTION_ARCHIVE_CACHE";
|
public static readonly string ActionArchiveCacheDirectory = "ACTIONS_RUNNER_ACTION_ARCHIVE_CACHE";
|
||||||
|
public static readonly string ActionsTerminationGracePeriodSeconds = "ACTIONS_RUNNER_TERMINATION_GRACE_PERIOD_SECONDS";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class System
|
public static class System
|
||||||
|
|||||||
@@ -34,9 +34,14 @@ namespace GitHub.Runner.Common
|
|||||||
T GetService<T>() where T : class, IRunnerService;
|
T GetService<T>() where T : class, IRunnerService;
|
||||||
void SetDefaultCulture(string name);
|
void SetDefaultCulture(string name);
|
||||||
event EventHandler Unloading;
|
event EventHandler Unloading;
|
||||||
void ShutdownRunner(ShutdownReason reason);
|
void ShutdownRunner(ShutdownReason reason, TimeSpan delay = default);
|
||||||
void WritePerfCounter(string counter);
|
void WritePerfCounter(string counter);
|
||||||
void LoadDefaultUserAgents();
|
void LoadDefaultUserAgents();
|
||||||
|
|
||||||
|
bool AllowAuthMigration { get; }
|
||||||
|
void EnableAuthMigration(string trace);
|
||||||
|
void DeferAuthMigration(TimeSpan deferred, string trace);
|
||||||
|
event EventHandler<AuthMigrationEventArgs> AuthMigrationChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum StartupType
|
public enum StartupType
|
||||||
@@ -69,13 +74,24 @@ namespace GitHub.Runner.Common
|
|||||||
private string _perfFile;
|
private string _perfFile;
|
||||||
private RunnerWebProxy _webProxy = new();
|
private RunnerWebProxy _webProxy = new();
|
||||||
private string _hostType = string.Empty;
|
private string _hostType = string.Empty;
|
||||||
|
private ShutdownReason _shutdownReason = ShutdownReason.UserCancelled;
|
||||||
|
private int _shutdownReasonSet = 0;
|
||||||
|
|
||||||
|
// disable auth migration by default
|
||||||
|
private readonly ManualResetEventSlim _allowAuthMigration = new ManualResetEventSlim(false);
|
||||||
|
private DateTime _deferredAuthMigrationTime = DateTime.MaxValue;
|
||||||
|
private readonly object _authMigrationLock = new object();
|
||||||
|
private CancellationTokenSource _authMigrationAutoReenableTaskCancellationTokenSource = new();
|
||||||
|
private Task _authMigrationAutoReenableTask;
|
||||||
|
|
||||||
public event EventHandler Unloading;
|
public event EventHandler Unloading;
|
||||||
|
public event EventHandler<AuthMigrationEventArgs> AuthMigrationChanged;
|
||||||
public CancellationToken RunnerShutdownToken => _runnerShutdownTokenSource.Token;
|
public CancellationToken RunnerShutdownToken => _runnerShutdownTokenSource.Token;
|
||||||
public ShutdownReason RunnerShutdownReason { get; private set; }
|
public ShutdownReason RunnerShutdownReason => _shutdownReason;
|
||||||
public ISecretMasker SecretMasker => _secretMasker;
|
public ISecretMasker SecretMasker => _secretMasker;
|
||||||
public List<ProductInfoHeaderValue> UserAgents => _userAgents;
|
public List<ProductInfoHeaderValue> UserAgents => _userAgents;
|
||||||
public RunnerWebProxy WebProxy => _webProxy;
|
public RunnerWebProxy WebProxy => _webProxy;
|
||||||
|
public bool AllowAuthMigration => _allowAuthMigration.IsSet;
|
||||||
public HostContext(string hostType, string logFile = null)
|
public HostContext(string hostType, string logFile = null)
|
||||||
{
|
{
|
||||||
// Validate args.
|
// Validate args.
|
||||||
@@ -207,6 +223,71 @@ namespace GitHub.Runner.Common
|
|||||||
LoadDefaultUserAgents();
|
LoadDefaultUserAgents();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// marked as internal for testing
|
||||||
|
internal async Task AuthMigrationAuthReenableAsync(TimeSpan refreshInterval, CancellationToken token)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (!token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
_trace.Verbose($"Auth migration defer timer is set to expire at {_deferredAuthMigrationTime.ToString("O")}. AllowAuthMigration: {_allowAuthMigration.IsSet}.");
|
||||||
|
await Task.Delay(refreshInterval, token);
|
||||||
|
if (!_allowAuthMigration.IsSet && DateTime.UtcNow > _deferredAuthMigrationTime)
|
||||||
|
{
|
||||||
|
_trace.Info($"Auth migration defer timer expired. Allowing auth migration.");
|
||||||
|
EnableAuthMigration("Auth migration defer timer expired.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (TaskCanceledException)
|
||||||
|
{
|
||||||
|
// Task was cancelled, exit the loop.
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_trace.Info("Error in auth migration reenable task.");
|
||||||
|
_trace.Error(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EnableAuthMigration(string trace)
|
||||||
|
{
|
||||||
|
_allowAuthMigration.Set();
|
||||||
|
|
||||||
|
lock (_authMigrationLock)
|
||||||
|
{
|
||||||
|
if (_authMigrationAutoReenableTask == null)
|
||||||
|
{
|
||||||
|
var refreshIntervalInMS = 60 * 1000;
|
||||||
|
#if DEBUG
|
||||||
|
// For L0, we will refresh faster
|
||||||
|
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("_GITHUB_ACTION_AUTH_MIGRATION_REFRESH_INTERVAL")))
|
||||||
|
{
|
||||||
|
refreshIntervalInMS = int.Parse(Environment.GetEnvironmentVariable("_GITHUB_ACTION_AUTH_MIGRATION_REFRESH_INTERVAL"));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
_authMigrationAutoReenableTask = AuthMigrationAuthReenableAsync(TimeSpan.FromMilliseconds(refreshIntervalInMS), _authMigrationAutoReenableTaskCancellationTokenSource.Token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_trace.Info($"Enable auth migration at {DateTime.UtcNow.ToString("O")}.");
|
||||||
|
AuthMigrationChanged?.Invoke(this, new AuthMigrationEventArgs(trace));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeferAuthMigration(TimeSpan deferred, string trace)
|
||||||
|
{
|
||||||
|
_allowAuthMigration.Reset();
|
||||||
|
|
||||||
|
// defer migration for a while
|
||||||
|
lock (_authMigrationLock)
|
||||||
|
{
|
||||||
|
_deferredAuthMigrationTime = DateTime.UtcNow.Add(deferred);
|
||||||
|
}
|
||||||
|
|
||||||
|
_trace.Info($"Disabled auth migration until {_deferredAuthMigrationTime.ToString("O")}.");
|
||||||
|
AuthMigrationChanged?.Invoke(this, new AuthMigrationEventArgs(trace));
|
||||||
|
}
|
||||||
|
|
||||||
public void LoadDefaultUserAgents()
|
public void LoadDefaultUserAgents()
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(WebProxy.HttpProxyAddress) && string.IsNullOrEmpty(WebProxy.HttpsProxyAddress))
|
if (string.IsNullOrEmpty(WebProxy.HttpProxyAddress) && string.IsNullOrEmpty(WebProxy.HttpsProxyAddress))
|
||||||
@@ -494,13 +575,29 @@ namespace GitHub.Runner.Common
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void ShutdownRunner(ShutdownReason reason)
|
public void ShutdownRunner(ShutdownReason reason, TimeSpan delay = default)
|
||||||
{
|
{
|
||||||
ArgUtil.NotNull(reason, nameof(reason));
|
ArgUtil.NotNull(reason, nameof(reason));
|
||||||
_trace.Info($"Runner will be shutdown for {reason.ToString()}");
|
_trace.Info($"Runner will be shutdown for {reason.ToString()} after {delay.TotalSeconds} seconds.");
|
||||||
RunnerShutdownReason = reason;
|
if (Interlocked.CompareExchange(ref _shutdownReasonSet, 1, 0) == 0)
|
||||||
|
{
|
||||||
|
// Set the shutdown reason only if it hasn't been set before.
|
||||||
|
_shutdownReason = reason;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_trace.Verbose($"Runner shutdown reason already set to {_shutdownReason.ToString()}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delay.TotalSeconds == 0)
|
||||||
|
{
|
||||||
_runnerShutdownTokenSource.Cancel();
|
_runnerShutdownTokenSource.Cancel();
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_runnerShutdownTokenSource.CancelAfter(delay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override void Dispose()
|
public override void Dispose()
|
||||||
{
|
{
|
||||||
@@ -549,6 +646,18 @@ namespace GitHub.Runner.Common
|
|||||||
_loadContext.Unloading -= LoadContext_Unloading;
|
_loadContext.Unloading -= LoadContext_Unloading;
|
||||||
_loadContext = null;
|
_loadContext = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_authMigrationAutoReenableTask != null)
|
||||||
|
{
|
||||||
|
_authMigrationAutoReenableTaskCancellationTokenSource?.Cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_authMigrationAutoReenableTaskCancellationTokenSource != null)
|
||||||
|
{
|
||||||
|
_authMigrationAutoReenableTaskCancellationTokenSource?.Dispose();
|
||||||
|
_authMigrationAutoReenableTaskCancellationTokenSource = null;
|
||||||
|
}
|
||||||
|
|
||||||
_httpTraceSubscription?.Dispose();
|
_httpTraceSubscription?.Dispose();
|
||||||
_diagListenerSubscription?.Dispose();
|
_diagListenerSubscription?.Dispose();
|
||||||
_traceManager?.Dispose();
|
_traceManager?.Dispose();
|
||||||
|
|||||||
@@ -32,18 +32,6 @@ namespace GitHub.Runner.Common
|
|||||||
string billingOwnerId,
|
string billingOwnerId,
|
||||||
CancellationToken token);
|
CancellationToken token);
|
||||||
|
|
||||||
Task CompleteJob2Async(
|
|
||||||
Guid planId,
|
|
||||||
Guid jobId,
|
|
||||||
TaskResult result,
|
|
||||||
Dictionary<String, VariableValue> outputs,
|
|
||||||
IList<StepResult> stepResults,
|
|
||||||
IList<Annotation> jobAnnotations,
|
|
||||||
string environmentUrl,
|
|
||||||
IList<Telemetry> telemetry,
|
|
||||||
string billingOwnerId,
|
|
||||||
CancellationToken token);
|
|
||||||
|
|
||||||
Task<RenewJobResponse> RenewJobAsync(Guid planId, Guid jobId, CancellationToken token);
|
Task<RenewJobResponse> RenewJobAsync(Guid planId, Guid jobId, CancellationToken token);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,7 +70,6 @@ namespace GitHub.Runner.Common
|
|||||||
ex is not TaskOrchestrationJobUnprocessableException); // HTTP status 422
|
ex is not TaskOrchestrationJobUnprocessableException); // HTTP status 422
|
||||||
}
|
}
|
||||||
|
|
||||||
// Legacy will be deleted when SkipRetryCompleteJobUponKnownErrors is cleaned up
|
|
||||||
public Task CompleteJobAsync(
|
public Task CompleteJobAsync(
|
||||||
Guid planId,
|
Guid planId,
|
||||||
Guid jobId,
|
Guid jobId,
|
||||||
@@ -94,23 +81,6 @@ namespace GitHub.Runner.Common
|
|||||||
IList<Telemetry> telemetry,
|
IList<Telemetry> telemetry,
|
||||||
string billingOwnerId,
|
string billingOwnerId,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
|
||||||
CheckConnection();
|
|
||||||
return RetryRequest(
|
|
||||||
async () => await _runServiceHttpClient.CompleteJobAsync(requestUri, planId, jobId, result, outputs, stepResults, jobAnnotations, environmentUrl, telemetry, billingOwnerId, cancellationToken), cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task CompleteJob2Async(
|
|
||||||
Guid planId,
|
|
||||||
Guid jobId,
|
|
||||||
TaskResult result,
|
|
||||||
Dictionary<String, VariableValue> outputs,
|
|
||||||
IList<StepResult> stepResults,
|
|
||||||
IList<Annotation> jobAnnotations,
|
|
||||||
string environmentUrl,
|
|
||||||
IList<Telemetry> telemetry,
|
|
||||||
string billingOwnerId,
|
|
||||||
CancellationToken cancellationToken)
|
|
||||||
{
|
{
|
||||||
CheckConnection();
|
CheckConnection();
|
||||||
return RetryRequest(
|
return RetryRequest(
|
||||||
|
|||||||
@@ -26,15 +26,18 @@ namespace GitHub.Runner.Listener
|
|||||||
private TaskAgentStatus runnerStatus = TaskAgentStatus.Online;
|
private TaskAgentStatus runnerStatus = TaskAgentStatus.Online;
|
||||||
private CancellationTokenSource _getMessagesTokenSource;
|
private CancellationTokenSource _getMessagesTokenSource;
|
||||||
private VssCredentials _creds;
|
private VssCredentials _creds;
|
||||||
|
private VssCredentials _credsV2;
|
||||||
private TaskAgentSession _session;
|
private TaskAgentSession _session;
|
||||||
private IRunnerServer _runnerServer;
|
private IRunnerServer _runnerServer;
|
||||||
private IBrokerServer _brokerServer;
|
private IBrokerServer _brokerServer;
|
||||||
|
private ICredentialManager _credMgr;
|
||||||
private readonly Dictionary<string, int> _sessionCreationExceptionTracker = new();
|
private readonly Dictionary<string, int> _sessionCreationExceptionTracker = new();
|
||||||
private bool _accessTokenRevoked = false;
|
private bool _accessTokenRevoked = false;
|
||||||
private readonly TimeSpan _sessionCreationRetryInterval = TimeSpan.FromSeconds(30);
|
private readonly TimeSpan _sessionCreationRetryInterval = TimeSpan.FromSeconds(30);
|
||||||
private readonly TimeSpan _sessionConflictRetryLimit = TimeSpan.FromMinutes(4);
|
private readonly TimeSpan _sessionConflictRetryLimit = TimeSpan.FromMinutes(4);
|
||||||
private readonly TimeSpan _clockSkewRetryLimit = TimeSpan.FromMinutes(30);
|
private readonly TimeSpan _clockSkewRetryLimit = TimeSpan.FromMinutes(30);
|
||||||
|
private bool _needRefreshCredsV2 = false;
|
||||||
|
private bool _handlerInitialized = false;
|
||||||
|
|
||||||
public override void Initialize(IHostContext hostContext)
|
public override void Initialize(IHostContext hostContext)
|
||||||
{
|
{
|
||||||
@@ -43,6 +46,7 @@ namespace GitHub.Runner.Listener
|
|||||||
_term = HostContext.GetService<ITerminal>();
|
_term = HostContext.GetService<ITerminal>();
|
||||||
_runnerServer = HostContext.GetService<IRunnerServer>();
|
_runnerServer = HostContext.GetService<IRunnerServer>();
|
||||||
_brokerServer = HostContext.GetService<IBrokerServer>();
|
_brokerServer = HostContext.GetService<IBrokerServer>();
|
||||||
|
_credMgr = HostContext.GetService<ICredentialManager>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<CreateSessionResult> CreateSessionAsync(CancellationToken token)
|
public async Task<CreateSessionResult> CreateSessionAsync(CancellationToken token)
|
||||||
@@ -63,8 +67,7 @@ namespace GitHub.Runner.Listener
|
|||||||
|
|
||||||
// Create connection.
|
// Create connection.
|
||||||
Trace.Info("Loading Credentials");
|
Trace.Info("Loading Credentials");
|
||||||
var credMgr = HostContext.GetService<ICredentialManager>();
|
_creds = _credMgr.LoadCredentials(allowAuthUrlV2: false);
|
||||||
_creds = credMgr.LoadCredentials();
|
|
||||||
|
|
||||||
var agent = new TaskAgentReference
|
var agent = new TaskAgentReference
|
||||||
{
|
{
|
||||||
@@ -87,7 +90,8 @@ namespace GitHub.Runner.Listener
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
Trace.Info("Connecting to the Broker Server...");
|
Trace.Info("Connecting to the Broker Server...");
|
||||||
await _brokerServer.ConnectAsync(new Uri(serverUrlV2), _creds);
|
_credsV2 = _credMgr.LoadCredentials(allowAuthUrlV2: true);
|
||||||
|
await _brokerServer.ConnectAsync(new Uri(serverUrlV2), _credsV2);
|
||||||
Trace.Info("VssConnection created");
|
Trace.Info("VssConnection created");
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(serverUrl) &&
|
if (!string.IsNullOrEmpty(serverUrl) &&
|
||||||
@@ -112,6 +116,13 @@ namespace GitHub.Runner.Listener
|
|||||||
encounteringError = false;
|
encounteringError = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!_handlerInitialized)
|
||||||
|
{
|
||||||
|
// Register event handler for auth migration state change
|
||||||
|
HostContext.AuthMigrationChanged += HandleAuthMigrationChanged;
|
||||||
|
_handlerInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
return CreateSessionResult.Success;
|
return CreateSessionResult.Success;
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException) when (token.IsCancellationRequested)
|
catch (OperationCanceledException) when (token.IsCancellationRequested)
|
||||||
@@ -130,7 +141,7 @@ namespace GitHub.Runner.Listener
|
|||||||
Trace.Error("Catch exception during create session.");
|
Trace.Error("Catch exception during create session.");
|
||||||
Trace.Error(ex);
|
Trace.Error(ex);
|
||||||
|
|
||||||
if (ex is VssOAuthTokenRequestException vssOAuthEx && _creds.Federated is VssOAuthCredential vssOAuthCred)
|
if (ex is VssOAuthTokenRequestException vssOAuthEx && _credsV2.Federated is VssOAuthCredential vssOAuthCred)
|
||||||
{
|
{
|
||||||
// "invalid_client" means the runner registration has been deleted from the server.
|
// "invalid_client" means the runner registration has been deleted from the server.
|
||||||
if (string.Equals(vssOAuthEx.Error, "invalid_client", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(vssOAuthEx.Error, "invalid_client", StringComparison.OrdinalIgnoreCase))
|
||||||
@@ -161,6 +172,12 @@ namespace GitHub.Runner.Listener
|
|||||||
return CreateSessionResult.Failure;
|
return CreateSessionResult.Failure;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (HostContext.AllowAuthMigration)
|
||||||
|
{
|
||||||
|
Trace.Info("Disable migration mode for 60 minutes.");
|
||||||
|
HostContext.DeferAuthMigration(TimeSpan.FromMinutes(60), $"Session creation failed with exception: {ex}");
|
||||||
|
}
|
||||||
|
|
||||||
if (!encounteringError) //print the message only on the first error
|
if (!encounteringError) //print the message only on the first error
|
||||||
{
|
{
|
||||||
_term.WriteError($"{DateTime.UtcNow:u}: Runner connect error: {ex.Message}. Retrying until reconnected.");
|
_term.WriteError($"{DateTime.UtcNow:u}: Runner connect error: {ex.Message}. Retrying until reconnected.");
|
||||||
@@ -177,6 +194,11 @@ namespace GitHub.Runner.Listener
|
|||||||
{
|
{
|
||||||
if (_session != null && _session.SessionId != Guid.Empty)
|
if (_session != null && _session.SessionId != Guid.Empty)
|
||||||
{
|
{
|
||||||
|
if (_handlerInitialized)
|
||||||
|
{
|
||||||
|
HostContext.AuthMigrationChanged -= HandleAuthMigrationChanged;
|
||||||
|
}
|
||||||
|
|
||||||
if (!_accessTokenRevoked)
|
if (!_accessTokenRevoked)
|
||||||
{
|
{
|
||||||
using (var ts = new CancellationTokenSource(TimeSpan.FromSeconds(30)))
|
using (var ts = new CancellationTokenSource(TimeSpan.FromSeconds(30)))
|
||||||
@@ -219,6 +241,13 @@ namespace GitHub.Runner.Listener
|
|||||||
_getMessagesTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token);
|
_getMessagesTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (_needRefreshCredsV2)
|
||||||
|
{
|
||||||
|
Trace.Info("Refreshing broker connection.");
|
||||||
|
await RefreshBrokerConnectionAsync();
|
||||||
|
_needRefreshCredsV2 = false;
|
||||||
|
}
|
||||||
|
|
||||||
message = await _brokerServer.GetRunnerMessageAsync(_session.SessionId,
|
message = await _brokerServer.GetRunnerMessageAsync(_session.SessionId,
|
||||||
runnerStatus,
|
runnerStatus,
|
||||||
BuildConstants.RunnerPackage.Version,
|
BuildConstants.RunnerPackage.Version,
|
||||||
@@ -298,6 +327,12 @@ namespace GitHub.Runner.Listener
|
|||||||
encounteringError = true;
|
encounteringError = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (HostContext.AllowAuthMigration)
|
||||||
|
{
|
||||||
|
Trace.Info("Disable migration mode for 60 minutes.");
|
||||||
|
HostContext.DeferAuthMigration(TimeSpan.FromMinutes(60), $"Get next message failed with exception: {ex}");
|
||||||
|
}
|
||||||
|
|
||||||
// re-create VssConnection before next retry
|
// re-create VssConnection before next retry
|
||||||
await RefreshBrokerConnectionAsync();
|
await RefreshBrokerConnectionAsync();
|
||||||
|
|
||||||
@@ -329,7 +364,7 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RefreshListenerTokenAsync(CancellationToken cancellationToken)
|
public async Task RefreshListenerTokenAsync()
|
||||||
{
|
{
|
||||||
await RefreshBrokerConnectionAsync();
|
await RefreshBrokerConnectionAsync();
|
||||||
}
|
}
|
||||||
@@ -432,17 +467,16 @@ namespace GitHub.Runner.Listener
|
|||||||
|
|
||||||
private async Task RefreshBrokerConnectionAsync()
|
private async Task RefreshBrokerConnectionAsync()
|
||||||
{
|
{
|
||||||
var configManager = HostContext.GetService<IConfigurationManager>();
|
Trace.Info("Reload credentials.");
|
||||||
_settings = configManager.LoadSettings();
|
_credsV2 = _credMgr.LoadCredentials(allowAuthUrlV2: true);
|
||||||
|
await _brokerServer.ConnectAsync(new Uri(_settings.ServerUrlV2), _credsV2);
|
||||||
if (string.IsNullOrEmpty(_settings.ServerUrlV2))
|
Trace.Info("Connection to Broker Server recreated.");
|
||||||
{
|
|
||||||
throw new InvalidOperationException("ServerUrlV2 is not set");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var credMgr = HostContext.GetService<ICredentialManager>();
|
private void HandleAuthMigrationChanged(object sender, EventArgs e)
|
||||||
VssCredentials creds = credMgr.LoadCredentials();
|
{
|
||||||
await _brokerServer.ConnectAsync(new Uri(_settings.ServerUrlV2), creds);
|
Trace.Info($"Auth migration changed. Current allow auth migration state: {HostContext.AllowAuthMigration}");
|
||||||
|
_needRefreshCredsV2 = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
runnerSettings.ServerUrl = inputUrl;
|
runnerSettings.ServerUrl = inputUrl;
|
||||||
// Get the credentials
|
// Get the credentials
|
||||||
credProvider = GetCredentialProvider(command, runnerSettings.ServerUrl);
|
credProvider = GetCredentialProvider(command, runnerSettings.ServerUrl);
|
||||||
creds = credProvider.GetVssCredentials(HostContext);
|
creds = credProvider.GetVssCredentials(HostContext, allowAuthUrlV2: false);
|
||||||
Trace.Info("legacy vss cred retrieved");
|
Trace.Info("legacy vss cred retrieved");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -135,8 +135,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
runnerSettings.GitHubUrl = inputUrl;
|
runnerSettings.GitHubUrl = inputUrl;
|
||||||
registerToken = await GetRunnerTokenAsync(command, inputUrl, "registration");
|
registerToken = await GetRunnerTokenAsync(command, inputUrl, "registration");
|
||||||
GitHubAuthResult authResult = await GetTenantCredential(inputUrl, registerToken, Constants.RunnerEvent.Register);
|
GitHubAuthResult authResult = await GetTenantCredential(inputUrl, registerToken, Constants.RunnerEvent.Register);
|
||||||
var hasLegacyUrl = !string.IsNullOrEmpty(authResult.LegacyUrl);
|
runnerSettings.ServerUrl = authResult.TenantUrl;
|
||||||
runnerSettings.ServerUrl = hasLegacyUrl ? authResult.LegacyUrl : authResult.TenantUrl;
|
|
||||||
runnerSettings.UseV2Flow = authResult.UseV2Flow;
|
runnerSettings.UseV2Flow = authResult.UseV2Flow;
|
||||||
Trace.Info($"Using V2 flow: {runnerSettings.UseV2Flow}");
|
Trace.Info($"Using V2 flow: {runnerSettings.UseV2Flow}");
|
||||||
creds = authResult.ToVssCredentials();
|
creds = authResult.ToVssCredentials();
|
||||||
@@ -367,7 +366,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
{
|
{
|
||||||
{ "clientId", agent.Authorization.ClientId.ToString("D") },
|
{ "clientId", agent.Authorization.ClientId.ToString("D") },
|
||||||
{ "authorizationUrl", agent.Authorization.AuthorizationUrl.AbsoluteUri },
|
{ "authorizationUrl", agent.Authorization.AuthorizationUrl.AbsoluteUri },
|
||||||
{ "requireFipsCryptography", agent.Properties.GetValue("RequireFipsCryptography", false).ToString() }
|
{ "requireFipsCryptography", agent.Properties.GetValue("RequireFipsCryptography", true).ToString() }
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -385,7 +384,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
if (!runnerSettings.UseV2Flow)
|
if (!runnerSettings.UseV2Flow)
|
||||||
{
|
{
|
||||||
var credMgr = HostContext.GetService<ICredentialManager>();
|
var credMgr = HostContext.GetService<ICredentialManager>();
|
||||||
VssCredentials credential = credMgr.LoadCredentials();
|
VssCredentials credential = credMgr.LoadCredentials(allowAuthUrlV2: false);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _runnerServer.ConnectAsync(new Uri(runnerSettings.ServerUrl), credential);
|
await _runnerServer.ConnectAsync(new Uri(runnerSettings.ServerUrl), credential);
|
||||||
@@ -520,7 +519,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
if (string.IsNullOrEmpty(settings.GitHubUrl))
|
if (string.IsNullOrEmpty(settings.GitHubUrl))
|
||||||
{
|
{
|
||||||
var credProvider = GetCredentialProvider(command, settings.ServerUrl);
|
var credProvider = GetCredentialProvider(command, settings.ServerUrl);
|
||||||
creds = credProvider.GetVssCredentials(HostContext);
|
creds = credProvider.GetVssCredentials(HostContext, allowAuthUrlV2: false);
|
||||||
Trace.Info("legacy vss cred retrieved");
|
Trace.Info("legacy vss cred retrieved");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
public interface ICredentialManager : IRunnerService
|
public interface ICredentialManager : IRunnerService
|
||||||
{
|
{
|
||||||
ICredentialProvider GetCredentialProvider(string credType);
|
ICredentialProvider GetCredentialProvider(string credType);
|
||||||
VssCredentials LoadCredentials();
|
VssCredentials LoadCredentials(bool allowAuthUrlV2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CredentialManager : RunnerService, ICredentialManager
|
public class CredentialManager : RunnerService, ICredentialManager
|
||||||
@@ -40,7 +40,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
return creds;
|
return creds;
|
||||||
}
|
}
|
||||||
|
|
||||||
public VssCredentials LoadCredentials()
|
public VssCredentials LoadCredentials(bool allowAuthUrlV2)
|
||||||
{
|
{
|
||||||
IConfigurationStore store = HostContext.GetService<IConfigurationStore>();
|
IConfigurationStore store = HostContext.GetService<IConfigurationStore>();
|
||||||
|
|
||||||
@@ -51,21 +51,16 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
|
|
||||||
CredentialData credData = store.GetCredentials();
|
CredentialData credData = store.GetCredentials();
|
||||||
var migratedCred = store.GetMigratedCredentials();
|
var migratedCred = store.GetMigratedCredentials();
|
||||||
if (migratedCred != null)
|
if (migratedCred != null &&
|
||||||
|
migratedCred.Scheme == Constants.Configuration.OAuth)
|
||||||
{
|
{
|
||||||
credData = migratedCred;
|
credData = migratedCred;
|
||||||
|
|
||||||
// Re-write .credentials with Token URL
|
|
||||||
store.SaveCredential(credData);
|
|
||||||
|
|
||||||
// Delete .credentials_migrated
|
|
||||||
store.DeleteMigratedCredential();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ICredentialProvider credProv = GetCredentialProvider(credData.Scheme);
|
ICredentialProvider credProv = GetCredentialProvider(credData.Scheme);
|
||||||
credProv.CredentialData = credData;
|
credProv.CredentialData = credData;
|
||||||
|
|
||||||
VssCredentials creds = credProv.GetVssCredentials(HostContext);
|
VssCredentials creds = credProv.GetVssCredentials(HostContext, allowAuthUrlV2);
|
||||||
|
|
||||||
return creds;
|
return creds;
|
||||||
}
|
}
|
||||||
@@ -96,23 +91,14 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
[DataMember(Name = "use_v2_flow")]
|
[DataMember(Name = "use_v2_flow")]
|
||||||
public bool UseV2Flow { get; set; }
|
public bool UseV2Flow { get; set; }
|
||||||
|
|
||||||
[DataMember(Name = "legacy_url")]
|
|
||||||
public string LegacyUrl { get; set; }
|
|
||||||
|
|
||||||
[DataMember(Name = "legacy_token")]
|
|
||||||
public string LegacyToken { get; set; }
|
|
||||||
|
|
||||||
public VssCredentials ToVssCredentials()
|
public VssCredentials ToVssCredentials()
|
||||||
{
|
{
|
||||||
ArgUtil.NotNullOrEmpty(TokenSchema, nameof(TokenSchema));
|
ArgUtil.NotNullOrEmpty(TokenSchema, nameof(TokenSchema));
|
||||||
ArgUtil.NotNullOrEmpty(Token, nameof(Token));
|
ArgUtil.NotNullOrEmpty(Token, nameof(Token));
|
||||||
|
|
||||||
var hasLegacyToken = !string.IsNullOrEmpty(LegacyToken);
|
|
||||||
var token = hasLegacyToken ? LegacyToken : Token;
|
|
||||||
|
|
||||||
if (string.Equals(TokenSchema, "OAuthAccessToken", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(TokenSchema, "OAuthAccessToken", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return new VssCredentials(new VssOAuthAccessTokenCredential(token), CredentialPromptType.DoNotPrompt);
|
return new VssCredentials(new VssOAuthAccessTokenCredential(Token), CredentialPromptType.DoNotPrompt);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using GitHub.Services.Common;
|
|
||||||
using GitHub.Runner.Common;
|
using GitHub.Runner.Common;
|
||||||
using GitHub.Runner.Sdk;
|
using GitHub.Runner.Sdk;
|
||||||
|
using GitHub.Services.Common;
|
||||||
using GitHub.Services.OAuth;
|
using GitHub.Services.OAuth;
|
||||||
|
|
||||||
namespace GitHub.Runner.Listener.Configuration
|
namespace GitHub.Runner.Listener.Configuration
|
||||||
@@ -10,7 +10,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
{
|
{
|
||||||
Boolean RequireInteractive { get; }
|
Boolean RequireInteractive { get; }
|
||||||
CredentialData CredentialData { get; set; }
|
CredentialData CredentialData { get; set; }
|
||||||
VssCredentials GetVssCredentials(IHostContext context);
|
VssCredentials GetVssCredentials(IHostContext context, bool allowAuthUrlV2);
|
||||||
void EnsureCredential(IHostContext context, CommandSettings command, string serverUrl);
|
void EnsureCredential(IHostContext context, CommandSettings command, string serverUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
public virtual Boolean RequireInteractive => false;
|
public virtual Boolean RequireInteractive => false;
|
||||||
public CredentialData CredentialData { get; set; }
|
public CredentialData CredentialData { get; set; }
|
||||||
|
|
||||||
public abstract VssCredentials GetVssCredentials(IHostContext context);
|
public abstract VssCredentials GetVssCredentials(IHostContext context, bool allowAuthUrlV2);
|
||||||
public abstract void EnsureCredential(IHostContext context, CommandSettings command, string serverUrl);
|
public abstract void EnsureCredential(IHostContext context, CommandSettings command, string serverUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
{
|
{
|
||||||
public OAuthAccessTokenCredential() : base(Constants.Configuration.OAuthAccessToken) { }
|
public OAuthAccessTokenCredential() : base(Constants.Configuration.OAuthAccessToken) { }
|
||||||
|
|
||||||
public override VssCredentials GetVssCredentials(IHostContext context)
|
public override VssCredentials GetVssCredentials(IHostContext context, bool allowAuthUrlV2)
|
||||||
{
|
{
|
||||||
ArgUtil.NotNull(context, nameof(context));
|
ArgUtil.NotNull(context, nameof(context));
|
||||||
Tracing trace = context.GetTrace(nameof(OAuthAccessTokenCredential));
|
Tracing trace = context.GetTrace(nameof(OAuthAccessTokenCredential));
|
||||||
|
|||||||
@@ -22,10 +22,18 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
// Nothing to verify here
|
// Nothing to verify here
|
||||||
}
|
}
|
||||||
|
|
||||||
public override VssCredentials GetVssCredentials(IHostContext context)
|
public override VssCredentials GetVssCredentials(IHostContext context, bool allowAuthUrlV2)
|
||||||
{
|
{
|
||||||
var clientId = this.CredentialData.Data.GetValueOrDefault("clientId", null);
|
var clientId = this.CredentialData.Data.GetValueOrDefault("clientId", null);
|
||||||
var authorizationUrl = this.CredentialData.Data.GetValueOrDefault("authorizationUrl", null);
|
var authorizationUrl = this.CredentialData.Data.GetValueOrDefault("authorizationUrl", null);
|
||||||
|
var authorizationUrlV2 = this.CredentialData.Data.GetValueOrDefault("authorizationUrlV2", null);
|
||||||
|
|
||||||
|
if (allowAuthUrlV2 &&
|
||||||
|
!string.IsNullOrEmpty(authorizationUrlV2) &&
|
||||||
|
context.AllowAuthMigration)
|
||||||
|
{
|
||||||
|
authorizationUrl = authorizationUrlV2;
|
||||||
|
}
|
||||||
|
|
||||||
// For back compat with .credential file that doesn't has 'oauthEndpointUrl' section
|
// For back compat with .credential file that doesn't has 'oauthEndpointUrl' section
|
||||||
var oauthEndpointUrl = this.CredentialData.Data.GetValueOrDefault("oauthEndpointUrl", authorizationUrl);
|
var oauthEndpointUrl = this.CredentialData.Data.GetValueOrDefault("oauthEndpointUrl", authorizationUrl);
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ namespace GitHub.Runner.Listener
|
|||||||
Task<TaskAgentMessage> GetNextMessageAsync(CancellationToken token);
|
Task<TaskAgentMessage> GetNextMessageAsync(CancellationToken token);
|
||||||
Task DeleteMessageAsync(TaskAgentMessage message);
|
Task DeleteMessageAsync(TaskAgentMessage message);
|
||||||
|
|
||||||
Task RefreshListenerTokenAsync(CancellationToken token);
|
Task RefreshListenerTokenAsync();
|
||||||
void OnJobStatus(object sender, JobStatusEventArgs e);
|
void OnJobStatus(object sender, JobStatusEventArgs e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,6 +44,7 @@ namespace GitHub.Runner.Listener
|
|||||||
private ITerminal _term;
|
private ITerminal _term;
|
||||||
private IRunnerServer _runnerServer;
|
private IRunnerServer _runnerServer;
|
||||||
private IBrokerServer _brokerServer;
|
private IBrokerServer _brokerServer;
|
||||||
|
private ICredentialManager _credMgr;
|
||||||
private TaskAgentSession _session;
|
private TaskAgentSession _session;
|
||||||
private TimeSpan _getNextMessageRetryInterval;
|
private TimeSpan _getNextMessageRetryInterval;
|
||||||
private bool _accessTokenRevoked = false;
|
private bool _accessTokenRevoked = false;
|
||||||
@@ -54,8 +55,9 @@ namespace GitHub.Runner.Listener
|
|||||||
private TaskAgentStatus runnerStatus = TaskAgentStatus.Online;
|
private TaskAgentStatus runnerStatus = TaskAgentStatus.Online;
|
||||||
private CancellationTokenSource _getMessagesTokenSource;
|
private CancellationTokenSource _getMessagesTokenSource;
|
||||||
private VssCredentials _creds;
|
private VssCredentials _creds;
|
||||||
|
private VssCredentials _credsV2;
|
||||||
private bool _isBrokerSession = false;
|
private bool _needRefreshCredsV2 = false;
|
||||||
|
private bool _handlerInitialized = false;
|
||||||
|
|
||||||
public override void Initialize(IHostContext hostContext)
|
public override void Initialize(IHostContext hostContext)
|
||||||
{
|
{
|
||||||
@@ -64,6 +66,7 @@ namespace GitHub.Runner.Listener
|
|||||||
_term = HostContext.GetService<ITerminal>();
|
_term = HostContext.GetService<ITerminal>();
|
||||||
_runnerServer = HostContext.GetService<IRunnerServer>();
|
_runnerServer = HostContext.GetService<IRunnerServer>();
|
||||||
_brokerServer = hostContext.GetService<IBrokerServer>();
|
_brokerServer = hostContext.GetService<IBrokerServer>();
|
||||||
|
_credMgr = hostContext.GetService<ICredentialManager>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<CreateSessionResult> CreateSessionAsync(CancellationToken token)
|
public async Task<CreateSessionResult> CreateSessionAsync(CancellationToken token)
|
||||||
@@ -78,8 +81,7 @@ namespace GitHub.Runner.Listener
|
|||||||
|
|
||||||
// Create connection.
|
// Create connection.
|
||||||
Trace.Info("Loading Credentials");
|
Trace.Info("Loading Credentials");
|
||||||
var credMgr = HostContext.GetService<ICredentialManager>();
|
_creds = _credMgr.LoadCredentials(allowAuthUrlV2: false);
|
||||||
_creds = credMgr.LoadCredentials();
|
|
||||||
|
|
||||||
var agent = new TaskAgentReference
|
var agent = new TaskAgentReference
|
||||||
{
|
{
|
||||||
@@ -113,16 +115,6 @@ namespace GitHub.Runner.Listener
|
|||||||
_settings.PoolId,
|
_settings.PoolId,
|
||||||
taskAgentSession,
|
taskAgentSession,
|
||||||
token);
|
token);
|
||||||
|
|
||||||
if (_session.BrokerMigrationMessage != null)
|
|
||||||
{
|
|
||||||
Trace.Info("Runner session is in migration mode: Creating Broker session with BrokerBaseUrl: {0}", _session.BrokerMigrationMessage.BrokerBaseUrl);
|
|
||||||
|
|
||||||
await _brokerServer.UpdateConnectionIfNeeded(_session.BrokerMigrationMessage.BrokerBaseUrl, _creds);
|
|
||||||
_session = await _brokerServer.CreateSessionAsync(taskAgentSession, token);
|
|
||||||
_isBrokerSession = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Trace.Info($"Session created.");
|
Trace.Info($"Session created.");
|
||||||
if (encounteringError)
|
if (encounteringError)
|
||||||
{
|
{
|
||||||
@@ -131,6 +123,13 @@ namespace GitHub.Runner.Listener
|
|||||||
encounteringError = false;
|
encounteringError = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!_handlerInitialized)
|
||||||
|
{
|
||||||
|
Trace.Info("Registering AuthMigrationChanged event handler.");
|
||||||
|
HostContext.AuthMigrationChanged += HandleAuthMigrationChanged;
|
||||||
|
_handlerInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
return CreateSessionResult.Success;
|
return CreateSessionResult.Success;
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException) when (token.IsCancellationRequested)
|
catch (OperationCanceledException) when (token.IsCancellationRequested)
|
||||||
@@ -196,16 +195,16 @@ namespace GitHub.Runner.Listener
|
|||||||
{
|
{
|
||||||
if (_session != null && _session.SessionId != Guid.Empty)
|
if (_session != null && _session.SessionId != Guid.Empty)
|
||||||
{
|
{
|
||||||
|
if (_handlerInitialized)
|
||||||
|
{
|
||||||
|
HostContext.AuthMigrationChanged -= HandleAuthMigrationChanged;
|
||||||
|
}
|
||||||
|
|
||||||
if (!_accessTokenRevoked)
|
if (!_accessTokenRevoked)
|
||||||
{
|
{
|
||||||
using (var ts = new CancellationTokenSource(TimeSpan.FromSeconds(30)))
|
using (var ts = new CancellationTokenSource(TimeSpan.FromSeconds(30)))
|
||||||
{
|
{
|
||||||
await _runnerServer.DeleteAgentSessionAsync(_settings.PoolId, _session.SessionId, ts.Token);
|
await _runnerServer.DeleteAgentSessionAsync(_settings.PoolId, _session.SessionId, ts.Token);
|
||||||
|
|
||||||
if (_isBrokerSession)
|
|
||||||
{
|
|
||||||
await _brokerServer.DeleteSessionAsync(ts.Token);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -261,12 +260,19 @@ namespace GitHub.Runner.Listener
|
|||||||
// Decrypt the message body if the session is using encryption
|
// Decrypt the message body if the session is using encryption
|
||||||
message = DecryptMessage(message);
|
message = DecryptMessage(message);
|
||||||
|
|
||||||
|
|
||||||
if (message != null && message.MessageType == BrokerMigrationMessage.MessageType)
|
if (message != null && message.MessageType == BrokerMigrationMessage.MessageType)
|
||||||
{
|
{
|
||||||
var migrationMessage = JsonUtility.FromString<BrokerMigrationMessage>(message.Body);
|
var migrationMessage = JsonUtility.FromString<BrokerMigrationMessage>(message.Body);
|
||||||
|
|
||||||
await _brokerServer.UpdateConnectionIfNeeded(migrationMessage.BrokerBaseUrl, _creds);
|
_credsV2 = _credMgr.LoadCredentials(allowAuthUrlV2: true);
|
||||||
|
await _brokerServer.UpdateConnectionIfNeeded(migrationMessage.BrokerBaseUrl, _credsV2);
|
||||||
|
if (_needRefreshCredsV2)
|
||||||
|
{
|
||||||
|
Trace.Info("Refreshing credentials for V2.");
|
||||||
|
await _brokerServer.ForceRefreshConnection(_credsV2);
|
||||||
|
_needRefreshCredsV2 = false;
|
||||||
|
}
|
||||||
|
|
||||||
message = await _brokerServer.GetRunnerMessageAsync(_session.SessionId,
|
message = await _brokerServer.GetRunnerMessageAsync(_session.SessionId,
|
||||||
runnerStatus,
|
runnerStatus,
|
||||||
BuildConstants.RunnerPackage.Version,
|
BuildConstants.RunnerPackage.Version,
|
||||||
@@ -322,6 +328,10 @@ namespace GitHub.Runner.Listener
|
|||||||
Trace.Error("Catch exception during get next message.");
|
Trace.Error("Catch exception during get next message.");
|
||||||
Trace.Error(ex);
|
Trace.Error(ex);
|
||||||
|
|
||||||
|
// clear out potential message for broker migration,
|
||||||
|
// in case the exception is thrown from get message from broker-listener.
|
||||||
|
message = null;
|
||||||
|
|
||||||
// don't retry if SkipSessionRecover = true, DT service will delete agent session to stop agent from taking more jobs.
|
// don't retry if SkipSessionRecover = true, DT service will delete agent session to stop agent from taking more jobs.
|
||||||
if (ex is TaskAgentSessionExpiredException && !_settings.SkipSessionRecover && (await CreateSessionAsync(token) == CreateSessionResult.Success))
|
if (ex is TaskAgentSessionExpiredException && !_settings.SkipSessionRecover && (await CreateSessionAsync(token) == CreateSessionResult.Success))
|
||||||
{
|
{
|
||||||
@@ -354,6 +364,12 @@ namespace GitHub.Runner.Listener
|
|||||||
encounteringError = true;
|
encounteringError = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (HostContext.AllowAuthMigration)
|
||||||
|
{
|
||||||
|
Trace.Info("Disable migration mode for 60 minutes.");
|
||||||
|
HostContext.DeferAuthMigration(TimeSpan.FromMinutes(60), $"Get next message failed with exception: {ex}");
|
||||||
|
}
|
||||||
|
|
||||||
// re-create VssConnection before next retry
|
// re-create VssConnection before next retry
|
||||||
await _runnerServer.RefreshConnectionAsync(RunnerConnectionType.MessageQueue, TimeSpan.FromSeconds(60));
|
await _runnerServer.RefreshConnectionAsync(RunnerConnectionType.MessageQueue, TimeSpan.FromSeconds(60));
|
||||||
|
|
||||||
@@ -411,10 +427,11 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RefreshListenerTokenAsync(CancellationToken cancellationToken)
|
public async Task RefreshListenerTokenAsync()
|
||||||
{
|
{
|
||||||
await _runnerServer.RefreshConnectionAsync(RunnerConnectionType.MessageQueue, TimeSpan.FromSeconds(60));
|
await _runnerServer.RefreshConnectionAsync(RunnerConnectionType.MessageQueue, TimeSpan.FromSeconds(60));
|
||||||
await _brokerServer.ForceRefreshConnection(_creds);
|
_credsV2 = _credMgr.LoadCredentials(allowAuthUrlV2: true);
|
||||||
|
await _brokerServer.ForceRefreshConnection(_credsV2);
|
||||||
}
|
}
|
||||||
|
|
||||||
private TaskAgentMessage DecryptMessage(TaskAgentMessage message)
|
private TaskAgentMessage DecryptMessage(TaskAgentMessage message)
|
||||||
@@ -545,5 +562,11 @@ namespace GitHub.Runner.Listener
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void HandleAuthMigrationChanged(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
Trace.Info($"Auth migration changed. Current allow auth migration state: {HostContext.AllowAuthMigration}");
|
||||||
|
_needRefreshCredsV2 = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -31,6 +32,13 @@ namespace GitHub.Runner.Listener
|
|||||||
private ITerminal _term;
|
private ITerminal _term;
|
||||||
private bool _inConfigStage;
|
private bool _inConfigStage;
|
||||||
private ManualResetEvent _completedCommand = new(false);
|
private ManualResetEvent _completedCommand = new(false);
|
||||||
|
private readonly ConcurrentQueue<string> _authMigrationTelemetries = new();
|
||||||
|
private Task _authMigrationTelemetryTask;
|
||||||
|
private readonly object _authMigrationTelemetryLock = new();
|
||||||
|
private IRunnerServer _runnerServer;
|
||||||
|
private CancellationTokenSource _authMigrationTelemetryTokenSource = new();
|
||||||
|
private bool _runnerExiting = false;
|
||||||
|
private bool _hasTerminationGracePeriod = false;
|
||||||
|
|
||||||
// <summary>
|
// <summary>
|
||||||
// Helps avoid excessive calls to Run Service when encountering non-retriable errors from /acquirejob.
|
// Helps avoid excessive calls to Run Service when encountering non-retriable errors from /acquirejob.
|
||||||
@@ -51,6 +59,7 @@ namespace GitHub.Runner.Listener
|
|||||||
base.Initialize(hostContext);
|
base.Initialize(hostContext);
|
||||||
_term = HostContext.GetService<ITerminal>();
|
_term = HostContext.GetService<ITerminal>();
|
||||||
_acquireJobThrottler = HostContext.CreateService<IErrorThrottler>();
|
_acquireJobThrottler = HostContext.CreateService<IErrorThrottler>();
|
||||||
|
_runnerServer = HostContext.GetService<IRunnerServer>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<int> ExecuteCommand(CommandSettings command)
|
public async Task<int> ExecuteCommand(CommandSettings command)
|
||||||
@@ -66,6 +75,8 @@ namespace GitHub.Runner.Listener
|
|||||||
//register a SIGTERM handler
|
//register a SIGTERM handler
|
||||||
HostContext.Unloading += Runner_Unloading;
|
HostContext.Unloading += Runner_Unloading;
|
||||||
|
|
||||||
|
HostContext.AuthMigrationChanged += HandleAuthMigrationChanged;
|
||||||
|
|
||||||
// TODO Unit test to cover this logic
|
// TODO Unit test to cover this logic
|
||||||
Trace.Info(nameof(ExecuteCommand));
|
Trace.Info(nameof(ExecuteCommand));
|
||||||
var configManager = HostContext.GetService<IConfigurationManager>();
|
var configManager = HostContext.GetService<IConfigurationManager>();
|
||||||
@@ -300,6 +311,21 @@ namespace GitHub.Runner.Listener
|
|||||||
_term.WriteLine("https://docs.github.com/en/actions/hosting-your-own-runners/autoscaling-with-self-hosted-runners#using-ephemeral-runners-for-autoscaling", ConsoleColor.Yellow);
|
_term.WriteLine("https://docs.github.com/en/actions/hosting-your-own-runners/autoscaling-with-self-hosted-runners#using-ephemeral-runners-for-autoscaling", ConsoleColor.Yellow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable(Constants.Variables.Agent.ActionsTerminationGracePeriodSeconds)))
|
||||||
|
{
|
||||||
|
_hasTerminationGracePeriod = true;
|
||||||
|
Trace.Verbose($"Runner has termination grace period set");
|
||||||
|
}
|
||||||
|
|
||||||
|
var cred = store.GetCredentials();
|
||||||
|
if (cred != null &&
|
||||||
|
cred.Scheme == Constants.Configuration.OAuth &&
|
||||||
|
cred.Data.ContainsKey("EnableAuthMigrationByDefault"))
|
||||||
|
{
|
||||||
|
Trace.Info("Enable auth migration by default.");
|
||||||
|
HostContext.EnableAuthMigration("EnableAuthMigrationByDefault");
|
||||||
|
}
|
||||||
|
|
||||||
// Run the runner interactively or as service
|
// Run the runner interactively or as service
|
||||||
return await RunAsync(settings, command.RunOnce || settings.Ephemeral);
|
return await RunAsync(settings, command.RunOnce || settings.Ephemeral);
|
||||||
}
|
}
|
||||||
@@ -311,6 +337,8 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
_authMigrationTelemetryTokenSource?.Cancel();
|
||||||
|
HostContext.AuthMigrationChanged -= HandleAuthMigrationChanged;
|
||||||
_term.CancelKeyPress -= CtrlCHandler;
|
_term.CancelKeyPress -= CtrlCHandler;
|
||||||
HostContext.Unloading -= Runner_Unloading;
|
HostContext.Unloading -= Runner_Unloading;
|
||||||
_completedCommand.Set();
|
_completedCommand.Set();
|
||||||
@@ -319,9 +347,10 @@ namespace GitHub.Runner.Listener
|
|||||||
|
|
||||||
private void Runner_Unloading(object sender, EventArgs e)
|
private void Runner_Unloading(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
|
_runnerExiting = true;
|
||||||
if ((!_inConfigStage) && (!HostContext.RunnerShutdownToken.IsCancellationRequested))
|
if ((!_inConfigStage) && (!HostContext.RunnerShutdownToken.IsCancellationRequested))
|
||||||
{
|
{
|
||||||
HostContext.ShutdownRunner(ShutdownReason.UserCancelled);
|
HostContext.ShutdownRunner(ShutdownReason.UserCancelled, GetShutdownDelay());
|
||||||
_completedCommand.WaitOne(Constants.Runner.ExitOnUnloadTimeout);
|
_completedCommand.WaitOne(Constants.Runner.ExitOnUnloadTimeout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -329,6 +358,7 @@ namespace GitHub.Runner.Listener
|
|||||||
private void CtrlCHandler(object sender, EventArgs e)
|
private void CtrlCHandler(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
_term.WriteLine("Exiting...");
|
_term.WriteLine("Exiting...");
|
||||||
|
_runnerExiting = true;
|
||||||
if (_inConfigStage)
|
if (_inConfigStage)
|
||||||
{
|
{
|
||||||
HostContext.Dispose();
|
HostContext.Dispose();
|
||||||
@@ -351,15 +381,27 @@ namespace GitHub.Runner.Listener
|
|||||||
reason = ShutdownReason.UserCancelled;
|
reason = ShutdownReason.UserCancelled;
|
||||||
}
|
}
|
||||||
|
|
||||||
HostContext.ShutdownRunner(reason);
|
HostContext.ShutdownRunner(reason, GetShutdownDelay());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
HostContext.ShutdownRunner(ShutdownReason.UserCancelled);
|
HostContext.ShutdownRunner(ShutdownReason.UserCancelled, GetShutdownDelay());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void HandleJobStatusEvent(object sender, JobStatusEventArgs e)
|
||||||
|
{
|
||||||
|
if (_hasTerminationGracePeriod &&
|
||||||
|
e != null &&
|
||||||
|
e.Status != TaskAgentStatus.Busy &&
|
||||||
|
_runnerExiting)
|
||||||
|
{
|
||||||
|
Trace.Info("Runner is no longer busy, shutting down.");
|
||||||
|
HostContext.ShutdownRunner(ShutdownReason.UserCancelled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private IMessageListener GetMessageListener(RunnerSettings settings)
|
private IMessageListener GetMessageListener(RunnerSettings settings)
|
||||||
{
|
{
|
||||||
if (settings.UseV2Flow)
|
if (settings.UseV2Flow)
|
||||||
@@ -410,9 +452,13 @@ namespace GitHub.Runner.Listener
|
|||||||
bool autoUpdateInProgress = false;
|
bool autoUpdateInProgress = false;
|
||||||
Task<bool> selfUpdateTask = null;
|
Task<bool> selfUpdateTask = null;
|
||||||
bool runOnceJobReceived = false;
|
bool runOnceJobReceived = false;
|
||||||
jobDispatcher = HostContext.CreateService<IJobDispatcher>();
|
jobDispatcher = HostContext.GetService<IJobDispatcher>();
|
||||||
|
|
||||||
jobDispatcher.JobStatus += _listener.OnJobStatus;
|
jobDispatcher.JobStatus += _listener.OnJobStatus;
|
||||||
|
if (_hasTerminationGracePeriod)
|
||||||
|
{
|
||||||
|
jobDispatcher.JobStatus += HandleJobStatusEvent;
|
||||||
|
}
|
||||||
|
|
||||||
while (!HostContext.RunnerShutdownToken.IsCancellationRequested)
|
while (!HostContext.RunnerShutdownToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
@@ -570,18 +616,18 @@ namespace GitHub.Runner.Listener
|
|||||||
|
|
||||||
// Create connection
|
// Create connection
|
||||||
var credMgr = HostContext.GetService<ICredentialManager>();
|
var credMgr = HostContext.GetService<ICredentialManager>();
|
||||||
var creds = credMgr.LoadCredentials();
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(messageRef.RunServiceUrl))
|
if (string.IsNullOrEmpty(messageRef.RunServiceUrl))
|
||||||
{
|
{
|
||||||
|
var creds = credMgr.LoadCredentials(allowAuthUrlV2: false);
|
||||||
var actionsRunServer = HostContext.CreateService<IActionsRunServer>();
|
var actionsRunServer = HostContext.CreateService<IActionsRunServer>();
|
||||||
await actionsRunServer.ConnectAsync(new Uri(settings.ServerUrl), creds);
|
await actionsRunServer.ConnectAsync(new Uri(settings.ServerUrl), creds);
|
||||||
jobRequestMessage = await actionsRunServer.GetJobMessageAsync(messageRef.RunnerRequestId, messageQueueLoopTokenSource.Token);
|
jobRequestMessage = await actionsRunServer.GetJobMessageAsync(messageRef.RunnerRequestId, messageQueueLoopTokenSource.Token);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
var credsV2 = credMgr.LoadCredentials(allowAuthUrlV2: true);
|
||||||
var runServer = HostContext.CreateService<IRunServer>();
|
var runServer = HostContext.CreateService<IRunServer>();
|
||||||
await runServer.ConnectAsync(new Uri(messageRef.RunServiceUrl), creds);
|
await runServer.ConnectAsync(new Uri(messageRef.RunServiceUrl), credsV2);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
jobRequestMessage = await runServer.GetJobMessageAsync(messageRef.RunnerRequestId, messageRef.BillingOwnerId, messageQueueLoopTokenSource.Token);
|
jobRequestMessage = await runServer.GetJobMessageAsync(messageRef.RunnerRequestId, messageRef.BillingOwnerId, messageQueueLoopTokenSource.Token);
|
||||||
@@ -599,6 +645,13 @@ namespace GitHub.Runner.Listener
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Trace.Error($"Caught exception from acquiring job message: {ex}");
|
Trace.Error($"Caught exception from acquiring job message: {ex}");
|
||||||
|
|
||||||
|
if (HostContext.AllowAuthMigration)
|
||||||
|
{
|
||||||
|
Trace.Info("Disable migration mode for 60 minutes.");
|
||||||
|
HostContext.DeferAuthMigration(TimeSpan.FromMinutes(60), $"Acquire job failed with exception: {ex}");
|
||||||
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -633,7 +686,7 @@ namespace GitHub.Runner.Listener
|
|||||||
else if (string.Equals(message.MessageType, TaskAgentMessageTypes.ForceTokenRefresh))
|
else if (string.Equals(message.MessageType, TaskAgentMessageTypes.ForceTokenRefresh))
|
||||||
{
|
{
|
||||||
Trace.Info("Received ForceTokenRefreshMessage");
|
Trace.Info("Received ForceTokenRefreshMessage");
|
||||||
await _listener.RefreshListenerTokenAsync(messageQueueLoopTokenSource.Token);
|
await _listener.RefreshListenerTokenAsync();
|
||||||
}
|
}
|
||||||
else if (string.Equals(message.MessageType, RunnerRefreshConfigMessage.MessageType))
|
else if (string.Equals(message.MessageType, RunnerRefreshConfigMessage.MessageType))
|
||||||
{
|
{
|
||||||
@@ -676,6 +729,10 @@ namespace GitHub.Runner.Listener
|
|||||||
{
|
{
|
||||||
if (jobDispatcher != null)
|
if (jobDispatcher != null)
|
||||||
{
|
{
|
||||||
|
if (_hasTerminationGracePeriod)
|
||||||
|
{
|
||||||
|
jobDispatcher.JobStatus -= HandleJobStatusEvent;
|
||||||
|
}
|
||||||
jobDispatcher.JobStatus -= _listener.OnJobStatus;
|
jobDispatcher.JobStatus -= _listener.OnJobStatus;
|
||||||
await jobDispatcher.ShutdownAsync();
|
await jobDispatcher.ShutdownAsync();
|
||||||
}
|
}
|
||||||
@@ -716,6 +773,101 @@ namespace GitHub.Runner.Listener
|
|||||||
return Constants.Runner.ReturnCode.Success;
|
return Constants.Runner.ReturnCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void HandleAuthMigrationChanged(object sender, AuthMigrationEventArgs e)
|
||||||
|
{
|
||||||
|
Trace.Verbose("Handle AuthMigrationChanged in Runner");
|
||||||
|
_authMigrationTelemetries.Enqueue($"{DateTime.UtcNow.ToString("O")}: {e.Trace}");
|
||||||
|
|
||||||
|
// only start the telemetry reporting task once auth migration is changed (enabled or disabled)
|
||||||
|
lock (_authMigrationTelemetryLock)
|
||||||
|
{
|
||||||
|
if (_authMigrationTelemetryTask == null)
|
||||||
|
{
|
||||||
|
_authMigrationTelemetryTask = ReportAuthMigrationTelemetryAsync(_authMigrationTelemetryTokenSource.Token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ReportAuthMigrationTelemetryAsync(CancellationToken token)
|
||||||
|
{
|
||||||
|
var configManager = HostContext.GetService<IConfigurationManager>();
|
||||||
|
var runnerSettings = configManager.LoadSettings();
|
||||||
|
|
||||||
|
while (!token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await HostContext.Delay(TimeSpan.FromSeconds(60), token);
|
||||||
|
}
|
||||||
|
catch (TaskCanceledException)
|
||||||
|
{
|
||||||
|
// Ignore cancellation
|
||||||
|
}
|
||||||
|
|
||||||
|
Trace.Verbose("Checking for auth migration telemetry to report");
|
||||||
|
while (_authMigrationTelemetries.TryDequeue(out var telemetry))
|
||||||
|
{
|
||||||
|
Trace.Verbose($"Reporting auth migration telemetry: {telemetry}");
|
||||||
|
if (runnerSettings != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)))
|
||||||
|
{
|
||||||
|
await _runnerServer.UpdateAgentUpdateStateAsync(runnerSettings.PoolId, runnerSettings.AgentId, "RefreshConfig", telemetry, tokenSource.Token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Trace.Error("Failed to report auth migration telemetry.");
|
||||||
|
Trace.Error(ex);
|
||||||
|
_authMigrationTelemetries.Enqueue(telemetry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await HostContext.Delay(TimeSpan.FromSeconds(10), token);
|
||||||
|
}
|
||||||
|
catch (TaskCanceledException)
|
||||||
|
{
|
||||||
|
// Ignore cancellation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private TimeSpan GetShutdownDelay()
|
||||||
|
{
|
||||||
|
TimeSpan delay = TimeSpan.Zero;
|
||||||
|
if (_hasTerminationGracePeriod)
|
||||||
|
{
|
||||||
|
var jobDispatcher = HostContext.GetService<IJobDispatcher>();
|
||||||
|
if (jobDispatcher.Busy)
|
||||||
|
{
|
||||||
|
Trace.Info("Runner is busy, checking for grace period.");
|
||||||
|
var delayEnv = Environment.GetEnvironmentVariable(Constants.Variables.Agent.ActionsTerminationGracePeriodSeconds);
|
||||||
|
if (!string.IsNullOrEmpty(delayEnv) &&
|
||||||
|
int.TryParse(delayEnv, out int delaySeconds) &&
|
||||||
|
delaySeconds > 0 &&
|
||||||
|
delaySeconds < 60 * 60) // 1 hour
|
||||||
|
{
|
||||||
|
Trace.Info($"Waiting for {delaySeconds} seconds before shutting down.");
|
||||||
|
delay = TimeSpan.FromSeconds(delaySeconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Trace.Verbose("Runner is not busy, no grace period.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return delay;
|
||||||
|
}
|
||||||
|
|
||||||
private void PrintUsage(CommandSettings command)
|
private void PrintUsage(CommandSettings command)
|
||||||
{
|
{
|
||||||
string separator;
|
string separator;
|
||||||
|
|||||||
@@ -197,11 +197,31 @@ namespace GitHub.Runner.Listener
|
|||||||
await ReportTelemetryAsync($"Credential clientId in refreshed config '{refreshedClientId ?? "Empty"}' does not match the current credential clientId '{clientId}'.");
|
await ReportTelemetryAsync($"Credential clientId in refreshed config '{refreshedClientId ?? "Empty"}' does not match the current credential clientId '{clientId}'.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// make sure the credential authorizationUrl in the refreshed config match the current credential authorizationUrl for OAuth auth scheme
|
||||||
|
var authorizationUrl = _credData.Data.GetValueOrDefault("authorizationUrl", null);
|
||||||
|
var refreshedAuthorizationUrl = refreshedCredConfig.Data.GetValueOrDefault("authorizationUrl", null);
|
||||||
|
if (authorizationUrl != refreshedAuthorizationUrl)
|
||||||
|
{
|
||||||
|
Trace.Error($"Credential authorizationUrl in refreshed config '{refreshedAuthorizationUrl ?? "Empty"}' does not match the current credential authorizationUrl '{authorizationUrl}'.");
|
||||||
|
await ReportTelemetryAsync($"Credential authorizationUrl in refreshed config '{refreshedAuthorizationUrl ?? "Empty"}' does not match the current credential authorizationUrl '{authorizationUrl}'.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// save the refreshed runner credentials as a separate file
|
// save the refreshed runner credentials as a separate file
|
||||||
_store.SaveMigratedCredential(refreshedCredConfig);
|
_store.SaveMigratedCredential(refreshedCredConfig);
|
||||||
await ReportTelemetryAsync("Runner credentials updated successfully.");
|
|
||||||
|
if (refreshedCredConfig.Data.ContainsKey("authorizationUrlV2"))
|
||||||
|
{
|
||||||
|
HostContext.EnableAuthMigration("Credential file updated");
|
||||||
|
await ReportTelemetryAsync("Runner credentials updated successfully. Auth migration is enabled.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
HostContext.DeferAuthMigration(TimeSpan.FromDays(365), "Credential file does not contain authorizationUrlV2");
|
||||||
|
await ReportTelemetryAsync("Runner credentials updated successfully. Auth migration is disabled.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> VerifyRunnerQualifiedId(string runnerQualifiedId)
|
private async Task<bool> VerifyRunnerQualifiedId(string runnerQualifiedId)
|
||||||
|
|||||||
@@ -317,25 +317,18 @@ namespace GitHub.Runner.Worker
|
|||||||
while (completeJobRetryLimit-- > 0)
|
while (completeJobRetryLimit-- > 0)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
|
||||||
if (jobContext.Global.Variables.GetBoolean(Constants.Runner.Features.SkipRetryCompleteJobUponKnownErrors) ?? false)
|
|
||||||
{
|
|
||||||
await runServer.CompleteJob2Async(message.Plan.PlanId, message.JobId, result, jobContext.JobOutputs, jobContext.Global.StepsResult, jobContext.Global.JobAnnotations, environmentUrl, telemetry, billingOwnerId: message.BillingOwnerId, default);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
await runServer.CompleteJobAsync(message.Plan.PlanId, message.JobId, result, jobContext.JobOutputs, jobContext.Global.StepsResult, jobContext.Global.JobAnnotations, environmentUrl, telemetry, billingOwnerId: message.BillingOwnerId, default);
|
await runServer.CompleteJobAsync(message.Plan.PlanId, message.JobId, result, jobContext.JobOutputs, jobContext.Global.StepsResult, jobContext.Global.JobAnnotations, environmentUrl, telemetry, billingOwnerId: message.BillingOwnerId, default);
|
||||||
}
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
catch (VssUnauthorizedException ex) when (jobContext.Global.Variables.GetBoolean(Constants.Runner.Features.SkipRetryCompleteJobUponKnownErrors) ?? false)
|
catch (VssUnauthorizedException ex)
|
||||||
{
|
{
|
||||||
Trace.Error($"Catch exception while attempting to complete job {message.JobId}, job request {message.RequestId}.");
|
Trace.Error($"Catch exception while attempting to complete job {message.JobId}, job request {message.RequestId}.");
|
||||||
Trace.Error(ex);
|
Trace.Error(ex);
|
||||||
exceptions.Add(ex);
|
exceptions.Add(ex);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
catch (TaskOrchestrationJobNotFoundException ex) when (jobContext.Global.Variables.GetBoolean(Constants.Runner.Features.SkipRetryCompleteJobUponKnownErrors) ?? false)
|
catch (TaskOrchestrationJobNotFoundException ex)
|
||||||
{
|
{
|
||||||
Trace.Error($"Catch exception while attempting to complete job {message.JobId}, job request {message.RequestId}.");
|
Trace.Error($"Catch exception while attempting to complete job {message.JobId}, job request {message.RequestId}.");
|
||||||
Trace.Error(ex);
|
Trace.Error(ex);
|
||||||
|
|||||||
@@ -253,11 +253,12 @@ namespace GitHub.Actions.RunService.WebApi
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string Truncate(string errorBody)
|
internal static string Truncate(string errorBody)
|
||||||
{
|
{
|
||||||
if (errorBody.Length > 100)
|
const int maxLength = 200;
|
||||||
|
if (errorBody.Length > maxLength)
|
||||||
{
|
{
|
||||||
return errorBody.Substring(0, 100) + "[truncated]";
|
return errorBody.Substring(0, maxLength) + "[truncated]";
|
||||||
}
|
}
|
||||||
|
|
||||||
return errorBody;
|
return errorBody;
|
||||||
|
|||||||
@@ -25,7 +25,10 @@ namespace GitHub.Services.WebApi.Jwt
|
|||||||
HS256,
|
HS256,
|
||||||
|
|
||||||
[EnumMember]
|
[EnumMember]
|
||||||
RS256
|
RS256,
|
||||||
|
|
||||||
|
[EnumMember]
|
||||||
|
PS256,
|
||||||
}
|
}
|
||||||
|
|
||||||
//JsonWebToken is marked as DataContract so
|
//JsonWebToken is marked as DataContract so
|
||||||
@@ -286,6 +289,7 @@ namespace GitHub.Services.WebApi.Jwt
|
|||||||
{
|
{
|
||||||
case JWTAlgorithm.HS256:
|
case JWTAlgorithm.HS256:
|
||||||
case JWTAlgorithm.RS256:
|
case JWTAlgorithm.RS256:
|
||||||
|
case JWTAlgorithm.PS256:
|
||||||
return signingCredentials.SignData(bytes);
|
return signingCredentials.SignData(bytes);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -166,6 +166,21 @@ namespace GitHub.Services.WebApi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override JWTAlgorithm SignatureAlgorithm
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (m_signaturePadding == RSASignaturePadding.Pss)
|
||||||
|
{
|
||||||
|
return JWTAlgorithm.PS256;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return base.SignatureAlgorithm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override Byte[] GetSignature(Byte[] input)
|
protected override Byte[] GetSignature(Byte[] input)
|
||||||
{
|
{
|
||||||
using (var rsa = m_factory())
|
using (var rsa = m_factory())
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
using GitHub.Runner.Common.Util;
|
using System;
|
||||||
using System;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace GitHub.Runner.Common.Tests
|
namespace GitHub.Runner.Common.Tests
|
||||||
@@ -172,6 +172,133 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Common")]
|
||||||
|
public void AuthMigrationDisabledByDefault()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Environment.SetEnvironmentVariable("_GITHUB_ACTION_AUTH_MIGRATION_REFRESH_INTERVAL", "100");
|
||||||
|
|
||||||
|
// Arrange.
|
||||||
|
Setup();
|
||||||
|
|
||||||
|
// Assert.
|
||||||
|
Assert.False(_hc.AllowAuthMigration);
|
||||||
|
|
||||||
|
// Change migration state is error free.
|
||||||
|
_hc.EnableAuthMigration("L0Test");
|
||||||
|
_hc.DeferAuthMigration(TimeSpan.FromHours(1), "L0Test");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Environment.SetEnvironmentVariable("_GITHUB_ACTION_AUTH_MIGRATION_REFRESH_INTERVAL", null);
|
||||||
|
// Cleanup.
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Common")]
|
||||||
|
public async Task AuthMigrationReenableTaskNotRunningByDefault()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Environment.SetEnvironmentVariable("_GITHUB_ACTION_AUTH_MIGRATION_REFRESH_INTERVAL", "50");
|
||||||
|
|
||||||
|
// Arrange.
|
||||||
|
Setup();
|
||||||
|
|
||||||
|
// Assert.
|
||||||
|
Assert.False(_hc.AllowAuthMigration);
|
||||||
|
await Task.Delay(TimeSpan.FromMilliseconds(200));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Environment.SetEnvironmentVariable("_GITHUB_ACTION_AUTH_MIGRATION_REFRESH_INTERVAL", null);
|
||||||
|
// Cleanup.
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
|
||||||
|
var logFile = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), $"trace_{nameof(HostContextL0)}_{nameof(AuthMigrationReenableTaskNotRunningByDefault)}.log");
|
||||||
|
var logContent = await File.ReadAllTextAsync(logFile);
|
||||||
|
Assert.Contains("HostContext", logContent);
|
||||||
|
Assert.DoesNotContain("Auth migration defer timer", logContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Common")]
|
||||||
|
public void AuthMigrationEnableDisable()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Arrange.
|
||||||
|
Setup();
|
||||||
|
|
||||||
|
var eventFiredCount = 0;
|
||||||
|
_hc.AuthMigrationChanged += (sender, e) =>
|
||||||
|
{
|
||||||
|
eventFiredCount++;
|
||||||
|
Assert.Equal("L0Test", e.Trace);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Assert.
|
||||||
|
_hc.EnableAuthMigration("L0Test");
|
||||||
|
Assert.True(_hc.AllowAuthMigration);
|
||||||
|
|
||||||
|
_hc.DeferAuthMigration(TimeSpan.FromHours(1), "L0Test");
|
||||||
|
Assert.False(_hc.AllowAuthMigration);
|
||||||
|
Assert.Equal(2, eventFiredCount);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// Cleanup.
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Common")]
|
||||||
|
public async Task AuthMigrationAutoReset()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Environment.SetEnvironmentVariable("_GITHUB_ACTION_AUTH_MIGRATION_REFRESH_INTERVAL", "100");
|
||||||
|
|
||||||
|
// Arrange.
|
||||||
|
Setup();
|
||||||
|
|
||||||
|
var eventFiredCount = 0;
|
||||||
|
_hc.AuthMigrationChanged += (sender, e) =>
|
||||||
|
{
|
||||||
|
eventFiredCount++;
|
||||||
|
Assert.NotEmpty(e.Trace);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Assert.
|
||||||
|
_hc.EnableAuthMigration("L0Test");
|
||||||
|
Assert.True(_hc.AllowAuthMigration);
|
||||||
|
|
||||||
|
_hc.DeferAuthMigration(TimeSpan.FromMilliseconds(500), "L0Test");
|
||||||
|
Assert.False(_hc.AllowAuthMigration);
|
||||||
|
|
||||||
|
await Task.Delay(TimeSpan.FromSeconds(1));
|
||||||
|
Assert.True(_hc.AllowAuthMigration);
|
||||||
|
Assert.Equal(3, eventFiredCount);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Environment.SetEnvironmentVariable("_GITHUB_ACTION_AUTH_MIGRATION_REFRESH_INTERVAL", null);
|
||||||
|
|
||||||
|
// Cleanup.
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void Setup([CallerMemberName] string testName = "")
|
private void Setup([CallerMemberName] string testName = "")
|
||||||
{
|
{
|
||||||
_tokenSource = new CancellationTokenSource();
|
_tokenSource = new CancellationTokenSource();
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.IO;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -18,8 +19,6 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
private readonly Mock<IBrokerServer> _brokerServer;
|
private readonly Mock<IBrokerServer> _brokerServer;
|
||||||
private readonly Mock<IRunnerServer> _runnerServer;
|
private readonly Mock<IRunnerServer> _runnerServer;
|
||||||
private readonly Mock<ICredentialManager> _credMgr;
|
private readonly Mock<ICredentialManager> _credMgr;
|
||||||
private Mock<IConfigurationStore> _store;
|
|
||||||
|
|
||||||
|
|
||||||
public BrokerMessageListenerL0()
|
public BrokerMessageListenerL0()
|
||||||
{
|
{
|
||||||
@@ -27,7 +26,6 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
_config = new Mock<IConfigurationManager>();
|
_config = new Mock<IConfigurationManager>();
|
||||||
_config.Setup(x => x.LoadSettings()).Returns(_settings);
|
_config.Setup(x => x.LoadSettings()).Returns(_settings);
|
||||||
_credMgr = new Mock<ICredentialManager>();
|
_credMgr = new Mock<ICredentialManager>();
|
||||||
_store = new Mock<IConfigurationStore>();
|
|
||||||
_brokerServer = new Mock<IBrokerServer>();
|
_brokerServer = new Mock<IBrokerServer>();
|
||||||
_runnerServer = new Mock<IRunnerServer>();
|
_runnerServer = new Mock<IRunnerServer>();
|
||||||
}
|
}
|
||||||
@@ -35,7 +33,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Runner")]
|
[Trait("Category", "Runner")]
|
||||||
public async void CreatesSession()
|
public async Task CreatesSession()
|
||||||
{
|
{
|
||||||
using (TestHostContext tc = CreateTestContext())
|
using (TestHostContext tc = CreateTestContext())
|
||||||
using (var tokenSource = new CancellationTokenSource())
|
using (var tokenSource = new CancellationTokenSource())
|
||||||
@@ -50,9 +48,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
tokenSource.Token))
|
tokenSource.Token))
|
||||||
.Returns(Task.FromResult(expectedSession));
|
.Returns(Task.FromResult(expectedSession));
|
||||||
|
|
||||||
_credMgr.Setup(x => x.LoadCredentials()).Returns(new VssCredentials());
|
_credMgr.Setup(x => x.LoadCredentials(true)).Returns(new VssCredentials());
|
||||||
_store.Setup(x => x.GetCredentials()).Returns(new CredentialData() { Scheme = Constants.Configuration.OAuthAccessToken });
|
|
||||||
_store.Setup(x => x.GetMigratedCredentials()).Returns(default(CredentialData));
|
|
||||||
|
|
||||||
// Act.
|
// Act.
|
||||||
BrokerMessageListener listener = new();
|
BrokerMessageListener listener = new();
|
||||||
@@ -70,12 +66,308 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task HandleAuthMigrationChanged()
|
||||||
|
{
|
||||||
|
using (TestHostContext tc = CreateTestContext())
|
||||||
|
using (var tokenSource = new CancellationTokenSource())
|
||||||
|
{
|
||||||
|
Tracing trace = tc.GetTrace();
|
||||||
|
|
||||||
|
// Arrange.
|
||||||
|
var expectedSession = new TaskAgentSession();
|
||||||
|
_brokerServer
|
||||||
|
.Setup(x => x.CreateSessionAsync(
|
||||||
|
It.Is<TaskAgentSession>(y => y != null),
|
||||||
|
tokenSource.Token))
|
||||||
|
.Returns(Task.FromResult(expectedSession));
|
||||||
|
|
||||||
|
_credMgr.Setup(x => x.LoadCredentials(true)).Returns(new VssCredentials());
|
||||||
|
|
||||||
|
// Act.
|
||||||
|
BrokerMessageListener listener = new();
|
||||||
|
listener.Initialize(tc);
|
||||||
|
|
||||||
|
CreateSessionResult result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||||
|
trace.Info("result: {0}", result);
|
||||||
|
|
||||||
|
// Assert.
|
||||||
|
Assert.Equal(CreateSessionResult.Success, result);
|
||||||
|
_brokerServer
|
||||||
|
.Verify(x => x.CreateSessionAsync(
|
||||||
|
It.Is<TaskAgentSession>(y => y != null),
|
||||||
|
tokenSource.Token), Times.Once());
|
||||||
|
|
||||||
|
tc.EnableAuthMigration("L0Test");
|
||||||
|
|
||||||
|
var traceFile = Path.GetTempFileName();
|
||||||
|
File.Copy(tc.TraceFileName, traceFile, true);
|
||||||
|
Assert.Contains("Auth migration changed", File.ReadAllText(traceFile));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task CreatesSession_DeferAuthMigration()
|
||||||
|
{
|
||||||
|
using (TestHostContext tc = CreateTestContext())
|
||||||
|
using (var tokenSource = new CancellationTokenSource())
|
||||||
|
{
|
||||||
|
Tracing trace = tc.GetTrace();
|
||||||
|
|
||||||
|
// Arrange.
|
||||||
|
var throwException = true;
|
||||||
|
var expectedSession = new TaskAgentSession();
|
||||||
|
_brokerServer
|
||||||
|
.Setup(x => x.CreateSessionAsync(
|
||||||
|
It.Is<TaskAgentSession>(y => y != null),
|
||||||
|
tokenSource.Token))
|
||||||
|
.Returns(async (TaskAgentSession session, CancellationToken token) =>
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
if (throwException)
|
||||||
|
{
|
||||||
|
throwException = false;
|
||||||
|
throw new NotSupportedException("Error during create session");
|
||||||
|
}
|
||||||
|
|
||||||
|
return expectedSession;
|
||||||
|
});
|
||||||
|
|
||||||
|
_credMgr.Setup(x => x.LoadCredentials(true)).Returns(new VssCredentials());
|
||||||
|
|
||||||
|
// Act.
|
||||||
|
BrokerMessageListener listener = new();
|
||||||
|
listener.Initialize(tc);
|
||||||
|
|
||||||
|
tc.EnableAuthMigration("L0Test");
|
||||||
|
Assert.True(tc.AllowAuthMigration);
|
||||||
|
|
||||||
|
CreateSessionResult result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||||
|
trace.Info("result: {0}", result);
|
||||||
|
|
||||||
|
// Assert.
|
||||||
|
Assert.Equal(CreateSessionResult.Success, result);
|
||||||
|
_brokerServer
|
||||||
|
.Verify(x => x.CreateSessionAsync(
|
||||||
|
It.Is<TaskAgentSession>(y => y != null),
|
||||||
|
tokenSource.Token), Times.Exactly(2));
|
||||||
|
_credMgr.Verify(x => x.LoadCredentials(true), Times.Exactly(2));
|
||||||
|
|
||||||
|
Assert.False(tc.AllowAuthMigration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task GetNextMessage()
|
||||||
|
{
|
||||||
|
using (TestHostContext tc = CreateTestContext())
|
||||||
|
using (var tokenSource = new CancellationTokenSource())
|
||||||
|
{
|
||||||
|
Tracing trace = tc.GetTrace();
|
||||||
|
|
||||||
|
// Arrange.
|
||||||
|
_credMgr.Setup(x => x.LoadCredentials(true)).Returns(new VssCredentials());
|
||||||
|
|
||||||
|
var expectedSession = new TaskAgentSession();
|
||||||
|
_brokerServer
|
||||||
|
.Setup(x => x.CreateSessionAsync(
|
||||||
|
It.Is<TaskAgentSession>(y => y != null),
|
||||||
|
tokenSource.Token))
|
||||||
|
.Returns(Task.FromResult(expectedSession));
|
||||||
|
|
||||||
|
var expectedMessage = new TaskAgentMessage();
|
||||||
|
_brokerServer
|
||||||
|
.Setup(x => x.GetRunnerMessageAsync(
|
||||||
|
It.IsAny<Guid?>(),
|
||||||
|
It.IsAny<TaskAgentStatus>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<bool>(),
|
||||||
|
It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(Task.FromResult(expectedMessage));
|
||||||
|
|
||||||
|
// Act.
|
||||||
|
BrokerMessageListener listener = new();
|
||||||
|
listener.Initialize(tc);
|
||||||
|
|
||||||
|
CreateSessionResult result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||||
|
trace.Info("result: {0}", result);
|
||||||
|
Assert.Equal(CreateSessionResult.Success, result);
|
||||||
|
|
||||||
|
TaskAgentMessage message = await listener.GetNextMessageAsync(tokenSource.Token);
|
||||||
|
trace.Info("message: {0}", message);
|
||||||
|
|
||||||
|
// Assert.
|
||||||
|
Assert.Equal(expectedMessage, message);
|
||||||
|
_brokerServer
|
||||||
|
.Verify(x => x.GetRunnerMessageAsync(
|
||||||
|
It.IsAny<Guid?>(),
|
||||||
|
It.IsAny<TaskAgentStatus>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<bool>(),
|
||||||
|
It.IsAny<CancellationToken>()), Times.Once());
|
||||||
|
|
||||||
|
_brokerServer.Verify(x => x.ConnectAsync(It.IsAny<Uri>(), It.IsAny<VssCredentials>()), Times.Once());
|
||||||
|
|
||||||
|
_credMgr.Verify(x => x.LoadCredentials(true), Times.Once());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task GetNextMessage_EnableAuthMigration()
|
||||||
|
{
|
||||||
|
using (TestHostContext tc = CreateTestContext())
|
||||||
|
using (var tokenSource = new CancellationTokenSource())
|
||||||
|
{
|
||||||
|
Tracing trace = tc.GetTrace();
|
||||||
|
|
||||||
|
// Arrange.
|
||||||
|
_credMgr.Setup(x => x.LoadCredentials(true)).Returns(new VssCredentials());
|
||||||
|
|
||||||
|
var expectedSession = new TaskAgentSession();
|
||||||
|
_brokerServer
|
||||||
|
.Setup(x => x.CreateSessionAsync(
|
||||||
|
It.Is<TaskAgentSession>(y => y != null),
|
||||||
|
tokenSource.Token))
|
||||||
|
.Returns(Task.FromResult(expectedSession));
|
||||||
|
|
||||||
|
var expectedMessage = new TaskAgentMessage();
|
||||||
|
_brokerServer
|
||||||
|
.Setup(x => x.GetRunnerMessageAsync(
|
||||||
|
It.IsAny<Guid?>(),
|
||||||
|
It.IsAny<TaskAgentStatus>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<bool>(),
|
||||||
|
It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(Task.FromResult(expectedMessage));
|
||||||
|
|
||||||
|
// Act.
|
||||||
|
BrokerMessageListener listener = new();
|
||||||
|
listener.Initialize(tc);
|
||||||
|
|
||||||
|
CreateSessionResult result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||||
|
trace.Info("result: {0}", result);
|
||||||
|
Assert.Equal(CreateSessionResult.Success, result);
|
||||||
|
|
||||||
|
tc.EnableAuthMigration("L0Test");
|
||||||
|
|
||||||
|
TaskAgentMessage message = await listener.GetNextMessageAsync(tokenSource.Token);
|
||||||
|
trace.Info("message: {0}", message);
|
||||||
|
|
||||||
|
// Assert.
|
||||||
|
Assert.Equal(expectedMessage, message);
|
||||||
|
_brokerServer
|
||||||
|
.Verify(x => x.GetRunnerMessageAsync(
|
||||||
|
It.IsAny<Guid?>(),
|
||||||
|
It.IsAny<TaskAgentStatus>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<bool>(),
|
||||||
|
It.IsAny<CancellationToken>()), Times.Once());
|
||||||
|
|
||||||
|
_brokerServer.Verify(x => x.ConnectAsync(It.IsAny<Uri>(), It.IsAny<VssCredentials>()), Times.Exactly(2));
|
||||||
|
|
||||||
|
_credMgr.Verify(x => x.LoadCredentials(true), Times.Exactly(2));
|
||||||
|
|
||||||
|
Assert.True(tc.AllowAuthMigration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task GetNextMessage_AuthMigrationFallback()
|
||||||
|
{
|
||||||
|
using (TestHostContext tc = CreateTestContext())
|
||||||
|
using (var tokenSource = new CancellationTokenSource())
|
||||||
|
{
|
||||||
|
Tracing trace = tc.GetTrace();
|
||||||
|
|
||||||
|
tc.EnableAuthMigration("L0Test");
|
||||||
|
|
||||||
|
// Arrange.
|
||||||
|
_credMgr.Setup(x => x.LoadCredentials(true)).Returns(new VssCredentials());
|
||||||
|
|
||||||
|
var expectedSession = new TaskAgentSession();
|
||||||
|
_brokerServer
|
||||||
|
.Setup(x => x.CreateSessionAsync(
|
||||||
|
It.Is<TaskAgentSession>(y => y != null),
|
||||||
|
tokenSource.Token))
|
||||||
|
.Returns(Task.FromResult(expectedSession));
|
||||||
|
|
||||||
|
var expectedMessage = new TaskAgentMessage();
|
||||||
|
_brokerServer
|
||||||
|
.Setup(x => x.GetRunnerMessageAsync(
|
||||||
|
It.IsAny<Guid?>(),
|
||||||
|
It.IsAny<TaskAgentStatus>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<bool>(),
|
||||||
|
It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(async (Guid? sessionId, TaskAgentStatus status, string version, string os, string architecture, bool disableUpdate, CancellationToken token) =>
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
if (tc.AllowAuthMigration)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("Error during get message");
|
||||||
|
}
|
||||||
|
|
||||||
|
return expectedMessage;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act.
|
||||||
|
BrokerMessageListener listener = new();
|
||||||
|
listener.Initialize(tc);
|
||||||
|
|
||||||
|
CreateSessionResult result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||||
|
trace.Info("result: {0}", result);
|
||||||
|
Assert.Equal(CreateSessionResult.Success, result);
|
||||||
|
|
||||||
|
Assert.True(tc.AllowAuthMigration);
|
||||||
|
|
||||||
|
TaskAgentMessage message = await listener.GetNextMessageAsync(tokenSource.Token);
|
||||||
|
trace.Info("message: {0}", message);
|
||||||
|
|
||||||
|
// Assert.
|
||||||
|
Assert.Equal(expectedMessage, message);
|
||||||
|
_brokerServer
|
||||||
|
.Verify(x => x.GetRunnerMessageAsync(
|
||||||
|
It.IsAny<Guid?>(),
|
||||||
|
It.IsAny<TaskAgentStatus>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<bool>(),
|
||||||
|
It.IsAny<CancellationToken>()), Times.Exactly(2));
|
||||||
|
|
||||||
|
_brokerServer.Verify(x => x.ConnectAsync(It.IsAny<Uri>(), It.IsAny<VssCredentials>()), Times.Exactly(3));
|
||||||
|
|
||||||
|
_credMgr.Verify(x => x.LoadCredentials(true), Times.Exactly(3));
|
||||||
|
|
||||||
|
Assert.False(tc.AllowAuthMigration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private TestHostContext CreateTestContext([CallerMemberName] String testName = "")
|
private TestHostContext CreateTestContext([CallerMemberName] String testName = "")
|
||||||
{
|
{
|
||||||
TestHostContext tc = new(this, testName);
|
TestHostContext tc = new(this, testName);
|
||||||
tc.SetSingleton<IConfigurationManager>(_config.Object);
|
tc.SetSingleton<IConfigurationManager>(_config.Object);
|
||||||
tc.SetSingleton<ICredentialManager>(_credMgr.Object);
|
tc.SetSingleton<ICredentialManager>(_credMgr.Object);
|
||||||
tc.SetSingleton<IConfigurationStore>(_store.Object);
|
|
||||||
tc.SetSingleton<IBrokerServer>(_brokerServer.Object);
|
tc.SetSingleton<IBrokerServer>(_brokerServer.Object);
|
||||||
tc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
tc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
return tc;
|
return tc;
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
using GitHub.Runner.Listener;
|
using System.Collections.Generic;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using GitHub.Runner.Listener;
|
||||||
using GitHub.Runner.Listener.Configuration;
|
using GitHub.Runner.Listener.Configuration;
|
||||||
using GitHub.Services.Common;
|
using GitHub.Services.Common;
|
||||||
using GitHub.Services.OAuth;
|
using GitHub.Services.OAuth;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
namespace GitHub.Runner.Common.Tests.Listener.Configuration
|
namespace GitHub.Runner.Common.Tests.Listener.Configuration
|
||||||
{
|
{
|
||||||
public class TestRunnerCredential : CredentialProvider
|
public class TestRunnerCredential : CredentialProvider
|
||||||
{
|
{
|
||||||
public TestRunnerCredential() : base("TEST") { }
|
public TestRunnerCredential() : base("TEST") { }
|
||||||
public override VssCredentials GetVssCredentials(IHostContext context)
|
public override VssCredentials GetVssCredentials(IHostContext context, bool allowAuthUrlV2)
|
||||||
{
|
{
|
||||||
Tracing trace = context.GetTrace("OuthAccessToken");
|
Tracing trace = context.GetTrace("OuthAccessToken");
|
||||||
trace.Info("GetVssCredentials()");
|
trace.Info("GetVssCredentials()");
|
||||||
@@ -23,4 +27,85 @@ namespace GitHub.Runner.Common.Tests.Listener.Configuration
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class OAuthCredentialTestsL0
|
||||||
|
{
|
||||||
|
private Mock<IRSAKeyManager> _rsaKeyManager = new Mock<IRSAKeyManager>();
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "OAuthCredential")]
|
||||||
|
public void NotUseAuthV2Url()
|
||||||
|
{
|
||||||
|
using (TestHostContext hc = new(this))
|
||||||
|
{
|
||||||
|
// Arrange.
|
||||||
|
var oauth = new OAuthCredential();
|
||||||
|
oauth.CredentialData = new CredentialData()
|
||||||
|
{
|
||||||
|
Scheme = Constants.Configuration.OAuth
|
||||||
|
};
|
||||||
|
oauth.CredentialData.Data.Add("clientId", "someClientId");
|
||||||
|
oauth.CredentialData.Data.Add("authorizationUrl", "http://myserver/");
|
||||||
|
oauth.CredentialData.Data.Add("authorizationUrlV2", "http://myserverv2/");
|
||||||
|
|
||||||
|
_rsaKeyManager.Setup(x => x.GetKey()).Returns(RSA.Create(2048));
|
||||||
|
hc.SetSingleton<IRSAKeyManager>(_rsaKeyManager.Object);
|
||||||
|
|
||||||
|
// Act.
|
||||||
|
var cred = oauth.GetVssCredentials(hc, false); // not allow auth v2
|
||||||
|
|
||||||
|
var cred2 = oauth.GetVssCredentials(hc, true); // use auth v2 but hostcontext doesn't
|
||||||
|
|
||||||
|
hc.EnableAuthMigration("L0Test");
|
||||||
|
var cred3 = oauth.GetVssCredentials(hc, false); // not use auth v2 but hostcontext does
|
||||||
|
|
||||||
|
oauth.CredentialData.Data.Remove("authorizationUrlV2");
|
||||||
|
var cred4 = oauth.GetVssCredentials(hc, true); // v2 url is not there
|
||||||
|
|
||||||
|
// Assert.
|
||||||
|
Assert.Equal("http://myserver/", (cred.Federated as VssOAuthCredential).AuthorizationUrl.AbsoluteUri);
|
||||||
|
Assert.Equal("someClientId", (cred.Federated as VssOAuthCredential).ClientCredential.ClientId);
|
||||||
|
|
||||||
|
Assert.Equal("http://myserver/", (cred2.Federated as VssOAuthCredential).AuthorizationUrl.AbsoluteUri);
|
||||||
|
Assert.Equal("someClientId", (cred2.Federated as VssOAuthCredential).ClientCredential.ClientId);
|
||||||
|
|
||||||
|
Assert.Equal("http://myserver/", (cred3.Federated as VssOAuthCredential).AuthorizationUrl.AbsoluteUri);
|
||||||
|
Assert.Equal("someClientId", (cred3.Federated as VssOAuthCredential).ClientCredential.ClientId);
|
||||||
|
|
||||||
|
Assert.Equal("http://myserver/", (cred4.Federated as VssOAuthCredential).AuthorizationUrl.AbsoluteUri);
|
||||||
|
Assert.Equal("someClientId", (cred4.Federated as VssOAuthCredential).ClientCredential.ClientId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "OAuthCredential")]
|
||||||
|
public void UseAuthV2Url()
|
||||||
|
{
|
||||||
|
using (TestHostContext hc = new(this))
|
||||||
|
{
|
||||||
|
// Arrange.
|
||||||
|
var oauth = new OAuthCredential();
|
||||||
|
oauth.CredentialData = new CredentialData()
|
||||||
|
{
|
||||||
|
Scheme = Constants.Configuration.OAuth
|
||||||
|
};
|
||||||
|
oauth.CredentialData.Data.Add("clientId", "someClientId");
|
||||||
|
oauth.CredentialData.Data.Add("authorizationUrl", "http://myserver/");
|
||||||
|
oauth.CredentialData.Data.Add("authorizationUrlV2", "http://myserverv2/");
|
||||||
|
|
||||||
|
_rsaKeyManager.Setup(x => x.GetKey()).Returns(RSA.Create(2048));
|
||||||
|
hc.SetSingleton<IRSAKeyManager>(_rsaKeyManager.Object);
|
||||||
|
|
||||||
|
// Act.
|
||||||
|
hc.EnableAuthMigration("L0Test");
|
||||||
|
var cred = oauth.GetVssCredentials(hc, true);
|
||||||
|
|
||||||
|
// Assert.
|
||||||
|
Assert.Equal("http://myserverv2/", (cred.Federated as VssOAuthCredential).AuthorizationUrl.AbsoluteUri);
|
||||||
|
Assert.Equal("someClientId", (cred.Federated as VssOAuthCredential).ClientCredential.ClientId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -51,7 +51,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Runner")]
|
[Trait("Category", "Runner")]
|
||||||
public async void CreatesSession()
|
public async Task CreatesSession()
|
||||||
{
|
{
|
||||||
using (TestHostContext tc = CreateTestContext())
|
using (TestHostContext tc = CreateTestContext())
|
||||||
using (var tokenSource = new CancellationTokenSource())
|
using (var tokenSource = new CancellationTokenSource())
|
||||||
@@ -67,7 +67,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
tokenSource.Token))
|
tokenSource.Token))
|
||||||
.Returns(Task.FromResult(expectedSession));
|
.Returns(Task.FromResult(expectedSession));
|
||||||
|
|
||||||
_credMgr.Setup(x => x.LoadCredentials()).Returns(new VssCredentials());
|
_credMgr.Setup(x => x.LoadCredentials(It.IsAny<bool>())).Returns(new VssCredentials());
|
||||||
_store.Setup(x => x.GetCredentials()).Returns(new CredentialData() { Scheme = Constants.Configuration.OAuthAccessToken });
|
_store.Setup(x => x.GetCredentials()).Returns(new CredentialData() { Scheme = Constants.Configuration.OAuthAccessToken });
|
||||||
_store.Setup(x => x.GetMigratedCredentials()).Returns(default(CredentialData));
|
_store.Setup(x => x.GetMigratedCredentials()).Returns(default(CredentialData));
|
||||||
|
|
||||||
@@ -95,69 +95,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Runner")]
|
[Trait("Category", "Runner")]
|
||||||
public async void CreatesSessionWithBrokerMigration()
|
public async Task DeleteSession()
|
||||||
{
|
|
||||||
using (TestHostContext tc = CreateTestContext())
|
|
||||||
using (var tokenSource = new CancellationTokenSource())
|
|
||||||
{
|
|
||||||
Tracing trace = tc.GetTrace();
|
|
||||||
|
|
||||||
// Arrange.
|
|
||||||
var expectedSession = new TaskAgentSession()
|
|
||||||
{
|
|
||||||
OwnerName = "legacy",
|
|
||||||
BrokerMigrationMessage = new BrokerMigrationMessage(new Uri("https://broker.actions.github.com"))
|
|
||||||
};
|
|
||||||
|
|
||||||
var expectedBrokerSession = new TaskAgentSession()
|
|
||||||
{
|
|
||||||
OwnerName = "broker"
|
|
||||||
};
|
|
||||||
|
|
||||||
_runnerServer
|
|
||||||
.Setup(x => x.CreateAgentSessionAsync(
|
|
||||||
_settings.PoolId,
|
|
||||||
It.Is<TaskAgentSession>(y => y != null),
|
|
||||||
tokenSource.Token))
|
|
||||||
.Returns(Task.FromResult(expectedSession));
|
|
||||||
|
|
||||||
_brokerServer
|
|
||||||
.Setup(x => x.CreateSessionAsync(
|
|
||||||
It.Is<TaskAgentSession>(y => y != null),
|
|
||||||
tokenSource.Token))
|
|
||||||
.Returns(Task.FromResult(expectedBrokerSession));
|
|
||||||
|
|
||||||
_credMgr.Setup(x => x.LoadCredentials()).Returns(new VssCredentials());
|
|
||||||
_store.Setup(x => x.GetCredentials()).Returns(new CredentialData() { Scheme = Constants.Configuration.OAuthAccessToken });
|
|
||||||
_store.Setup(x => x.GetMigratedCredentials()).Returns(default(CredentialData));
|
|
||||||
|
|
||||||
// Act.
|
|
||||||
MessageListener listener = new();
|
|
||||||
listener.Initialize(tc);
|
|
||||||
|
|
||||||
CreateSessionResult result = await listener.CreateSessionAsync(tokenSource.Token);
|
|
||||||
trace.Info("result: {0}", result);
|
|
||||||
|
|
||||||
// Assert.
|
|
||||||
Assert.Equal(CreateSessionResult.Success, result);
|
|
||||||
|
|
||||||
_runnerServer
|
|
||||||
.Verify(x => x.CreateAgentSessionAsync(
|
|
||||||
_settings.PoolId,
|
|
||||||
It.Is<TaskAgentSession>(y => y != null),
|
|
||||||
tokenSource.Token), Times.Once());
|
|
||||||
|
|
||||||
_brokerServer
|
|
||||||
.Verify(x => x.CreateSessionAsync(
|
|
||||||
It.Is<TaskAgentSession>(y => y != null),
|
|
||||||
tokenSource.Token), Times.Once());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "Runner")]
|
|
||||||
public async void DeleteSession()
|
|
||||||
{
|
{
|
||||||
using (TestHostContext tc = CreateTestContext())
|
using (TestHostContext tc = CreateTestContext())
|
||||||
using (var tokenSource = new CancellationTokenSource())
|
using (var tokenSource = new CancellationTokenSource())
|
||||||
@@ -177,7 +115,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
tokenSource.Token))
|
tokenSource.Token))
|
||||||
.Returns(Task.FromResult(expectedSession));
|
.Returns(Task.FromResult(expectedSession));
|
||||||
|
|
||||||
_credMgr.Setup(x => x.LoadCredentials()).Returns(new VssCredentials());
|
_credMgr.Setup(x => x.LoadCredentials(It.IsAny<bool>())).Returns(new VssCredentials());
|
||||||
_store.Setup(x => x.GetCredentials()).Returns(new CredentialData() { Scheme = Constants.Configuration.OAuthAccessToken });
|
_store.Setup(x => x.GetCredentials()).Returns(new CredentialData() { Scheme = Constants.Configuration.OAuthAccessToken });
|
||||||
_store.Setup(x => x.GetMigratedCredentials()).Returns(default(CredentialData));
|
_store.Setup(x => x.GetMigratedCredentials()).Returns(default(CredentialData));
|
||||||
|
|
||||||
@@ -204,84 +142,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Runner")]
|
[Trait("Category", "Runner")]
|
||||||
public async void DeleteSessionWithBrokerMigration()
|
public async Task GetNextMessage()
|
||||||
{
|
|
||||||
using (TestHostContext tc = CreateTestContext())
|
|
||||||
using (var tokenSource = new CancellationTokenSource())
|
|
||||||
{
|
|
||||||
Tracing trace = tc.GetTrace();
|
|
||||||
|
|
||||||
// Arrange.
|
|
||||||
var expectedSession = new TaskAgentSession()
|
|
||||||
{
|
|
||||||
OwnerName = "legacy",
|
|
||||||
BrokerMigrationMessage = new BrokerMigrationMessage(new Uri("https://broker.actions.github.com"))
|
|
||||||
};
|
|
||||||
|
|
||||||
var expectedBrokerSession = new TaskAgentSession()
|
|
||||||
{
|
|
||||||
SessionId = Guid.NewGuid(),
|
|
||||||
OwnerName = "broker"
|
|
||||||
};
|
|
||||||
|
|
||||||
_runnerServer
|
|
||||||
.Setup(x => x.CreateAgentSessionAsync(
|
|
||||||
_settings.PoolId,
|
|
||||||
It.Is<TaskAgentSession>(y => y != null),
|
|
||||||
tokenSource.Token))
|
|
||||||
.Returns(Task.FromResult(expectedSession));
|
|
||||||
|
|
||||||
_brokerServer
|
|
||||||
.Setup(x => x.CreateSessionAsync(
|
|
||||||
It.Is<TaskAgentSession>(y => y != null),
|
|
||||||
tokenSource.Token))
|
|
||||||
.Returns(Task.FromResult(expectedBrokerSession));
|
|
||||||
|
|
||||||
_credMgr.Setup(x => x.LoadCredentials()).Returns(new VssCredentials());
|
|
||||||
_store.Setup(x => x.GetCredentials()).Returns(new CredentialData() { Scheme = Constants.Configuration.OAuthAccessToken });
|
|
||||||
_store.Setup(x => x.GetMigratedCredentials()).Returns(default(CredentialData));
|
|
||||||
|
|
||||||
// Act.
|
|
||||||
MessageListener listener = new();
|
|
||||||
listener.Initialize(tc);
|
|
||||||
|
|
||||||
CreateSessionResult result = await listener.CreateSessionAsync(tokenSource.Token);
|
|
||||||
trace.Info("result: {0}", result);
|
|
||||||
|
|
||||||
Assert.Equal(CreateSessionResult.Success, result);
|
|
||||||
|
|
||||||
_runnerServer
|
|
||||||
.Verify(x => x.CreateAgentSessionAsync(
|
|
||||||
_settings.PoolId,
|
|
||||||
It.Is<TaskAgentSession>(y => y != null),
|
|
||||||
tokenSource.Token), Times.Once());
|
|
||||||
|
|
||||||
_brokerServer
|
|
||||||
.Verify(x => x.CreateSessionAsync(
|
|
||||||
It.Is<TaskAgentSession>(y => y != null),
|
|
||||||
tokenSource.Token), Times.Once());
|
|
||||||
|
|
||||||
_brokerServer
|
|
||||||
.Setup(x => x.DeleteSessionAsync(It.IsAny<CancellationToken>()))
|
|
||||||
.Returns(Task.CompletedTask);
|
|
||||||
|
|
||||||
// Act.
|
|
||||||
await listener.DeleteSessionAsync();
|
|
||||||
|
|
||||||
|
|
||||||
//Assert
|
|
||||||
_runnerServer
|
|
||||||
.Verify(x => x.DeleteAgentSessionAsync(
|
|
||||||
_settings.PoolId, expectedBrokerSession.SessionId, It.IsAny<CancellationToken>()), Times.Once());
|
|
||||||
_brokerServer
|
|
||||||
.Verify(x => x.DeleteSessionAsync(It.IsAny<CancellationToken>()), Times.Once());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "Runner")]
|
|
||||||
public async void GetNextMessage()
|
|
||||||
{
|
{
|
||||||
using (TestHostContext tc = CreateTestContext())
|
using (TestHostContext tc = CreateTestContext())
|
||||||
using (var tokenSource = new CancellationTokenSource())
|
using (var tokenSource = new CancellationTokenSource())
|
||||||
@@ -301,7 +162,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
tokenSource.Token))
|
tokenSource.Token))
|
||||||
.Returns(Task.FromResult(expectedSession));
|
.Returns(Task.FromResult(expectedSession));
|
||||||
|
|
||||||
_credMgr.Setup(x => x.LoadCredentials()).Returns(new VssCredentials());
|
_credMgr.Setup(x => x.LoadCredentials(It.IsAny<bool>())).Returns(new VssCredentials());
|
||||||
_store.Setup(x => x.GetCredentials()).Returns(new CredentialData() { Scheme = Constants.Configuration.OAuthAccessToken });
|
_store.Setup(x => x.GetCredentials()).Returns(new CredentialData() { Scheme = Constants.Configuration.OAuthAccessToken });
|
||||||
_store.Setup(x => x.GetMigratedCredentials()).Returns(default(CredentialData));
|
_store.Setup(x => x.GetMigratedCredentials()).Returns(default(CredentialData));
|
||||||
|
|
||||||
@@ -362,7 +223,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Runner")]
|
[Trait("Category", "Runner")]
|
||||||
public async void GetNextMessageWithBrokerMigration()
|
public async Task GetNextMessageWithBrokerMigration()
|
||||||
{
|
{
|
||||||
using (TestHostContext tc = CreateTestContext())
|
using (TestHostContext tc = CreateTestContext())
|
||||||
using (var tokenSource = new CancellationTokenSource())
|
using (var tokenSource = new CancellationTokenSource())
|
||||||
@@ -382,7 +243,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
tokenSource.Token))
|
tokenSource.Token))
|
||||||
.Returns(Task.FromResult(expectedSession));
|
.Returns(Task.FromResult(expectedSession));
|
||||||
|
|
||||||
_credMgr.Setup(x => x.LoadCredentials()).Returns(new VssCredentials());
|
_credMgr.Setup(x => x.LoadCredentials(It.IsAny<bool>())).Returns(new VssCredentials());
|
||||||
_store.Setup(x => x.GetCredentials()).Returns(new CredentialData() { Scheme = Constants.Configuration.OAuthAccessToken });
|
_store.Setup(x => x.GetCredentials()).Returns(new CredentialData() { Scheme = Constants.Configuration.OAuthAccessToken });
|
||||||
_store.Setup(x => x.GetMigratedCredentials()).Returns(default(CredentialData));
|
_store.Setup(x => x.GetMigratedCredentials()).Returns(default(CredentialData));
|
||||||
|
|
||||||
@@ -462,13 +323,22 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
_brokerServer
|
_brokerServer
|
||||||
.Verify(x => x.GetRunnerMessageAsync(
|
.Verify(x => x.GetRunnerMessageAsync(
|
||||||
expectedSession.SessionId, TaskAgentStatus.Online, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()), Times.Exactly(brokerMessages.Length));
|
expectedSession.SessionId, TaskAgentStatus.Online, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()), Times.Exactly(brokerMessages.Length));
|
||||||
|
|
||||||
|
_credMgr
|
||||||
|
.Verify(x => x.LoadCredentials(true), Times.Exactly(brokerMessages.Length));
|
||||||
|
|
||||||
|
_brokerServer
|
||||||
|
.Verify(x => x.UpdateConnectionIfNeeded(brokerMigrationMesage.BrokerBaseUrl, It.IsAny<VssCredentials>()), Times.Exactly(brokerMessages.Length));
|
||||||
|
|
||||||
|
_brokerServer
|
||||||
|
.Verify(x => x.ForceRefreshConnection(It.IsAny<VssCredentials>()), Times.Never);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Runner")]
|
[Trait("Category", "Runner")]
|
||||||
public async void CreateSessionWithOriginalCredential()
|
public async Task CreateSessionWithOriginalCredential()
|
||||||
{
|
{
|
||||||
using (TestHostContext tc = CreateTestContext())
|
using (TestHostContext tc = CreateTestContext())
|
||||||
using (var tokenSource = new CancellationTokenSource())
|
using (var tokenSource = new CancellationTokenSource())
|
||||||
@@ -484,7 +354,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
tokenSource.Token))
|
tokenSource.Token))
|
||||||
.Returns(Task.FromResult(expectedSession));
|
.Returns(Task.FromResult(expectedSession));
|
||||||
|
|
||||||
_credMgr.Setup(x => x.LoadCredentials()).Returns(new VssCredentials());
|
_credMgr.Setup(x => x.LoadCredentials(It.IsAny<bool>())).Returns(new VssCredentials());
|
||||||
|
|
||||||
var originalCred = new CredentialData() { Scheme = Constants.Configuration.OAuth };
|
var originalCred = new CredentialData() { Scheme = Constants.Configuration.OAuth };
|
||||||
originalCred.Data["authorizationUrl"] = "https://s.server";
|
originalCred.Data["authorizationUrl"] = "https://s.server";
|
||||||
@@ -513,7 +383,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Runner")]
|
[Trait("Category", "Runner")]
|
||||||
public async void SkipDeleteSession_WhenGetNextMessageGetTaskAgentAccessTokenExpiredException()
|
public async Task SkipDeleteSession_WhenGetNextMessageGetTaskAgentAccessTokenExpiredException()
|
||||||
{
|
{
|
||||||
using (TestHostContext tc = CreateTestContext())
|
using (TestHostContext tc = CreateTestContext())
|
||||||
using (var tokenSource = new CancellationTokenSource())
|
using (var tokenSource = new CancellationTokenSource())
|
||||||
@@ -533,7 +403,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
tokenSource.Token))
|
tokenSource.Token))
|
||||||
.Returns(Task.FromResult(expectedSession));
|
.Returns(Task.FromResult(expectedSession));
|
||||||
|
|
||||||
_credMgr.Setup(x => x.LoadCredentials()).Returns(new VssCredentials());
|
_credMgr.Setup(x => x.LoadCredentials(It.IsAny<bool>())).Returns(new VssCredentials());
|
||||||
_store.Setup(x => x.GetCredentials()).Returns(new CredentialData() { Scheme = Constants.Configuration.OAuthAccessToken });
|
_store.Setup(x => x.GetCredentials()).Returns(new CredentialData() { Scheme = Constants.Configuration.OAuthAccessToken });
|
||||||
_store.Setup(x => x.GetMigratedCredentials()).Returns(default(CredentialData));
|
_store.Setup(x => x.GetMigratedCredentials()).Returns(default(CredentialData));
|
||||||
|
|
||||||
@@ -571,5 +441,301 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
_settings.PoolId, expectedSession.SessionId, It.IsAny<CancellationToken>()), Times.Never);
|
_settings.PoolId, expectedSession.SessionId, It.IsAny<CancellationToken>()), Times.Never);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task HandleAuthMigrationChanged()
|
||||||
|
{
|
||||||
|
using (TestHostContext tc = CreateTestContext())
|
||||||
|
using (var tokenSource = new CancellationTokenSource())
|
||||||
|
{
|
||||||
|
Tracing trace = tc.GetTrace();
|
||||||
|
|
||||||
|
// Arrange.
|
||||||
|
var expectedSession = new TaskAgentSession();
|
||||||
|
_runnerServer
|
||||||
|
.Setup(x => x.CreateAgentSessionAsync(
|
||||||
|
_settings.PoolId,
|
||||||
|
It.Is<TaskAgentSession>(y => y != null),
|
||||||
|
tokenSource.Token))
|
||||||
|
.Returns(Task.FromResult(expectedSession));
|
||||||
|
|
||||||
|
_credMgr.Setup(x => x.LoadCredentials(It.IsAny<bool>())).Returns(new VssCredentials());
|
||||||
|
|
||||||
|
// Act.
|
||||||
|
MessageListener listener = new();
|
||||||
|
listener.Initialize(tc);
|
||||||
|
|
||||||
|
CreateSessionResult result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||||
|
trace.Info("result: {0}", result);
|
||||||
|
|
||||||
|
// Assert.
|
||||||
|
Assert.Equal(CreateSessionResult.Success, result);
|
||||||
|
_runnerServer
|
||||||
|
.Verify(x => x.CreateAgentSessionAsync(
|
||||||
|
_settings.PoolId,
|
||||||
|
It.Is<TaskAgentSession>(y => y != null),
|
||||||
|
tokenSource.Token), Times.Once());
|
||||||
|
_brokerServer
|
||||||
|
.Verify(x => x.CreateSessionAsync(
|
||||||
|
It.Is<TaskAgentSession>(y => y != null),
|
||||||
|
tokenSource.Token), Times.Never());
|
||||||
|
|
||||||
|
tc.EnableAuthMigration("L0Test");
|
||||||
|
|
||||||
|
var traceFile = Path.GetTempFileName();
|
||||||
|
File.Copy(tc.TraceFileName, traceFile, true);
|
||||||
|
Assert.Contains("Auth migration changed", File.ReadAllText(traceFile));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task GetNextMessageWithBrokerMigration_AuthMigrationFallback()
|
||||||
|
{
|
||||||
|
using (TestHostContext tc = CreateTestContext())
|
||||||
|
using (var tokenSource = new CancellationTokenSource())
|
||||||
|
{
|
||||||
|
Tracing trace = tc.GetTrace();
|
||||||
|
|
||||||
|
// Arrange.
|
||||||
|
var expectedSession = new TaskAgentSession();
|
||||||
|
PropertyInfo sessionIdProperty = expectedSession.GetType().GetProperty("SessionId", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
||||||
|
Assert.NotNull(sessionIdProperty);
|
||||||
|
sessionIdProperty.SetValue(expectedSession, Guid.NewGuid());
|
||||||
|
|
||||||
|
_runnerServer
|
||||||
|
.Setup(x => x.CreateAgentSessionAsync(
|
||||||
|
_settings.PoolId,
|
||||||
|
It.Is<TaskAgentSession>(y => y != null),
|
||||||
|
tokenSource.Token))
|
||||||
|
.Returns(Task.FromResult(expectedSession));
|
||||||
|
|
||||||
|
_credMgr.Setup(x => x.LoadCredentials(It.IsAny<bool>())).Returns(new VssCredentials());
|
||||||
|
_store.Setup(x => x.GetCredentials()).Returns(new CredentialData() { Scheme = Constants.Configuration.OAuthAccessToken });
|
||||||
|
_store.Setup(x => x.GetMigratedCredentials()).Returns(default(CredentialData));
|
||||||
|
|
||||||
|
// Act.
|
||||||
|
MessageListener listener = new();
|
||||||
|
listener.Initialize(tc);
|
||||||
|
|
||||||
|
tc.EnableAuthMigration("L0Test");
|
||||||
|
|
||||||
|
CreateSessionResult result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||||
|
Assert.Equal(CreateSessionResult.Success, result);
|
||||||
|
|
||||||
|
var brokerMigrationMesage = new BrokerMigrationMessage(new Uri("https://actions.broker.com"));
|
||||||
|
|
||||||
|
var arMessages = new TaskAgentMessage[]
|
||||||
|
{
|
||||||
|
new TaskAgentMessage
|
||||||
|
{
|
||||||
|
Body = JsonUtility.ToString(brokerMigrationMesage),
|
||||||
|
MessageType = BrokerMigrationMessage.MessageType
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
var brokerMessages = new TaskAgentMessage[]
|
||||||
|
{
|
||||||
|
new TaskAgentMessage
|
||||||
|
{
|
||||||
|
Body = "somebody1",
|
||||||
|
MessageId = 4234,
|
||||||
|
MessageType = JobRequestMessageTypes.PipelineAgentJobRequest
|
||||||
|
},
|
||||||
|
new TaskAgentMessage
|
||||||
|
{
|
||||||
|
Body = "somebody2",
|
||||||
|
MessageId = 4235,
|
||||||
|
MessageType = JobCancelMessage.MessageType
|
||||||
|
},
|
||||||
|
null, //should be skipped by GetNextMessageAsync implementation
|
||||||
|
null,
|
||||||
|
new TaskAgentMessage
|
||||||
|
{
|
||||||
|
Body = "somebody3",
|
||||||
|
MessageId = 4236,
|
||||||
|
MessageType = JobRequestMessageTypes.PipelineAgentJobRequest
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var brokerMessageQueue = new Queue<TaskAgentMessage>(brokerMessages);
|
||||||
|
|
||||||
|
_runnerServer
|
||||||
|
.Setup(x => x.GetAgentMessageAsync(
|
||||||
|
_settings.PoolId, expectedSession.SessionId, It.IsAny<long?>(), TaskAgentStatus.Online, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(async (Int32 poolId, Guid sessionId, Int64? lastMessageId, TaskAgentStatus status, string runnerVersion, string os, string architecture, bool disableUpdate, CancellationToken cancellationToken) =>
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
return arMessages[0]; // always send migration message
|
||||||
|
});
|
||||||
|
|
||||||
|
var counter = 0;
|
||||||
|
_brokerServer
|
||||||
|
.Setup(x => x.GetRunnerMessageAsync(
|
||||||
|
expectedSession.SessionId, TaskAgentStatus.Online, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(async (Guid sessionId, TaskAgentStatus status, string runnerVersion, string os, string architecture, bool disableUpdate, CancellationToken cancellationToken) =>
|
||||||
|
{
|
||||||
|
counter++;
|
||||||
|
await Task.Yield();
|
||||||
|
if (counter == 2)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("Something wrong.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return brokerMessageQueue.Dequeue();
|
||||||
|
});
|
||||||
|
|
||||||
|
TaskAgentMessage message1 = await listener.GetNextMessageAsync(tokenSource.Token);
|
||||||
|
TaskAgentMessage message2 = await listener.GetNextMessageAsync(tokenSource.Token);
|
||||||
|
TaskAgentMessage message3 = await listener.GetNextMessageAsync(tokenSource.Token);
|
||||||
|
Assert.Equal(brokerMessages[0], message1);
|
||||||
|
Assert.Equal(brokerMessages[1], message2);
|
||||||
|
Assert.Equal(brokerMessages[4], message3);
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
_runnerServer
|
||||||
|
.Verify(x => x.GetAgentMessageAsync(
|
||||||
|
_settings.PoolId, expectedSession.SessionId, It.IsAny<long?>(), TaskAgentStatus.Online, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()), Times.Exactly(brokerMessages.Length + 1));
|
||||||
|
|
||||||
|
_brokerServer
|
||||||
|
.Verify(x => x.GetRunnerMessageAsync(
|
||||||
|
expectedSession.SessionId, TaskAgentStatus.Online, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()), Times.Exactly(brokerMessages.Length + 1));
|
||||||
|
|
||||||
|
_credMgr
|
||||||
|
.Verify(x => x.LoadCredentials(true), Times.Exactly(brokerMessages.Length + 1));
|
||||||
|
|
||||||
|
_brokerServer
|
||||||
|
.Verify(x => x.UpdateConnectionIfNeeded(brokerMigrationMesage.BrokerBaseUrl, It.IsAny<VssCredentials>()), Times.Exactly(brokerMessages.Length + 1));
|
||||||
|
|
||||||
|
_brokerServer
|
||||||
|
.Verify(x => x.ForceRefreshConnection(It.IsAny<VssCredentials>()), Times.Once());
|
||||||
|
|
||||||
|
Assert.False(tc.AllowAuthMigration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task GetNextMessageWithBrokerMigration_EnableAuthMigration()
|
||||||
|
{
|
||||||
|
using (TestHostContext tc = CreateTestContext())
|
||||||
|
using (var tokenSource = new CancellationTokenSource())
|
||||||
|
{
|
||||||
|
Tracing trace = tc.GetTrace();
|
||||||
|
|
||||||
|
// Arrange.
|
||||||
|
var expectedSession = new TaskAgentSession();
|
||||||
|
PropertyInfo sessionIdProperty = expectedSession.GetType().GetProperty("SessionId", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
||||||
|
Assert.NotNull(sessionIdProperty);
|
||||||
|
sessionIdProperty.SetValue(expectedSession, Guid.NewGuid());
|
||||||
|
|
||||||
|
_runnerServer
|
||||||
|
.Setup(x => x.CreateAgentSessionAsync(
|
||||||
|
_settings.PoolId,
|
||||||
|
It.Is<TaskAgentSession>(y => y != null),
|
||||||
|
tokenSource.Token))
|
||||||
|
.Returns(Task.FromResult(expectedSession));
|
||||||
|
|
||||||
|
_credMgr.Setup(x => x.LoadCredentials(It.IsAny<bool>())).Returns(new VssCredentials());
|
||||||
|
_store.Setup(x => x.GetCredentials()).Returns(new CredentialData() { Scheme = Constants.Configuration.OAuthAccessToken });
|
||||||
|
_store.Setup(x => x.GetMigratedCredentials()).Returns(default(CredentialData));
|
||||||
|
|
||||||
|
// Act.
|
||||||
|
MessageListener listener = new();
|
||||||
|
listener.Initialize(tc);
|
||||||
|
|
||||||
|
CreateSessionResult result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||||
|
Assert.Equal(CreateSessionResult.Success, result);
|
||||||
|
|
||||||
|
var brokerMigrationMesage = new BrokerMigrationMessage(new Uri("https://actions.broker.com"));
|
||||||
|
|
||||||
|
var arMessages = new TaskAgentMessage[]
|
||||||
|
{
|
||||||
|
new TaskAgentMessage
|
||||||
|
{
|
||||||
|
Body = JsonUtility.ToString(brokerMigrationMesage),
|
||||||
|
MessageType = BrokerMigrationMessage.MessageType
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
var brokerMessages = new TaskAgentMessage[]
|
||||||
|
{
|
||||||
|
new TaskAgentMessage
|
||||||
|
{
|
||||||
|
Body = "somebody1",
|
||||||
|
MessageId = 4234,
|
||||||
|
MessageType = JobRequestMessageTypes.PipelineAgentJobRequest
|
||||||
|
},
|
||||||
|
new TaskAgentMessage
|
||||||
|
{
|
||||||
|
Body = "somebody2",
|
||||||
|
MessageId = 4235,
|
||||||
|
MessageType = JobCancelMessage.MessageType
|
||||||
|
},
|
||||||
|
null, //should be skipped by GetNextMessageAsync implementation
|
||||||
|
null,
|
||||||
|
new TaskAgentMessage
|
||||||
|
{
|
||||||
|
Body = "somebody3",
|
||||||
|
MessageId = 4236,
|
||||||
|
MessageType = JobRequestMessageTypes.PipelineAgentJobRequest
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var brokerMessageQueue = new Queue<TaskAgentMessage>(brokerMessages);
|
||||||
|
|
||||||
|
_runnerServer
|
||||||
|
.Setup(x => x.GetAgentMessageAsync(
|
||||||
|
_settings.PoolId, expectedSession.SessionId, It.IsAny<long?>(), TaskAgentStatus.Online, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(async (Int32 poolId, Guid sessionId, Int64? lastMessageId, TaskAgentStatus status, string runnerVersion, string os, string architecture, bool disableUpdate, CancellationToken cancellationToken) =>
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
return arMessages[0]; // always send migration message
|
||||||
|
});
|
||||||
|
|
||||||
|
_brokerServer
|
||||||
|
.Setup(x => x.GetRunnerMessageAsync(
|
||||||
|
expectedSession.SessionId, TaskAgentStatus.Online, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(async (Guid sessionId, TaskAgentStatus status, string runnerVersion, string os, string architecture, bool disableUpdate, CancellationToken cancellationToken) =>
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
if (!tc.AllowAuthMigration)
|
||||||
|
{
|
||||||
|
tc.EnableAuthMigration("L0Test");
|
||||||
|
}
|
||||||
|
|
||||||
|
return brokerMessageQueue.Dequeue();
|
||||||
|
});
|
||||||
|
|
||||||
|
TaskAgentMessage message1 = await listener.GetNextMessageAsync(tokenSource.Token);
|
||||||
|
TaskAgentMessage message2 = await listener.GetNextMessageAsync(tokenSource.Token);
|
||||||
|
TaskAgentMessage message3 = await listener.GetNextMessageAsync(tokenSource.Token);
|
||||||
|
Assert.Equal(brokerMessages[0], message1);
|
||||||
|
Assert.Equal(brokerMessages[1], message2);
|
||||||
|
Assert.Equal(brokerMessages[4], message3);
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
_runnerServer
|
||||||
|
.Verify(x => x.GetAgentMessageAsync(
|
||||||
|
_settings.PoolId, expectedSession.SessionId, It.IsAny<long?>(), TaskAgentStatus.Online, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()), Times.Exactly(brokerMessages.Length));
|
||||||
|
|
||||||
|
_brokerServer
|
||||||
|
.Verify(x => x.GetRunnerMessageAsync(
|
||||||
|
expectedSession.SessionId, TaskAgentStatus.Online, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()), Times.Exactly(brokerMessages.Length));
|
||||||
|
|
||||||
|
_credMgr
|
||||||
|
.Verify(x => x.LoadCredentials(true), Times.Exactly(brokerMessages.Length));
|
||||||
|
|
||||||
|
_brokerServer
|
||||||
|
.Verify(x => x.UpdateConnectionIfNeeded(brokerMigrationMesage.BrokerBaseUrl, It.IsAny<VssCredentials>()), Times.Exactly(brokerMessages.Length));
|
||||||
|
|
||||||
|
_brokerServer
|
||||||
|
.Verify(x => x.ForceRefreshConnection(It.IsAny<VssCredentials>()), Times.Once());
|
||||||
|
|
||||||
|
Assert.True(tc.AllowAuthMigration);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using GitHub.Runner.Listener;
|
|
||||||
using GitHub.Runner.Common;
|
using GitHub.Runner.Common;
|
||||||
|
using GitHub.Runner.Common.Tests;
|
||||||
|
using GitHub.Runner.Listener;
|
||||||
using GitHub.Runner.Sdk;
|
using GitHub.Runner.Sdk;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using System.Threading;
|
|
||||||
using GitHub.Runner.Common.Tests;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace GitHub.Runner.Tests.Listener
|
namespace GitHub.Runner.Tests.Listener
|
||||||
{
|
{
|
||||||
@@ -210,9 +210,9 @@ namespace GitHub.Runner.Tests.Listener
|
|||||||
var encodedConfig = Convert.ToBase64String(Encoding.UTF8.GetBytes(StringUtil.ConvertToJson(credData)));
|
var encodedConfig = Convert.ToBase64String(Encoding.UTF8.GetBytes(StringUtil.ConvertToJson(credData)));
|
||||||
_runnerServer.Setup(x => x.RefreshRunnerConfigAsync(It.IsAny<int>(), It.Is<string>(s => s == "credentials"), It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(encodedConfig);
|
_runnerServer.Setup(x => x.RefreshRunnerConfigAsync(It.IsAny<int>(), It.Is<string>(s => s == "credentials"), It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(encodedConfig);
|
||||||
|
|
||||||
|
|
||||||
var _runnerConfigUpdater = new RunnerConfigUpdater();
|
var _runnerConfigUpdater = new RunnerConfigUpdater();
|
||||||
_runnerConfigUpdater.Initialize(hc);
|
_runnerConfigUpdater.Initialize(hc);
|
||||||
|
hc.EnableAuthMigration("L0Test");
|
||||||
|
|
||||||
var validRunnerQualifiedId = "valid/runner/qualifiedid/1";
|
var validRunnerQualifiedId = "valid/runner/qualifiedid/1";
|
||||||
var configType = "credentials";
|
var configType = "credentials";
|
||||||
@@ -226,6 +226,7 @@ namespace GitHub.Runner.Tests.Listener
|
|||||||
_runnerServer.Verify(x => x.RefreshRunnerConfigAsync(1, "credentials", It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Once);
|
_runnerServer.Verify(x => x.RefreshRunnerConfigAsync(1, "credentials", It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||||
_runnerServer.Verify(x => x.UpdateAgentUpdateStateAsync(It.IsAny<int>(), It.IsAny<ulong>(), It.IsAny<string>(), It.Is<string>(s => s.Contains("Runner credentials updated successfully")), It.IsAny<CancellationToken>()), Times.Once);
|
_runnerServer.Verify(x => x.UpdateAgentUpdateStateAsync(It.IsAny<int>(), It.IsAny<ulong>(), It.IsAny<string>(), It.Is<string>(s => s.Contains("Runner credentials updated successfully")), It.IsAny<CancellationToken>()), Times.Once);
|
||||||
_configurationStore.Verify(x => x.SaveMigratedCredential(It.IsAny<CredentialData>()), Times.Once);
|
_configurationStore.Verify(x => x.SaveMigratedCredential(It.IsAny<CredentialData>()), Times.Once);
|
||||||
|
Assert.False(hc.AllowAuthMigration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,7 +307,7 @@ namespace GitHub.Runner.Tests.Listener
|
|||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Runner")]
|
[Trait("Category", "Runner")]
|
||||||
public async Task UpdateRunnerConfigAsync_RefreshRunnerCredetialsFailure_ShouldReportTelemetry()
|
public async Task UpdateRunnerConfigAsync_RefreshRunnerCredentialsFailure_ShouldReportTelemetry()
|
||||||
{
|
{
|
||||||
using (var hc = new TestHostContext(this))
|
using (var hc = new TestHostContext(this))
|
||||||
{
|
{
|
||||||
@@ -510,6 +511,56 @@ namespace GitHub.Runner.Tests.Listener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task UpdateRunnerConfigAsync_RefreshOAuthCredentialsWithDifferentAuthUrl_ShouldReportTelemetry()
|
||||||
|
{
|
||||||
|
using (var hc = new TestHostContext(this))
|
||||||
|
{
|
||||||
|
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
|
|
||||||
|
// Arrange
|
||||||
|
var setting = new RunnerSettings { AgentId = 1, AgentName = "agent1" };
|
||||||
|
_configurationStore.Setup(x => x.GetSettings()).Returns(setting);
|
||||||
|
var credData = new CredentialData
|
||||||
|
{
|
||||||
|
Scheme = "OAuth"
|
||||||
|
};
|
||||||
|
credData.Data.Add("clientId", "12345");
|
||||||
|
credData.Data.Add("authorizationUrl", "http://example.com/");
|
||||||
|
_configurationStore.Setup(x => x.GetCredentials()).Returns(credData);
|
||||||
|
|
||||||
|
IOUtil.SaveObject(setting, hc.GetConfigFile(WellKnownConfigFile.Runner));
|
||||||
|
IOUtil.SaveObject(credData, hc.GetConfigFile(WellKnownConfigFile.Credentials));
|
||||||
|
|
||||||
|
var differentCredData = new CredentialData
|
||||||
|
{
|
||||||
|
Scheme = "OAuth"
|
||||||
|
};
|
||||||
|
differentCredData.Data.Add("clientId", "12345");
|
||||||
|
differentCredData.Data.Add("authorizationUrl", "http://example2.com/");
|
||||||
|
var encodedConfig = Convert.ToBase64String(Encoding.UTF8.GetBytes(StringUtil.ConvertToJson(differentCredData)));
|
||||||
|
_runnerServer.Setup(x => x.RefreshRunnerConfigAsync(It.IsAny<int>(), It.Is<string>(s => s == "credentials"), It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(encodedConfig);
|
||||||
|
|
||||||
|
var _runnerConfigUpdater = new RunnerConfigUpdater();
|
||||||
|
_runnerConfigUpdater.Initialize(hc);
|
||||||
|
|
||||||
|
var validRunnerQualifiedId = "valid/runner/qualifiedid/1";
|
||||||
|
var configType = "credentials";
|
||||||
|
var serviceType = "pipelines";
|
||||||
|
var configRefreshUrl = "http://example.com";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _runnerConfigUpdater.UpdateRunnerConfigAsync(validRunnerQualifiedId, configType, serviceType, configRefreshUrl);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_runnerServer.Verify(x => x.UpdateAgentUpdateStateAsync(It.IsAny<int>(), It.IsAny<ulong>(), It.IsAny<string>(), It.Is<string>(s => s.Contains("Credential authorizationUrl in refreshed config")), It.IsAny<CancellationToken>()), Times.Once);
|
||||||
|
_configurationStore.Verify(x => x.SaveMigratedCredential(It.IsAny<CredentialData>()), Times.Never);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Runner")]
|
[Trait("Category", "Runner")]
|
||||||
@@ -575,5 +626,53 @@ namespace GitHub.Runner.Tests.Listener
|
|||||||
_configurationStore.Verify(x => x.SaveMigratedSettings(It.IsAny<RunnerSettings>()), Times.Never);
|
_configurationStore.Verify(x => x.SaveMigratedSettings(It.IsAny<RunnerSettings>()), Times.Never);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task UpdateRunnerConfigAsync_UpdateRunnerCredentials_EnableDisableAuthMigration()
|
||||||
|
{
|
||||||
|
using (var hc = new TestHostContext(this))
|
||||||
|
{
|
||||||
|
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
|
|
||||||
|
// Arrange
|
||||||
|
var setting = new RunnerSettings { AgentId = 1, AgentName = "agent1" };
|
||||||
|
_configurationStore.Setup(x => x.GetSettings()).Returns(setting);
|
||||||
|
var credData = new CredentialData
|
||||||
|
{
|
||||||
|
Scheme = "OAuth"
|
||||||
|
};
|
||||||
|
credData.Data.Add("ClientId", "12345");
|
||||||
|
credData.Data.Add("AuthorizationUrl", "https://example.com");
|
||||||
|
credData.Data.Add("AuthorizationUrlV2", "https://example2.com");
|
||||||
|
_configurationStore.Setup(x => x.GetCredentials()).Returns(credData);
|
||||||
|
|
||||||
|
IOUtil.SaveObject(setting, hc.GetConfigFile(WellKnownConfigFile.Runner));
|
||||||
|
IOUtil.SaveObject(credData, hc.GetConfigFile(WellKnownConfigFile.Credentials));
|
||||||
|
|
||||||
|
var encodedConfig = Convert.ToBase64String(Encoding.UTF8.GetBytes(StringUtil.ConvertToJson(credData)));
|
||||||
|
_runnerServer.Setup(x => x.RefreshRunnerConfigAsync(It.IsAny<int>(), It.Is<string>(s => s == "credentials"), It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(encodedConfig);
|
||||||
|
|
||||||
|
var _runnerConfigUpdater = new RunnerConfigUpdater();
|
||||||
|
_runnerConfigUpdater.Initialize(hc);
|
||||||
|
Assert.False(hc.AllowAuthMigration);
|
||||||
|
|
||||||
|
var validRunnerQualifiedId = "valid/runner/qualifiedid/1";
|
||||||
|
var configType = "credentials";
|
||||||
|
var serviceType = "pipelines";
|
||||||
|
var configRefreshUrl = "http://example.com";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _runnerConfigUpdater.UpdateRunnerConfigAsync(validRunnerQualifiedId, configType, serviceType, configRefreshUrl);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_runnerServer.Verify(x => x.RefreshRunnerConfigAsync(1, "credentials", It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||||
|
_runnerServer.Verify(x => x.UpdateAgentUpdateStateAsync(It.IsAny<int>(), It.IsAny<ulong>(), It.IsAny<string>(), It.Is<string>(s => s.Contains("Runner credentials updated successfully")), It.IsAny<CancellationToken>()), Times.Once);
|
||||||
|
_configurationStore.Verify(x => x.SaveMigratedCredential(It.IsAny<CredentialData>()), Times.Once);
|
||||||
|
Assert.True(hc.AllowAuthMigration);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
using GitHub.DistributedTask.WebApi;
|
using System;
|
||||||
using GitHub.Runner.Listener;
|
|
||||||
using GitHub.Runner.Listener.Configuration;
|
|
||||||
using Moq;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Xunit;
|
using GitHub.DistributedTask.WebApi;
|
||||||
|
using GitHub.Runner.Listener;
|
||||||
|
using GitHub.Runner.Listener.Configuration;
|
||||||
|
using GitHub.Services.Common;
|
||||||
using GitHub.Services.WebApi;
|
using GitHub.Services.WebApi;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
using Pipelines = GitHub.DistributedTask.Pipelines;
|
using Pipelines = GitHub.DistributedTask.Pipelines;
|
||||||
|
|
||||||
namespace GitHub.Runner.Common.Tests.Listener
|
namespace GitHub.Runner.Common.Tests.Listener
|
||||||
@@ -24,6 +26,9 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
private Mock<IConfigurationStore> _configStore;
|
private Mock<IConfigurationStore> _configStore;
|
||||||
private Mock<ISelfUpdater> _updater;
|
private Mock<ISelfUpdater> _updater;
|
||||||
private Mock<IErrorThrottler> _acquireJobThrottler;
|
private Mock<IErrorThrottler> _acquireJobThrottler;
|
||||||
|
private Mock<ICredentialManager> _credentialManager;
|
||||||
|
private Mock<IActionsRunServer> _actionsRunServer;
|
||||||
|
private Mock<IRunServer> _runServer;
|
||||||
|
|
||||||
public RunnerL0()
|
public RunnerL0()
|
||||||
{
|
{
|
||||||
@@ -37,6 +42,9 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
_configStore = new Mock<IConfigurationStore>();
|
_configStore = new Mock<IConfigurationStore>();
|
||||||
_updater = new Mock<ISelfUpdater>();
|
_updater = new Mock<ISelfUpdater>();
|
||||||
_acquireJobThrottler = new Mock<IErrorThrottler>();
|
_acquireJobThrottler = new Mock<IErrorThrottler>();
|
||||||
|
_credentialManager = new Mock<ICredentialManager>();
|
||||||
|
_actionsRunServer = new Mock<IActionsRunServer>();
|
||||||
|
_runServer = new Mock<IRunServer>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Pipelines.AgentJobRequestMessage CreateJobRequestMessage(string jobName)
|
private Pipelines.AgentJobRequestMessage CreateJobRequestMessage(string jobName)
|
||||||
@@ -57,7 +65,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Runner")]
|
[Trait("Category", "Runner")]
|
||||||
//process 2 new job messages, and one cancel message
|
//process 2 new job messages, and one cancel message
|
||||||
public async void TestRunAsync()
|
public async Task TestRunAsync()
|
||||||
{
|
{
|
||||||
using (var hc = new TestHostContext(this))
|
using (var hc = new TestHostContext(this))
|
||||||
{
|
{
|
||||||
@@ -118,7 +126,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
hc.EnqueueInstance<IJobDispatcher>(_jobDispatcher.Object);
|
hc.SetSingleton<IJobDispatcher>(_jobDispatcher.Object);
|
||||||
|
|
||||||
_configStore.Setup(x => x.IsServiceConfigured()).Returns(false);
|
_configStore.Setup(x => x.IsServiceConfigured()).Returns(false);
|
||||||
//Act
|
//Act
|
||||||
@@ -169,7 +177,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
[MemberData(nameof(RunAsServiceTestData))]
|
[MemberData(nameof(RunAsServiceTestData))]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Runner")]
|
[Trait("Category", "Runner")]
|
||||||
public async void TestExecuteCommandForRunAsService(string[] args, bool configureAsService, Times expectedTimes)
|
public async Task TestExecuteCommandForRunAsService(string[] args, bool configureAsService, Times expectedTimes)
|
||||||
{
|
{
|
||||||
using (var hc = new TestHostContext(this))
|
using (var hc = new TestHostContext(this))
|
||||||
{
|
{
|
||||||
@@ -177,6 +185,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
hc.SetSingleton<IPromptManager>(_promptManager.Object);
|
hc.SetSingleton<IPromptManager>(_promptManager.Object);
|
||||||
hc.SetSingleton<IMessageListener>(_messageListener.Object);
|
hc.SetSingleton<IMessageListener>(_messageListener.Object);
|
||||||
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
|
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
hc.EnqueueInstance<IErrorThrottler>(_acquireJobThrottler.Object);
|
hc.EnqueueInstance<IErrorThrottler>(_acquireJobThrottler.Object);
|
||||||
|
|
||||||
var command = new CommandSettings(hc, args);
|
var command = new CommandSettings(hc, args);
|
||||||
@@ -201,7 +210,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Runner")]
|
[Trait("Category", "Runner")]
|
||||||
public async void TestMachineProvisionerCLI()
|
public async Task TestMachineProvisionerCLI()
|
||||||
{
|
{
|
||||||
using (var hc = new TestHostContext(this))
|
using (var hc = new TestHostContext(this))
|
||||||
{
|
{
|
||||||
@@ -209,6 +218,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
hc.SetSingleton<IPromptManager>(_promptManager.Object);
|
hc.SetSingleton<IPromptManager>(_promptManager.Object);
|
||||||
hc.SetSingleton<IMessageListener>(_messageListener.Object);
|
hc.SetSingleton<IMessageListener>(_messageListener.Object);
|
||||||
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
|
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
hc.EnqueueInstance<IErrorThrottler>(_acquireJobThrottler.Object);
|
hc.EnqueueInstance<IErrorThrottler>(_acquireJobThrottler.Object);
|
||||||
|
|
||||||
var command = new CommandSettings(hc, new[] { "run" });
|
var command = new CommandSettings(hc, new[] { "run" });
|
||||||
@@ -235,7 +245,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Runner")]
|
[Trait("Category", "Runner")]
|
||||||
public async void TestRunOnce()
|
public async Task TestRunOnce()
|
||||||
{
|
{
|
||||||
using (var hc = new TestHostContext(this))
|
using (var hc = new TestHostContext(this))
|
||||||
{
|
{
|
||||||
@@ -299,7 +309,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
hc.EnqueueInstance<IJobDispatcher>(_jobDispatcher.Object);
|
hc.SetSingleton<IJobDispatcher>(_jobDispatcher.Object);
|
||||||
|
|
||||||
_configStore.Setup(x => x.IsServiceConfigured()).Returns(false);
|
_configStore.Setup(x => x.IsServiceConfigured()).Returns(false);
|
||||||
//Act
|
//Act
|
||||||
@@ -332,7 +342,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Runner")]
|
[Trait("Category", "Runner")]
|
||||||
public async void TestRunOnceOnlyTakeOneJobMessage()
|
public async Task TestRunOnceOnlyTakeOneJobMessage()
|
||||||
{
|
{
|
||||||
using (var hc = new TestHostContext(this))
|
using (var hc = new TestHostContext(this))
|
||||||
{
|
{
|
||||||
@@ -403,7 +413,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
hc.EnqueueInstance<IJobDispatcher>(_jobDispatcher.Object);
|
hc.SetSingleton<IJobDispatcher>(_jobDispatcher.Object);
|
||||||
|
|
||||||
_configStore.Setup(x => x.IsServiceConfigured()).Returns(false);
|
_configStore.Setup(x => x.IsServiceConfigured()).Returns(false);
|
||||||
//Act
|
//Act
|
||||||
@@ -433,7 +443,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Runner")]
|
[Trait("Category", "Runner")]
|
||||||
public async void TestRunOnceHandleUpdateMessage()
|
public async Task TestRunOnceHandleUpdateMessage()
|
||||||
{
|
{
|
||||||
using (var hc = new TestHostContext(this))
|
using (var hc = new TestHostContext(this))
|
||||||
{
|
{
|
||||||
@@ -493,7 +503,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
hc.EnqueueInstance<IJobDispatcher>(_jobDispatcher.Object);
|
hc.SetSingleton<IJobDispatcher>(_jobDispatcher.Object);
|
||||||
|
|
||||||
_configStore.Setup(x => x.IsServiceConfigured()).Returns(false);
|
_configStore.Setup(x => x.IsServiceConfigured()).Returns(false);
|
||||||
//Act
|
//Act
|
||||||
@@ -523,13 +533,14 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Runner")]
|
[Trait("Category", "Runner")]
|
||||||
public async void TestRemoveLocalRunnerConfig()
|
public async Task TestRemoveLocalRunnerConfig()
|
||||||
{
|
{
|
||||||
using (var hc = new TestHostContext(this))
|
using (var hc = new TestHostContext(this))
|
||||||
{
|
{
|
||||||
hc.SetSingleton<IConfigurationManager>(_configurationManager.Object);
|
hc.SetSingleton<IConfigurationManager>(_configurationManager.Object);
|
||||||
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
|
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
|
||||||
hc.SetSingleton<IPromptManager>(_promptManager.Object);
|
hc.SetSingleton<IPromptManager>(_promptManager.Object);
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
hc.EnqueueInstance<IErrorThrottler>(_acquireJobThrottler.Object);
|
hc.EnqueueInstance<IErrorThrottler>(_acquireJobThrottler.Object);
|
||||||
|
|
||||||
var command = new CommandSettings(hc, new[] { "remove", "--local" });
|
var command = new CommandSettings(hc, new[] { "remove", "--local" });
|
||||||
@@ -549,5 +560,521 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
_configurationManager.Verify(x => x.DeleteLocalRunnerConfig(), Times.Once());
|
_configurationManager.Verify(x => x.DeleteLocalRunnerConfig(), Times.Once());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task TestReportAuthMigrationTelemetry()
|
||||||
|
{
|
||||||
|
using (var hc = new TestHostContext(this))
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
var runner = new Runner.Listener.Runner();
|
||||||
|
hc.SetSingleton<IConfigurationManager>(_configurationManager.Object);
|
||||||
|
hc.SetSingleton<IJobNotification>(_jobNotification.Object);
|
||||||
|
hc.SetSingleton<IMessageListener>(_messageListener.Object);
|
||||||
|
hc.SetSingleton<IPromptManager>(_promptManager.Object);
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
|
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
|
||||||
|
hc.SetSingleton<ICredentialManager>(_credentialManager.Object);
|
||||||
|
hc.EnqueueInstance<IErrorThrottler>(_acquireJobThrottler.Object);
|
||||||
|
hc.SetSingleton<IJobDispatcher>(_jobDispatcher.Object);
|
||||||
|
|
||||||
|
runner.Initialize(hc);
|
||||||
|
var settings = new RunnerSettings
|
||||||
|
{
|
||||||
|
PoolId = 43242,
|
||||||
|
AgentId = 5678,
|
||||||
|
Ephemeral = true
|
||||||
|
};
|
||||||
|
|
||||||
|
var message1 = new TaskAgentMessage()
|
||||||
|
{
|
||||||
|
MessageId = 4234,
|
||||||
|
MessageType = "unknown"
|
||||||
|
};
|
||||||
|
|
||||||
|
var messages = new Queue<TaskAgentMessage>();
|
||||||
|
messages.Enqueue(message1);
|
||||||
|
_updater.Setup(x => x.SelfUpdate(It.IsAny<AgentRefreshMessage>(), It.IsAny<IJobDispatcher>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(Task.FromResult(true));
|
||||||
|
_configurationManager.Setup(x => x.LoadSettings())
|
||||||
|
.Returns(settings);
|
||||||
|
_configurationManager.Setup(x => x.IsConfigured())
|
||||||
|
.Returns(true);
|
||||||
|
_messageListener.Setup(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(Task.FromResult<CreateSessionResult>(CreateSessionResult.Success));
|
||||||
|
_messageListener.Setup(x => x.GetNextMessageAsync(It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(async (CancellationToken token) =>
|
||||||
|
{
|
||||||
|
hc.GetTrace().Info("Waiting for message");
|
||||||
|
Assert.False(hc.AllowAuthMigration);
|
||||||
|
await Task.Delay(100, token);
|
||||||
|
|
||||||
|
var traceFile = Path.GetTempFileName();
|
||||||
|
File.Copy(hc.TraceFileName, traceFile, true);
|
||||||
|
Assert.DoesNotContain("Checking for auth migration telemetry to report", File.ReadAllText(traceFile));
|
||||||
|
|
||||||
|
hc.EnableAuthMigration("L0Test");
|
||||||
|
hc.DeferAuthMigration(TimeSpan.FromSeconds(1), "L0Test");
|
||||||
|
hc.EnableAuthMigration("L0Test");
|
||||||
|
hc.DeferAuthMigration(TimeSpan.FromSeconds(1), "L0Test");
|
||||||
|
|
||||||
|
await Task.Delay(1000, token);
|
||||||
|
|
||||||
|
hc.ShutdownRunner(ShutdownReason.UserCancelled);
|
||||||
|
|
||||||
|
File.Copy(hc.TraceFileName, traceFile, true);
|
||||||
|
Assert.Contains("Checking for auth migration telemetry to report", File.ReadAllText(traceFile));
|
||||||
|
|
||||||
|
return messages.Dequeue();
|
||||||
|
});
|
||||||
|
_messageListener.Setup(x => x.DeleteSessionAsync())
|
||||||
|
.Returns(Task.CompletedTask);
|
||||||
|
_messageListener.Setup(x => x.DeleteMessageAsync(It.IsAny<TaskAgentMessage>()))
|
||||||
|
.Returns(Task.CompletedTask);
|
||||||
|
_jobNotification.Setup(x => x.StartClient(It.IsAny<String>()))
|
||||||
|
.Callback(() =>
|
||||||
|
{
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
_configStore.Setup(x => x.IsServiceConfigured()).Returns(false);
|
||||||
|
|
||||||
|
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(It.IsAny<int>(), It.IsAny<ulong>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(Task.FromResult(new TaskAgent()));
|
||||||
|
|
||||||
|
//Act
|
||||||
|
var command = new CommandSettings(hc, new string[] { "run" });
|
||||||
|
var returnCode = await runner.ExecuteCommand(command);
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
Assert.Equal(Constants.Runner.ReturnCode.Success, returnCode);
|
||||||
|
|
||||||
|
_messageListener.Verify(x => x.GetNextMessageAsync(It.IsAny<CancellationToken>()), Times.AtLeastOnce());
|
||||||
|
_messageListener.Verify(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()), Times.Once());
|
||||||
|
_messageListener.Verify(x => x.DeleteSessionAsync(), Times.Once());
|
||||||
|
_messageListener.Verify(x => x.DeleteMessageAsync(It.IsAny<TaskAgentMessage>()), Times.Once());
|
||||||
|
|
||||||
|
_runnerServer.Verify(x => x.UpdateAgentUpdateStateAsync(It.IsAny<int>(), It.IsAny<ulong>(), It.IsAny<string>(), It.Is<string>(s => s.Contains("L0Test")), It.IsAny<CancellationToken>()), Times.Exactly(4));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task TestRunnerJobRequestMessageFromPipeline()
|
||||||
|
{
|
||||||
|
using (var hc = new TestHostContext(this))
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
var runner = new Runner.Listener.Runner();
|
||||||
|
hc.SetSingleton<IConfigurationManager>(_configurationManager.Object);
|
||||||
|
hc.SetSingleton<IJobNotification>(_jobNotification.Object);
|
||||||
|
hc.SetSingleton<IMessageListener>(_messageListener.Object);
|
||||||
|
hc.SetSingleton<IPromptManager>(_promptManager.Object);
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
|
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
|
||||||
|
hc.SetSingleton<ISelfUpdater>(_updater.Object);
|
||||||
|
hc.SetSingleton<ICredentialManager>(_credentialManager.Object);
|
||||||
|
hc.EnqueueInstance<IErrorThrottler>(_acquireJobThrottler.Object);
|
||||||
|
hc.EnqueueInstance<IActionsRunServer>(_actionsRunServer.Object);
|
||||||
|
hc.SetSingleton<IJobDispatcher>(_jobDispatcher.Object);
|
||||||
|
|
||||||
|
runner.Initialize(hc);
|
||||||
|
var settings = new RunnerSettings
|
||||||
|
{
|
||||||
|
PoolId = 43242,
|
||||||
|
AgentId = 5678,
|
||||||
|
Ephemeral = true,
|
||||||
|
ServerUrl = "https://github.com",
|
||||||
|
};
|
||||||
|
|
||||||
|
var message1 = new TaskAgentMessage()
|
||||||
|
{
|
||||||
|
Body = JsonUtility.ToString(new RunnerJobRequestRef() { BillingOwnerId = "github", RunnerRequestId = "999" }),
|
||||||
|
MessageId = 4234,
|
||||||
|
MessageType = JobRequestMessageTypes.RunnerJobRequest
|
||||||
|
};
|
||||||
|
|
||||||
|
var messages = new Queue<TaskAgentMessage>();
|
||||||
|
messages.Enqueue(message1);
|
||||||
|
_updater.Setup(x => x.SelfUpdate(It.IsAny<AgentRefreshMessage>(), It.IsAny<IJobDispatcher>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(Task.FromResult(true));
|
||||||
|
_configurationManager.Setup(x => x.LoadSettings())
|
||||||
|
.Returns(settings);
|
||||||
|
_configurationManager.Setup(x => x.IsConfigured())
|
||||||
|
.Returns(true);
|
||||||
|
_messageListener.Setup(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(Task.FromResult<CreateSessionResult>(CreateSessionResult.Success));
|
||||||
|
_messageListener.Setup(x => x.GetNextMessageAsync(It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(async (CancellationToken token) =>
|
||||||
|
{
|
||||||
|
if (0 == messages.Count)
|
||||||
|
{
|
||||||
|
await Task.Delay(2000, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
return messages.Dequeue();
|
||||||
|
});
|
||||||
|
_messageListener.Setup(x => x.DeleteSessionAsync())
|
||||||
|
.Returns(Task.CompletedTask);
|
||||||
|
_messageListener.Setup(x => x.DeleteMessageAsync(It.IsAny<TaskAgentMessage>()))
|
||||||
|
.Returns(Task.CompletedTask);
|
||||||
|
_jobNotification.Setup(x => x.StartClient(It.IsAny<String>()))
|
||||||
|
.Callback(() =>
|
||||||
|
{
|
||||||
|
|
||||||
|
});
|
||||||
|
_actionsRunServer.Setup(x => x.GetJobMessageAsync("999", It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(Task.FromResult(CreateJobRequestMessage("test")));
|
||||||
|
|
||||||
|
_credentialManager.Setup(x => x.LoadCredentials(false)).Returns(new VssCredentials());
|
||||||
|
|
||||||
|
_configStore.Setup(x => x.IsServiceConfigured()).Returns(false);
|
||||||
|
|
||||||
|
var completedTask = new TaskCompletionSource<bool>();
|
||||||
|
completedTask.SetResult(true);
|
||||||
|
_jobDispatcher.Setup(x => x.RunOnceJobCompleted).Returns(completedTask);
|
||||||
|
|
||||||
|
//Act
|
||||||
|
var command = new CommandSettings(hc, new string[] { "run" });
|
||||||
|
Task<int> runnerTask = runner.ExecuteCommand(command);
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
//wait for the runner to exit with right return code
|
||||||
|
await Task.WhenAny(runnerTask, Task.Delay(30000));
|
||||||
|
|
||||||
|
Assert.True(runnerTask.IsCompleted, $"{nameof(runner.ExecuteCommand)} timed out.");
|
||||||
|
Assert.True(!runnerTask.IsFaulted, runnerTask.Exception?.ToString());
|
||||||
|
if (runnerTask.IsCompleted)
|
||||||
|
{
|
||||||
|
Assert.Equal(Constants.Runner.ReturnCode.Success, await runnerTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
_jobDispatcher.Verify(x => x.Run(It.IsAny<Pipelines.AgentJobRequestMessage>(), true), Times.Once());
|
||||||
|
_messageListener.Verify(x => x.GetNextMessageAsync(It.IsAny<CancellationToken>()), Times.AtLeastOnce());
|
||||||
|
_messageListener.Verify(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()), Times.Once());
|
||||||
|
_messageListener.Verify(x => x.DeleteSessionAsync(), Times.Once());
|
||||||
|
_messageListener.Verify(x => x.DeleteMessageAsync(It.IsAny<TaskAgentMessage>()), Times.Once());
|
||||||
|
_credentialManager.Verify(x => x.LoadCredentials(false), Times.Once());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task TestRunnerJobRequestMessageFromRunService()
|
||||||
|
{
|
||||||
|
using (var hc = new TestHostContext(this))
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
var runner = new Runner.Listener.Runner();
|
||||||
|
hc.SetSingleton<IConfigurationManager>(_configurationManager.Object);
|
||||||
|
hc.SetSingleton<IJobNotification>(_jobNotification.Object);
|
||||||
|
hc.SetSingleton<IMessageListener>(_messageListener.Object);
|
||||||
|
hc.SetSingleton<IPromptManager>(_promptManager.Object);
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
|
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
|
||||||
|
hc.SetSingleton<ISelfUpdater>(_updater.Object);
|
||||||
|
hc.SetSingleton<ICredentialManager>(_credentialManager.Object);
|
||||||
|
hc.EnqueueInstance<IErrorThrottler>(_acquireJobThrottler.Object);
|
||||||
|
hc.EnqueueInstance<IRunServer>(_runServer.Object);
|
||||||
|
hc.SetSingleton<IJobDispatcher>(_jobDispatcher.Object);
|
||||||
|
|
||||||
|
runner.Initialize(hc);
|
||||||
|
var settings = new RunnerSettings
|
||||||
|
{
|
||||||
|
PoolId = 43242,
|
||||||
|
AgentId = 5678,
|
||||||
|
Ephemeral = true,
|
||||||
|
ServerUrl = "https://github.com",
|
||||||
|
};
|
||||||
|
|
||||||
|
var message1 = new TaskAgentMessage()
|
||||||
|
{
|
||||||
|
Body = JsonUtility.ToString(new RunnerJobRequestRef() { BillingOwnerId = "github", RunnerRequestId = "999", RunServiceUrl = "https://run-service.com" }),
|
||||||
|
MessageId = 4234,
|
||||||
|
MessageType = JobRequestMessageTypes.RunnerJobRequest
|
||||||
|
};
|
||||||
|
|
||||||
|
var messages = new Queue<TaskAgentMessage>();
|
||||||
|
messages.Enqueue(message1);
|
||||||
|
_updater.Setup(x => x.SelfUpdate(It.IsAny<AgentRefreshMessage>(), It.IsAny<IJobDispatcher>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(Task.FromResult(true));
|
||||||
|
_configurationManager.Setup(x => x.LoadSettings())
|
||||||
|
.Returns(settings);
|
||||||
|
_configurationManager.Setup(x => x.IsConfigured())
|
||||||
|
.Returns(true);
|
||||||
|
_messageListener.Setup(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(Task.FromResult<CreateSessionResult>(CreateSessionResult.Success));
|
||||||
|
_messageListener.Setup(x => x.GetNextMessageAsync(It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(async (CancellationToken token) =>
|
||||||
|
{
|
||||||
|
if (0 == messages.Count)
|
||||||
|
{
|
||||||
|
await Task.Delay(2000, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
return messages.Dequeue();
|
||||||
|
});
|
||||||
|
_messageListener.Setup(x => x.DeleteSessionAsync())
|
||||||
|
.Returns(Task.CompletedTask);
|
||||||
|
_messageListener.Setup(x => x.DeleteMessageAsync(It.IsAny<TaskAgentMessage>()))
|
||||||
|
.Returns(Task.CompletedTask);
|
||||||
|
_jobNotification.Setup(x => x.StartClient(It.IsAny<String>()))
|
||||||
|
.Callback(() =>
|
||||||
|
{
|
||||||
|
|
||||||
|
});
|
||||||
|
_runServer.Setup(x => x.GetJobMessageAsync("999", "github", It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(Task.FromResult(CreateJobRequestMessage("test")));
|
||||||
|
|
||||||
|
_credentialManager.Setup(x => x.LoadCredentials(true)).Returns(new VssCredentials());
|
||||||
|
|
||||||
|
_configStore.Setup(x => x.IsServiceConfigured()).Returns(false);
|
||||||
|
|
||||||
|
var completedTask = new TaskCompletionSource<bool>();
|
||||||
|
completedTask.SetResult(true);
|
||||||
|
_jobDispatcher.Setup(x => x.RunOnceJobCompleted).Returns(completedTask);
|
||||||
|
|
||||||
|
//Act
|
||||||
|
var command = new CommandSettings(hc, new string[] { "run" });
|
||||||
|
Task<int> runnerTask = runner.ExecuteCommand(command);
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
//wait for the runner to exit with right return code
|
||||||
|
await Task.WhenAny(runnerTask, Task.Delay(30000));
|
||||||
|
|
||||||
|
Assert.True(runnerTask.IsCompleted, $"{nameof(runner.ExecuteCommand)} timed out.");
|
||||||
|
Assert.True(!runnerTask.IsFaulted, runnerTask.Exception?.ToString());
|
||||||
|
if (runnerTask.IsCompleted)
|
||||||
|
{
|
||||||
|
Assert.Equal(Constants.Runner.ReturnCode.Success, await runnerTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
_jobDispatcher.Verify(x => x.Run(It.IsAny<Pipelines.AgentJobRequestMessage>(), true), Times.Once());
|
||||||
|
_messageListener.Verify(x => x.GetNextMessageAsync(It.IsAny<CancellationToken>()), Times.AtLeastOnce());
|
||||||
|
_messageListener.Verify(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()), Times.Once());
|
||||||
|
_messageListener.Verify(x => x.DeleteSessionAsync(), Times.Once());
|
||||||
|
_messageListener.Verify(x => x.DeleteMessageAsync(It.IsAny<TaskAgentMessage>()), Times.Once());
|
||||||
|
_credentialManager.Verify(x => x.LoadCredentials(true), Times.Once());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task TestRunnerJobRequestMessageFromRunService_AuthMigrationFallback()
|
||||||
|
{
|
||||||
|
using (var hc = new TestHostContext(this))
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
var runner = new Runner.Listener.Runner();
|
||||||
|
hc.SetSingleton<IConfigurationManager>(_configurationManager.Object);
|
||||||
|
hc.SetSingleton<IJobNotification>(_jobNotification.Object);
|
||||||
|
hc.SetSingleton<IMessageListener>(_messageListener.Object);
|
||||||
|
hc.SetSingleton<IPromptManager>(_promptManager.Object);
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
|
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
|
||||||
|
hc.SetSingleton<ISelfUpdater>(_updater.Object);
|
||||||
|
hc.SetSingleton<ICredentialManager>(_credentialManager.Object);
|
||||||
|
hc.EnqueueInstance<IErrorThrottler>(_acquireJobThrottler.Object);
|
||||||
|
hc.SetSingleton<IJobDispatcher>(_jobDispatcher.Object);
|
||||||
|
hc.EnqueueInstance<IRunServer>(_runServer.Object);
|
||||||
|
hc.EnqueueInstance<IRunServer>(_runServer.Object);
|
||||||
|
|
||||||
|
runner.Initialize(hc);
|
||||||
|
var settings = new RunnerSettings
|
||||||
|
{
|
||||||
|
PoolId = 43242,
|
||||||
|
AgentId = 5678,
|
||||||
|
Ephemeral = true,
|
||||||
|
ServerUrl = "https://github.com",
|
||||||
|
};
|
||||||
|
|
||||||
|
var message1 = new TaskAgentMessage()
|
||||||
|
{
|
||||||
|
Body = JsonUtility.ToString(new RunnerJobRequestRef() { BillingOwnerId = "github", RunnerRequestId = "999", RunServiceUrl = "https://run-service.com" }),
|
||||||
|
MessageId = 4234,
|
||||||
|
MessageType = JobRequestMessageTypes.RunnerJobRequest
|
||||||
|
};
|
||||||
|
|
||||||
|
var messages = new Queue<TaskAgentMessage>();
|
||||||
|
messages.Enqueue(message1);
|
||||||
|
messages.Enqueue(message1);
|
||||||
|
_updater.Setup(x => x.SelfUpdate(It.IsAny<AgentRefreshMessage>(), It.IsAny<IJobDispatcher>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(Task.FromResult(true));
|
||||||
|
_configurationManager.Setup(x => x.LoadSettings())
|
||||||
|
.Returns(settings);
|
||||||
|
_configurationManager.Setup(x => x.IsConfigured())
|
||||||
|
.Returns(true);
|
||||||
|
_messageListener.Setup(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(Task.FromResult<CreateSessionResult>(CreateSessionResult.Success));
|
||||||
|
_messageListener.Setup(x => x.GetNextMessageAsync(It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(async (CancellationToken token) =>
|
||||||
|
{
|
||||||
|
if (2 == messages.Count)
|
||||||
|
{
|
||||||
|
hc.EnableAuthMigration("L0Test");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (0 == messages.Count)
|
||||||
|
{
|
||||||
|
await Task.Delay(2000, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
return messages.Dequeue();
|
||||||
|
});
|
||||||
|
_messageListener.Setup(x => x.DeleteSessionAsync())
|
||||||
|
.Returns(Task.CompletedTask);
|
||||||
|
_messageListener.Setup(x => x.DeleteMessageAsync(It.IsAny<TaskAgentMessage>()))
|
||||||
|
.Returns(Task.CompletedTask);
|
||||||
|
_jobNotification.Setup(x => x.StartClient(It.IsAny<String>()))
|
||||||
|
.Callback(() =>
|
||||||
|
{
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
var throwError = true;
|
||||||
|
_runServer.Setup(x => x.GetJobMessageAsync("999", "github", It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(() =>
|
||||||
|
{
|
||||||
|
if (throwError)
|
||||||
|
{
|
||||||
|
Assert.True(hc.AllowAuthMigration);
|
||||||
|
throwError = false;
|
||||||
|
throw new NotSupportedException("some error");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult(CreateJobRequestMessage("test"));
|
||||||
|
});
|
||||||
|
|
||||||
|
_credentialManager.Setup(x => x.LoadCredentials(true)).Returns(new VssCredentials());
|
||||||
|
|
||||||
|
_configStore.Setup(x => x.IsServiceConfigured()).Returns(false);
|
||||||
|
|
||||||
|
var completedTask = new TaskCompletionSource<bool>();
|
||||||
|
completedTask.SetResult(true);
|
||||||
|
_jobDispatcher.Setup(x => x.RunOnceJobCompleted).Returns(completedTask);
|
||||||
|
|
||||||
|
//Act
|
||||||
|
var command = new CommandSettings(hc, new string[] { "run" });
|
||||||
|
Task<int> runnerTask = runner.ExecuteCommand(command);
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
//wait for the runner to exit with right return code
|
||||||
|
await Task.WhenAny(runnerTask, Task.Delay(30000));
|
||||||
|
|
||||||
|
Assert.True(runnerTask.IsCompleted, $"{nameof(runner.ExecuteCommand)} timed out.");
|
||||||
|
Assert.True(!runnerTask.IsFaulted, runnerTask.Exception?.ToString());
|
||||||
|
if (runnerTask.IsCompleted)
|
||||||
|
{
|
||||||
|
Assert.Equal(Constants.Runner.ReturnCode.Success, await runnerTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
_jobDispatcher.Verify(x => x.Run(It.IsAny<Pipelines.AgentJobRequestMessage>(), true), Times.Once());
|
||||||
|
_messageListener.Verify(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()), Times.Once());
|
||||||
|
_messageListener.Verify(x => x.GetNextMessageAsync(It.IsAny<CancellationToken>()), Times.AtLeast(2));
|
||||||
|
_messageListener.Verify(x => x.DeleteMessageAsync(It.IsAny<TaskAgentMessage>()), Times.AtLeast(2));
|
||||||
|
_messageListener.Verify(x => x.DeleteSessionAsync(), Times.Once());
|
||||||
|
_credentialManager.Verify(x => x.LoadCredentials(true), Times.Exactly(2));
|
||||||
|
|
||||||
|
Assert.False(hc.AllowAuthMigration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task TestRunnerEnableAuthMigrationByDefault()
|
||||||
|
{
|
||||||
|
using (var hc = new TestHostContext(this))
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
var runner = new Runner.Listener.Runner();
|
||||||
|
hc.SetSingleton<IConfigurationManager>(_configurationManager.Object);
|
||||||
|
hc.SetSingleton<IJobNotification>(_jobNotification.Object);
|
||||||
|
hc.SetSingleton<IMessageListener>(_messageListener.Object);
|
||||||
|
hc.SetSingleton<IPromptManager>(_promptManager.Object);
|
||||||
|
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
|
||||||
|
hc.SetSingleton<ICredentialManager>(_credentialManager.Object);
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
|
hc.EnqueueInstance<IErrorThrottler>(_acquireJobThrottler.Object);
|
||||||
|
|
||||||
|
runner.Initialize(hc);
|
||||||
|
var settings = new RunnerSettings
|
||||||
|
{
|
||||||
|
PoolId = 43242,
|
||||||
|
AgentId = 5678,
|
||||||
|
Ephemeral = true,
|
||||||
|
ServerUrl = "https://github.com",
|
||||||
|
};
|
||||||
|
|
||||||
|
var message1 = new TaskAgentMessage()
|
||||||
|
{
|
||||||
|
Body = JsonUtility.ToString(new RunnerJobRequestRef() { BillingOwnerId = "github", RunnerRequestId = "999", RunServiceUrl = "https://run-service.com" }),
|
||||||
|
MessageId = 4234,
|
||||||
|
MessageType = JobRequestMessageTypes.RunnerJobRequest
|
||||||
|
};
|
||||||
|
|
||||||
|
var messages = new Queue<TaskAgentMessage>();
|
||||||
|
messages.Enqueue(message1);
|
||||||
|
messages.Enqueue(message1);
|
||||||
|
_updater.Setup(x => x.SelfUpdate(It.IsAny<AgentRefreshMessage>(), It.IsAny<IJobDispatcher>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(Task.FromResult(true));
|
||||||
|
_configurationManager.Setup(x => x.LoadSettings())
|
||||||
|
.Returns(settings);
|
||||||
|
_configurationManager.Setup(x => x.IsConfigured())
|
||||||
|
.Returns(true);
|
||||||
|
_messageListener.Setup(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(Task.FromResult<CreateSessionResult>(CreateSessionResult.Failure));
|
||||||
|
_jobNotification.Setup(x => x.StartClient(It.IsAny<String>()))
|
||||||
|
.Callback(() =>
|
||||||
|
{
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
var throwError = true;
|
||||||
|
_runServer.Setup(x => x.GetJobMessageAsync("999", "github", It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(() =>
|
||||||
|
{
|
||||||
|
if (throwError)
|
||||||
|
{
|
||||||
|
Assert.True(hc.AllowAuthMigration);
|
||||||
|
throwError = false;
|
||||||
|
throw new NotSupportedException("some error");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult(CreateJobRequestMessage("test"));
|
||||||
|
});
|
||||||
|
|
||||||
|
_credentialManager.Setup(x => x.LoadCredentials(true)).Returns(new VssCredentials());
|
||||||
|
|
||||||
|
_configStore.Setup(x => x.IsServiceConfigured()).Returns(false);
|
||||||
|
|
||||||
|
var credData = new CredentialData()
|
||||||
|
{
|
||||||
|
Scheme = Constants.Configuration.OAuth,
|
||||||
|
};
|
||||||
|
credData.Data["ClientId"] = "testClientId";
|
||||||
|
credData.Data["AuthUrl"] = "https://github.com";
|
||||||
|
credData.Data["EnableAuthMigrationByDefault"] = "true";
|
||||||
|
_configStore.Setup(x => x.GetCredentials()).Returns(credData);
|
||||||
|
|
||||||
|
Assert.False(hc.AllowAuthMigration);
|
||||||
|
|
||||||
|
//Act
|
||||||
|
var command = new CommandSettings(hc, new string[] { "run" });
|
||||||
|
var returnCode = await runner.ExecuteCommand(command);
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
Assert.Equal(Constants.Runner.ReturnCode.TerminatedError, returnCode);
|
||||||
|
|
||||||
|
_messageListener.Verify(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()), Times.Once());
|
||||||
|
|
||||||
|
Assert.True(hc.AllowAuthMigration);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
20
src/Test/L0/Sdk/RSWebApi/RunServiceHttpClientL0.cs
Normal file
20
src/Test/L0/Sdk/RSWebApi/RunServiceHttpClientL0.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using GitHub.Actions.RunService.WebApi;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace GitHub.Actions.RunService.WebApi.Tests;
|
||||||
|
|
||||||
|
public sealed class RunServiceHttpClientL0
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Truncate()
|
||||||
|
{
|
||||||
|
TestTruncate(string.Empty.PadLeft(199, 'a'), string.Empty.PadLeft(199, 'a'));
|
||||||
|
TestTruncate(string.Empty.PadLeft(200, 'a'), string.Empty.PadLeft(200, 'a'));
|
||||||
|
TestTruncate(string.Empty.PadLeft(201, 'a'), string.Empty.PadLeft(200, 'a') + "[truncated]");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TestTruncate(string errorBody, string expected)
|
||||||
|
{
|
||||||
|
Assert.Equal(expected, RunServiceHttpClient.Truncate(errorBody));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,15 @@
|
|||||||
using GitHub.Runner.Common.Util;
|
using System;
|
||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Reflection;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.Loader;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Runtime.Loader;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using GitHub.DistributedTask.Logging;
|
using GitHub.DistributedTask.Logging;
|
||||||
using System.Net.Http.Headers;
|
|
||||||
using GitHub.Runner.Sdk;
|
using GitHub.Runner.Sdk;
|
||||||
|
|
||||||
namespace GitHub.Runner.Common.Tests
|
namespace GitHub.Runner.Common.Tests
|
||||||
@@ -31,6 +30,7 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
private StartupType _startupType;
|
private StartupType _startupType;
|
||||||
public event EventHandler Unloading;
|
public event EventHandler Unloading;
|
||||||
public event EventHandler<DelayEventArgs> Delaying;
|
public event EventHandler<DelayEventArgs> Delaying;
|
||||||
|
public event EventHandler<AuthMigrationEventArgs> AuthMigrationChanged;
|
||||||
public CancellationToken RunnerShutdownToken => _runnerShutdownTokenSource.Token;
|
public CancellationToken RunnerShutdownToken => _runnerShutdownTokenSource.Token;
|
||||||
public ShutdownReason RunnerShutdownReason { get; private set; }
|
public ShutdownReason RunnerShutdownReason { get; private set; }
|
||||||
public ISecretMasker SecretMasker => _secretMasker;
|
public ISecretMasker SecretMasker => _secretMasker;
|
||||||
@@ -92,6 +92,8 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
|
|
||||||
public RunnerWebProxy WebProxy => new();
|
public RunnerWebProxy WebProxy => new();
|
||||||
|
|
||||||
|
public bool AllowAuthMigration { get; set; }
|
||||||
|
|
||||||
public async Task Delay(TimeSpan delay, CancellationToken token)
|
public async Task Delay(TimeSpan delay, CancellationToken token)
|
||||||
{
|
{
|
||||||
// Event callback
|
// Event callback
|
||||||
@@ -101,8 +103,8 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
handler(this, new DelayEventArgs(delay, token));
|
handler(this, new DelayEventArgs(delay, token));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delay zero
|
// Delay 10ms
|
||||||
await Task.Delay(TimeSpan.Zero);
|
await Task.Delay(TimeSpan.FromMilliseconds(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
public T CreateService<T>() where T : class, IRunnerService
|
public T CreateService<T>() where T : class, IRunnerService
|
||||||
@@ -337,7 +339,7 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
return _traceManager[name];
|
return _traceManager[name];
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ShutdownRunner(ShutdownReason reason)
|
public void ShutdownRunner(ShutdownReason reason, TimeSpan delay = default)
|
||||||
{
|
{
|
||||||
ArgUtil.NotNull(reason, nameof(reason));
|
ArgUtil.NotNull(reason, nameof(reason));
|
||||||
RunnerShutdownReason = reason;
|
RunnerShutdownReason = reason;
|
||||||
@@ -387,6 +389,18 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void EnableAuthMigration(string trace)
|
||||||
|
{
|
||||||
|
AllowAuthMigration = true;
|
||||||
|
AuthMigrationChanged?.Invoke(this, new AuthMigrationEventArgs(trace));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeferAuthMigration(TimeSpan deferred, string trace)
|
||||||
|
{
|
||||||
|
AllowAuthMigration = false;
|
||||||
|
AuthMigrationChanged?.Invoke(this, new AuthMigrationEventArgs(trace));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DelayEventArgs : EventArgs
|
public class DelayEventArgs : EventArgs
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ LAYOUT_DIR="$SCRIPT_DIR/../_layout"
|
|||||||
DOWNLOAD_DIR="$SCRIPT_DIR/../_downloads/netcore2x"
|
DOWNLOAD_DIR="$SCRIPT_DIR/../_downloads/netcore2x"
|
||||||
PACKAGE_DIR="$SCRIPT_DIR/../_package"
|
PACKAGE_DIR="$SCRIPT_DIR/../_package"
|
||||||
DOTNETSDK_ROOT="$SCRIPT_DIR/../_dotnetsdk"
|
DOTNETSDK_ROOT="$SCRIPT_DIR/../_dotnetsdk"
|
||||||
DOTNETSDK_VERSION="8.0.407"
|
DOTNETSDK_VERSION="8.0.408"
|
||||||
DOTNETSDK_INSTALLDIR="$DOTNETSDK_ROOT/$DOTNETSDK_VERSION"
|
DOTNETSDK_INSTALLDIR="$DOTNETSDK_ROOT/$DOTNETSDK_VERSION"
|
||||||
RUNNER_VERSION=$(cat runnerversion)
|
RUNNER_VERSION=$(cat runnerversion)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"sdk": {
|
"sdk": {
|
||||||
"version": "8.0.407"
|
"version": "8.0.408"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user