Compare commits

..

2 Commits

Author SHA1 Message Date
Luke Tomlinson
a755837bc8 more fallback 2025-04-07 15:07:41 -07:00
Luke Tomlinson
08b22bff1d start work for legacy url 2025-04-07 14:49:25 -07:00
30 changed files with 335 additions and 1996 deletions

View File

@@ -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.408" "version": "8.0.407"
}, },
"ghcr.io/devcontainers/features/node:1": { "ghcr.io/devcontainers/features/node:1": {
"version": "20" "version": "20"

View File

@@ -20,6 +20,7 @@ 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, '')
@@ -29,7 +30,7 @@ jobs:
return return
} }
try { try {
const release = await github.rest.repos.getReleaseByTag({ const release = await github.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
@@ -175,6 +176,7 @@ 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)

View File

@@ -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.7.0 ARG RUNNER_CONTAINER_HOOKS_VERSION=0.6.1
ARG DOCKER_VERSION=28.0.1 ARG DOCKER_VERSION=28.0.1
ARG BUILDX_VERSION=0.21.2 ARG BUILDX_VERSION=0.21.2

View File

@@ -1,13 +0,0 @@
using System;
namespace GitHub.Runner.Common
{
public class AuthMigrationEventArgs : EventArgs
{
public AuthMigrationEventArgs(string trace)
{
Trace = trace;
}
public string Trace { get; private set; }
}
}

View File

@@ -37,7 +37,6 @@ 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);
@@ -89,12 +88,7 @@ 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)

View File

@@ -161,6 +161,7 @@ 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";
} }
@@ -257,7 +258,6 @@ 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

View File

@@ -34,14 +34,9 @@ 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, TimeSpan delay = default); void ShutdownRunner(ShutdownReason reason);
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
@@ -74,24 +69,13 @@ 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 => _shutdownReason; public ShutdownReason RunnerShutdownReason { get; private set; }
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.
@@ -223,71 +207,6 @@ 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))
@@ -575,28 +494,12 @@ namespace GitHub.Runner.Common
} }
public void ShutdownRunner(ShutdownReason reason, TimeSpan delay = default) public void ShutdownRunner(ShutdownReason reason)
{ {
ArgUtil.NotNull(reason, nameof(reason)); ArgUtil.NotNull(reason, nameof(reason));
_trace.Info($"Runner will be shutdown for {reason.ToString()} after {delay.TotalSeconds} seconds."); _trace.Info($"Runner will be shutdown for {reason.ToString()}");
if (Interlocked.CompareExchange(ref _shutdownReasonSet, 1, 0) == 0) RunnerShutdownReason = reason;
{ _runnerShutdownTokenSource.Cancel();
// 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();
}
else
{
_runnerShutdownTokenSource.CancelAfter(delay);
}
} }
public override void Dispose() public override void Dispose()
@@ -646,18 +549,6 @@ 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();

View File

