Check platform compatibility at startup

- Refactor LibGit2Sharp repository creation into factory class
- PlatformIdentifer repository check now runs via this factory
- Removed CredentialsProvider, integrated into RepositoryFactory
This commit is contained in:
Jordan Brown
2020-04-20 12:13:35 -04:00
parent 3a89d8ebc0
commit 0d1b19d01a
9 changed files with 208 additions and 105 deletions
@@ -91,11 +91,6 @@ namespace Tgstation.Server.Host.Components
/// </summary>
readonly IJobManager jobManager;
/// <summary>
/// The <see cref="ICredentialsProvider"/> for the <see cref="InstanceFactory"/>
/// </summary>
readonly ICredentialsProvider credentialsProvider;
/// <summary>
/// The <see cref="INetworkPromptReaper"/> for the <see cref="InstanceFactory"/>
/// </summary>
@@ -111,6 +106,11 @@ namespace Tgstation.Server.Host.Components
/// </summary>
readonly IPlatformIdentifier platformIdentifier;
/// <summary>
/// The <see cref="IRepositoryFactory"/> for the <see cref="InstanceFactory"/>.
/// </summary>
readonly IRepositoryFactory repositoryFactory;
/// <summary>
/// Construct an <see cref="InstanceFactory"/>
/// </summary>
@@ -128,10 +128,10 @@ namespace Tgstation.Server.Host.Components
/// <param name="postWriteHandler">The value of <see cref="postWriteHandler"/></param>
/// <param name="watchdogFactory">The value of <see cref="watchdogFactory"/></param>
/// <param name="jobManager">The value of <see cref="jobManager"/></param>
/// <param name="credentialsProvider">The value of <see cref="credentialsProvider"/></param>
/// <param name="networkPromptReaper">The value of <see cref="networkPromptReaper"/></param>
/// <param name="gitHubClientFactory">The value of <see cref="gitHubClientFactory"/></param>
/// <param name="platformIdentifier">The value of <see cref="platformIdentifier"/></param>
/// <param name="repositoryFactory">The value of <see cref="repositoryFactory"/>.</param>
public InstanceFactory(
IIOManager ioManager,
IDatabaseContextFactory databaseContextFactory,
@@ -147,10 +147,10 @@ namespace Tgstation.Server.Host.Components
IPostWriteHandler postWriteHandler,
IWatchdogFactory watchdogFactory,
IJobManager jobManager,
ICredentialsProvider credentialsProvider,
INetworkPromptReaper networkPromptReaper,
IGitHubClientFactory gitHubClientFactory,
IPlatformIdentifier platformIdentifier)
IPlatformIdentifier platformIdentifier,
IRepositoryFactory repositoryFactory)
{
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory));
@@ -166,10 +166,10 @@ namespace Tgstation.Server.Host.Components
this.postWriteHandler = postWriteHandler ?? throw new ArgumentNullException(nameof(postWriteHandler));
this.watchdogFactory = watchdogFactory ?? throw new ArgumentNullException(nameof(watchdogFactory));
this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
this.credentialsProvider = credentialsProvider ?? throw new ArgumentNullException(nameof(credentialsProvider));
this.networkPromptReaper = networkPromptReaper ?? throw new ArgumentNullException(nameof(networkPromptReaper));
this.gitHubClientFactory = gitHubClientFactory ?? throw new ArgumentNullException(nameof(gitHubClientFactory));
this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier));
this.repositoryFactory = repositoryFactory ?? throw new ArgumentNullException(nameof(repositoryFactory));
}
/// <inheritdoc />
@@ -187,7 +187,13 @@ namespace Tgstation.Server.Host.Components
var configuration = new StaticFiles.Configuration(configurationIoManager, synchronousIOManager, symlinkFactory, processExecutor, postWriteHandler, platformIdentifier, loggerFactory.CreateLogger<StaticFiles.Configuration>());
var eventConsumer = new EventConsumer(configuration);
var repoManager = new RepositoryManager(metadata.RepositorySettings, repoIoManager, eventConsumer, credentialsProvider, loggerFactory.CreateLogger<Repository.Repository>(), loggerFactory.CreateLogger<RepositoryManager>());
var repoManager = new RepositoryManager(
repositoryFactory,
repoIoManager,
eventConsumer,
loggerFactory.CreateLogger<Repository.Repository>(),
loggerFactory.CreateLogger<RepositoryManager>(),
metadata.RepositorySettings);
try
{
var byond = new ByondManager(byondIOManager, byondInstaller, eventConsumer, loggerFactory.CreateLogger<ByondManager>());
@@ -10,6 +10,7 @@ using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.Database;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.Jobs;
using Tgstation.Server.Host.System;
namespace Tgstation.Server.Host.Components
{
@@ -46,6 +47,11 @@ namespace Tgstation.Server.Host.Components
/// </summary>
readonly IServerControl serverControl;
/// <summary>
/// The <see cref="IPlatformIdentifier"/> for the <see cref="InstanceManager"/>
/// </summary>
readonly IPlatformIdentifier platformIdentifier;
/// <summary>
/// The <see cref="ILogger"/> for the <see cref="InstanceManager"/>
/// </summary>
@@ -75,8 +81,17 @@ namespace Tgstation.Server.Host.Components
/// <param name="application">The value of <see cref="application"/></param>
/// <param name="jobManager">The value of <see cref="jobManager"/></param>
/// <param name="serverControl">The value of <see cref="serverControl"/></param>
/// <param name="platformIdentifier">The value of <see cref="platformIdentifier"/>.</param>
/// <param name="logger">The value of <see cref="logger"/></param>
public InstanceManager(IInstanceFactory instanceFactory, IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, IJobManager jobManager, IServerControl serverControl, ILogger<InstanceManager> logger)
public InstanceManager(
IInstanceFactory instanceFactory,
IIOManager ioManager,
IDatabaseContextFactory databaseContextFactory,
IApplication application,
IJobManager jobManager,
IServerControl serverControl,
IPlatformIdentifier platformIdentifier,
ILogger<InstanceManager> logger)
{
this.instanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory));
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
@@ -84,6 +99,7 @@ namespace Tgstation.Server.Host.Components
this.application = application ?? throw new ArgumentNullException(nameof(application));
this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
this.serverControl = serverControl ?? throw new ArgumentNullException(nameof(serverControl));
this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
serverControl.RegisterForRestart(this);
@@ -212,6 +228,7 @@ namespace Tgstation.Server.Host.Components
{
try
{
platformIdentifier.CheckCompatibility();
var factoryStartup = instanceFactory.StartAsync(cancellationToken);
await databaseContext.Initialize(cancellationToken).ConfigureAwait(false);
await jobManager.StartAsync(cancellationToken).ConfigureAwait(false);
@@ -1,53 +0,0 @@
using LibGit2Sharp;
using LibGit2Sharp.Handlers;
using Microsoft.Extensions.Logging;
using System;
using Tgstation.Server.Host.Jobs;
namespace Tgstation.Server.Host.Components.Repository
{
/// <inheritdoc />
sealed class CredentialsProvider : ICredentialsProvider
{
/// <summary>
/// The <see cref="ILogger"/> for the <see cref="CredentialsProvider"/>
/// </summary>
readonly ILogger<CredentialsProvider> logger;
/// <summary>
/// Construct a <see cref="CredentialsProvider"/>
/// </summary>
/// <param name="logger">The value of <see cref="logger"/></param>
public CredentialsProvider(ILogger<CredentialsProvider> logger)
{
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
/// <inheritdoc />
public CredentialsHandler GenerateHandler(string username, string password) => (a, b, supportedCredentialTypes) =>
{
var hasCreds = username != null;
var supportsUserPass = supportedCredentialTypes.HasFlag(SupportedCredentialTypes.UsernamePassword);
var supportsAnonymous = supportedCredentialTypes.HasFlag(SupportedCredentialTypes.Default);
logger.LogTrace("Credentials requested. Present: {0}. Supports anonymous: {1}. Supports user/pass: {2}", hasCreds, supportsAnonymous, supportsUserPass);
if (supportsUserPass)
{
if (hasCreds)
return new UsernamePasswordCredentials
{
Username = username,
Password = password
};
}
if (supportsAnonymous)
return new DefaultCredentials();
if (hasCreds)
throw new JobException("Remote does not support anonymous authentication!");
throw new JobException("Server does not support anonymous or username/password authentication!");
};
}
}
@@ -13,6 +13,6 @@ namespace Tgstation.Server.Host.Components.Repository
/// <param name="username">The optional username to use in the <see cref="CredentialsHandler"/></param>
/// <param name="password">The optional password to use in the <see cref="CredentialsHandler"/></param>
/// <returns>A new <see cref="CredentialsHandler"/></returns>
CredentialsHandler GenerateHandler(string username, string password);
CredentialsHandler GenerateCredentialsHandler(string username, string password);
}
}
@@ -0,0 +1,36 @@
using LibGit2Sharp;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Tgstation.Server.Host.Components.Repository
{
/// <summary>
/// Factory for creating <see cref="LibGit2Sharp.IRepository"/>s.
/// </summary>
interface IRepositoryFactory : ICredentialsProvider
{
/// <summary>
/// Create an in-memeory <see cref="LibGit2Sharp.IRepository"/>.
/// </summary>
/// <returns>A new in-memory <see cref="LibGit2Sharp.IRepository"/>.</returns>
LibGit2Sharp.IRepository CreateInMemory();
/// <summary>
/// Load a <see cref="LibGit2Sharp.IRepository"/> from a given <paramref name="path"/>.
/// </summary>
/// <param name="path">The full path to the <see cref="LibGit2Sharp.IRepository"/>.</param>
/// <returns>The loaded <see cref="LibGit2Sharp.IRepository"/>.</returns>
LibGit2Sharp.IRepository CreateFromPath(string path);
/// <summary>
/// Clone a remote <see cref="LibGit2Sharp.IRepository"/>.
/// </summary>
/// <param name="url">The <see cref="Uri"/> of the remote.</param>
/// <param name="cloneOptions">The <see cref="CloneOptions"/>.</param>
/// <param name="path">The full path to the cloned <see cref="LibGit2Sharp.IRepository"/>.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
Task Clone(Uri url, CloneOptions cloneOptions, string path, CancellationToken cancellationToken);
}
}
@@ -0,0 +1,84 @@
using LibGit2Sharp;
using LibGit2Sharp.Handlers;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Host.Jobs;
namespace Tgstation.Server.Host.Components.Repository
{
/// <inheritdoc />
sealed class RepositoryFactory : IRepositoryFactory
{
/// <summary>
/// The <see cref="ILogger"/> for the <see cref="RepositoryFactory"/>.
/// </summary>
readonly ILogger<RepositoryFactory> logger;
/// <summary>
/// Initializes a new instance of the <see cref="RepositoryFactory"/> <see langword="class"/>.
/// </summary>
/// <param name="logger">The value of <see cref="logger"/>.</param>
public RepositoryFactory(ILogger<RepositoryFactory> logger)
{
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
/// <inheritdoc />
public LibGit2Sharp.IRepository CreateInMemory()
{
logger.LogTrace("Creating in-memory LibGit2Sharp...");
return new LibGit2Sharp.Repository();
}
/// <inheritdoc />
public LibGit2Sharp.IRepository CreateFromPath(string path)
{
if (path == null)
throw new ArgumentNullException(nameof(path));
logger.LogTrace("Creating LibGit2Sharp Repostory at {0}...", path);
return new LibGit2Sharp.Repository(path);
}
/// <inheritdoc />
public Task Clone(Uri url, CloneOptions cloneOptions, string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
{
try
{
LibGit2Sharp.Repository.Clone(url.ToString(), path, cloneOptions);
}
catch (UserCancelledException)
{
cancellationToken.ThrowIfCancellationRequested();
}
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
/// <inheritdoc />
public CredentialsHandler GenerateCredentialsHandler(string username, string password) => (a, b, supportedCredentialTypes) =>
{
var hasCreds = username != null;
var supportsUserPass = supportedCredentialTypes.HasFlag(SupportedCredentialTypes.UsernamePassword);
var supportsAnonymous = supportedCredentialTypes.HasFlag(SupportedCredentialTypes.Default);
logger.LogTrace("Credentials requested. Present: {0}. Supports anonymous: {1}. Supports user/pass: {2}", hasCreds, supportsAnonymous, supportsUserPass);
if (supportsUserPass)
{
if (hasCreds)
return new UsernamePasswordCredentials
{
Username = username,
Password = password
};
}
if (supportsAnonymous)
return new DefaultCredentials();
if (hasCreds)
throw new JobException("Remote does not support anonymous authentication!");
throw new JobException("Server does not support anonymous or username/password authentication!");
};
}
}
@@ -18,6 +18,11 @@ namespace Tgstation.Server.Host.Components.Repository
/// <inheritdoc />
public bool CloneInProgress { get; private set; }
/// <summary>
/// The <see cref="IRepositoryFactory"/> for the <see cref="RepositoryManager"/>
/// </summary>
readonly IRepositoryFactory repositoryFactory;
/// <summary>
/// The <see cref="IIOManager"/> for the <see cref="RepositoryManager"/>
/// </summary>
@@ -28,11 +33,6 @@ namespace Tgstation.Server.Host.Components.Repository
/// </summary>
readonly IEventConsumer eventConsumer;
/// <summary>
/// The <see cref="ICredentialsProvider"/> for the <see cref="RepositoryManager"/>
/// </summary>
readonly ICredentialsProvider credentialsProvider;
/// <summary>
/// The <see cref="ILogger"/> created <see cref="Repository"/>s
/// </summary>
@@ -56,18 +56,23 @@ namespace Tgstation.Server.Host.Components.Repository
/// <summary>
/// Construct a <see cref="RepositoryManager"/>
/// </summary>
/// <param name="repositorySettings">The value of <see cref="repositorySettings"/></param>
/// <param name="repositoryFactory">The value of <see cref="repositoryFactory"/>.</param>
/// <param name="ioManager">The value of <see cref="ioManager"/></param>
/// <param name="eventConsumer">The value of <see cref="eventConsumer"/></param>
/// <param name="credentialsProvider">The value of <see cref="credentialsProvider"/></param>
/// <param name="repositoryLogger">The value of <see cref="repositoryLogger"/></param>
/// <param name="logger">The value of <see cref="logger"/></param>
public RepositoryManager(RepositorySettings repositorySettings, IIOManager ioManager, IEventConsumer eventConsumer, ICredentialsProvider credentialsProvider, ILogger<Repository> repositoryLogger, ILogger<RepositoryManager> logger)
/// <param name="repositorySettings">The value of <see cref="repositorySettings"/></param>
public RepositoryManager(
IRepositoryFactory repositoryFactory,
IIOManager ioManager,
IEventConsumer eventConsumer,
ILogger<Repository> repositoryLogger,
ILogger<RepositoryManager> logger,
RepositorySettings repositorySettings)
{
this.repositorySettings = repositorySettings ?? throw new ArgumentNullException(nameof(repositorySettings));
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer));
this.credentialsProvider = credentialsProvider ?? throw new ArgumentNullException(nameof(credentialsProvider));
this.repositoryLogger = repositoryLogger ?? throw new ArgumentNullException(nameof(repositoryLogger));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
semaphore = new SemaphoreSlim(1);
@@ -104,30 +109,28 @@ namespace Tgstation.Server.Host.Components.Repository
if (!await ioManager.DirectoryExists(".", cancellationToken).ConfigureAwait(false))
try
{
await Task.Factory.StartNew(() =>
var cloneOptions = new CloneOptions
{
string path = null;
try
OnProgress = (a) => !cancellationToken.IsCancellationRequested,
OnTransferProgress = (a) =>
{
path = LibGit2Sharp.Repository.Clone(url.ToString(), ioManager.ResolvePath("."), new CloneOptions
{
OnProgress = (a) => !cancellationToken.IsCancellationRequested,
OnTransferProgress = (a) =>
{
var percentage = 100 * (((float)a.IndexedObjects + a.ReceivedObjects) / (a.TotalObjects * 2));
progressReporter((int)percentage);
return !cancellationToken.IsCancellationRequested;
},
RecurseSubmodules = true,
OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested,
RepositoryOperationStarting = (a) => !cancellationToken.IsCancellationRequested,
BranchName = initialBranch,
CredentialsProvider = credentialsProvider.GenerateHandler(username, password)
});
}
catch (UserCancelledException) { }
cancellationToken.ThrowIfCancellationRequested();
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
var percentage = 100 * (((float)a.IndexedObjects + a.ReceivedObjects) / (a.TotalObjects * 2));
progressReporter((int)percentage);
return !cancellationToken.IsCancellationRequested;
},
RecurseSubmodules = true,
OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested,
RepositoryOperationStarting = (a) => !cancellationToken.IsCancellationRequested,
BranchName = initialBranch,
CredentialsProvider = repositoryFactory.GenerateCredentialsHandler(username, password)
};
await repositoryFactory.Clone(
url,
cloneOptions,
ioManager.ResolvePath("."),
cancellationToken)
.ConfigureAwait(false);
}
catch
{
@@ -168,13 +171,13 @@ namespace Tgstation.Server.Host.Components.Repository
if (CloneInProgress)
throw new InvalidOperationException("The repository is being cloned!");
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
LibGit2Sharp.Repository repo = null;
LibGit2Sharp.IRepository repo = null;
await Task.Factory.StartNew(() =>
{
try
{
logger.LogTrace("Creating LibGit2Sharp.Repository...");
repo = new LibGit2Sharp.Repository(ioManager.ResolvePath("."));
repo = repositoryFactory.CreateFromPath(ioManager.ResolvePath("."));
}
catch (RepositoryNotFoundException e)
{
@@ -193,7 +196,7 @@ namespace Tgstation.Server.Host.Components.Repository
return null;
}
return new Repository(repo, ioManager, eventConsumer, credentialsProvider, repositoryLogger, () =>
return new Repository(repo, ioManager, eventConsumer, repositoryFactory, repositoryLogger, () =>
{
logger.LogTrace("Releasing semaphore due to Repository disposal...");
semaphore.Release();
@@ -347,7 +347,7 @@ namespace Tgstation.Server.Host.Core
});
// configure component services
services.AddSingleton<ICredentialsProvider, CredentialsProvider>();
services.AddSingleton<IRepositoryFactory, RepositoryFactory>();
services.AddSingleton<IProviderFactory, ProviderFactory>();
services.AddSingleton<IChatFactory, ChatFactory>();
services.AddSingleton<IInstanceFactory, InstanceFactory>();
@@ -1,7 +1,7 @@
using LibGit2Sharp;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging;
using System;
using System.Runtime.InteropServices;
using Tgstation.Server.Host.Components.Repository;
using Tgstation.Server.Host.Security;
namespace Tgstation.Server.Host.System
@@ -20,6 +20,11 @@ namespace Tgstation.Server.Host.System
/// </summary>
readonly ISystemIdentityFactory systemIdentityFactory;
/// <summary>
/// The <see cref="IRepositoryFactory"/> for the <see cref="PlatformIdentifier"/>.
/// </summary>
readonly IRepositoryFactory repositoryFactory;
/// <summary>
/// The <see cref="ILogger"/> for the <see cref="PlatformIdentifier"/>.
/// </summary>
@@ -28,11 +33,16 @@ namespace Tgstation.Server.Host.System
/// <summary>
/// Construct a <see cref="PlatformIdentifier"/>
/// </summary>
/// <param name="systemIdentityFactory">The value of <see cref="ISystemIdentityFactory"/>.</param>
/// <param name="systemIdentityFactory">The value of <see cref="systemIdentityFactory"/>.</param>
/// <param name="repositoryFactory">The value of <see cref="repositoryFactory"/>.</param>
/// <param name="logger">The value of <see cref="logger"/>.</param>
public PlatformIdentifier(ISystemIdentityFactory systemIdentityFactory, ILogger<PlatformIdentifier> logger)
public PlatformIdentifier(
ISystemIdentityFactory systemIdentityFactory,
IRepositoryFactory repositoryFactory,
ILogger<PlatformIdentifier> logger)
{
this.systemIdentityFactory = systemIdentityFactory ?? throw new ArgumentNullException(nameof(systemIdentityFactory));
this.repositoryFactory = repositoryFactory ?? throw new ArgumentNullException(nameof(repositoryFactory));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
@@ -44,7 +54,7 @@ namespace Tgstation.Server.Host.System
{
try
{
new Repository().Dispose();
repositoryFactory.CreateInMemory().Dispose();
}
catch
{