Support refresh runner configs with pipelines service. (#3706)

This commit is contained in:
Tingluo Huang
2025-03-19 12:37:08 -04:00
committed by GitHub
parent 68ca457917
commit f5e4e7e47c
13 changed files with 1059 additions and 21 deletions

View File

@@ -119,8 +119,11 @@ namespace GitHub.Runner.Common
CredentialData GetCredentials();
CredentialData GetMigratedCredentials();
RunnerSettings GetSettings();
RunnerSettings GetMigratedSettings();
void SaveCredential(CredentialData credential);
void SaveMigratedCredential(CredentialData credential);
void SaveSettings(RunnerSettings settings);
void SaveMigratedSettings(RunnerSettings settings);
void DeleteCredential();
void DeleteMigratedCredential();
void DeleteSettings();
@@ -130,6 +133,7 @@ namespace GitHub.Runner.Common
{
private string _binPath;
private string _configFilePath;
private string _migratedConfigFilePath;
private string _credFilePath;
private string _migratedCredFilePath;
private string _serviceConfigFilePath;
@@ -137,6 +141,7 @@ namespace GitHub.Runner.Common
private CredentialData _creds;
private CredentialData _migratedCreds;
private RunnerSettings _settings;
private RunnerSettings _migratedSettings;
public override void Initialize(IHostContext hostContext)
{
@@ -154,6 +159,9 @@ namespace GitHub.Runner.Common
_configFilePath = hostContext.GetConfigFile(WellKnownConfigFile.Runner);
Trace.Info("ConfigFilePath: {0}", _configFilePath);
_migratedConfigFilePath = hostContext.GetConfigFile(WellKnownConfigFile.MigratedRunner);
Trace.Info("MigratedConfigFilePath: {0}", _migratedConfigFilePath);
_credFilePath = hostContext.GetConfigFile(WellKnownConfigFile.Credentials);
Trace.Info("CredFilePath: {0}", _credFilePath);
@@ -169,7 +177,7 @@ namespace GitHub.Runner.Common
public bool HasCredentials()
{
Trace.Info("HasCredentials()");
bool credsStored = (new FileInfo(_credFilePath)).Exists || (new FileInfo(_migratedCredFilePath)).Exists;
bool credsStored = new FileInfo(_credFilePath).Exists || new FileInfo(_migratedCredFilePath).Exists;
Trace.Info("stored {0}", credsStored);
return credsStored;
}
@@ -177,7 +185,7 @@ namespace GitHub.Runner.Common
public bool IsConfigured()
{
Trace.Info("IsConfigured()");
bool configured = new FileInfo(_configFilePath).Exists;
bool configured = new FileInfo(_configFilePath).Exists || new FileInfo(_migratedConfigFilePath).Exists;
Trace.Info("IsConfigured: {0}", configured);
return configured;
}
@@ -185,7 +193,7 @@ namespace GitHub.Runner.Common
public bool IsServiceConfigured()
{
Trace.Info("IsServiceConfigured()");
bool serviceConfigured = (new FileInfo(_serviceConfigFilePath)).Exists;
bool serviceConfigured = new FileInfo(_serviceConfigFilePath).Exists;
Trace.Info($"IsServiceConfigured: {serviceConfigured}");
return serviceConfigured;
}
@@ -229,6 +237,25 @@ namespace GitHub.Runner.Common
return _settings;
}
public RunnerSettings GetMigratedSettings()
{
if (_migratedSettings == null)
{
RunnerSettings configuredSettings = null;
if (File.Exists(_migratedConfigFilePath))
{
string json = File.ReadAllText(_migratedConfigFilePath, Encoding.UTF8);
Trace.Info($"Read migrated setting file: {json.Length} chars");
configuredSettings = StringUtil.ConvertFromJson<RunnerSettings>(json);
}
ArgUtil.NotNull(configuredSettings, nameof(configuredSettings));
_migratedSettings = configuredSettings;
}
return _migratedSettings;
}
public void SaveCredential(CredentialData credential)
{
Trace.Info("Saving {0} credential @ {1}", credential.Scheme, _credFilePath);
@@ -244,6 +271,21 @@ namespace GitHub.Runner.Common
File.SetAttributes(_credFilePath, File.GetAttributes(_credFilePath) | FileAttributes.Hidden);
}
public void SaveMigratedCredential(CredentialData credential)
{
Trace.Info("Saving {0} migrated credential @ {1}", credential.Scheme, _migratedCredFilePath);
if (File.Exists(_migratedCredFilePath))
{
// Delete existing credential file first, since the file is hidden and not able to overwrite.
Trace.Info("Delete exist runner migrated credential file.");
IOUtil.DeleteFile(_migratedCredFilePath);
}
IOUtil.SaveObject(credential, _migratedCredFilePath);
Trace.Info("Migrated Credentials Saved.");
File.SetAttributes(_migratedCredFilePath, File.GetAttributes(_migratedCredFilePath) | FileAttributes.Hidden);
}
public void SaveSettings(RunnerSettings settings)
{
Trace.Info("Saving runner settings.");
@@ -259,6 +301,21 @@ namespace GitHub.Runner.Common
File.SetAttributes(_configFilePath, File.GetAttributes(_configFilePath) | FileAttributes.Hidden);
}
public void SaveMigratedSettings(RunnerSettings settings)
{
Trace.Info("Saving runner migrated settings");
if (File.Exists(_migratedConfigFilePath))
{
// Delete existing settings file first, since the file is hidden and not able to overwrite.
Trace.Info("Delete exist runner migrated settings file.");
IOUtil.DeleteFile(_migratedConfigFilePath);
}
IOUtil.SaveObject(settings, _migratedConfigFilePath);
Trace.Info("Migrated Settings Saved.");
File.SetAttributes(_migratedConfigFilePath, File.GetAttributes(_migratedConfigFilePath) | FileAttributes.Hidden);
}
public void DeleteCredential()
{
IOUtil.Delete(_credFilePath, default(CancellationToken));
@@ -273,6 +330,12 @@ namespace GitHub.Runner.Common
public void DeleteSettings()
{
IOUtil.Delete(_configFilePath, default(CancellationToken));
IOUtil.Delete(_migratedConfigFilePath, default(CancellationToken));
}
public void DeleteMigratedSettings()
{
IOUtil.Delete(_migratedConfigFilePath, default(CancellationToken));
}
}
}

View File

@@ -18,6 +18,7 @@ namespace GitHub.Runner.Common
public enum WellKnownConfigFile
{
Runner,
MigratedRunner,
Credentials,
MigratedCredentials,
RSACredentials,

View File

@@ -343,6 +343,12 @@ namespace GitHub.Runner.Common
".runner");
break;
case WellKnownConfigFile.MigratedRunner:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Root),
".runner_migrated");
break;
case WellKnownConfigFile.Credentials:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Root),