@@ -32,6 +32,18 @@ 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);
} }
@@ -70,6 +82,7 @@ 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,
@@ -81,6 +94,23 @@ 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(

View File

@@ -26,18 +26,15 @@ 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)
{ {
@@ -46,7 +43,6 @@ 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)
@@ -67,7 +63,8 @@ namespace GitHub.Runner.Listener
// Create connection. // Create connection.
Trace.Info("Loading Credentials"); Trace.Info("Loading Credentials");
_creds = _credMgr.LoadCredentials(allowAuthUrlV2: false); var credMgr = HostContext.GetService<ICredentialManager>();
_creds = credMgr.LoadCredentials();
var agent = new TaskAgentReference var agent = new TaskAgentReference
{ {
@@ -90,8 +87,7 @@ namespace GitHub.Runner.Listener
try try
{ {
Trace.Info("Connecting to the Broker Server..."); Trace.Info("Connecting to the Broker Server...");
_credsV2 = _credMgr.LoadCredentials(allowAuthUrlV2: true); await _brokerServer.ConnectAsync(new Uri(serverUrlV2), _creds);
await _brokerServer.ConnectAsync(new Uri(serverUrlV2), _credsV2);
Trace.Info("VssConnection created"); Trace.Info("VssConnection created");
if (!string.IsNullOrEmpty(serverUrl) && if (!string.IsNullOrEmpty(serverUrl) &&
@@ -116,13 +112,6 @@ 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)
@@ -141,7 +130,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 && _credsV2.Federated is VssOAuthCredential vssOAuthCred) if (ex is VssOAuthTokenRequestException vssOAuthEx && _creds.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))
@@ -172,12 +161,6 @@ 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.");
@@ -194,11 +177,6 @@ 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)))
@@ -241,13 +219,6 @@ 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,
@@ -327,12 +298,6 @@ 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();
@@ -364,7 +329,7 @@ namespace GitHub.Runner.Listener
} }
} }
public async Task RefreshListenerTokenAsync() public async Task RefreshListenerTokenAsync(CancellationToken cancellationToken)
{ {
await RefreshBrokerConnectionAsync(); await RefreshBrokerConnectionAsync();
} }
@@ -467,16 +432,17 @@ namespace GitHub.Runner.Listener
private async Task RefreshBrokerConnectionAsync() private async Task RefreshBrokerConnectionAsync()
{ {
Trace.Info("Reload credentials."); var configManager = HostContext.GetService<IConfigurationManager>();
_credsV2 = _credMgr.LoadCredentials(allowAuthUrlV2: true); _settings = configManager.LoadSettings();
await _brokerServer.ConnectAsync(new Uri(_settings.ServerUrlV2), _credsV2);
Trace.Info("Connection to Broker Server recreated.");
}
private void HandleAuthMigrationChanged(object sender, EventArgs e) if (string.IsNullOrEmpty(_settings.ServerUrlV2))
{ {
Trace.Info($"Auth migration changed. Current allow auth migration state: {HostContext.AllowAuthMigration}"); throw new InvalidOperationException("ServerUrlV2 is not set");
_needRefreshCredsV2 = true; }
var credMgr = HostContext.GetService<ICredentialManager>();
VssCredentials creds = credMgr.LoadCredentials();
await _brokerServer.ConnectAsync(new Uri(_settings.ServerUrlV2), creds);
} }
} }
} }

View File

@@ -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, allowAuthUrlV2: false); creds = credProvider.GetVssCredentials(HostContext);
Trace.Info("legacy vss cred retrieved"); Trace.Info("legacy vss cred retrieved");
} }
else else
@@ -135,7 +135,8 @@ 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);
runnerSettings.ServerUrl = authResult.TenantUrl; var hasLegacyUrl = !string.IsNullOrEmpty(authResult.LegacyUrl);
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();
@@ -366,7 +367,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", true).ToString() } { "requireFipsCryptography", agent.Properties.GetValue("RequireFipsCryptography", false).ToString() }
}, },
}; };
@@ -384,7 +385,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(allowAuthUrlV2: false); VssCredentials credential = credMgr.LoadCredentials();
try try
{ {
await _runnerServer.ConnectAsync(new Uri(runnerSettings.ServerUrl), credential); await _runnerServer.ConnectAsync(new Uri(runnerSettings.ServerUrl), credential);
@@ -519,7 +520,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, allowAuthUrlV2: false); creds = credProvider.GetVssCredentials(HostContext);
Trace.Info("legacy vss cred retrieved"); Trace.Info("legacy vss cred retrieved");
} }
else else

View File

