mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-27 07:04:57 +01:00
GitHubService and factory + AdministrationController replacement
This commit is contained in:
@@ -19,7 +19,6 @@ using Tgstation.Server.Api.Rights;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.Extensions;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.Security;
|
||||
using Tgstation.Server.Host.System;
|
||||
@@ -40,9 +39,9 @@ namespace Tgstation.Server.Host.Controllers
|
||||
const string OctokitException = "Bad GitHub API response, check configuration!";
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IGitHubClientFactory"/> for the <see cref="AdministrationController"/>.
|
||||
/// The <see cref="IGitHubService"/> for the <see cref="AdministrationController"/>.
|
||||
/// </summary>
|
||||
readonly IGitHubClientFactory gitHubClientFactory;
|
||||
readonly IGitHubService gitHubService;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IServerControl"/> for the <see cref="AdministrationController"/>.
|
||||
@@ -74,11 +73,6 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// </summary>
|
||||
readonly IFileTransferTicketProvider fileTransferService;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="UpdatesConfiguration"/> for the <see cref="AdministrationController"/>.
|
||||
/// </summary>
|
||||
readonly UpdatesConfiguration updatesConfiguration;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="FileLoggingConfiguration"/> for the <see cref="AdministrationController"/>.
|
||||
/// </summary>
|
||||
@@ -89,7 +83,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// </summary>
|
||||
/// <param name="databaseContext">The <see cref="IDatabaseContext"/> for the <see cref="ApiController"/>.</param>
|
||||
/// <param name="authenticationContextFactory">The <see cref="IAuthenticationContextFactory"/> for the <see cref="ApiController"/>.</param>
|
||||
/// <param name="gitHubClientFactory">The value of <see cref="gitHubClientFactory"/>.</param>
|
||||
/// <param name="gitHubService">The value of <see cref="gitHubService"/>.</param>
|
||||
/// <param name="serverControl">The value of <see cref="serverControl"/>.</param>
|
||||
/// <param name="serverUpdateInitiator">The value of <see cref="serverUpdateInitiator"/>.</param>
|
||||
/// <param name="assemblyInformationProvider">The value of <see cref="assemblyInformationProvider"/>.</param>
|
||||
@@ -97,12 +91,11 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// <param name="platformIdentifier">The value of <see cref="platformIdentifier"/>.</param>
|
||||
/// <param name="fileTransferService">The value of <see cref="fileTransferService"/>.</param>
|
||||
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="ApiController"/>.</param>
|
||||
/// <param name="updatesConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing value of <see cref="updatesConfiguration"/>.</param>
|
||||
/// <param name="fileLoggingConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing value of <see cref="fileLoggingConfiguration"/>.</param>
|
||||
public AdministrationController(
|
||||
IDatabaseContext databaseContext,
|
||||
IAuthenticationContextFactory authenticationContextFactory,
|
||||
IGitHubClientFactory gitHubClientFactory,
|
||||
IGitHubService gitHubService,
|
||||
IServerControl serverControl,
|
||||
IServerUpdateInitiator serverUpdateInitiator,
|
||||
IAssemblyInformationProvider assemblyInformationProvider,
|
||||
@@ -110,7 +103,6 @@ namespace Tgstation.Server.Host.Controllers
|
||||
IPlatformIdentifier platformIdentifier,
|
||||
IFileTransferTicketProvider fileTransferService,
|
||||
ILogger<AdministrationController> logger,
|
||||
IOptions<UpdatesConfiguration> updatesConfigurationOptions,
|
||||
IOptions<FileLoggingConfiguration> fileLoggingConfigurationOptions)
|
||||
: base(
|
||||
databaseContext,
|
||||
@@ -118,14 +110,13 @@ namespace Tgstation.Server.Host.Controllers
|
||||
logger,
|
||||
true)
|
||||
{
|
||||
this.gitHubClientFactory = gitHubClientFactory ?? throw new ArgumentNullException(nameof(gitHubClientFactory));
|
||||
this.gitHubService = gitHubService ?? throw new ArgumentNullException(nameof(gitHubService));
|
||||
this.serverControl = serverControl ?? throw new ArgumentNullException(nameof(serverControl));
|
||||
this.serverUpdateInitiator = serverUpdateInitiator ?? throw new ArgumentNullException(nameof(serverUpdateInitiator));
|
||||
this.assemblyInformationProvider = assemblyInformationProvider ?? throw new ArgumentNullException(nameof(assemblyInformationProvider));
|
||||
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
|
||||
this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier));
|
||||
this.fileTransferService = fileTransferService ?? throw new ArgumentNullException(nameof(fileTransferService));
|
||||
updatesConfiguration = updatesConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(updatesConfigurationOptions));
|
||||
fileLoggingConfiguration = fileLoggingConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(fileLoggingConfigurationOptions));
|
||||
}
|
||||
|
||||
@@ -150,26 +141,19 @@ namespace Tgstation.Server.Host.Controllers
|
||||
Uri repoUrl = null;
|
||||
try
|
||||
{
|
||||
var gitHubClient = gitHubClientFactory.CreateClient();
|
||||
var repositoryTask = gitHubClient
|
||||
.Repository
|
||||
.Get(updatesConfiguration.GitHubRepositoryId)
|
||||
.WithToken(cancellationToken);
|
||||
var releases = (await gitHubClient
|
||||
.Repository
|
||||
.Release
|
||||
.GetAll(updatesConfiguration.GitHubRepositoryId)
|
||||
.WithToken(cancellationToken))
|
||||
.Where(x => x.TagName.StartsWith(
|
||||
updatesConfiguration.GitTagPrefix,
|
||||
StringComparison.InvariantCulture));
|
||||
var repositoryUrlTask = gitHubService.GetUpdatesRepositoryUrl(cancellationToken);
|
||||
var releases = await gitHubService.GetTgsReleases(cancellationToken);
|
||||
|
||||
foreach (var release in releases)
|
||||
if (Version.TryParse(release.TagName.Replace(updatesConfiguration.GitTagPrefix, String.Empty, StringComparison.Ordinal), out var version)
|
||||
&& version.Major > 3 // Forward/backward compatible but not before TGS4
|
||||
foreach (var kvp in releases)
|
||||
{
|
||||
var version = kvp.Key;
|
||||
var release = kvp.Value;
|
||||
if (version.Major > 3 // Forward/backward compatible but not before TGS4
|
||||
&& (greatestVersion == null || version > greatestVersion))
|
||||
greatestVersion = version;
|
||||
repoUrl = new Uri((await repositoryTask).HtmlUrl);
|
||||
}
|
||||
|
||||
repoUrl = await repositoryUrlTask;
|
||||
}
|
||||
catch (NotFoundException e)
|
||||
{
|
||||
|
||||
@@ -370,7 +370,10 @@ namespace Tgstation.Server.Host.Core
|
||||
services.AddSingleton<IFileDownloader, FileDownloader>();
|
||||
services.AddSingleton<IServerPortProvider, ServerPortProivder>();
|
||||
services.AddSingleton<ITopicClientFactory, TopicClientFactory>();
|
||||
|
||||
services.AddSingleton<IGitHubClientFactory, GitHubClientFactory>();
|
||||
services.AddSingleton<IGitHubServiceFactory, GitHubServiceFactory>();
|
||||
services.AddSingleton(x => x.GetRequiredService<IGitHubServiceFactory>().CreateService());
|
||||
|
||||
// configure root services
|
||||
services.AddSingleton<IJobService, JobService>();
|
||||
|
||||
@@ -67,7 +67,9 @@ namespace Tgstation.Server.Host.Utils
|
||||
public IGitHubClient CreateClient() => GetOrCreateClient(generalConfiguration.GitHubAccessToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public IGitHubClient CreateClient(string accessToken) => GetOrCreateClient(accessToken ?? throw new ArgumentNullException(nameof(accessToken)));
|
||||
public IGitHubClient CreateClient(string accessToken)
|
||||
=> GetOrCreateClient(
|
||||
accessToken ?? throw new ArgumentNullException(nameof(accessToken)));
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve a <see cref="GitHubClient"/> from the <see cref="clientCache"/> or add a new one based on a given <paramref name="accessToken"/>.
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using Octokit;
|
||||
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.Extensions;
|
||||
|
||||
namespace Tgstation.Server.Host.Utils
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class GitHubService : IGitHubService
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IGitHubClient"/> for the <see cref="GitHubService"/>.
|
||||
/// </summary>
|
||||
readonly IGitHubClient gitHubClient;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILogger"/> for the <see cref="GitHubService"/>.
|
||||
/// </summary>
|
||||
readonly ILogger<GitHubService> logger;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="UpdatesConfiguration"/> for the <see cref="GitHubService"/>.
|
||||
/// </summary>
|
||||
readonly UpdatesConfiguration updatesConfiguration;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GitHubService"/> class.
|
||||
/// </summary>
|
||||
/// <param name="gitHubClient">The value of <see cref="gitHubClient"/>.</param>
|
||||
/// <param name="logger">The value of <see cref="logger"/>.</param>
|
||||
/// <param name="updatesConfiguration">The value of <see cref="updatesConfiguration"/>.</param>
|
||||
public GitHubService(IGitHubClient gitHubClient, ILogger<GitHubService> logger, UpdatesConfiguration updatesConfiguration)
|
||||
{
|
||||
this.gitHubClient = gitHubClient ?? throw new ArgumentNullException(nameof(gitHubClient));
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
this.updatesConfiguration = updatesConfiguration ?? throw new ArgumentNullException(nameof(updatesConfiguration));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<Dictionary<Version, Release>> GetTgsReleases(CancellationToken cancellationToken)
|
||||
{
|
||||
logger.LogTrace("GetTgsReleases");
|
||||
var allReleases = await gitHubClient
|
||||
.Repository
|
||||
.Release
|
||||
.GetAll(updatesConfiguration.GitHubRepositoryId)
|
||||
.WithToken(cancellationToken);
|
||||
|
||||
logger.LogTrace("{totalReleases} total releases", allReleases.Count);
|
||||
var releases = allReleases
|
||||
.Select(release =>
|
||||
{
|
||||
if (!release.TagName.StartsWith(updatesConfiguration.GitTagPrefix, StringComparison.InvariantCulture)
|
||||
|| !Version.TryParse(release.TagName.Replace(updatesConfiguration.GitTagPrefix, String.Empty, StringComparison.Ordinal), out var version))
|
||||
return null;
|
||||
|
||||
return Tuple.Create(version, release);
|
||||
})
|
||||
.Where(tuple => tuple != null)
|
||||
.ToDictionary(tuple => tuple.Item1, tuple => tuple.Item2);
|
||||
|
||||
logger.LogTrace("{parsedReleases} parsed releases", releases.Count);
|
||||
return releases;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<Uri> GetUpdatesRepositoryUrl(CancellationToken cancellationToken)
|
||||
{
|
||||
logger.LogTrace("GetUpdatesRepositoryUrl");
|
||||
var repository = await gitHubClient
|
||||
.Repository
|
||||
.Get(updatesConfiguration.GitHubRepositoryId)
|
||||
.WithToken(cancellationToken);
|
||||
|
||||
var repoUrl = new Uri(repository.HtmlUrl);
|
||||
logger.LogTrace("Maps to {repostioryUrl}", repoUrl);
|
||||
|
||||
return repoUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
using System;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
using Octokit;
|
||||
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
|
||||
namespace Tgstation.Server.Host.Utils
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class GitHubServiceFactory : IGitHubServiceFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IGitHubClientFactory"/> for the <see cref="GitHubServiceFactory"/>.
|
||||
/// </summary>
|
||||
readonly IGitHubClientFactory gitHubClientFactory;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILoggerFactory"/> for the <see cref="GitHubServiceFactory"/>.
|
||||
/// </summary>
|
||||
readonly ILoggerFactory loggerFactory;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="UpdatesConfiguration"/> for the <see cref="GitHubServiceFactory"/>.
|
||||
/// </summary>
|
||||
readonly UpdatesConfiguration updatesConfiguration;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GitHubServiceFactory"/> class.
|
||||
/// </summary>
|
||||
/// <param name="gitHubClientFactory">The value of <see cref="gitHubClientFactory"/>.</param>
|
||||
/// <param name="loggerFactory">The value of <see cref="loggerFactory"/>.</param>
|
||||
/// <param name="updatesConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing value of <see cref="updatesConfiguration"/>.</param>
|
||||
public GitHubServiceFactory(
|
||||
IGitHubClientFactory gitHubClientFactory,
|
||||
ILoggerFactory loggerFactory,
|
||||
IOptions<UpdatesConfiguration> updatesConfigurationOptions)
|
||||
{
|
||||
this.gitHubClientFactory = gitHubClientFactory ?? throw new ArgumentNullException(nameof(gitHubClientFactory));
|
||||
this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
|
||||
updatesConfiguration = updatesConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(updatesConfigurationOptions));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IGitHubService CreateService() => CreateServiceImpl(gitHubClientFactory.CreateClient());
|
||||
|
||||
/// <inheritdoc />
|
||||
public IGitHubService CreateService(string accessToken)
|
||||
=> CreateServiceImpl(
|
||||
gitHubClientFactory.CreateClient(
|
||||
accessToken ?? throw new ArgumentNullException(nameof(accessToken))));
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="GitHubService"/>.
|
||||
/// </summary>
|
||||
/// <param name="gitHubClient">The <see cref="IGitHubClient"/> for the <see cref="GitHubService"/>.</param>
|
||||
/// <returns>A new <see cref="GitHubService"/>.</returns>
|
||||
GitHubService CreateServiceImpl(IGitHubClient gitHubClient)
|
||||
=> new (
|
||||
gitHubClient,
|
||||
loggerFactory.CreateLogger<GitHubService>(),
|
||||
updatesConfiguration);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Octokit;
|
||||
|
||||
namespace Tgstation.Server.Host.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// Service for interacting with GitHub.
|
||||
/// </summary>
|
||||
public interface IGitHubService
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the <see cref="Uri"/> of the repository designated as the updates repository.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="Uri"/> of the designated updates repository.</returns>
|
||||
Task<Uri> GetUpdatesRepositoryUrl(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Get all valid TGS <see cref="Release"/>s from the configured update source.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in a <see cref="Dictionary{TKey, TValue}"/> of TGS <see cref="Release"/>s keyed by their <see cref="Version"/>.</returns>
|
||||
Task<Dictionary<Version, Release>> GetTgsReleases(CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace Tgstation.Server.Host.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// Factory for <see cref="IGitHubService"/>s.
|
||||
/// </summary>
|
||||
public interface IGitHubServiceFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a <see cref="IGitHubService"/>.
|
||||
/// </summary>
|
||||
/// <returns>A new <see cref="IGitHubService"/>.</returns>
|
||||
public IGitHubService CreateService();
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="IGitHubService"/>.
|
||||
/// </summary>
|
||||
/// <param name="accessToken">The access token to use for communication with GitHub.</param>
|
||||
/// <returns>A new <see cref="IGitHubService"/>.</returns>
|
||||
public IGitHubService CreateService(string accessToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
using Moq;
|
||||
|
||||
using Octokit;
|
||||
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
|
||||
namespace Tgstation.Server.Host.Utils.Tests
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class TestGitHubServiceFactory
|
||||
{
|
||||
[TestMethod]
|
||||
public void TestConstructor()
|
||||
{
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new GitHubServiceFactory(null, null, null));
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new GitHubServiceFactory(Mock.Of<IGitHubClientFactory>(), null, null));
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new GitHubServiceFactory(Mock.Of<IGitHubClientFactory>(), Mock.Of<ILoggerFactory>(), null));
|
||||
var mockOptions = new Mock<IOptions<UpdatesConfiguration>>();
|
||||
mockOptions.SetupGet(x => x.Value).Returns(new UpdatesConfiguration());
|
||||
|
||||
_ = new GitHubServiceFactory(Mock.Of<IGitHubClientFactory>(), Mock.Of<ILoggerFactory>(), mockOptions.Object);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestCreateService()
|
||||
{
|
||||
var mockFactory = new Mock<IGitHubClientFactory>();
|
||||
|
||||
mockFactory.Setup(x => x.CreateClient()).Returns(Mock.Of<IGitHubClient>()).Verifiable();
|
||||
|
||||
var mockToken = "asdf";
|
||||
mockFactory.Setup(x => x.CreateClient(mockToken)).Returns(Mock.Of<IGitHubClient>()).Verifiable();
|
||||
|
||||
var mockOptions = new Mock<IOptions<UpdatesConfiguration>>();
|
||||
mockOptions.SetupGet(x => x.Value).Returns(new UpdatesConfiguration());
|
||||
|
||||
var factory = new GitHubServiceFactory(mockFactory.Object, Mock.Of<ILoggerFactory>(), mockOptions.Object);
|
||||
|
||||
Assert.ThrowsException<ArgumentNullException>(() => factory.CreateService(null));
|
||||
Assert.AreEqual(0, mockFactory.Invocations.Count);
|
||||
|
||||
var result1 = factory.CreateService();
|
||||
Assert.IsNotNull(result1);
|
||||
|
||||
var result2 = factory.CreateService(mockToken);
|
||||
Assert.IsNotNull(result2);
|
||||
|
||||
mockFactory.VerifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user