Abstract HttpClient and FileDownloader

- Set all requests to use assemblyInformationProvider UserAgent by default.
- Removed DefaultIOManager's dependency on IAssemblyInformationProvider.
- Fixed bad Dispose scope for requests under construction in SwarmService.
This commit is contained in:
Dominion
2023-04-21 03:15:39 -04:00
parent 6aa40ef547
commit a1b5dbe6ac
39 changed files with 430 additions and 222 deletions
+1 -1
View File
@@ -7,7 +7,7 @@
<TgsConfigVersion>4.5.0</TgsConfigVersion>
<TgsApiVersion>9.9.0</TgsApiVersion>
<TgsApiLibraryVersion>10.3.0</TgsApiLibraryVersion>
<TgsClientVersion>11.3.0</TgsClientVersion>
<TgsClientVersion>11.3.1</TgsClientVersion>
<TgsDmapiVersion>6.4.2</TgsDmapiVersion>
<TgsInteropVersion>5.6.0</TgsInteropVersion>
<TgsHostWatchdogVersion>1.2.2</TgsHostWatchdogVersion>
+2 -1
View File
@@ -20,6 +20,7 @@ using Newtonsoft.Json.Serialization;
using Tgstation.Server.Api;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Response;
using Tgstation.Server.Common;
namespace Tgstation.Server.Client
{
@@ -50,7 +51,7 @@ namespace Tgstation.Server.Client
}
/// <summary>
/// The <see cref="HttpClientImplementation"/> for the <see cref="ApiClient"/>.
/// The <see cref="IHttpClient"/> for the <see cref="ApiClient"/>.
/// </summary>
readonly IHttpClient httpClient;
@@ -1,6 +1,7 @@
using System;
using Tgstation.Server.Api;
using Tgstation.Server.Common;
namespace Tgstation.Server.Client
{
@@ -13,7 +14,7 @@ namespace Tgstation.Server.Client
ApiHeaders apiHeaders,
ApiHeaders? tokenRefreshHeaders,
bool authless) => new ApiClient(
new HttpClientImplementation(),
new HttpClient(),
url,
apiHeaders,
tokenRefreshHeaders,
@@ -1,37 +0,0 @@
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace Tgstation.Server.Client
{
/// <inheritdoc />
sealed class HttpClientImplementation : IHttpClient
{
/// <inheritdoc />
public TimeSpan Timeout
{
get => httpClient.Timeout;
set => httpClient.Timeout = value;
}
/// <summary>
/// The real <see cref="HttpClient"/>.
/// </summary>
readonly HttpClient httpClient;
/// <summary>
/// Initializes a new instance of the <see cref="HttpClientImplementation"/> class.
/// </summary>
public HttpClientImplementation()
{
httpClient = new HttpClient();
}
/// <inheritdoc />
public void Dispose() => httpClient.Dispose();
/// <inheritdoc />
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) => httpClient.SendAsync(request, cancellationToken);
}
}
@@ -41,6 +41,7 @@
<ItemGroup>
<ProjectReference Include="..\Tgstation.Server.Api\Tgstation.Server.Api.csproj" />
<ProjectReference Include="..\Tgstation.Server.Common\Tgstation.Server.Common.csproj" />
</ItemGroup>
<ItemGroup>
+50
View File
@@ -0,0 +1,50 @@
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
namespace Tgstation.Server.Common
{
/// <inheritdoc />
public sealed class HttpClient : IHttpClient
{
/// <inheritdoc />
public TimeSpan Timeout
{
get => httpClient.Timeout;
set => httpClient.Timeout = value;
}
/// <inheritdoc />
public HttpRequestHeaders DefaultRequestHeaders => httpClient.DefaultRequestHeaders;
/// <summary>
/// The real <see cref="System.Net.Http.HttpClient"/>.
/// </summary>
readonly System.Net.Http.HttpClient httpClient;
/// <summary>
/// Initializes a new instance of the <see cref="HttpClient"/> class.
/// </summary>
/// <param name="implementation">The <see cref="System.Net.Http.HttpClient"/> to wrap.</param>
public HttpClient(System.Net.Http.HttpClient implementation)
{
httpClient = implementation ?? throw new ArgumentNullException(nameof(implementation));
}
/// <summary>
/// Initializes a new instance of the <see cref="HttpClient"/> class.
/// </summary>
public HttpClient()
: this(new System.Net.Http.HttpClient())
{
}
/// <inheritdoc />
public void Dispose() => httpClient.Dispose();
/// <inheritdoc />
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) => httpClient.SendAsync(request, cancellationToken);
}
}
@@ -1,20 +1,26 @@
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
namespace Tgstation.Server.Client
namespace Tgstation.Server.Common
{
/// <summary>
/// For sending HTTP requests.
/// </summary>
interface IHttpClient : IDisposable
public interface IHttpClient : IDisposable
{
/// <summary>
/// The request timeout.
/// </summary>
TimeSpan Timeout { get; set; }
/// <summary>
/// The <see cref="HttpRequestHeaders"/> used on every request.
/// </summary>
HttpRequestHeaders DefaultRequestHeaders { get; }
/// <summary>
/// Send an HTTP request.
/// </summary>
@@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../../build/Version.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<DebugType>Full</DebugType>
<Version>$(TgsCoreVersion)</Version>
<CodeAnalysisRuleSet>../../build/analyzers.ruleset</CodeAnalysisRuleSet>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<DocumentationFile>bin\$(Configuration)\netstandard2.0\Tgstation.Server.Client.xml</DocumentationFile>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarningsAsErrors />
</PropertyGroup>
<ItemGroup>
<!-- Usage: Linting -->
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="../../build/stylecop.json" />
</ItemGroup>
</Project>
@@ -39,14 +39,21 @@ namespace Tgstation.Server.Host.Components.Byond
/// </summary>
protected ILogger<ByondInstallerBase> Logger { get; }
/// <summary>
/// The <see cref="IFileDownloader"/> for the <see cref="ByondInstallerBase"/>.
/// </summary>
readonly IFileDownloader fileDownloader;
/// <summary>
/// Initializes a new instance of the <see cref="ByondInstallerBase"/> class.
/// </summary>
/// <param name="ioManager">The value of <see cref="IOManager"/>.</param>
/// <param name="fileDownloader">The value of <see cref="fileDownloader"/>.</param>
/// <param name="logger">The value of <see cref="Logger"/>.</param>
protected ByondInstallerBase(IIOManager ioManager, ILogger<ByondInstallerBase> logger)
protected ByondInstallerBase(IIOManager ioManager, IFileDownloader fileDownloader, ILogger<ByondInstallerBase> logger)
{
IOManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
this.fileDownloader = fileDownloader ?? throw new ArgumentNullException(nameof(fileDownloader));
Logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
@@ -88,11 +95,9 @@ namespace Tgstation.Server.Host.Components.Byond
if (version == null)
throw new ArgumentNullException(nameof(version));
Logger.LogTrace("Downloading BYOND version {major}.{minor}...", version.Major, version.Minor);
var url = String.Format(CultureInfo.InvariantCulture, ByondRevisionsUrlTemplate, version.Major, version.Minor);
Logger.LogTrace("Downloading from: {0}", url);
return IOManager.DownloadFile(new Uri(url), cancellationToken);
return fileDownloader.DownloadFile(new Uri(url), cancellationToken);
}
}
}
@@ -49,9 +49,14 @@ namespace Tgstation.Server.Host.Components.Byond
/// </summary>
/// <param name="postWriteHandler">The value of <see cref="postWriteHandler"/>.</param>
/// <param name="ioManager">The <see cref="IIOManager"/> for the <see cref="ByondInstallerBase"/>.</param>
/// <param name="fileDownloader">The <see cref="IFileDownloader"/> for the <see cref="ByondInstallerBase"/>.</param>
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="ByondInstallerBase"/>.</param>
public PosixByondInstaller(IPostWriteHandler postWriteHandler, IIOManager ioManager, ILogger<PosixByondInstaller> logger)
: base(ioManager, logger)
public PosixByondInstaller(
IPostWriteHandler postWriteHandler,
IIOManager ioManager,
IFileDownloader fileDownloader,
ILogger<PosixByondInstaller> logger)
: base(ioManager, fileDownloader, logger)
{
this.postWriteHandler = postWriteHandler ?? throw new ArgumentNullException(nameof(postWriteHandler));
@@ -89,7 +94,7 @@ namespace Tgstation.Server.Host.Components.Byond
async Task WriteAndMakeExecutable(string pathToScript, string script)
{
Logger.LogTrace("Writing script {0}:{1}{2}", pathToScript, Environment.NewLine, script);
Logger.LogTrace("Writing script {path}:{newLine}{scriptContents}", pathToScript, Environment.NewLine, script);
await IOManager.WriteAllBytes(pathToScript, Encoding.ASCII.GetBytes(script), cancellationToken);
postWriteHandler.HandleWrite(IOManager.ResolvePath(pathToScript));
}
@@ -81,9 +81,15 @@ namespace Tgstation.Server.Host.Components.Byond
/// <param name="processExecutor">The value of <see cref="processExecutor"/>.</param>
/// <param name="generalConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="generalConfiguration"/>.</param>
/// <param name="ioManager">The <see cref="IIOManager"/> for the <see cref="ByondInstallerBase"/>.</param>
/// <param name="fileDownloader">The <see cref="IFileDownloader"/> for the <see cref="ByondInstallerBase"/>.</param>
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="ByondInstallerBase"/>.</param>
public WindowsByondInstaller(IProcessExecutor processExecutor, IIOManager ioManager, IOptions<GeneralConfiguration> generalConfigurationOptions, ILogger<WindowsByondInstaller> logger)
: base(ioManager, logger)
public WindowsByondInstaller(
IProcessExecutor processExecutor,
IIOManager ioManager,
IFileDownloader fileDownloader,
IOptions<GeneralConfiguration> generalConfigurationOptions,
ILogger<WindowsByondInstaller> logger)
: base(ioManager, fileDownloader, logger)
{
this.processExecutor = processExecutor ?? throw new ArgumentNullException(nameof(processExecutor));
generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
@@ -0,0 +1,63 @@
using System;
using System.Net.Http;
using Microsoft.Extensions.Logging;
using Tgstation.Server.Common;
using Tgstation.Server.Host.System;
namespace Tgstation.Server.Host.Core
{
/// <inheritdoc />
sealed class AbstractHttpClientFactory : IAbstractHttpClientFactory
{
/// <summary>
/// The real <see cref="IHttpClientFactory"/>.
/// </summary>
readonly IHttpClientFactory httpClientFactory;
/// <summary>
/// The <see cref="IAssemblyInformationProvider"/> for the <see cref="AbstractHttpClientFactory"/>.
/// </summary>
readonly IAssemblyInformationProvider assemblyInformationProvider;
/// <summary>
/// The <see cref="ILogger"/> for the <see cref="AbstractHttpClientFactory"/>.
/// </summary>
readonly ILogger<AbstractHttpClientFactory> logger;
/// <summary>
/// Initializes a new instance of the <see cref="AbstractHttpClientFactory"/> class.
/// </summary>
/// <param name="httpClientFactory">The value of <see cref="httpClientFactory"/>.</param>
/// <param name="assemblyInformationProvider">The value of <see cref="assemblyInformationProvider"/>.</param>
/// <param name="logger">The value of <see cref="logger"/>.</param>
public AbstractHttpClientFactory(
IHttpClientFactory httpClientFactory,
IAssemblyInformationProvider assemblyInformationProvider,
ILogger<AbstractHttpClientFactory> logger)
{
this.httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));
this.assemblyInformationProvider = assemblyInformationProvider ?? throw new ArgumentNullException(nameof(assemblyInformationProvider));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
/// <inheritdoc />
public IHttpClient CreateClient()
{
logger.LogTrace("Creating client...");
var innerClient = httpClientFactory.CreateClient();
try
{
var client = new Tgstation.Server.Common.HttpClient(innerClient);
client.DefaultRequestHeaders.UserAgent.Add(assemblyInformationProvider.ProductInfoHeaderValue);
return client;
}
catch
{
innerClient.Dispose();
throw;
}
}
}
}
@@ -73,7 +73,7 @@ namespace Tgstation.Server.Host.Core
public static IServerFactory CreateDefaultServerFactory()
{
var assemblyInformationProvider = new AssemblyInformationProvider();
var ioManager = new DefaultIOManager(assemblyInformationProvider);
var ioManager = new DefaultIOManager();
return new ServerFactory(
assemblyInformationProvider,
ioManager);
@@ -253,6 +253,7 @@ namespace Tgstation.Server.Host.Core
// Enable managed HTTP clients
services.AddHttpClient();
services.AddSingleton<IAbstractHttpClientFactory, AbstractHttpClientFactory>();
void AddTypedContext<TContext>() where TContext : DatabaseContext
{
@@ -350,6 +351,7 @@ namespace Tgstation.Server.Host.Core
services.AddSingleton<IProviderFactory, ProviderFactory>();
services.AddSingleton<IChatManagerFactory, ChatManagerFactory>();
services.AddSingleton<ISynchronousIOManager, SynchronousIOManager>();
services.AddSingleton<IFileDownloader, FileDownloader>();
services.AddSingleton<FileTransferService>();
services.AddSingleton<IFileTransferStreamHandler>(x => x.GetRequiredService<FileTransferService>());
services.AddSingleton<IFileTransferTicketProvider>(x => x.GetRequiredService<FileTransferService>());
@@ -0,0 +1,16 @@
using Tgstation.Server.Common;
namespace Tgstation.Server.Host.Core
{
/// <summary>
/// Creates <see cref="IHttpClient"/>s.
/// </summary>
public interface IAbstractHttpClientFactory
{
/// <summary>
/// Create a <see cref="IHttpClient"/>.
/// </summary>
/// <returns>A new <see cref="IHttpClient"/>.</returns>
IHttpClient CreateClient();
}
}
@@ -30,6 +30,11 @@ namespace Tgstation.Server.Host.Core
/// </summary>
readonly IIOManager ioManager;
/// <summary>
/// The <see cref="IFileDownloader"/> for the <see cref="ServerUpdater"/>.
/// </summary>
readonly IFileDownloader fileDownloader;
/// <summary>
/// The <see cref="IServerControl"/> for the <see cref="ServerUpdater"/>.
/// </summary>
@@ -55,18 +60,21 @@ namespace Tgstation.Server.Host.Core
/// </summary>
/// <param name="gitHubClientFactory">The value of <see cref="gitHubClientFactory"/>.</param>
/// <param name="ioManager">The value of <see cref="ioManager"/>.</param>
/// <param name="fileDownloader">The value of <see cref="fileDownloader"/>.</param>
/// <param name="serverControl">The value of <see cref="serverControl"/>.</param>
/// <param name="logger">The value of <see cref="logger"/>.</param>
/// <param name="updatesConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="updatesConfiguration"/>.</param>
public ServerUpdater(
IGitHubClientFactory gitHubClientFactory,
IIOManager ioManager,
IFileDownloader fileDownloader,
IServerControl serverControl,
ILogger<ServerUpdater> logger,
IOptions<UpdatesConfiguration> updatesConfigurationOptions)
{
this.gitHubClientFactory = gitHubClientFactory ?? throw new ArgumentNullException(nameof(gitHubClientFactory));
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
this.fileDownloader = fileDownloader ?? throw new ArgumentNullException(nameof(fileDownloader));
this.serverControl = serverControl ?? throw new ArgumentNullException(nameof(serverControl));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
updatesConfiguration = updatesConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(updatesConfigurationOptions));
@@ -154,7 +162,7 @@ namespace Tgstation.Server.Host.Core
try
{
logger.LogTrace("Downloading zip package...");
updateZipData = await ioManager.DownloadFile(serverUpdateOperation.UpdateZipUrl, cancellationToken);
updateZipData = await fileDownloader.DownloadFile(serverUpdateOperation.UpdateZipUrl, cancellationToken);
}
catch (Exception e1)
{
@@ -3,12 +3,10 @@ using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.System;
namespace Tgstation.Server.Host.IO
{
@@ -32,11 +30,6 @@ namespace Tgstation.Server.Host.IO
/// </summary>
public const TaskCreationOptions BlockingTaskCreationOptions = TaskCreationOptions.None;
/// <summary>
/// The <see cref="IAssemblyInformationProvider"/> for the <see cref="DefaultIOManager"/>.
/// </summary>
readonly IAssemblyInformationProvider assemblyInformationProvider;
/// <summary>
/// Recursively empty a directory.
/// </summary>
@@ -67,22 +60,6 @@ namespace Tgstation.Server.Host.IO
dir.Delete(true);
}
/// <summary>
/// Initializes a new instance of the <see cref="DefaultIOManager"/> class.
/// </summary>
/// <param name="assemblyInformationProvider">The value of <see cref="assemblyInformationProvider"/>.</param>
public DefaultIOManager(IAssemblyInformationProvider assemblyInformationProvider)
{
this.assemblyInformationProvider = assemblyInformationProvider ?? throw new ArgumentNullException(nameof(assemblyInformationProvider));
}
/// <summary>
/// Initializes a new instance of the <see cref="DefaultIOManager"/> class.
/// </summary>
protected DefaultIOManager()
{
}
/// <inheritdoc />
public async Task CopyDirectory(
string src,
@@ -296,29 +273,6 @@ namespace Tgstation.Server.Host.IO
BlockingTaskCreationOptions,
TaskScheduler.Current);
/// <inheritdoc />
public async Task<MemoryStream> DownloadFile(Uri url, CancellationToken cancellationToken)
{
using var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.UserAgent.Add(assemblyInformationProvider.ProductInfoHeaderValue);
var webRequestTask = httpClient.GetAsync(url, cancellationToken);
using var response = await webRequestTask;
response.EnsureSuccessStatusCode();
using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken);
var memoryStream = new MemoryStream();
try
{
await responseStream.CopyToAsync(memoryStream, cancellationToken);
memoryStream.Seek(0, SeekOrigin.Begin);
return memoryStream;
}
catch
{
memoryStream.Dispose();
throw;
}
}
/// <inheritdoc />
public Task ZipToDirectory(string path, Stream zipFile, CancellationToken cancellationToken) => Task.Factory.StartNew(
() =>
@@ -0,0 +1,64 @@
using System;
using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Tgstation.Server.Host.Core;
namespace Tgstation.Server.Host.IO
{
/// <inheritdoc />
public sealed class FileDownloader : IFileDownloader
{
/// <summary>
/// The <see cref="IAbstractHttpClientFactory"/> for the <see cref="FileDownloader"/>.
/// </summary>
readonly IAbstractHttpClientFactory httpClientFactory;
/// <summary>
/// The <see cref="ILogger"/> for the <see cref="FileDownloader"/>.
/// </summary>
readonly ILogger<FileDownloader> logger;
/// <summary>
/// Initializes a new instance of the <see cref="FileDownloader"/> class.
/// </summary>
/// <param name="httpClientFactory">The value of <see cref="httpClientFactory"/>.</param>
/// <param name="logger">The value of <see cref="logger"/>.</param>
public FileDownloader(IAbstractHttpClientFactory httpClientFactory, ILogger<FileDownloader> logger)
{
this.httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
/// <inheritdoc />
public async Task<MemoryStream> DownloadFile(Uri url, CancellationToken cancellationToken)
{
logger.LogDebug("Starting download of {url}...", url);
using var httpClient = httpClientFactory.CreateClient();
using var request = new HttpRequestMessage(
HttpMethod.Get,
url);
var webRequestTask = httpClient.SendAsync(request, cancellationToken);
using var response = await webRequestTask;
response.EnsureSuccessStatusCode();
using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken);
var memoryStream = new MemoryStream();
try
{
await responseStream.CopyToAsync(memoryStream, cancellationToken);
memoryStream.Seek(0, SeekOrigin.Begin);
return memoryStream;
}
catch
{
memoryStream.Dispose();
throw;
}
}
}
}
@@ -0,0 +1,21 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace Tgstation.Server.Host.IO
{
/// <summary>
/// Downloads files.
/// </summary>
interface IFileDownloader
{
/// <summary>
/// Downloads a file from <paramref name="url"/>.
/// </summary>
/// <param name="url">The URL to download.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in a <see cref="MemoryStream"/> of the downloaded file.</returns>
Task<MemoryStream> DownloadFile(Uri url, CancellationToken cancellationToken);
}
}
@@ -192,14 +192,6 @@ namespace Tgstation.Server.Host.IO
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
Task MoveDirectory(string source, string destination, CancellationToken cancellationToken);
/// <summary>
/// Downloads a file from <paramref name="url"/>.
/// </summary>
/// <param name="url">The URL to download.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in a <see cref="MemoryStream"/> of the downloaded file.</returns>
Task<MemoryStream> DownloadFile(Uri url, CancellationToken cancellationToken);
/// <summary>
/// Extract a set of <paramref name="zipFile"/> to a given <paramref name="path"/>.
/// </summary>
@@ -1,11 +1,10 @@
using System;
using System.Net.Http;
using Microsoft.Extensions.Logging;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.System;
using Tgstation.Server.Host.Core;
namespace Tgstation.Server.Host.Security.OAuth
{
@@ -18,29 +17,27 @@ namespace Tgstation.Server.Host.Security.OAuth
public override OAuthProvider Provider => OAuthProvider.Discord;
/// <inheritdoc />
protected override Uri TokenUrl => new Uri("https://discord.com/api/oauth2/token");
protected override Uri TokenUrl => new ("https://discord.com/api/oauth2/token");
/// <inheritdoc />
protected override Uri UserInformationUrl => new Uri("https://discord.com/api/users/@me");
protected override Uri UserInformationUrl => new ("https://discord.com/api/users/@me");
/// <summary>
/// Initializes a new instance of the <see cref="DiscordOAuthValidator"/> class.
/// </summary>
/// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/> for the <see cref="GenericOAuthValidator"/>.</param>
/// <param name="assemblyInformationProvider">The <see cref="IAssemblyInformationProvider"/> for the <see cref="GenericOAuthValidator"/>.</param>
/// <param name="httpClientFactory">The <see cref="IAbstractHttpClientFactory"/> for the <see cref="GenericOAuthValidator"/>.</param>
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="GenericOAuthValidator"/>.</param>
/// <param name="oAuthConfiguration">The <see cref="OAuthConfiguration"/> for the <see cref="GenericOAuthValidator"/>.</param>
public DiscordOAuthValidator(
IHttpClientFactory httpClientFactory,
IAssemblyInformationProvider assemblyInformationProvider,
IAbstractHttpClientFactory httpClientFactory,
ILogger<DiscordOAuthValidator> logger,
OAuthConfiguration oAuthConfiguration)
: base(httpClientFactory, assemblyInformationProvider, logger, oAuthConfiguration)
: base(httpClientFactory, logger, oAuthConfiguration)
{
}
/// <inheritdoc />
protected override OAuthTokenRequest CreateTokenRequest(string code) => new OAuthTokenRequest(OAuthConfiguration, code, "identify");
protected override OAuthTokenRequest CreateTokenRequest(string code) => new (OAuthConfiguration, code, "identify");
/// <inheritdoc />
protected override string DecodeTokenPayload(dynamic responseJson) => responseJson.access_token;
@@ -13,8 +13,9 @@ using Newtonsoft.Json.Serialization;
using Tgstation.Server.Api;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Common;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.System;
using Tgstation.Server.Host.Core;
namespace Tgstation.Server.Host.Security.OAuth
{
@@ -49,18 +50,13 @@ namespace Tgstation.Server.Host.Security.OAuth
/// <summary>
/// The <see cref="IHttpClientFactory"/> for the <see cref="GenericOAuthValidator"/>.
/// </summary>
readonly IHttpClientFactory httpClientFactory;
/// <summary>
/// The <see cref="IAssemblyInformationProvider"/> for the <see cref="GenericOAuthValidator"/>.
/// </summary>
readonly IAssemblyInformationProvider assemblyInformationProvider;
readonly IAbstractHttpClientFactory httpClientFactory;
/// <summary>
/// Gets <see cref="JsonSerializerSettings"/> that should be used.
/// </summary>
/// <returns>A new <see cref="JsonSerializerSettings"/> <see cref="object"/>.</returns>
protected static JsonSerializerSettings SerializerSettings() => new JsonSerializerSettings
protected static JsonSerializerSettings SerializerSettings() => new ()
{
ContractResolver = new DefaultContractResolver
{
@@ -72,17 +68,14 @@ namespace Tgstation.Server.Host.Security.OAuth
/// Initializes a new instance of the <see cref="GenericOAuthValidator"/> class.
/// </summary>
/// <param name="httpClientFactory">The value of <see cref="httpClientFactory"/>.</param>
/// <param name="assemblyInformationProvider">The value of <see cref="assemblyInformationProvider"/>.</param>
/// <param name="logger">The value of <see cref="Logger"/>.</param>
/// <param name="oAuthConfiguration">The value of <see cref="OAuthConfiguration"/>.</param>
public GenericOAuthValidator(
IHttpClientFactory httpClientFactory,
IAssemblyInformationProvider assemblyInformationProvider,
IAbstractHttpClientFactory httpClientFactory,
ILogger<GenericOAuthValidator> logger,
OAuthConfiguration oAuthConfiguration)
{
this.httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));
this.assemblyInformationProvider = assemblyInformationProvider ?? throw new ArgumentNullException(nameof(assemblyInformationProvider));
Logger = logger ?? throw new ArgumentNullException(nameof(logger));
OAuthConfiguration = oAuthConfiguration ?? throw new ArgumentNullException(nameof(oAuthConfiguration));
}
@@ -109,7 +102,7 @@ namespace Tgstation.Server.Host.Security.OAuth
tokenRequest.Content = new FormUrlEncodedContent(tokenRequestDictionary);
var tokenResponse = await httpClient.SendAsync(tokenRequest, cancellationToken);
tokenResponsePayload = await tokenResponse.Content.ReadAsStringAsync();
tokenResponsePayload = await tokenResponse.Content.ReadAsStringAsync(cancellationToken);
tokenResponse.EnsureSuccessStatusCode();
var tokenResponseJson = JObject.Parse(tokenResponsePayload);
@@ -129,7 +122,7 @@ namespace Tgstation.Server.Host.Security.OAuth
accessToken);
var userInformationResponse = await httpClient.SendAsync(userInformationRequest, cancellationToken);
userInformationPayload = await userInformationResponse.Content.ReadAsStringAsync();
userInformationPayload = await userInformationResponse.Content.ReadAsStringAsync(cancellationToken);
userInformationResponse.EnsureSuccessStatusCode();
var userInformationJson = JObject.Parse(userInformationPayload);
@@ -178,16 +171,15 @@ namespace Tgstation.Server.Host.Security.OAuth
protected abstract OAuthTokenRequest CreateTokenRequest(string code);
/// <summary>
/// Create a new configured <see cref="HttpClient"/>.
/// Create a new configured <see cref="IHttpClient"/>.
/// </summary>
/// <returns>A new configured <see cref="HttpClient"/>.</returns>
HttpClient CreateHttpClient()
/// <returns>A new configured <see cref="IHttpClient"/>.</returns>
IHttpClient CreateHttpClient()
{
var httpClient = httpClientFactory.CreateClient();
try
{
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Json));
httpClient.DefaultRequestHeaders.UserAgent.Add(assemblyInformationProvider.ProductInfoHeaderValue);
return httpClient;
}
catch
@@ -1,11 +1,10 @@
using System;
using System.Net.Http;
using Microsoft.Extensions.Logging;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.System;
using Tgstation.Server.Host.Core;
namespace Tgstation.Server.Host.Security.OAuth
{
@@ -18,29 +17,27 @@ namespace Tgstation.Server.Host.Security.OAuth
public override OAuthProvider Provider => OAuthProvider.InvisionCommunity;
/// <inheritdoc />
protected override Uri TokenUrl => new Uri($"{OAuthConfiguration.ServerUrl}/oauth/token/"); // This needs the trailing slash or it doesnt get the token. Do not remove.
protected override Uri TokenUrl => new ($"{OAuthConfiguration.ServerUrl}/oauth/token/"); // This needs the trailing slash or it doesnt get the token. Do not remove.
/// <inheritdoc />
protected override Uri UserInformationUrl => new Uri($"{OAuthConfiguration.ServerUrl}/api/core/me");
protected override Uri UserInformationUrl => new ($"{OAuthConfiguration.ServerUrl}/api/core/me");
/// <summary>
/// Initializes a new instance of the <see cref="InvisionCommunityOAuthValidator"/> class.
/// </summary>
/// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/> for the <see cref="GenericOAuthValidator"/>.</param>
/// <param name="assemblyInformationProvider">The <see cref="IAssemblyInformationProvider"/> for the <see cref="GenericOAuthValidator"/>.</param>
/// <param name="httpClientFactory">The <see cref="IAbstractHttpClientFactory"/> for the <see cref="GenericOAuthValidator"/>.</param>
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="GenericOAuthValidator"/>.</param>
/// <param name="oAuthConfiguration">The <see cref="OAuthConfiguration"/> for the <see cref="GenericOAuthValidator"/>.</param>
public InvisionCommunityOAuthValidator(
IHttpClientFactory httpClientFactory,
IAssemblyInformationProvider assemblyInformationProvider,
IAbstractHttpClientFactory httpClientFactory,
ILogger<InvisionCommunityOAuthValidator> logger,
OAuthConfiguration oAuthConfiguration)
: base(httpClientFactory, assemblyInformationProvider, logger, oAuthConfiguration)
: base(httpClientFactory, logger, oAuthConfiguration)
{
}
/// <inheritdoc />
protected override OAuthTokenRequest CreateTokenRequest(string code) => new OAuthTokenRequest(OAuthConfiguration, code, "profile");
protected override OAuthTokenRequest CreateTokenRequest(string code) => new (OAuthConfiguration, code, "profile");
/// <inheritdoc />
protected override string DecodeTokenPayload(dynamic responseJson) => responseJson.access_token;
@@ -1,11 +1,10 @@
using System;
using System.Net.Http;
using Microsoft.Extensions.Logging;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.System;
using Tgstation.Server.Host.Core;
namespace Tgstation.Server.Host.Security.OAuth
{
@@ -18,10 +17,10 @@ namespace Tgstation.Server.Host.Security.OAuth
public override OAuthProvider Provider => OAuthProvider.Keycloak;
/// <inheritdoc />
protected override Uri TokenUrl => new Uri($"{BaseProtocolPath}/token");
protected override Uri TokenUrl => new ($"{BaseProtocolPath}/token");
/// <inheritdoc />
protected override Uri UserInformationUrl => new Uri($"{BaseProtocolPath}/userinfo");
protected override Uri UserInformationUrl => new ($"{BaseProtocolPath}/userinfo");
/// <summary>
/// Base path to the server's OAuth endpoint.
@@ -31,21 +30,19 @@ namespace Tgstation.Server.Host.Security.OAuth
/// <summary>
/// Initializes a new instance of the <see cref="KeycloakOAuthValidator"/> class.
/// </summary>
/// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/> for the <see cref="GenericOAuthValidator"/>.</param>
/// <param name="assemblyInformationProvider">The <see cref="IAssemblyInformationProvider"/> for the <see cref="GenericOAuthValidator"/>.</param>
/// <param name="httpClientFactory">The <see cref="IAbstractHttpClientFactory"/> for the <see cref="GenericOAuthValidator"/>.</param>
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="GenericOAuthValidator"/>.</param>
/// <param name="oAuthConfiguration">The <see cref="OAuthConfiguration"/> for the <see cref="GenericOAuthValidator"/>.</param>
public KeycloakOAuthValidator(
IHttpClientFactory httpClientFactory,
IAssemblyInformationProvider assemblyInformationProvider,
IAbstractHttpClientFactory httpClientFactory,
ILogger<KeycloakOAuthValidator> logger,
OAuthConfiguration oAuthConfiguration)
: base(httpClientFactory, assemblyInformationProvider, logger, oAuthConfiguration)
: base(httpClientFactory, logger, oAuthConfiguration)
{
}
/// <inheritdoc />
protected override OAuthTokenRequest CreateTokenRequest(string code) => new OAuthTokenRequest(OAuthConfiguration, code, "openid");
protected override OAuthTokenRequest CreateTokenRequest(string code) => new (OAuthConfiguration, code, "openid");
/// <inheritdoc />
protected override string DecodeTokenPayload(dynamic responseJson) => responseJson.access_token;
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
@@ -11,7 +10,6 @@ using Microsoft.Extensions.Options;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.System;
namespace Tgstation.Server.Host.Security.OAuth
{
@@ -27,14 +25,12 @@ namespace Tgstation.Server.Host.Security.OAuth
/// Initializes a new instance of the <see cref="OAuthProviders"/> class.
/// </summary>
/// <param name="gitHubClientFactory">The <see cref="IGitHubClientFactory"/> to use.</param>
/// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/> to use.</param>
/// <param name="assemblyInformationProvider">The <see cref="IAssemblyInformationProvider"/> to use.</param>
/// <param name="httpClientFactory">The <see cref="IAbstractHttpClientFactory"/> to use.</param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/> to use.</param>
/// <param name="securityConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the <see cref="SecurityConfiguration"/> to use.</param>
public OAuthProviders(
IGitHubClientFactory gitHubClientFactory,
IHttpClientFactory httpClientFactory,
IAssemblyInformationProvider assemblyInformationProvider,
IAbstractHttpClientFactory httpClientFactory,
ILoggerFactory loggerFactory,
IOptions<SecurityConfiguration> securityConfigurationOptions)
{
@@ -60,7 +56,6 @@ namespace Tgstation.Server.Host.Security.OAuth
validatorsBuilder.Add(
new DiscordOAuthValidator(
httpClientFactory,
assemblyInformationProvider,
loggerFactory.CreateLogger<DiscordOAuthValidator>(),
discordConfig));
@@ -68,7 +63,6 @@ namespace Tgstation.Server.Host.Security.OAuth
validatorsBuilder.Add(
new TGForumsOAuthValidator(
httpClientFactory,
assemblyInformationProvider,
loggerFactory.CreateLogger<TGForumsOAuthValidator>(),
tgConfig));
@@ -76,7 +70,6 @@ namespace Tgstation.Server.Host.Security.OAuth
validatorsBuilder.Add(
new KeycloakOAuthValidator(
httpClientFactory,
assemblyInformationProvider,
loggerFactory.CreateLogger<KeycloakOAuthValidator>(),
keyCloakConfig));
@@ -84,7 +77,6 @@ namespace Tgstation.Server.Host.Security.OAuth
validatorsBuilder.Add(
new InvisionCommunityOAuthValidator(
httpClientFactory,
assemblyInformationProvider,
loggerFactory.CreateLogger<InvisionCommunityOAuthValidator>(),
invisionConfig));
}
@@ -1,11 +1,10 @@
using System;
using System.Net.Http;
using Microsoft.Extensions.Logging;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.System;
using Tgstation.Server.Host.Core;
namespace Tgstation.Server.Host.Security.OAuth
{
@@ -26,18 +25,15 @@ namespace Tgstation.Server.Host.Security.OAuth
/// <summary>
/// Initializes a new instance of the <see cref="TGForumsOAuthValidator"/> class.
/// </summary>
/// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/> for the <see cref="GenericOAuthValidator"/>.</param>
/// <param name="assemblyInformationProvider">The <see cref="IAssemblyInformationProvider"/> for the <see cref="GenericOAuthValidator"/>.</param>
/// <param name="httpClientFactory">The <see cref="IAbstractHttpClientFactory"/> for the <see cref="GenericOAuthValidator"/>.</param>
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="GenericOAuthValidator"/>.</param>
/// <param name="oAuthConfiguration">The <see cref="OAuthConfiguration"/> for the <see cref="GenericOAuthValidator"/>.</param>
public TGForumsOAuthValidator(
IHttpClientFactory httpClientFactory,
IAssemblyInformationProvider assemblyInformationProvider,
IAbstractHttpClientFactory httpClientFactory,
ILogger<TGForumsOAuthValidator> logger,
OAuthConfiguration oAuthConfiguration)
: base(
httpClientFactory,
assemblyInformationProvider,
logger,
oAuthConfiguration)
{
+29 -31
View File
@@ -71,11 +71,6 @@ namespace Tgstation.Server.Host.Swarm
/// </summary>
bool SwarmMode => swarmConfiguration.PrivateKey != null;
/// <summary>
/// Lazily constructed <see cref="IRestartRegistration"/>.
/// </summary>
readonly Lazy<IRestartRegistration> lazyRestartRegistration;
/// <summary>
/// The <see cref="IDatabaseContextFactory"/> for the <see cref="SwarmService"/>.
/// </summary>
@@ -92,9 +87,9 @@ namespace Tgstation.Server.Host.Swarm
readonly IAssemblyInformationProvider assemblyInformationProvider;
/// <summary>
/// The <see cref="IHttpClientFactory"/> for the <see cref="SwarmService"/>.
/// The <see cref="IAbstractHttpClientFactory"/> for the <see cref="SwarmService"/>.
/// </summary>
readonly IHttpClientFactory httpClientFactory;
readonly IAbstractHttpClientFactory httpClientFactory;
/// <summary>
/// The <see cref="IAsyncDelayer"/> for the <see cref="SwarmService"/>.
@@ -106,6 +101,11 @@ namespace Tgstation.Server.Host.Swarm
/// </summary>
readonly IServerUpdater serverUpdater;
/// <summary>
/// The <see cref="IRestartRegistration"/> for the <see cref="SwarmService"/>.
/// </summary>
readonly IRestartRegistration restartRegistration;
/// <summary>
/// The <see cref="ILogger"/> for the <see cref="SwarmService"/>.
/// </summary>
@@ -202,7 +202,7 @@ namespace Tgstation.Server.Host.Swarm
IDatabaseContextFactory databaseContextFactory,
IDatabaseSeeder databaseSeeder,
IAssemblyInformationProvider assemblyInformationProvider,
IHttpClientFactory httpClientFactory,
IAbstractHttpClientFactory httpClientFactory,
IServerControl serverControl,
IServerUpdater serverUpdater,
IAsyncDelayer asyncDelayer,
@@ -215,6 +215,7 @@ namespace Tgstation.Server.Host.Swarm
this.httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));
if (serverControl == null)
throw new ArgumentNullException(nameof(serverControl));
restartRegistration = serverControl.RegisterForRestart(this);
this.serverUpdater = serverUpdater ?? throw new ArgumentNullException(nameof(serverUpdater));
this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer));
@@ -249,12 +250,14 @@ namespace Tgstation.Server.Host.Swarm
updateSynchronizationLock = new object();
}
lazyRestartRegistration = new Lazy<IRestartRegistration>(() => serverControl.RegisterForRestart(this));
}
/// <inheritdoc />
public void Dispose() => serverHealthCheckCancellationTokenSource?.Dispose();
public void Dispose()
{
restartRegistration.Dispose();
serverHealthCheckCancellationTokenSource?.Dispose();
}
/// <inheritdoc />
public async Task RemoteAbortUpdate(CancellationToken cancellationToken)
@@ -379,7 +382,7 @@ namespace Tgstation.Server.Host.Swarm
if (!commitGoAhead)
{
logger.LogDebug(
"Update commit failed!{0}",
"Update commit failed!{maybeTimeout}",
timeoutTask.IsCompleted
? " Timed out!"
: String.Empty);
@@ -465,8 +468,6 @@ namespace Tgstation.Server.Host.Swarm
else
logger.LogTrace("Swarm mode disabled");
_ = lazyRestartRegistration.Value;
SwarmRegistrationResult result;
if (swarmController)
{
@@ -1164,25 +1165,22 @@ namespace Tgstation.Server.Host.Swarm
var request = new HttpRequestMessage(
httpMethod,
swarmServer.Address + subroute[1..]);
request.Headers.Add(SwarmConstants.ApiKeyHeader, swarmConfiguration.PrivateKey);
request.Headers.UserAgent.Clear();
request.Headers.UserAgent.Add(assemblyInformationProvider.ProductInfoHeaderValue);
request.Headers.Accept.Clear();
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Json));
if (registrationIdOverride.HasValue)
request.Headers.Add(SwarmConstants.RegistrationIdHeader, registrationIdOverride.Value.ToString());
else if (swarmController)
{
lock (swarmServers)
if (registrationIds.TryGetValue(swarmServer.Identifier, out var registrationId))
request.Headers.Add(SwarmConstants.RegistrationIdHeader, registrationId.ToString());
}
else if (controllerRegistration.HasValue)
request.Headers.Add(SwarmConstants.RegistrationIdHeader, controllerRegistration.Value.ToString());
try
{
request.Headers.Add(SwarmConstants.ApiKeyHeader, swarmConfiguration.PrivateKey);
request.Headers.Accept.Clear();
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Json));
if (registrationIdOverride.HasValue)
request.Headers.Add(SwarmConstants.RegistrationIdHeader, registrationIdOverride.Value.ToString());
else if (swarmController)
{
lock (swarmServers)
if (registrationIds.TryGetValue(swarmServer.Identifier, out var registrationId))
request.Headers.Add(SwarmConstants.RegistrationIdHeader, registrationId.ToString());
}
else if (controllerRegistration.HasValue)
request.Headers.Add(SwarmConstants.RegistrationIdHeader, controllerRegistration.Value.ToString());
if (body != null)
request.Content = new StringContent(
JsonConvert.SerializeObject(body, SerializerSettings),
@@ -139,6 +139,7 @@
<ItemGroup>
<ProjectReference Include="..\Tgstation.Server.Api\Tgstation.Server.Api.csproj" />
<ProjectReference Include="..\Tgstation.Server.Common\Tgstation.Server.Common.csproj" />
<ProjectReference Include="..\Tgstation.Server.Host.Common\Tgstation.Server.Host.Common.csproj" />
</ItemGroup>
@@ -11,8 +11,8 @@ using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Api;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Response;
using Tgstation.Server.Common;
namespace Tgstation.Server.Client.Tests
{
@@ -15,14 +15,16 @@ namespace Tgstation.Server.Host.Components.Byond.Tests
[TestMethod]
public void TestConstruction()
{
Assert.ThrowsException<ArgumentNullException>(() => new PosixByondInstaller(null, null, null));
Assert.ThrowsException<ArgumentNullException>(() => new PosixByondInstaller(null, null, null, null));
var mockPostWriteHandler = new Mock<IPostWriteHandler>();
Assert.ThrowsException<ArgumentNullException>(() => new PosixByondInstaller(mockPostWriteHandler.Object, null, null));
Assert.ThrowsException<ArgumentNullException>(() => new PosixByondInstaller(mockPostWriteHandler.Object, null, null, null));
var mockIOManager = new Mock<IIOManager>();
Assert.ThrowsException<ArgumentNullException>(() => new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, null));
Assert.ThrowsException<ArgumentNullException>(() => new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, null, null));
var mockFileDownloader = Mock.Of<IFileDownloader>();
Assert.ThrowsException<ArgumentNullException>(() => new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockFileDownloader, null));
var mockLogger = new Mock<ILogger<PosixByondInstaller>>();
_ = new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockLogger.Object);
_ = new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockFileDownloader, mockLogger.Object);
}
[TestMethod]
@@ -31,7 +33,8 @@ namespace Tgstation.Server.Host.Components.Byond.Tests
var mockPostWriteHandler = new Mock<IPostWriteHandler>();
var mockIOManager = new Mock<IIOManager>();
var mockLogger = new Mock<ILogger<PosixByondInstaller>>();
var installer = new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockLogger.Object);
var mockFileDownloader = Mock.Of<IFileDownloader>();
var installer = new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockFileDownloader, mockLogger.Object);
await installer.CleanCache(default);
}
@@ -42,12 +45,13 @@ namespace Tgstation.Server.Host.Components.Byond.Tests
var mockIOManager = new Mock<IIOManager>();
var mockPostWriteHandler = new Mock<IPostWriteHandler>();
var mockLogger = new Mock<ILogger<PosixByondInstaller>>();
var installer = new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockLogger.Object);
var mockFileDownloader = new Mock<IFileDownloader>();
var installer = new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockFileDownloader.Object, mockLogger.Object);
await Assert.ThrowsExceptionAsync<ArgumentNullException>(() => installer.DownloadVersion(null, default));
var ourArray = Array.Empty<byte>();
mockIOManager.Setup(x => x.DownloadFile(It.Is<Uri>(uri => uri == new Uri("https://secure.byond.com/download/build/511/511.1385_byond_linux.zip")), default)).Returns(Task.FromResult(new MemoryStream(ourArray))).Verifiable();
mockFileDownloader.Setup(x => x.DownloadFile(It.Is<Uri>(uri => uri == new Uri("https://secure.byond.com/download/build/511/511.1385_byond_linux.zip")), default)).Returns(Task.FromResult(new MemoryStream(ourArray))).Verifiable();
var result = await installer.DownloadVersion(new Version(511, 1385), default);
@@ -61,7 +65,8 @@ namespace Tgstation.Server.Host.Components.Byond.Tests
var mockIOManager = new Mock<IIOManager>();
var mockPostWriteHandler = new Mock<IPostWriteHandler>();
var mockLogger = new Mock<ILogger<PosixByondInstaller>>();
var installer = new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockLogger.Object);
var mockFileDownloader = Mock.Of<IFileDownloader>();
var installer = new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockFileDownloader, mockLogger.Object);
const string FakePath = "fake";
await Assert.ThrowsExceptionAsync<ArgumentNullException>(() => installer.InstallByond(null, null, default));
@@ -10,7 +10,7 @@ namespace Tgstation.Server.Host.IO.Tests
[TestClass]
public sealed class TestIOManager
{
readonly IIOManager ioManager = new DefaultIOManager(new AssemblyInformationProvider());
readonly IIOManager ioManager = new DefaultIOManager();
[TestMethod]
public async Task TestDeleteDirectory()
@@ -57,7 +57,7 @@ namespace Tgstation.Server.Host.System.Tests
processExecutor = new ProcessExecutor(
new PosixProcessFeatures(
new Lazy<IProcessExecutor>(() => processExecutor),
new DefaultIOManager(new AssemblyInformationProvider()),
new DefaultIOManager(),
loggerFactory.CreateLogger<PosixProcessFeatures>()),
Mock.Of<IIOManager>(),
loggerFactory.CreateLogger<ProcessExecutor>(),
@@ -22,7 +22,7 @@ namespace Tgstation.Server.Host.System.Tests
{
features = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? (IProcessFeatures)new WindowsProcessFeatures(Mock.Of<ILogger<WindowsProcessFeatures>>())
: new PosixProcessFeatures(new Lazy<IProcessExecutor>(() => null), new DefaultIOManager(new AssemblyInformationProvider()), Mock.Of<ILogger<PosixProcessFeatures>>());
: new PosixProcessFeatures(new Lazy<IProcessExecutor>(() => null), new DefaultIOManager(), Mock.Of<ILogger<PosixProcessFeatures>>());
}
[TestMethod]
@@ -84,7 +84,7 @@ namespace Tgstation.Server.Host.Tests
mockServer.Setup(x => x.Run(It.IsAny<CancellationToken>())).Throws(exception);
mockServer.SetupGet(x => x.RestartRequested).Returns(true);
var mockServerFactory = new Mock<IServerFactory>();
mockServerFactory.SetupGet(x => x.IOManager).Returns(new DefaultIOManager(new AssemblyInformationProvider()));
mockServerFactory.SetupGet(x => x.IOManager).Returns(new DefaultIOManager());
mockServerFactory.Setup(x => x.CreateServer(It.IsNotNull<string[]>(), It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(mockServer.Object);
var program = new Program
{
@@ -0,0 +1,10 @@
using Tgstation.Server.Common;
using Tgstation.Server.Host.Core;
namespace Tgstation.Server.Tests
{
sealed class ConcreteHttpClientFactory : IAbstractHttpClientFactory
{
public IHttpClient CreateClient() => new HttpClient();
}
}
@@ -105,12 +105,18 @@ namespace Tgstation.Server.Tests.Instance
var byondInstaller = new PlatformIdentifier().IsWindows
? (IByondInstaller)new WindowsByondInstaller(
Mock.Of<IProcessExecutor>(),
new DefaultIOManager(new AssemblyInformationProvider()),
Mock.Of<IIOManager>(),
new FileDownloader(
new ConcreteHttpClientFactory(),
Mock.Of<ILogger<FileDownloader>>()),
generalConfigOptionsMock.Object,
Mock.Of<ILogger<WindowsByondInstaller>>())
: new PosixByondInstaller(
Mock.Of<IPostWriteHandler>(),
new DefaultIOManager(new AssemblyInformationProvider()),
Mock.Of<IIOManager>(),
new FileDownloader(
new ConcreteHttpClientFactory(),
Mock.Of<ILogger<FileDownloader>>()),
Mock.Of<ILogger<PosixByondInstaller>>());
using var windowsByondInstaller = byondInstaller as WindowsByondInstaller;
@@ -103,7 +103,7 @@ namespace Tgstation.Server.Tests.Instance
Task SetupDMApiTests(CancellationToken cancellationToken)
{
// just use an I/O manager here
var ioManager = new DefaultIOManager(new AssemblyInformationProvider());
var ioManager = new DefaultIOManager();
return Task.WhenAll(
ioManager.CopyDirectory(
"../../../../DMAPI",
+10
View File
@@ -193,6 +193,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Migrator.C
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Host.Common", "src\Tgstation.Server.Host.Common\Tgstation.Server.Host.Common.csproj", "{CF3968A0-EA81-4464-B2D4-C7D40F6B5BCB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tgstation.Server.Common", "src\Tgstation.Server.Common\Tgstation.Server.Common.csproj", "{70CD9A98-D31A-44A4-81D1-D02764CEEEFD}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -341,6 +343,14 @@ Global
{CF3968A0-EA81-4464-B2D4-C7D40F6B5BCB}.Release|Any CPU.Build.0 = Release|Any CPU
{CF3968A0-EA81-4464-B2D4-C7D40F6B5BCB}.ReleaseNoService|Any CPU.ActiveCfg = Release|Any CPU
{CF3968A0-EA81-4464-B2D4-C7D40F6B5BCB}.ReleaseNoService|Any CPU.Build.0 = Release|Any CPU
{70CD9A98-D31A-44A4-81D1-D02764CEEEFD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{70CD9A98-D31A-44A4-81D1-D02764CEEEFD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{70CD9A98-D31A-44A4-81D1-D02764CEEEFD}.DebugNoService|Any CPU.ActiveCfg = Debug|Any CPU
{70CD9A98-D31A-44A4-81D1-D02764CEEEFD}.DebugNoService|Any CPU.Build.0 = Debug|Any CPU
{70CD9A98-D31A-44A4-81D1-D02764CEEEFD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{70CD9A98-D31A-44A4-81D1-D02764CEEEFD}.Release|Any CPU.Build.0 = Release|Any CPU
{70CD9A98-D31A-44A4-81D1-D02764CEEEFD}.ReleaseNoService|Any CPU.ActiveCfg = Release|Any CPU
{70CD9A98-D31A-44A4-81D1-D02764CEEEFD}.ReleaseNoService|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -0,0 +1,10 @@
using Tgstation.Server.Common;
using Tgstation.Server.Host.Core;
namespace Tgstation.Server.Migrator
{
sealed class ConcreteHttpClientFactory : IAbstractHttpClientFactory
{
public IHttpClient CreateClient() => new HttpClient();
}
}
+10 -2
View File
@@ -15,11 +15,15 @@ using System.ServiceProcess;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Octokit;
using Tgstation.Server.Api;
using Tgstation.Server.Client;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.Setup;
using Tgstation.Server.Migrator;
using FileMode = System.IO.FileMode;
@@ -377,10 +381,14 @@ try
// TGS5 DOWNLOAD AND UNZIP
Console.WriteLine("Downloading TGS5...");
using (var tgsFiveZipMemoryStream = await SetupApplication.IOManager.DownloadFile(new Uri(serverServiceAsset.BrowserDownloadUrl), default))
var httpClientFactory = new ConcreteHttpClientFactory();
using (var loggerFactory = LoggerFactory.Create(builder => { }))
{
var fileDownloader = new FileDownloader(httpClientFactory, loggerFactory.CreateLogger<FileDownloader>());
using var tgsFiveZipMemoryStream = await fileDownloader.DownloadFile(new Uri(serverServiceAsset.BrowserDownloadUrl), default);
Console.WriteLine("Unzipping TGS5...");
await SetupApplication.IOManager.ZipToDirectory(tgsInstallPath, tgsFiveZipMemoryStream, default);
await serverFactory.IOManager.ZipToDirectory(tgsInstallPath, tgsFiveZipMemoryStream, default);
}
// TGS5 CONFIG SETUP