@@ -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(bool allowAuthUrlV2); VssCredentials LoadCredentials();
} }
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(bool allowAuthUrlV2) public VssCredentials LoadCredentials()
{ {
IConfigurationStore store = HostContext.GetService<IConfigurationStore>(); IConfigurationStore store = HostContext.GetService<IConfigurationStore>();
@@ -51,16 +51,21 @@ 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, allowAuthUrlV2); VssCredentials creds = credProv.GetVssCredentials(HostContext);
return creds; return creds;
} }
@@ -91,14 +96,23 @@ 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
{ {

View File

@@ -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, bool allowAuthUrlV2); VssCredentials GetVssCredentials(IHostContext context);
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, bool allowAuthUrlV2); public abstract VssCredentials GetVssCredentials(IHostContext context);
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, bool allowAuthUrlV2) public override VssCredentials GetVssCredentials(IHostContext context)
{ {
ArgUtil.NotNull(context, nameof(context)); ArgUtil.NotNull(context, nameof(context));
Tracing trace = context.GetTrace(nameof(OAuthAccessTokenCredential)); Tracing trace = context.GetTrace(nameof(OAuthAccessTokenCredential));

View File

@@ -22,18 +22,10 @@ namespace GitHub.Runner.Listener.Configuration
// Nothing to verify here // Nothing to verify here
} }
public override VssCredentials GetVssCredentials(IHostContext context, bool allowAuthUrlV2) public override VssCredentials GetVssCredentials(IHostContext context)
{ {
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);

View File

@@ -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(); Task RefreshListenerTokenAsync(CancellationToken token);
void OnJobStatus(object sender, JobStatusEventArgs e); void OnJobStatus(object sender, JobStatusEventArgs e);
} }
@@ -44,7 +44,6 @@ 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;
@@ -55,9 +54,8 @@ 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 _needRefreshCredsV2 = false; private bool _isBrokerSession = false;
private bool _handlerInitialized = false;
public override void Initialize(IHostContext hostContext) public override void Initialize(IHostContext hostContext)
{ {
@@ -66,7 +64,6 @@ 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)
@@ -81,7 +78,8 @@ namespace GitHub.Runner.Listener
// Create connection. // Create connection.
Trace.Info("Loading Credentials"); Trace.Info("Loading Credentials");
_creds = _credMgr.LoadCredentials(allowAuthUrlV2: false); var credMgr = HostContext.GetService<ICredentialManager>();
_creds = credMgr.LoadCredentials();
var agent = new TaskAgentReference var agent = new TaskAgentReference
{ {
@@ -115,6 +113,16 @@ 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)
{ {
@@ -123,13 +131,6 @@ 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)
@@ -195,16 +196,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
@@ -260,19 +261,12 @@ 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);
_credsV2 = _credMgr.LoadCredentials(allowAuthUrlV2: true); await _brokerServer.UpdateConnectionIfNeeded(migrationMessage.BrokerBaseUrl, _creds);
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,
@@ -328,10 +322,6 @@ 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))
{ {
@@ -364,12 +354,6 @@ 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));
@@ -427,11 +411,10 @@ namespace GitHub.Runner.Listener
} }
} }
public async Task RefreshListenerTokenAsync() public async Task RefreshListenerTokenAsync(CancellationToken cancellationToken)
{ {
await _runnerServer.RefreshConnectionAsync(RunnerConnectionType.MessageQueue, TimeSpan.FromSeconds(60)); await _runnerServer.RefreshConnectionAsync(RunnerConnectionType.MessageQueue, TimeSpan.FromSeconds(60));
_credsV2 = _credMgr.LoadCredentials(allowAuthUrlV2: true); await _brokerServer.ForceRefreshConnection(_creds);
await _brokerServer.ForceRefreshConnection(_credsV2);
} }
private TaskAgentMessage DecryptMessage(TaskAgentMessage message) private TaskAgentMessage DecryptMessage(TaskAgentMessage message)
@@ -562,11 +545,5 @@ 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;
}
} }
} }

View File

@@ -1,5 +1,4 @@
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;
@@ -32,13 +31,6 @@ 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.
@@ -59,7 +51,6 @@ 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)
@@ -75,8 +66,6 @@ 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>();
@@ -311,21 +300,6 @@ 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);
} }
@@ -337,8 +311,6 @@ 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();
@@ -347,10 +319,9 @@ 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, GetShutdownDelay()); HostContext.ShutdownRunner(ShutdownReason.UserCancelled);
_completedCommand.WaitOne(Constants.Runner.ExitOnUnloadTimeout); _completedCommand.WaitOne(Constants.Runner.ExitOnUnloadTimeout);
} }
} }
@@ -358,7 +329,6 @@ 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();
@@ -381,27 +351,15 @@ namespace GitHub.Runner.Listener
reason = ShutdownReason.UserCancelled; reason = ShutdownReason.UserCancelled;
} }
HostContext.ShutdownRunner(reason, GetShutdownDelay()); HostContext.ShutdownRunner(reason);
} }
else else
{ {
HostContext.ShutdownRunner(ShutdownReason.UserCancelled, GetShutdownDelay()); HostContext.ShutdownRunner(ShutdownReason.UserCancelled);
} }
} }
} }
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)
@@ -452,13 +410,9 @@ 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.GetService<IJobDispatcher>(); jobDispatcher = HostContext.CreateService<IJobDispatcher>();
jobDispatcher.JobStatus += _listener.OnJobStatus; jobDispatcher.JobStatus += _listener.OnJobStatus;
if (_hasTerminationGracePeriod)
{
jobDispatcher.JobStatus += HandleJobStatusEvent;
}
while (!HostContext.RunnerShutdownToken.IsCancellationRequested) while (!HostContext.RunnerShutdownToken.IsCancellationRequested)
{ {
@@ -616,18 +570,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), credsV2); await runServer.ConnectAsync(new Uri(messageRef.RunServiceUrl), creds);
try try
{ {
jobRequestMessage = await runServer.GetJobMessageAsync(messageRef.RunnerRequestId, messageRef.BillingOwnerId, messageQueueLoopTokenSource.Token); jobRequestMessage = await runServer.GetJobMessageAsync(messageRef.RunnerRequestId, messageRef.BillingOwnerId, messageQueueLoopTokenSource.Token);
@@ -645,13 +599,6 @@ 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;
} }
} }
@@ -686,7 +633,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(); await _listener.RefreshListenerTokenAsync(messageQueueLoopTokenSource.Token);
} }
else if (string.Equals(message.MessageType, RunnerRefreshConfigMessage.MessageType)) else if (string.Equals(message.MessageType, RunnerRefreshConfigMessage.MessageType))
{ {
@@ -729,10 +676,6 @@ 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();
} }
@@ -773,101 +716,6 @@ 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;