View File

@@ -1,11 +1,11 @@
using GitHub.DistributedTask.WebApi;
using System;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using GitHub.Services.WebApi;
using GitHub.Services.Common;
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Sdk;
using GitHub.Services.Common;
using GitHub.Services.WebApi;
namespace GitHub.Runner.Common
{
@@ -50,7 +50,10 @@ namespace GitHub.Runner.Common
Task<PackageMetadata> GetPackageAsync(string packageType, string platform, string version, bool includeToken, CancellationToken cancellationToken);
// agent update
Task<TaskAgent> UpdateAgentUpdateStateAsync(int agentPoolId, ulong agentId, string currentState, string trace);
Task<TaskAgent> UpdateAgentUpdateStateAsync(int agentPoolId, ulong agentId, string currentState, string trace, CancellationToken cancellationToken = default);
// runner config refresh
Task<string> RefreshRunnerConfigAsync(int agentId, string configType, string encodedRunnerConfig, CancellationToken cancellationToken);
}
public sealed class RunnerServer : RunnerService, IRunnerServer
@@ -315,10 +318,17 @@ namespace GitHub.Runner.Common
return _genericTaskAgentClient.GetPackageAsync(packageType, platform, version, includeToken, cancellationToken: cancellationToken);
}
public Task<TaskAgent> UpdateAgentUpdateStateAsync(int agentPoolId, ulong agentId, string currentState, string trace)
public Task<TaskAgent> UpdateAgentUpdateStateAsync(int agentPoolId, ulong agentId, string currentState, string trace, CancellationToken cancellationToken = default)
{
CheckConnection(RunnerConnectionType.Generic);
return _genericTaskAgentClient.UpdateAgentUpdateStateAsync(agentPoolId, agentId, currentState, trace);
return _genericTaskAgentClient.UpdateAgentUpdateStateAsync(agentPoolId, agentId, currentState, trace, cancellationToken: cancellationToken);
}
// runner config refresh
public Task<string> RefreshRunnerConfigAsync(int agentId, string configType, string encodedRunnerConfig, CancellationToken cancellationToken)
{
CheckConnection(RunnerConnectionType.Generic);
return _genericTaskAgentClient.RefreshRunnerConfigAsync(agentId, configType, encodedRunnerConfig, cancellationToken: cancellationToken);
}
}
}

View File

