mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-31 09:02:41 +01:00
Merge branch 'dev' of https://github.com/tgstation/tgstation-server into 921-NoSpam
This commit is contained in:
@@ -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>());
|
||||
@@ -244,12 +250,21 @@ namespace Tgstation.Server.Host.Components
|
||||
throw;
|
||||
}
|
||||
}
|
||||
#pragma warning restore CA1506
|
||||
#pragma warning restore CA1506
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task StartAsync(CancellationToken cancellationToken) => byondInstaller.CleanCache(cancellationToken);
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
CheckSystemCompatibility();
|
||||
return byondInstaller.CleanCache(cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
|
||||
/// <summary>
|
||||
/// Test that the <see cref="repositoryFactory"/> is functional.
|
||||
/// </summary>
|
||||
private void CheckSystemCompatibility() => repositoryFactory.CreateInMemory().Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
using Tgstation.Server.Host.Security;
|
||||
using Tgstation.Server.Host.System;
|
||||
|
||||
namespace Tgstation.Server.Host.Components
|
||||
{
|
||||
@@ -46,6 +48,16 @@ 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="ISystemIdentityFactory"/> for the <see cref="InstanceManager"/>
|
||||
/// </summary>
|
||||
readonly ISystemIdentityFactory systemIdentityFactory;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILogger"/> for the <see cref="InstanceManager"/>
|
||||
/// </summary>
|
||||
@@ -75,8 +87,19 @@ 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="systemIdentityFactory">The value of <see cref="systemIdentityFactory"/>.</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,
|
||||
ISystemIdentityFactory systemIdentityFactory,
|
||||
ILogger<InstanceManager> logger)
|
||||
{
|
||||
this.instanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory));
|
||||
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
|
||||
@@ -84,6 +107,8 @@ 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.systemIdentityFactory = systemIdentityFactory ?? throw new ArgumentNullException(nameof(systemIdentityFactory));
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
serverControl.RegisterForRestart(this);
|
||||
@@ -212,6 +237,7 @@ namespace Tgstation.Server.Host.Components
|
||||
{
|
||||
try
|
||||
{
|
||||
CheckSystemCompatibility();
|
||||
var factoryStartup = instanceFactory.StartAsync(cancellationToken);
|
||||
await databaseContext.Initialize(cancellationToken).ConfigureAwait(false);
|
||||
await jobManager.StartAsync(cancellationToken).ConfigureAwait(false);
|
||||
@@ -266,5 +292,15 @@ namespace Tgstation.Server.Host.Components
|
||||
downgradeVersion = updateVersion != null && updateVersion < application.Version ? updateVersion : null;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check we have a valid system identity.
|
||||
/// </summary>
|
||||
private void CheckSystemCompatibility()
|
||||
{
|
||||
using (var systemIdentity = systemIdentityFactory.GetCurrent())
|
||||
if (!systemIdentity.CanCreateSymlinks)
|
||||
throw new InvalidOperationException("The user running tgstation-server cannot create symlinks! Please try running as an administrative user!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,37 @@
|
||||
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>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in, the loaded <see cref="LibGit2Sharp.IRepository"/>.</returns>
|
||||
Task<LibGit2Sharp.IRepository> CreateFromPath(string path, CancellationToken cancellationToken);
|
||||
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
@@ -175,7 +175,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
progressReporter(50 + ((int)(50 * ((float)sentBytes) / totalBytes)));
|
||||
return !cancellationToken.IsCancellationRequested;
|
||||
},
|
||||
CredentialsProvider = credentialsProvider.GenerateHandler(username, password)
|
||||
CredentialsProvider = credentialsProvider.GenerateCredentialsHandler(username, password)
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -262,7 +262,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
return !cancellationToken.IsCancellationRequested;
|
||||
},
|
||||
OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested,
|
||||
CredentialsProvider = credentialsProvider.GenerateHandler(username, password)
|
||||
CredentialsProvider = credentialsProvider.GenerateCredentialsHandler(username, password)
|
||||
}, logMessage);
|
||||
}
|
||||
catch (UserCancelledException) { }
|
||||
@@ -375,7 +375,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
return !cancellationToken.IsCancellationRequested;
|
||||
},
|
||||
OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested,
|
||||
CredentialsProvider = credentialsProvider.GenerateHandler(username, password)
|
||||
CredentialsProvider = credentialsProvider.GenerateCredentialsHandler(username, password)
|
||||
}, "Fetch origin commits");
|
||||
}
|
||||
catch (UserCancelledException)
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
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 repository...");
|
||||
return new LibGit2Sharp.Repository();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<LibGit2Sharp.IRepository> CreateFromPath(string path, CancellationToken cancellationToken)
|
||||
{
|
||||
if (path == null)
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
logger.LogTrace("Creating repostory at {0}...", path);
|
||||
return Task.Factory.StartNew(
|
||||
() => (LibGit2Sharp.IRepository)new LibGit2Sharp.Repository(path),
|
||||
cancellationToken,
|
||||
TaskCreationOptions.LongRunning,
|
||||
TaskScheduler.Current);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Clone(Uri url, CloneOptions cloneOptions, string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
logger.LogTrace("Cloning {0} into {1}...", url, path);
|
||||
LibGit2Sharp.Repository.Clone(url.ToString(), path, cloneOptions);
|
||||
}
|
||||
catch (UserCancelledException ex)
|
||||
{
|
||||
logger.LogTrace("Suppressing clone cancellation exception: {0}", ex);
|
||||
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 && 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,20 +56,26 @@ 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.repositoryFactory = repositoryFactory ?? throw new ArgumentNullException(nameof(repositoryFactory));
|
||||
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));
|
||||
this.repositorySettings = repositorySettings ?? throw new ArgumentNullException(nameof(repositorySettings));
|
||||
semaphore = new SemaphoreSlim(1);
|
||||
}
|
||||
|
||||
@@ -89,7 +95,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
throw new ArgumentNullException(nameof(progressReporter));
|
||||
|
||||
logger.LogInformation("Begin clone {0} (Branch: {1})", url, initialBranch);
|
||||
lock (this)
|
||||
lock (semaphore)
|
||||
{
|
||||
if (CloneInProgress)
|
||||
throw new InvalidOperationException("The repository is already being cloned!");
|
||||
@@ -104,30 +110,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
|
||||
{
|
||||
@@ -164,40 +168,29 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
public async Task<IRepository> LoadRepository(CancellationToken cancellationToken)
|
||||
{
|
||||
logger.LogTrace("Begin LoadRepository...");
|
||||
lock (this)
|
||||
lock (semaphore)
|
||||
if (CloneInProgress)
|
||||
throw new InvalidOperationException("The repository is being cloned!");
|
||||
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
LibGit2Sharp.Repository repo = null;
|
||||
await Task.Factory.StartNew(() =>
|
||||
{
|
||||
using (var context = await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
|
||||
try
|
||||
{
|
||||
logger.LogTrace("Creating LibGit2Sharp.Repository...");
|
||||
repo = new LibGit2Sharp.Repository(ioManager.ResolvePath("."));
|
||||
var repo = await repositoryFactory.CreateFromPath(ioManager.ResolvePath("."), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (repo == null)
|
||||
return null;
|
||||
|
||||
return new Repository(repo, ioManager, eventConsumer, repositoryFactory, repositoryLogger, () =>
|
||||
{
|
||||
logger.LogTrace("Releasing semaphore due to Repository disposal...");
|
||||
semaphore.Release();
|
||||
});
|
||||
}
|
||||
catch (RepositoryNotFoundException e)
|
||||
{
|
||||
logger.LogDebug("Repository not found!");
|
||||
logger.LogTrace("Exception: {0}", e);
|
||||
return null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
semaphore.Release();
|
||||
throw;
|
||||
}
|
||||
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
|
||||
if (repo == null)
|
||||
{
|
||||
semaphore.Release();
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Repository(repo, ioManager, eventConsumer, credentialsProvider, repositoryLogger, () =>
|
||||
{
|
||||
logger.LogTrace("Releasing semaphore due to Repository disposal...");
|
||||
semaphore.Release();
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -19,6 +19,11 @@ namespace Tgstation.Server.Host.Security
|
||||
/// </summary>
|
||||
string Username { get; }
|
||||
|
||||
/// <summary>
|
||||
/// If this system identity has permissions to create symlinks.
|
||||
/// </summary>
|
||||
bool CanCreateSymlinks { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Clone the <see cref="ISystemIdentity"/> creating another copy that must have <see cref="IDisposable.Dispose"/> called on it
|
||||
/// </summary>
|
||||
|
||||
@@ -9,6 +9,12 @@ namespace Tgstation.Server.Host.Security
|
||||
/// </summary>
|
||||
public interface ISystemIdentityFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieves a <see cref="ISystemIdentity"/> representing the user executing tgstation-server.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="ISystemIdentity"/> representing the user executing tgstation-server.</returns>
|
||||
ISystemIdentity GetCurrent();
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="ISystemIdentity"/> for a given <paramref name="user"/>
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Tgstation.Server.Host.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="ISystemIdentity"/> for POSIX systems.
|
||||
/// </summary>
|
||||
sealed class PosixSystemIdentity : ISystemIdentity
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Uid => throw new NotImplementedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Username => throw new NotImplementedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool CanCreateSymlinks => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ISystemIdentity Clone() => throw new NotImplementedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task RunImpersonated(Action action, CancellationToken cancellationToken) => throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,9 @@ namespace Tgstation.Server.Host.Security
|
||||
/// <remarks>TODO: Blocked by https://github.com/dotnet/corefx/issues/3187</remarks>
|
||||
sealed class PosixSystemIdentityFactory : ISystemIdentityFactory
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public ISystemIdentity GetCurrent() => new PosixSystemIdentity();
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ISystemIdentity> CreateSystemIdentity(User user, CancellationToken cancellationToken) => throw new NotImplementedException();
|
||||
|
||||
|
||||
@@ -17,6 +17,9 @@ namespace Tgstation.Server.Host.Security
|
||||
/// <inheritdoc />
|
||||
public string Username => userPrincipal?.Name ?? identity.Name;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool CanCreateSymlinks => canCreateSymlinks ?? throw new NotSupportedException();
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="WindowsIdentity"/> for the <see cref="WindowsSystemIdentity"/>
|
||||
/// </summary>
|
||||
@@ -27,6 +30,11 @@ namespace Tgstation.Server.Host.Security
|
||||
/// </summary>
|
||||
readonly UserPrincipal userPrincipal;
|
||||
|
||||
/// <summary>
|
||||
/// Backing field for <see cref="CanCreateSymlinks"/>.
|
||||
/// </summary>
|
||||
readonly bool? canCreateSymlinks;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="WindowsSystemIdentity"/> using a <see cref="WindowsIdentity"/>
|
||||
/// </summary>
|
||||
@@ -34,6 +42,7 @@ namespace Tgstation.Server.Host.Security
|
||||
public WindowsSystemIdentity(WindowsIdentity identity)
|
||||
{
|
||||
this.identity = identity ?? throw new ArgumentNullException(nameof(identity));
|
||||
canCreateSymlinks = new WindowsPrincipal(identity).IsInRole(WindowsBuiltInRole.Administrator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -41,6 +41,9 @@ namespace Tgstation.Server.Host.Security
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ISystemIdentity GetCurrent() => new WindowsSystemIdentity(WindowsIdentity.GetCurrent());
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ISystemIdentity> CreateSystemIdentity(User user, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
|
||||
{
|
||||
|
||||
@@ -15,9 +15,7 @@ using Tgstation.Server.Host.IO;
|
||||
namespace Tgstation.Server.Host
|
||||
{
|
||||
/// <inheritdoc />
|
||||
#pragma warning disable CA1001 // Types that own disposable fields should be disposable
|
||||
sealed class Server : IServer, IServerControl
|
||||
#pragma warning restore CA1001 // Types that own disposable fields should be disposable
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public bool RestartRequested { get; private set; }
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
using LibGit2Sharp;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Repository.Tests
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests for <see cref="RepositoryFactory"/>.
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public sealed class TestRepositoryFactory
|
||||
{
|
||||
static IRepositoryFactory CreateFactory() => new RepositoryFactory(Mock.Of<ILogger<RepositoryFactory>>());
|
||||
|
||||
static Task<LibGit2Sharp.IRepository> TestRepoLoading(
|
||||
string path,
|
||||
IRepositoryFactory repositoryFactory = null) =>
|
||||
(repositoryFactory ?? CreateFactory())
|
||||
.CreateFromPath(path, default);
|
||||
|
||||
[TestMethod]
|
||||
public void TestConstructionThrows() => Assert.ThrowsException<ArgumentNullException>(() => new RepositoryFactory(null));
|
||||
|
||||
[TestMethod]
|
||||
public void TestInMemoryRepoCreation()
|
||||
{
|
||||
new RepositoryFactory(Mock.Of<ILogger<RepositoryFactory>>()).CreateInMemory().Dispose();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task TestCloning()
|
||||
{
|
||||
var tempDir = Path.GetTempFileName();
|
||||
File.Delete(tempDir);
|
||||
try
|
||||
{
|
||||
var factory = CreateFactory();
|
||||
await factory.Clone(
|
||||
new Uri("https://github.com/Cyberboss/Test"),
|
||||
new CloneOptions
|
||||
{
|
||||
CredentialsProvider = factory.GenerateCredentialsHandler(null, null)
|
||||
},
|
||||
tempDir,
|
||||
default);
|
||||
|
||||
using (var repo = await TestRepoLoading(tempDir))
|
||||
{
|
||||
var gitObject = repo.Lookup("f636418bf47d238d33b0e4a34f0072b23a8aad0e");
|
||||
Assert.IsNotNull(gitObject);
|
||||
var commit = gitObject.Peel<Commit>();
|
||||
|
||||
Assert.AreEqual("Update Test.md", commit.Message);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Takes a while to release the repo handle sometimes...
|
||||
for (var i = 0; i < 5; ++i)
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.Delete(tempDir, true);
|
||||
break;
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(3)).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user