View File

@@ -197,31 +197,11 @@ 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)

View File

@@ -318,17 +318,24 @@ namespace GitHub.Runner.Worker
{ {
try try
{ {
await runServer.CompleteJobAsync(message.Plan.PlanId, message.JobId, result, jobContext.JobOutputs, jobContext.Global.StepsResult, jobContext.Global.JobAnnotations, environmentUrl, telemetry, billingOwnerId: message.BillingOwnerId, default); 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);
}
return result; return result;
} }
catch (VssUnauthorizedException ex) catch (VssUnauthorizedException ex) when (jobContext.Global.Variables.GetBoolean(Constants.Runner.Features.SkipRetryCompleteJobUponKnownErrors) ?? false)
{ {
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) catch (TaskOrchestrationJobNotFoundException ex) when (jobContext.Global.Variables.GetBoolean(Constants.Runner.Features.SkipRetryCompleteJobUponKnownErrors) ?? false)
{ {
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);

View File

@@ -253,12 +253,11 @@ namespace GitHub.Actions.RunService.WebApi
return false; return false;
} }
internal static string Truncate(string errorBody) private static string Truncate(string errorBody)
{ {
const int maxLength = 200; if (errorBody.Length > 100)
if (errorBody.Length > maxLength)
{ {
return errorBody.Substring(0, maxLength) + "[truncated]"; return errorBody.Substring(0, 100) + "[truncated]";
} }
return errorBody; return errorBody;

View File

@@ -25,10 +25,7 @@ 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
@@ -289,7 +286,6 @@ 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:

View File

@@ -166,21 +166,6 @@ 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())

View File

@@ -1,10 +1,10 @@
using System; using GitHub.Runner.Common.Util;
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,133 +172,6 @@ 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();

View File

@@ -1,5 +1,4 @@
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;
@@ -19,6 +18,8 @@ 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()
{ {
@@ -26,6 +27,7 @@ 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>();
} }
@@ -33,7 +35,7 @@ namespace GitHub.Runner.Common.Tests.Listener
[Fact] [Fact]
[Trait("Level", "L0")] [Trait("Level", "L0")]
[Trait("Category", "Runner")] [Trait("Category", "Runner")]
public async Task CreatesSession() public async void CreatesSession()
{ {
using (TestHostContext tc = CreateTestContext()) using (TestHostContext tc = CreateTestContext())
using (var tokenSource = new CancellationTokenSource()) using (var tokenSource = new CancellationTokenSource())
@@ -48,7 +50,9 @@ namespace GitHub.Runner.Common.Tests.Listener
tokenSource.Token)) tokenSource.Token))
.Returns(Task.FromResult(expectedSession)); .Returns(Task.FromResult(expectedSession));
_credMgr.Setup(x => x.LoadCredentials(true)).Returns(new VssCredentials()); _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. // Act.
BrokerMessageListener listener = new(); BrokerMessageListener listener = new();
@@ -66,308 +70,12 @@ 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;

View File

@@ -1,18 +1,14 @@
using System.Collections.Generic; using GitHub.Runner.Listener;
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, bool allowAuthUrlV2) public override VssCredentials GetVssCredentials(IHostContext context)
{ {
Tracing trace = context.GetTrace("OuthAccessToken"); Tracing trace = context.GetTrace("OuthAccessToken");
trace.Info("GetVssCredentials()"); trace.Info("GetVssCredentials()");
@@ -27,85 +23,4 @@ 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);
}
}
}
}