@@ -533,7 +533,8 @@ namespace GitHub.Runner.Listener
}
else if (ex is TaskAgentPoolNotFoundException ||
ex is AccessDeniedException ||
ex is VssUnauthorizedException)
ex is VssUnauthorizedException ||
(ex is VssOAuthTokenRequestException oauthEx && oauthEx.Error != "server_error"))
{
Trace.Info($"Non-retriable exception: {ex.Message}");
return false;

View File

@@ -635,6 +635,17 @@ namespace GitHub.Runner.Listener
Trace.Info("Received ForceTokenRefreshMessage");
await _listener.RefreshListenerTokenAsync(messageQueueLoopTokenSource.Token);
}
else if (string.Equals(message.MessageType, RunnerRefreshConfigMessage.MessageType))
{
var runnerRefreshConfigMessage = JsonUtility.FromString<RunnerRefreshConfigMessage>(message.Body);
Trace.Info($"Received RunnerRefreshConfigMessage for '{runnerRefreshConfigMessage.ConfigType}' config file");
var configUpdater = HostContext.GetService<IRunnerConfigUpdater>();
await configUpdater.UpdateRunnerConfigAsync(
runnerQualifiedId: runnerRefreshConfigMessage.RunnerQualifiedId,
configType: runnerRefreshConfigMessage.ConfigType,
serviceType: runnerRefreshConfigMessage.ServiceType,
configRefreshUrl: runnerRefreshConfigMessage.ConfigRefreshUrl);
}
else
{
Trace.Error($"Received message {message.MessageId} with unsupported message type {message.MessageType}.");

View File

@@ -0,0 +1,267 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using GitHub.Runner.Common;
using GitHub.Runner.Sdk;
using GitHub.Services.Common;
namespace GitHub.Runner.Listener
{
[ServiceLocator(Default = typeof(RunnerConfigUpdater))]
public interface IRunnerConfigUpdater : IRunnerService
{
Task UpdateRunnerConfigAsync(string runnerQualifiedId, string configType, string serviceType, string configRefreshUrl);
}
public sealed class RunnerConfigUpdater : RunnerService, IRunnerConfigUpdater
{
private RunnerSettings _settings;
private CredentialData _credData;
private IRunnerServer _runnerServer;
private IConfigurationStore _store;
public override void Initialize(IHostContext hostContext)
{
base.Initialize(hostContext);
_store = hostContext.GetService<IConfigurationStore>();
_settings = _store.GetSettings();
_credData = _store.GetCredentials();
_runnerServer = HostContext.GetService<IRunnerServer>();
}
public async Task UpdateRunnerConfigAsync(string runnerQualifiedId, string configType, string serviceType, string configRefreshUrl)
{
Trace.Entering();
try
{
ArgUtil.NotNullOrEmpty(runnerQualifiedId, nameof(runnerQualifiedId));
ArgUtil.NotNullOrEmpty(configType, nameof(configType));
ArgUtil.NotNullOrEmpty(serviceType, nameof(serviceType));
ArgUtil.NotNullOrEmpty(configRefreshUrl, nameof(configRefreshUrl));
// make sure the runner qualified id matches the current runner
if (!await VerifyRunnerQualifiedId(runnerQualifiedId))
{
return;
}
// keep the timeout short to avoid blocking the main thread
using (var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)))
{
switch (configType.ToLowerInvariant())
{
case "runner":
await UpdateRunnerSettingsAsync(serviceType, configRefreshUrl, tokenSource.Token);
break;
case "credentials":
await UpdateRunnerCredentialsAsync(serviceType, configRefreshUrl, tokenSource.Token);
break;
default:
Trace.Error($"Invalid config type '{configType}'.");
await ReportTelemetryAsync($"Invalid config type '{configType}'.");
return;
}
}
}
catch (Exception ex)
{
Trace.Error($"Failed to update runner '{configType}' config.");
Trace.Error(ex);
await ReportTelemetryAsync($"Failed to update runner '{configType}' config: {ex}");
}
}
private async Task UpdateRunnerSettingsAsync(string serviceType, string configRefreshUrl, CancellationToken token)
{
Trace.Entering();
// read the current runner settings and encode with base64
var runnerConfig = HostContext.GetConfigFile(WellKnownConfigFile.Runner);
string runnerConfigContent = File.ReadAllText(runnerConfig, Encoding.UTF8);
var encodedConfig = Convert.ToBase64String(Encoding.UTF8.GetBytes(runnerConfigContent));
if (string.IsNullOrEmpty(encodedConfig))
{
await ReportTelemetryAsync("Failed to get encoded runner settings.");
return;
}
// exchange the encoded runner settings with the service
string refreshedEncodedConfig = await RefreshRunnerConfigAsync(encodedConfig, serviceType, "runner", configRefreshUrl, token);
if (string.IsNullOrEmpty(refreshedEncodedConfig))
{
// service will return empty string if there is no change in the config
return;
}
var decodedConfig = Encoding.UTF8.GetString(Convert.FromBase64String(refreshedEncodedConfig));
RunnerSettings refreshedRunnerConfig;
try
{
refreshedRunnerConfig = StringUtil.ConvertFromJson<RunnerSettings>(decodedConfig);
}
catch (Exception ex)
{
Trace.Error($"Failed to convert runner config from json '{decodedConfig}'.");
Trace.Error(ex);
await ReportTelemetryAsync($"Failed to convert runner config '{decodedConfig}' from json: {ex}");
return;
}
// make sure the runner id and name in the refreshed config match the current runner
if (refreshedRunnerConfig?.AgentId != _settings.AgentId)
{
Trace.Error($"Runner id in refreshed config '{refreshedRunnerConfig?.AgentId.ToString() ?? "Empty"}' does not match the current runner '{_settings.AgentId}'.");
await ReportTelemetryAsync($"Runner id in refreshed config '{refreshedRunnerConfig?.AgentId.ToString() ?? "Empty"}' does not match the current runner '{_settings.AgentId}'.");
return;
}
if (refreshedRunnerConfig?.AgentName != _settings.AgentName)
{
Trace.Error($"Runner name in refreshed config '{refreshedRunnerConfig?.AgentName ?? "Empty"}' does not match the current runner '{_settings.AgentName}'.");
await ReportTelemetryAsync($"Runner name in refreshed config '{refreshedRunnerConfig?.AgentName ?? "Empty"}' does not match the current runner '{_settings.AgentName}'.");
return;
}
// save the refreshed runner settings as a separate file
_store.SaveMigratedSettings(refreshedRunnerConfig);
await ReportTelemetryAsync("Runner settings updated successfully.");
}
private async Task UpdateRunnerCredentialsAsync(string serviceType, string configRefreshUrl, CancellationToken token)
{
Trace.Entering();
// read the current runner credentials and encode with base64
var credConfig = HostContext.GetConfigFile(WellKnownConfigFile.Credentials);
string credConfigContent = File.ReadAllText(credConfig, Encoding.UTF8);
var encodedConfig = Convert.ToBase64String(Encoding.UTF8.GetBytes(credConfigContent));
if (string.IsNullOrEmpty(encodedConfig))
{
await ReportTelemetryAsync("Failed to get encoded credentials.");
return;
}
CredentialData currentCred = _store.GetCredentials();
if (currentCred == null)
{
await ReportTelemetryAsync("Failed to get current credentials.");
return;
}
// we only support refreshing OAuth credentials which is used by self-hosted runners.
if (currentCred.Scheme != Constants.Configuration.OAuth)
{
await ReportTelemetryAsync($"Not supported credential scheme '{currentCred.Scheme}'.");
return;
}
// exchange the encoded runner credentials with the service
string refreshedEncodedConfig = await RefreshRunnerConfigAsync(encodedConfig, serviceType, "credentials", configRefreshUrl, token);
if (string.IsNullOrEmpty(refreshedEncodedConfig))
{
// service will return empty string if there is no change in the config
return;
}
var decodedConfig = Encoding.UTF8.GetString(Convert.FromBase64String(refreshedEncodedConfig));
CredentialData refreshedCredConfig;
try
{
refreshedCredConfig = StringUtil.ConvertFromJson<CredentialData>(decodedConfig);
}
catch (Exception ex)
{
Trace.Error($"Failed to convert credentials config from json '{decodedConfig}'.");
Trace.Error(ex);
await ReportTelemetryAsync($"Failed to convert credentials config '{decodedConfig}' from json: {ex}");
return;
}
// make sure the credential scheme in the refreshed config match the current credential scheme
if (refreshedCredConfig?.Scheme != _credData.Scheme)
{
Trace.Error($"Credential scheme in refreshed config '{refreshedCredConfig?.Scheme ?? "Empty"}' does not match the current credential scheme '{_credData.Scheme}'.");
await ReportTelemetryAsync($"Credential scheme in refreshed config '{refreshedCredConfig?.Scheme ?? "Empty"}' does not match the current credential scheme '{_credData.Scheme}'.");
return;
}
if (_credData.Scheme == Constants.Configuration.OAuth)
{
// make sure the credential clientId in the refreshed config match the current credential clientId for OAuth auth scheme
var clientId = _credData.Data.GetValueOrDefault("clientId", null);
var refreshedClientId = refreshedCredConfig.Data.GetValueOrDefault("clientId", null);
if (clientId != refreshedClientId)
{
Trace.Error($"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;
}
}
// save the refreshed runner credentials as a separate file
_store.SaveMigratedCredential(refreshedCredConfig);
await ReportTelemetryAsync("Runner credentials updated successfully.");
}
private async Task<bool> VerifyRunnerQualifiedId(string runnerQualifiedId)
{
Trace.Entering();
Trace.Info($"Verifying runner qualified id: {runnerQualifiedId}");
var idParts = runnerQualifiedId.Split("/", StringSplitOptions.RemoveEmptyEntries);
if (idParts.Length != 4 || idParts[3] != _settings.AgentId.ToString())
{
Trace.Error($"Runner qualified id '{runnerQualifiedId}' does not match the current runner '{_settings.AgentId}'.");
await ReportTelemetryAsync($"Runner qualified id '{runnerQualifiedId}' does not match the current runner '{_settings.AgentId}'.");
return false;
}
return true;
}
private async Task<string> RefreshRunnerConfigAsync(string encodedConfig, string serviceType, string configType, string configRefreshUrl, CancellationToken token)
{
string refreshedEncodedConfig;
switch (serviceType.ToLowerInvariant())
{
case "pipelines":
try
{
refreshedEncodedConfig = await _runnerServer.RefreshRunnerConfigAsync((int)_settings.AgentId, configType, encodedConfig, token);
}
catch (Exception ex)
{
Trace.Error($"Failed to refresh runner {configType} config with service.");
Trace.Error(ex);
await ReportTelemetryAsync($"Failed to refresh {configType} config: {ex}");
return null;
}
break;
case "runner-admin":
throw new NotSupportedException("Runner admin service is not supported.");
default:
Trace.Error($"Invalid service type '{serviceType}'.");
await ReportTelemetryAsync($"Invalid service type '{serviceType}'.");
return null;
}
return refreshedEncodedConfig;
}
private async Task ReportTelemetryAsync(string telemetry)
{
Trace.Entering();
try
{
using (var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)))
{
await _runnerServer.UpdateAgentUpdateStateAsync(_settings.PoolId, _settings.AgentId, "RefreshConfig", telemetry, tokenSource.Token);
}
}
catch (Exception ex)
{
Trace.Error("Failed to report telemetry.");
Trace.Error(ex);
}
}
}
}