View File

@@ -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 Task CreatesSession() public async void 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(It.IsAny<bool>())).Returns(new VssCredentials()); _credMgr.Setup(x => x.LoadCredentials()).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,7 +95,69 @@ namespace GitHub.Runner.Common.Tests.Listener
[Fact] [Fact]
[Trait("Level", "L0")] [Trait("Level", "L0")]
[Trait("Category", "Runner")] [Trait("Category", "Runner")]
public async Task DeleteSession() public async void CreatesSessionWithBrokerMigration()
{
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())
@@ -115,7 +177,7 @@ namespace GitHub.Runner.Common.Tests.Listener
tokenSource.Token)) tokenSource.Token))
.Returns(Task.FromResult(expectedSession)); .Returns(Task.FromResult(expectedSession));
_credMgr.Setup(x => x.LoadCredentials(It.IsAny<bool>())).Returns(new VssCredentials()); _credMgr.Setup(x => x.LoadCredentials()).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));
@@ -142,7 +204,84 @@ namespace GitHub.Runner.Common.Tests.Listener
[Fact] [Fact]
[Trait("Level", "L0")] [Trait("Level", "L0")]
[Trait("Category", "Runner")] [Trait("Category", "Runner")]
public async Task GetNextMessage() public async void DeleteSessionWithBrokerMigration()
{
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())
@@ -162,7 +301,7 @@ namespace GitHub.Runner.Common.Tests.Listener
tokenSource.Token)) tokenSource.Token))
.Returns(Task.FromResult(expectedSession)); .Returns(Task.FromResult(expectedSession));
_credMgr.Setup(x => x.LoadCredentials(It.IsAny<bool>())).Returns(new VssCredentials()); _credMgr.Setup(x => x.LoadCredentials()).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));
@@ -223,7 +362,7 @@ namespace GitHub.Runner.Common.Tests.Listener
[Fact] [Fact]
[Trait("Level", "L0")] [Trait("Level", "L0")]
[Trait("Category", "Runner")] [Trait("Category", "Runner")]
public async Task GetNextMessageWithBrokerMigration() public async void GetNextMessageWithBrokerMigration()
{ {
using (TestHostContext tc = CreateTestContext()) using (TestHostContext tc = CreateTestContext())
using (var tokenSource = new CancellationTokenSource()) using (var tokenSource = new CancellationTokenSource())
@@ -243,7 +382,7 @@ namespace GitHub.Runner.Common.Tests.Listener
tokenSource.Token)) tokenSource.Token))
.Returns(Task.FromResult(expectedSession)); .Returns(Task.FromResult(expectedSession));
_credMgr.Setup(x => x.LoadCredentials(It.IsAny<bool>())).Returns(new VssCredentials()); _credMgr.Setup(x => x.LoadCredentials()).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));
@@ -323,22 +462,13 @@ 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 Task CreateSessionWithOriginalCredential() public async void CreateSessionWithOriginalCredential()
{ {
using (TestHostContext tc = CreateTestContext()) using (TestHostContext tc = CreateTestContext())
using (var tokenSource = new CancellationTokenSource()) using (var tokenSource = new CancellationTokenSource())
@@ -354,7 +484,7 @@ namespace GitHub.Runner.Common.Tests.Listener
tokenSource.Token)) tokenSource.Token))
.Returns(Task.FromResult(expectedSession)); .Returns(Task.FromResult(expectedSession));
_credMgr.Setup(x => x.LoadCredentials(It.IsAny<bool>())).Returns(new VssCredentials()); _credMgr.Setup(x => x.LoadCredentials()).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";
@@ -383,7 +513,7 @@ namespace GitHub.Runner.Common.Tests.Listener
[Fact] [Fact]
[Trait("Level", "L0")] [Trait("Level", "L0")]
[Trait("Category", "Runner")] [Trait("Category", "Runner")]
public async Task SkipDeleteSession_WhenGetNextMessageGetTaskAgentAccessTokenExpiredException() public async void SkipDeleteSession_WhenGetNextMessageGetTaskAgentAccessTokenExpiredException()
{ {
using (TestHostContext tc = CreateTestContext()) using (TestHostContext tc = CreateTestContext())
using (var tokenSource = new CancellationTokenSource()) using (var tokenSource = new CancellationTokenSource())
@@ -403,7 +533,7 @@ namespace GitHub.Runner.Common.Tests.Listener
tokenSource.Token)) tokenSource.Token))
.Returns(Task.FromResult(expectedSession)); .Returns(Task.FromResult(expectedSession));
_credMgr.Setup(x => x.LoadCredentials(It.IsAny<bool>())).Returns(new VssCredentials()); _credMgr.Setup(x => x.LoadCredentials()).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));
@@ -441,301 +571,5 @@ 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);
}
}
} }
} }