View File

@@ -23,8 +23,8 @@ using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using GitHub.Services.Common;
@@ -827,5 +827,36 @@ namespace GitHub.DistributedTask.WebApi
userState: userState,
cancellationToken: cancellationToken);
}
/// <summary>
/// [Preview API]
/// </summary>
/// <param name="agentId"></param>
/// <param name="configType"></param>
/// <param name="encodedRunnerConfig"></param>
/// <param name="userState"></param>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
[EditorBrowsable(EditorBrowsableState.Never)]
public virtual Task<string> RefreshRunnerConfigAsync(
int agentId,
string configType,
string encodedRunnerConfig,
object userState = null,
CancellationToken cancellationToken = default)
{
HttpMethod httpMethod = new HttpMethod("POST");
Guid locationId = new Guid("13b5d709-74aa-470b-a8e9-bf9f3ded3f18");
object routeValues = new { agentId = agentId, configType = configType };
HttpContent content = new ObjectContent<string>(encodedRunnerConfig, new VssJsonMediaTypeFormatter(true));
return SendAsync<string>(
httpMethod,
locationId,
routeValues: routeValues,
version: new ApiResourceVersion(6.0, 1),
userState: userState,
cancellationToken: cancellationToken,
content: content);
}
}
}

View File

@@ -0,0 +1,58 @@
using System;
using System.Runtime.Serialization;
using GitHub.Services.WebApi;
using Newtonsoft.Json;
namespace GitHub.DistributedTask.WebApi
{
[DataContract]
public sealed class RunnerRefreshConfigMessage
{
public static readonly String MessageType = "RunnerRefreshConfig";
[JsonConstructor]
internal RunnerRefreshConfigMessage()
{
}
public RunnerRefreshConfigMessage(
string runnerQualifiedId,
string configType,
string serviceType,
string configRefreshUrl)
{
this.RunnerQualifiedId = runnerQualifiedId;
this.ConfigType = configType;
this.ServiceType = serviceType;
this.ConfigRefreshUrl = configRefreshUrl;
}
[DataMember(Name = "runnerQualifiedId")]
public String RunnerQualifiedId
{
get;
private set;
}
[DataMember(Name = "configType")]
public String ConfigType
{
get;
private set;
}
[DataMember(Name = "serviceType")]
public String ServiceType
{
get;
private set;
}
[DataMember(Name = "configRefreshURL")]
public String ConfigRefreshUrl
{
get;
private set;
}
}
}

View File

@@ -0,0 +1,579 @@
using System;
using System.Threading.Tasks;
using GitHub.Runner.Listener;
using GitHub.Runner.Common;
using GitHub.Runner.Sdk;
using Moq;
using Xunit;
using System.Threading;
using GitHub.Runner.Common.Tests;
using System.Text;
namespace GitHub.Runner.Tests.Listener
{
public class RunnerConfigUpdaterL0
{
private Mock<IConfigurationStore> _configurationStore;
private Mock<IRunnerServer> _runnerServer;
public RunnerConfigUpdaterL0()
{
_configurationStore = new Mock<IConfigurationStore>();
_runnerServer = new Mock<IRunnerServer>();
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Runner")]
public async Task UpdateRunnerConfigAsync_InvalidRunnerQualifiedId_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);
IOUtil.SaveObject(setting, hc.GetConfigFile(WellKnownConfigFile.Runner));
var _runnerConfigUpdater = new RunnerConfigUpdater();
_runnerConfigUpdater.Initialize(hc);
var invalidRunnerQualifiedId = "invalid/runner/qualified/id";
var configType = "runner";
var serviceType = "pipelines";
var configRefreshUrl = "http://example.com";
// Act
await _runnerConfigUpdater.UpdateRunnerConfigAsync(invalidRunnerQualifiedId, configType, serviceType, configRefreshUrl);
// Assert
_runnerServer.Verify(x => x.UpdateAgentUpdateStateAsync(It.IsAny<int>(), It.IsAny<ulong>(), It.IsAny<string>(), It.Is<string>((s) => s.Contains("Runner qualified id")), It.IsAny<CancellationToken>()), Times.Once);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Runner")]
public async Task UpdateRunnerConfigAsync_ValidRunnerQualifiedId_ShouldNotReportTelemetry()
{
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);
IOUtil.SaveObject(setting, hc.GetConfigFile(WellKnownConfigFile.Runner));
var _runnerConfigUpdater = new RunnerConfigUpdater();
_runnerConfigUpdater.Initialize(hc);
var validRunnerQualifiedId = "valid/runner/qualifiedid/1";
var configType = "runner";
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("Runner qualified id")), It.IsAny<CancellationToken>()), Times.Never);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Runner")]
public async Task UpdateRunnerConfigAsync_InvalidConfigType_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);
IOUtil.SaveObject(setting, hc.GetConfigFile(WellKnownConfigFile.Runner));
var _runnerConfigUpdater = new RunnerConfigUpdater();
_runnerConfigUpdater.Initialize(hc);
var validRunnerQualifiedId = "valid/runner/qualifiedid/1";
var invalidConfigType = "invalidConfigType";
var serviceType = "pipelines";
var configRefreshUrl = "http://example.com";
// Act
await _runnerConfigUpdater.UpdateRunnerConfigAsync(validRunnerQualifiedId, invalidConfigType, serviceType, configRefreshUrl);
// Assert
_runnerServer.Verify(x => x.UpdateAgentUpdateStateAsync(It.IsAny<int>(), It.IsAny<ulong>(), It.IsAny<string>(), It.Is<string>((s) => s.Contains("Invalid config type")), It.IsAny<CancellationToken>()), Times.Once);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Runner")]
public async Task UpdateRunnerConfigAsync_UpdateRunnerSettings_ShouldSucceed()
{
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);
IOUtil.SaveObject(setting, hc.GetConfigFile(WellKnownConfigFile.Runner));
var encodedConfig = Convert.ToBase64String(Encoding.UTF8.GetBytes(StringUtil.ConvertToJson(setting)));
_runnerServer.Setup(x => x.RefreshRunnerConfigAsync(It.IsAny<int>(), It.Is<string>(s => s == "runner"), It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(encodedConfig);
var _runnerConfigUpdater = new RunnerConfigUpdater();
_runnerConfigUpdater.Initialize(hc);
var validRunnerQualifiedId = "valid/runner/qualifiedid/1";
var configType = "runner";
var serviceType = "pipelines";
var configRefreshUrl = "http://example.com";
// Act
await _runnerConfigUpdater.UpdateRunnerConfigAsync(validRunnerQualifiedId, configType, serviceType, configRefreshUrl);
// Assert
_runnerServer.Verify(x => x.RefreshRunnerConfigAsync(1, "runner", 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 settings updated successfully")), It.IsAny<CancellationToken>()), Times.Once);
_configurationStore.Verify(x => x.SaveMigratedSettings(It.IsAny<RunnerSettings>()), Times.Once);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Runner")]
public async Task UpdateRunnerConfigAsync_UpdateRunnerSettings_IgnoredEmptyRefreshResult()
{
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);
IOUtil.SaveObject(setting, hc.GetConfigFile(WellKnownConfigFile.Runner));
var _runnerConfigUpdater = new RunnerConfigUpdater();
_runnerConfigUpdater.Initialize(hc);
var validRunnerQualifiedId = "valid/runner/qualifiedid/1";
var configType = "runner";
var serviceType = "pipelines";
var configRefreshUrl = "http://example.com";
// Act
await _runnerConfigUpdater.UpdateRunnerConfigAsync(validRunnerQualifiedId, configType, serviceType, configRefreshUrl);
// Assert
_runnerServer.Verify(x => x.RefreshRunnerConfigAsync(1, "runner", 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 settings updated successfully")), It.IsAny<CancellationToken>()), Times.Never);
_configurationStore.Verify(x => x.SaveMigratedSettings(It.IsAny<RunnerSettings>()), Times.Never);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Runner")]
public async Task UpdateRunnerConfigAsync_UpdateRunnerCredentials_ShouldSucceed()
{
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");
_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);
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);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Runner")]
public async Task UpdateRunnerConfigAsync_UpdateRunnerCredentials_IgnoredEmptyRefreshResult()
{
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");
_configurationStore.Setup(x => x.GetCredentials()).Returns(credData);
IOUtil.SaveObject(setting, hc.GetConfigFile(WellKnownConfigFile.Runner));
IOUtil.SaveObject(credData, hc.GetConfigFile(WellKnownConfigFile.Credentials));
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.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.Never);
_configurationStore.Verify(x => x.SaveMigratedCredential(It.IsAny<CredentialData>()), Times.Never);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Runner")]
public async Task UpdateRunnerConfigAsync_RefreshRunnerSettingsFailure_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);
IOUtil.SaveObject(setting, hc.GetConfigFile(WellKnownConfigFile.Runner));
_runnerServer.Setup(x => x.RefreshRunnerConfigAsync(It.IsAny<int>(), It.Is<string>(s => s == "runner"), It.IsAny<string>(), It.IsAny<CancellationToken>())).ThrowsAsync(new Exception("Refresh failed"));
var _runnerConfigUpdater = new RunnerConfigUpdater();
_runnerConfigUpdater.Initialize(hc);
var validRunnerQualifiedId = "valid/runner/qualifiedid/1";
var configType = "runner";
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("Failed to refresh")), It.IsAny<CancellationToken>()), Times.Once);
_configurationStore.Verify(x => x.SaveMigratedSettings(It.IsAny<RunnerSettings>()), Times.Never);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Runner")]
public async Task UpdateRunnerConfigAsync_RefreshRunnerCredetialsFailure_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");
_configurationStore.Setup(x => x.GetCredentials()).Returns(credData);
_runnerServer.Setup(x => x.RefreshRunnerConfigAsync(It.IsAny<int>(), It.Is<string>(s => s == "credentials"), It.IsAny<string>(), It.IsAny<CancellationToken>())).ThrowsAsync(new Exception("Refresh failed"));
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("Failed to refresh")), It.IsAny<CancellationToken>()), Times.Once);
_configurationStore.Verify(x => x.SaveMigratedSettings(It.IsAny<RunnerSettings>()), Times.Never);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Runner")]
public async Task UpdateRunnerConfigAsync_RefreshRunnerSettingsWithDifferentRunnerId_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);
IOUtil.SaveObject(setting, hc.GetConfigFile(WellKnownConfigFile.Runner));
var differentRunnerSetting = new RunnerSettings { AgentId = 2, AgentName = "agent1" };
var encodedConfig = Convert.ToBase64String(Encoding.UTF8.GetBytes(StringUtil.ConvertToJson(differentRunnerSetting)));
_runnerServer.Setup(x => x.RefreshRunnerConfigAsync(It.IsAny<int>(), It.Is<string>(s => s == "runner"), It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(encodedConfig);
var _runnerConfigUpdater = new RunnerConfigUpdater();
_runnerConfigUpdater.Initialize(hc);
var validRunnerQualifiedId = "valid/runner/qualifiedid/1";
var configType = "runner";
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("Runner id in refreshed config")), It.IsAny<CancellationToken>()), Times.Once);
_configurationStore.Verify(x => x.SaveMigratedSettings(It.IsAny<RunnerSettings>()), Times.Never);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Runner")]
public async Task UpdateRunnerConfigAsync_RefreshRunnerSettingsWithDifferentRunnerName_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);
IOUtil.SaveObject(setting, hc.GetConfigFile(WellKnownConfigFile.Runner));
var differentRunnerSetting = new RunnerSettings { AgentId = 1, AgentName = "agent2" };
var encodedConfig = Convert.ToBase64String(Encoding.UTF8.GetBytes(StringUtil.ConvertToJson(differentRunnerSetting)));
_runnerServer.Setup(x => x.RefreshRunnerConfigAsync(It.IsAny<int>(), It.Is<string>(s => s == "runner"), It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(encodedConfig);
var _runnerConfigUpdater = new RunnerConfigUpdater();
_runnerConfigUpdater.Initialize(hc);
var validRunnerQualifiedId = "valid/runner/qualifiedid/1";
var configType = "runner";
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("Runner name in refreshed config")), It.IsAny<CancellationToken>()), Times.Once);
_configurationStore.Verify(x => x.SaveMigratedSettings(It.IsAny<RunnerSettings>()), Times.Never);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Runner")]
public async Task UpdateRunnerConfigAsync_RefreshCredentialsWithDifferentScheme_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");
_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 = "PAT"
};
differentCredData.Data.Add("ClientId", "12345");
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 scheme in refreshed config")), It.IsAny<CancellationToken>()), Times.Once);
_configurationStore.Verify(x => x.SaveMigratedCredential(It.IsAny<CredentialData>()), Times.Never);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Runner")]
public async Task UpdateRunnerConfigAsync_RefreshOAuthCredentialsWithDifferentClientId_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");
_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", "67890");
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 clientId in refreshed config")), It.IsAny<CancellationToken>()), Times.Once);
_configurationStore.Verify(x => x.SaveMigratedCredential(It.IsAny<CredentialData>()), Times.Never);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Runner")]
public async Task UpdateRunnerConfigAsync_UnsupportedServiceType_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);
IOUtil.SaveObject(setting, hc.GetConfigFile(WellKnownConfigFile.Runner));
var _runnerConfigUpdater = new RunnerConfigUpdater();
_runnerConfigUpdater.Initialize(hc);
var validRunnerQualifiedId = "valid/runner/qualifiedid/1";
var configType = "runner";
var serviceType = "unsupported-service";
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("Invalid service type")), It.IsAny<CancellationToken>()), Times.Once);
_runnerServer.Verify(x => x.RefreshRunnerConfigAsync(It.IsAny<int>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Never);
_configurationStore.Verify(x => x.SaveMigratedSettings(It.IsAny<RunnerSettings>()), Times.Never);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Runner")]
public async Task UpdateRunnerConfigAsync_RunnerAdminService_ShouldThrowNotSupported()
{
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);
IOUtil.SaveObject(setting, hc.GetConfigFile(WellKnownConfigFile.Runner));
var _runnerConfigUpdater = new RunnerConfigUpdater();
_runnerConfigUpdater.Initialize(hc);
var validRunnerQualifiedId = "valid/runner/qualifiedid/1";
var configType = "runner";
var serviceType = "runner-admin";
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("Runner admin service is not supported")), It.IsAny<CancellationToken>()), Times.Once);
_runnerServer.Verify(x => x.RefreshRunnerConfigAsync(It.IsAny<int>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Never);
_configurationStore.Verify(x => x.SaveMigratedSettings(It.IsAny<RunnerSettings>()), Times.Never);
}
}
}
}