View File

@@ -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.Common;
using GitHub.Runner.Common.Tests;
using GitHub.Runner.Listener; using GitHub.Runner.Listener;
using GitHub.Runner.Common;
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,7 +226,6 @@ 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);
} }
} }
@@ -307,7 +306,7 @@ namespace GitHub.Runner.Tests.Listener
[Fact] [Fact]
[Trait("Level", "L0")] [Trait("Level", "L0")]
[Trait("Category", "Runner")] [Trait("Category", "Runner")]
public async Task UpdateRunnerConfigAsync_RefreshRunnerCredentialsFailure_ShouldReportTelemetry() public async Task UpdateRunnerConfigAsync_RefreshRunnerCredetialsFailure_ShouldReportTelemetry()
{ {
using (var hc = new TestHostContext(this)) using (var hc = new TestHostContext(this))
{ {
@@ -511,56 +510,6 @@ 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")]
@@ -626,53 +575,5 @@ 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);
}
}
} }
} }

View File

@@ -1,15 +1,13 @@
using System; using GitHub.DistributedTask.WebApi;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Listener; using GitHub.Runner.Listener;
using GitHub.Runner.Listener.Configuration; using GitHub.Runner.Listener.Configuration;
using GitHub.Services.Common;
using GitHub.Services.WebApi;
using Moq; using Moq;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Xunit; using Xunit;
using GitHub.Services.WebApi;
using Pipelines = GitHub.DistributedTask.Pipelines; using Pipelines = GitHub.DistributedTask.Pipelines;
namespace GitHub.Runner.Common.Tests.Listener namespace GitHub.Runner.Common.Tests.Listener
@@ -26,9 +24,6 @@ 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()
{ {
@@ -42,9 +37,6 @@ 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)
@@ -65,7 +57,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 Task TestRunAsync() public async void TestRunAsync()
{ {
using (var hc = new TestHostContext(this)) using (var hc = new TestHostContext(this))
{ {
@@ -126,7 +118,7 @@ namespace GitHub.Runner.Common.Tests.Listener
}); });
hc.SetSingleton<IJobDispatcher>(_jobDispatcher.Object); hc.EnqueueInstance<IJobDispatcher>(_jobDispatcher.Object);
_configStore.Setup(x => x.IsServiceConfigured()).Returns(false); _configStore.Setup(x => x.IsServiceConfigured()).Returns(false);
//Act //Act
@@ -177,7 +169,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 Task TestExecuteCommandForRunAsService(string[] args, bool configureAsService, Times expectedTimes) public async void TestExecuteCommandForRunAsService(string[] args, bool configureAsService, Times expectedTimes)
{ {
using (var hc = new TestHostContext(this)) using (var hc = new TestHostContext(this))
{ {
@@ -185,7 +177,6 @@ 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);
@@ -210,7 +201,7 @@ namespace GitHub.Runner.Common.Tests.Listener
[Fact] [Fact]
[Trait("Level", "L0")] [Trait("Level", "L0")]
[Trait("Category", "Runner")] [Trait("Category", "Runner")]
public async Task TestMachineProvisionerCLI() public async void TestMachineProvisionerCLI()
{ {
using (var hc = new TestHostContext(this)) using (var hc = new TestHostContext(this))
{ {
@@ -218,7 +209,6 @@ 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" });
@@ -245,7 +235,7 @@ namespace GitHub.Runner.Common.Tests.Listener
[Fact] [Fact]
[Trait("Level", "L0")] [Trait("Level", "L0")]
[Trait("Category", "Runner")] [Trait("Category", "Runner")]
public async Task TestRunOnce() public async void TestRunOnce()
{ {
using (var hc = new TestHostContext(this)) using (var hc = new TestHostContext(this))
{ {
@@ -309,7 +299,7 @@ namespace GitHub.Runner.Common.Tests.Listener
}); });
hc.SetSingleton<IJobDispatcher>(_jobDispatcher.Object); hc.EnqueueInstance<IJobDispatcher>(_jobDispatcher.Object);
_configStore.Setup(x => x.IsServiceConfigured()).Returns(false); _configStore.Setup(x => x.IsServiceConfigured()).Returns(false);
//Act //Act
@@ -342,7 +332,7 @@ namespace GitHub.Runner.Common.Tests.Listener
[Fact] [Fact]
[Trait("Level", "L0")] [Trait("Level", "L0")]
[Trait("Category", "Runner")] [Trait("Category", "Runner")]
public async Task TestRunOnceOnlyTakeOneJobMessage() public async void TestRunOnceOnlyTakeOneJobMessage()
{ {
using (var hc = new TestHostContext(this)) using (var hc = new TestHostContext(this))
{ {
@@ -413,7 +403,7 @@ namespace GitHub.Runner.Common.Tests.Listener
}); });
hc.SetSingleton<IJobDispatcher>(_jobDispatcher.Object); hc.EnqueueInstance<IJobDispatcher>(_jobDispatcher.Object);
_configStore.Setup(x => x.IsServiceConfigured()).Returns(false); _configStore.Setup(x => x.IsServiceConfigured()).Returns(false);
//Act //Act
@@ -443,7 +433,7 @@ namespace GitHub.Runner.Common.Tests.Listener
[Fact] [Fact]
[Trait("Level", "L0")] [Trait("Level", "L0")]
[Trait("Category", "Runner")] [Trait("Category", "Runner")]
public async Task TestRunOnceHandleUpdateMessage() public async void TestRunOnceHandleUpdateMessage()
{ {
using (var hc = new TestHostContext(this)) using (var hc = new TestHostContext(this))
{ {
@@ -503,7 +493,7 @@ namespace GitHub.Runner.Common.Tests.Listener
}); });
hc.SetSingleton<IJobDispatcher>(_jobDispatcher.Object); hc.EnqueueInstance<IJobDispatcher>(_jobDispatcher.Object);
_configStore.Setup(x => x.IsServiceConfigured()).Returns(false); _configStore.Setup(x => x.IsServiceConfigured()).Returns(false);
//Act //Act
@@ -533,14 +523,13 @@ namespace GitHub.Runner.Common.Tests.Listener
[Fact] [Fact]
[Trait("Level", "L0")] [Trait("Level", "L0")]
[Trait("Category", "Runner")] [Trait("Category", "Runner")]
public async Task TestRemoveLocalRunnerConfig() public async void 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" });
@@ -560,521 +549,5 @@ 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);
}
}
} }
} }

View File

@@ -1,20 +0,0 @@
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));
}
}