View File

@@ -107,8 +107,8 @@ namespace GitHub.Runner.Common.Tests.Listener
hc.EnqueueInstance<IProcessInvoker>(p3);
updater.Initialize(hc);
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
.Callback((int p, ulong a, string s, string t) =>
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
.Callback((int p, ulong a, string s, string t, CancellationToken token) =>
{
hc.GetTrace().Info(t);
})
@@ -168,8 +168,8 @@ namespace GitHub.Runner.Common.Tests.Listener
_runnerServer.Setup(x => x.GetPackageAsync("agent", BuildConstants.RunnerPackage.PackageName, "2.200.0", true, It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(new PackageMetadata() { Platform = BuildConstants.RunnerPackage.PackageName, Version = new PackageVersion("2.200.0"), DownloadUrl = _packageUrl }));
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
.Callback((int p, ulong a, string s, string t) =>
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
.Callback((int p, ulong a, string s, string t, CancellationToken token) =>
{
hc.GetTrace().Info(t);
})
@@ -220,8 +220,8 @@ namespace GitHub.Runner.Common.Tests.Listener
hc.EnqueueInstance<IProcessInvoker>(p3);
updater.Initialize(hc);
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
.Callback((int p, ulong a, string s, string t) =>
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
.Callback((int p, ulong a, string s, string t, CancellationToken token) =>
{
hc.GetTrace().Info(t);
})
@@ -273,8 +273,8 @@ namespace GitHub.Runner.Common.Tests.Listener
hc.EnqueueInstance<IProcessInvoker>(p3);
updater.Initialize(hc);
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
.Callback((int p, ulong a, string s, string t) =>
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
.Callback((int p, ulong a, string s, string t, CancellationToken token) =>
{
hc.GetTrace().Info(t);
})

View File

@@ -256,12 +256,24 @@ namespace GitHub.Runner.Common.Tests
".agent");
break;
case WellKnownConfigFile.MigratedRunner:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Root),
".agent_migrated");
break;
case WellKnownConfigFile.Credentials:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Root),
".credentials");
break;
case WellKnownConfigFile.MigratedCredentials:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Root),
".credentials_migrated");
break;
case WellKnownConfigFile.RSACredentials:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Root),

View File

@@ -18,7 +18,6 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="xunit" Version="2.7.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.8" />
<PackageReference Include="System.Buffers" Version="4.5.1" />
<PackageReference Include="System.Reflection.TypeExtensions" Version="4.7.0" />
<PackageReference Include="System.Threading.ThreadPool" Version="4.3.0" />
<PackageReference Include="Moq" Version="4.20.72" />