View File

@@ -1,15 +1,16 @@
using System; using GitHub.Runner.Common.Util;
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
@@ -30,7 +31,6 @@ 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,8 +92,6 @@ 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
@@ -103,8 +101,8 @@ namespace GitHub.Runner.Common.Tests
handler(this, new DelayEventArgs(delay, token)); handler(this, new DelayEventArgs(delay, token));
} }
// Delay 10ms // Delay zero
await Task.Delay(TimeSpan.FromMilliseconds(10)); await Task.Delay(TimeSpan.Zero);
} }
public T CreateService<T>() where T : class, IRunnerService public T CreateService<T>() where T : class, IRunnerService
@@ -339,7 +337,7 @@ namespace GitHub.Runner.Common.Tests
return _traceManager[name]; return _traceManager[name];
} }
public void ShutdownRunner(ShutdownReason reason, TimeSpan delay = default) public void ShutdownRunner(ShutdownReason reason)
{ {
ArgUtil.NotNull(reason, nameof(reason)); ArgUtil.NotNull(reason, nameof(reason));
RunnerShutdownReason = reason; RunnerShutdownReason = reason;
@@ -389,18 +387,6 @@ 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

View File

@@ -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.408" DOTNETSDK_VERSION="8.0.407"
DOTNETSDK_INSTALLDIR="$DOTNETSDK_ROOT/$DOTNETSDK_VERSION" DOTNETSDK_INSTALLDIR="$DOTNETSDK_ROOT/$DOTNETSDK_VERSION"
RUNNER_VERSION=$(cat runnerversion) RUNNER_VERSION=$(cat runnerversion)

View File

@@ -1,5 +1,5 @@
{ {
"sdk": { "sdk": {
"version": "8.0.408" "version": "8.0.407"
} }
} }