From 19e29e3340c5e184289fc7496496b48d03aae85a Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 8 Sep 2018 12:37:23 -0400 Subject: [PATCH 001/230] Remove old command line test project --- .../Tgstation.Server.CommandLine.Tests.csproj | 20 ------------------- 1 file changed, 20 deletions(-) delete mode 100644 tests/Tgstation.Server.CommandLine.Tests/Tgstation.Server.CommandLine.Tests.csproj diff --git a/tests/Tgstation.Server.CommandLine.Tests/Tgstation.Server.CommandLine.Tests.csproj b/tests/Tgstation.Server.CommandLine.Tests/Tgstation.Server.CommandLine.Tests.csproj deleted file mode 100644 index 43a487525f..0000000000 --- a/tests/Tgstation.Server.CommandLine.Tests/Tgstation.Server.CommandLine.Tests.csproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - netcoreapp2.0 - - false - latest - - - - - - - - - - - - - From 50e58782eb0347813d42ab4477a8387f7c79a20a Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 8 Sep 2018 12:39:36 -0400 Subject: [PATCH 002/230] Fix trusted mode prompt still showing on Windows --- .../Components/Byond/WindowsByondInstaller.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs b/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs index a17aa4eece..99ac434050 100644 --- a/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs +++ b/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs @@ -21,7 +21,7 @@ namespace Tgstation.Server.Host.Components.Byond /// /// Directory to byond installation configuration /// - const string ByondConfigDir = "byond/config"; + const string ByondConfigDir = "byond/cfg"; /// /// BYOND's DreamDaemon config file /// From 24925f89cb4baf210ebb5286c880172eae7d3c38 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 8 Sep 2018 12:52:06 -0400 Subject: [PATCH 003/230] Add validation for incorrect committer names/emails --- .../Controllers/RepositoryController.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index 721e8b6bd7..1d1d26b4e8 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -287,6 +287,12 @@ namespace Tgstation.Server.Host.Controllers if (model.NewTestMerges?.Any(x => model.NewTestMerges.Any(y => x != y && x.Number == y.Number)) == true) return BadRequest(new ErrorMessage { Message = "Cannot test merge the same PR twice in one job!" }); + if (model.CommitterName?.Length == 0) + return BadRequest(new ErrorMessage { Message = "Cannot set empty committer name!" }); + + if (model.CommitterEmail?.Length == 0) + return BadRequest(new ErrorMessage { Message = "Cannot set empty committer e=mail!" }); + var newTestMerges = model.NewTestMerges != null && model.NewTestMerges.Count > 0; var userRights = (RepositoryRights)AuthenticationContext.GetRight(RightsType.Repository); if (newTestMerges && !userRights.HasFlag(RepositoryRights.MergePullRequest)) From d4389d3c3365ec1a2e23e1282c557e171fe9d6d5 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 8 Sep 2018 12:54:04 -0400 Subject: [PATCH 004/230] Throw a JobException if watchdog startup fails from controller --- src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs index 59dfd9016b..1100022216 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs @@ -71,7 +71,7 @@ namespace Tgstation.Server.Host.Controllers { var result = await instance.Watchdog.Launch(innerCt).ConfigureAwait(false); if (result == null) - throw new InvalidOperationException("Watchdog already running!"); + throw new JobException("Watchdog already running!"); }, cancellationToken).ConfigureAwait(false); return Accepted(job.ToApi()); From d684886dcd1d222eff6032ddf6b1a280bd5558b2 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 8 Sep 2018 13:13:56 -0400 Subject: [PATCH 005/230] Start watchdog on startup onlining via a job. Clean up dependencies a little --- .../Components/InstanceFactory.cs | 12 ++++-- .../Components/Watchdog/IWatchdogFactory.cs | 8 +++- .../Components/Watchdog/Watchdog.cs | 28 +++++++++++-- .../Components/Watchdog/WatchdogFactory.cs | 41 +++---------------- src/Tgstation.Server.Host/Core/Application.cs | 6 ++- 5 files changed, 50 insertions(+), 45 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index d676351557..cec17226f2 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -84,6 +84,11 @@ namespace Tgstation.Server.Host.Components /// readonly IPostWriteHandler postWriteHandler; + /// + /// The for the + /// + readonly IWatchdogFactory watchdogFactory; + /// /// Construct an /// @@ -100,7 +105,8 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// The value of - public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IByondTopicSender byondTopicSender, IServerControl serverUpdater, ICryptographySuite cryptographySuite, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IByondInstaller byondInstaller, IProviderFactory providerFactory, IProcessExecutor processExecutor, IPostWriteHandler postWriteHandler) + /// The value of + public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IByondTopicSender byondTopicSender, IServerControl serverUpdater, ICryptographySuite cryptographySuite, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IByondInstaller byondInstaller, IProviderFactory providerFactory, IProcessExecutor processExecutor, IPostWriteHandler postWriteHandler, IWatchdogFactory watchdogFactory) { this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); @@ -115,6 +121,7 @@ namespace Tgstation.Server.Host.Components this.providerFactory = providerFactory ?? throw new ArgumentNullException(nameof(providerFactory)); this.processExecutor = processExecutor ?? throw new ArgumentNullException(nameof(processExecutor)); this.postWriteHandler = postWriteHandler ?? throw new ArgumentNullException(nameof(postWriteHandler)); + this.watchdogFactory = watchdogFactory ?? throw new ArgumentNullException(nameof(watchdogFactory)); } /// @@ -148,8 +155,7 @@ namespace Tgstation.Server.Host.Components { var sessionControllerFactory = new SessionControllerFactory(processExecutor, byond, byondTopicSender, cryptographySuite, application, gameIoManager, chat, loggerFactory, metadata.CloneMetadata()); var reattachInfoHandler = new ReattachInfoHandler(databaseContextFactory, dmbFactory, metadata.CloneMetadata()); - var watchdogFactory = new WatchdogFactory(chat, sessionControllerFactory, serverUpdater, loggerFactory, reattachInfoHandler, databaseContextFactory, byondTopicSender, eventConsumer, metadata.CloneMetadata()); - var watchdog = watchdogFactory.CreateWatchdog(dmbFactory, metadata.DreamDaemonSettings); + var watchdog = watchdogFactory.CreateWatchdog(chat, dmbFactory, reattachInfoHandler, configuration, sessionControllerFactory, metadata.CloneMetadata(), metadata.DreamDaemonSettings); eventConsumer.SetWatchdog(watchdog); commandFactory.SetWatchdog(watchdog); try diff --git a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdogFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdogFactory.cs index 68717edc9c..7c4091e959 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdogFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdogFactory.cs @@ -1,4 +1,5 @@ using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Components.Compiler; namespace Tgstation.Server.Host.Components.Watchdog @@ -11,9 +12,14 @@ namespace Tgstation.Server.Host.Components.Watchdog /// /// Creates a /// + /// The for the /// The for the with + /// The for the + /// The for the + /// The for the + /// The for the /// The initial for the /// A new - IWatchdog CreateWatchdog(IDmbFactory dmbFactory, DreamDaemonSettings settings); + IWatchdog CreateWatchdog(IChat chat, IDmbFactory dmbFactory, IReattachInfoHandler reattachInfoHandler, IEventConsumer eventConsumer, ISessionControllerFactory sessionControllerFactory, Api.Models.Instance instance, DreamDaemonSettings settings); } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index e0f82236a9..a2aeafe404 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -10,6 +10,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Components.Compiler; using Tgstation.Server.Host.Components.Interop; @@ -86,6 +87,11 @@ namespace Tgstation.Server.Host.Components.Watchdog /// readonly IEventConsumer eventConsumer; + /// + /// The for the + /// + readonly IJobManager jobManager; + /// /// The for the /// @@ -141,11 +147,12 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The value of /// The value of /// The value of + /// The value of + /// The value of /// The initial value of /// The value of /// The value of - /// The value of - public Watchdog(IChat chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, IServerControl serverUpdater, ILogger logger, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IEventConsumer eventConsumer, DreamDaemonLaunchParameters initialLaunchParameters, Api.Models.Instance instance, bool autoStart) + public Watchdog(IChat chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, IServerControl serverUpdater, ILogger logger, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IEventConsumer eventConsumer, IJobManager jobManager, DreamDaemonLaunchParameters initialLaunchParameters, Api.Models.Instance instance, bool autoStart) { this.chat = chat ?? throw new ArgumentNullException(nameof(chat)); this.sessionControllerFactory = sessionControllerFactory ?? throw new ArgumentNullException(nameof(sessionControllerFactory)); @@ -155,6 +162,7 @@ namespace Tgstation.Server.Host.Components.Watchdog this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); this.byondTopicSender = byondTopicSender ?? throw new ArgumentNullException(nameof(byondTopicSender)); this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer)); + this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); this.instance = instance ?? throw new ArgumentNullException(nameof(instance)); this.autoStart = autoStart; @@ -789,8 +797,20 @@ namespace Tgstation.Server.Host.Components.Watchdog /// public async Task StartAsync(CancellationToken cancellationToken) { - if (autoStart) - await LaunchNoLock(true, true, true, cancellationToken).ConfigureAwait(false); + if (!autoStart) + return; + + var job = new Models.Job + { + StartedBy = new Models.User + { + Id = 1 //just use admin for this cause whatever + }, + Description = "Instance startup watchdog launch", + CancelRight = (ulong)DreamDaemonRights.Shutdown, + CancelRightsType = RightsType.DreamDaemon + }; + await jobManager.RegisterOperation(job, (j, serviceProvider, progressFunction, ct) => Launch(ct), cancellationToken).ConfigureAwait(false); } /// diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs index 65b0452042..b200d8f93b 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs @@ -11,16 +11,6 @@ namespace Tgstation.Server.Host.Components.Watchdog /// sealed class WatchdogFactory : IWatchdogFactory { - /// - /// The for the - /// - readonly IChat chat; - - /// - /// The for the - /// - readonly ISessionControllerFactory sessionControllerFactory; - /// /// The for the /// @@ -31,11 +21,6 @@ namespace Tgstation.Server.Host.Components.Watchdog /// readonly ILoggerFactory loggerFactory; - /// - /// The for the - /// - readonly IReattachInfoHandler reattachInfoHandler; - /// /// The for the /// @@ -47,42 +32,28 @@ namespace Tgstation.Server.Host.Components.Watchdog readonly IByondTopicSender byondTopicSender; /// - /// The for the + /// The for the /// - readonly IEventConsumer eventConsumer; - - /// - /// The for the - /// - readonly Api.Models.Instance instance; - + readonly IJobManager jobManager; /// /// Construct a /// - /// The value of - /// The value of /// The value of /// The value of - /// The value of /// The value of /// The value of - /// The value of - /// The value of - public WatchdogFactory(IChat chat, ISessionControllerFactory sessionControllerFactory, IServerControl serverUpdater, ILoggerFactory loggerFactory, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IEventConsumer eventConsumer, Api.Models.Instance instance) + /// The value of + public WatchdogFactory(IServerControl serverUpdater, ILoggerFactory loggerFactory, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IJobManager jobManager) { - this.chat = chat ?? throw new ArgumentNullException(nameof(chat)); - this.sessionControllerFactory = sessionControllerFactory ?? throw new ArgumentNullException(nameof(sessionControllerFactory)); this.serverUpdater = serverUpdater ?? throw new ArgumentNullException(nameof(serverUpdater)); this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); - this.reattachInfoHandler = reattachInfoHandler ?? throw new ArgumentNullException(nameof(reattachInfoHandler)); this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); this.byondTopicSender = byondTopicSender ?? throw new ArgumentNullException(nameof(byondTopicSender)); - this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer)); - this.instance = instance ?? throw new ArgumentNullException(nameof(instance)); + this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); } /// - public IWatchdog CreateWatchdog(IDmbFactory dmbFactory, DreamDaemonSettings settings) => new Watchdog(chat, sessionControllerFactory, dmbFactory, serverUpdater, loggerFactory.CreateLogger(), reattachInfoHandler, databaseContextFactory, byondTopicSender, eventConsumer, settings, instance, settings.AutoStart.Value); + public IWatchdog CreateWatchdog(IChat chat, IDmbFactory dmbFactory, IReattachInfoHandler reattachInfoHandler, IEventConsumer eventConsumer, ISessionControllerFactory sessionControllerFactory, Api.Models.Instance instance, DreamDaemonSettings settings) => new Watchdog(chat, sessionControllerFactory, dmbFactory, serverUpdater, loggerFactory.CreateLogger(), reattachInfoHandler, databaseContextFactory, byondTopicSender, eventConsumer, jobManager, settings, instance, settings.AutoStart.Value); } } diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index a397f43b17..dd9de80415 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -22,6 +22,7 @@ using Tgstation.Server.Host.Components; using Tgstation.Server.Host.Components.Byond; using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Components.StaticFiles; +using Tgstation.Server.Host.Components.Watchdog; using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Controllers; using Tgstation.Server.Host.IO; @@ -200,8 +201,9 @@ namespace Tgstation.Server.Host.Core SendTimeout = 5000 }); - services.AddSingleton(); - services.AddSingleton(x => x.GetRequiredService()); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(x => x.GetRequiredService()); services.AddSingleton(x => x.GetRequiredService()); From 4068843f939f89d14cb2ec39ab0b734cebbae47a Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 8 Sep 2018 13:23:47 -0400 Subject: [PATCH 006/230] Add missing instance field to watchdog onlining job --- src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index a2aeafe404..4d36e949d0 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -806,6 +806,10 @@ namespace Tgstation.Server.Host.Components.Watchdog { Id = 1 //just use admin for this cause whatever }, + Instance = new Models.Instance + { + Id = instance.Id + }, Description = "Instance startup watchdog launch", CancelRight = (ulong)DreamDaemonRights.Shutdown, CancelRightsType = RightsType.DreamDaemon From 66663fba2a752e048c7eeea8ca8b4b742bf0fee3 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 8 Sep 2018 13:33:59 -0400 Subject: [PATCH 007/230] Add default listening URL --- src/Tgstation.Server.Host/appsettings.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Tgstation.Server.Host/appsettings.json b/src/Tgstation.Server.Host/appsettings.json index d16d0bb4f5..84f836f297 100644 --- a/src/Tgstation.Server.Host/appsettings.json +++ b/src/Tgstation.Server.Host/appsettings.json @@ -4,6 +4,13 @@ "DisableFileLogging": false, "MinimumPasswordLength": 15 }, + "Kestrel": { + "EndPoints": { + "Http": { + "Url": "http://0.0.0.0:5000" + } + } + }, "Logging": { "IncludeScopes": false, "Debug": { From d947eb009b63b2413fa388a81048fef7fd4ed2fa Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 8 Sep 2018 13:47:57 -0400 Subject: [PATCH 008/230] Add some logging to ReattachInfoHandler. Fix crashing instance auto start. --- .../Components/InstanceFactory.cs | 2 +- .../Components/ReattachInfoHandler.cs | 23 ++++++++++++++++--- .../Watchdog/WatchdogReattachInformation.cs | 7 +++++- .../Models/ReattachInformationBase.cs | 5 +++- 4 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index d676351557..0b5ddc9107 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -147,7 +147,7 @@ namespace Tgstation.Server.Host.Components try { var sessionControllerFactory = new SessionControllerFactory(processExecutor, byond, byondTopicSender, cryptographySuite, application, gameIoManager, chat, loggerFactory, metadata.CloneMetadata()); - var reattachInfoHandler = new ReattachInfoHandler(databaseContextFactory, dmbFactory, metadata.CloneMetadata()); + var reattachInfoHandler = new ReattachInfoHandler(databaseContextFactory, dmbFactory, loggerFactory.CreateLogger(), metadata.CloneMetadata()); var watchdogFactory = new WatchdogFactory(chat, sessionControllerFactory, serverUpdater, loggerFactory, reattachInfoHandler, databaseContextFactory, byondTopicSender, eventConsumer, metadata.CloneMetadata()); var watchdog = watchdogFactory.CreateWatchdog(dmbFactory, metadata.DreamDaemonSettings); eventConsumer.SetWatchdog(watchdog); diff --git a/src/Tgstation.Server.Host/Components/ReattachInfoHandler.cs b/src/Tgstation.Server.Host/Components/ReattachInfoHandler.cs index 0aba902d06..8623cf0ab8 100644 --- a/src/Tgstation.Server.Host/Components/ReattachInfoHandler.cs +++ b/src/Tgstation.Server.Host/Components/ReattachInfoHandler.cs @@ -1,4 +1,5 @@ using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; using System; using System.Linq; using System.Threading; @@ -22,6 +23,11 @@ namespace Tgstation.Server.Host.Components /// readonly IDmbFactory dmbFactory; + /// + /// The for the + /// + readonly ILogger logger; + /// /// The for the /// @@ -33,16 +39,22 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// The value of - public ReattachInfoHandler(IDatabaseContextFactory databaseContextFactory, IDmbFactory dmbFactory, Api.Models.Instance metadata) + public ReattachInfoHandler(IDatabaseContextFactory databaseContextFactory, IDmbFactory dmbFactory, ILogger logger, Api.Models.Instance metadata) { this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); this.dmbFactory = dmbFactory ?? throw new ArgumentNullException(nameof(dmbFactory)); + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); this.metadata = metadata ?? throw new ArgumentNullException(nameof(metadata)); } /// public Task Save(WatchdogReattachInformation reattachInformation, CancellationToken cancellationToken) => databaseContextFactory.UseContext(async (db) => { + if (reattachInformation == null) + throw new ArgumentNullException(nameof(reattachInformation)); + + logger.LogDebug("Saving reattach information: {0}...", reattachInformation); + var instance = new Models.Instance { Id = metadata.Id }; db.Instances.Attach(instance); @@ -83,10 +95,15 @@ namespace Tgstation.Server.Host.Components ).ConfigureAwait(false); if (result == default) - throw new JobException("Unable to load reattach information!"); + { + logger.LogDebug("Reattach information not found!"); + return null; + } var bravoDmbTask = dmbFactory.FromCompileJob(result.Bravo.CompileJob, cancellationToken); - return new WatchdogReattachInformation(result, await dmbFactory.FromCompileJob(result.Alpha.CompileJob, cancellationToken).ConfigureAwait(false), await bravoDmbTask.ConfigureAwait(false)); + var info = new WatchdogReattachInformation(result, await dmbFactory.FromCompileJob(result.Alpha.CompileJob, cancellationToken).ConfigureAwait(false), await bravoDmbTask.ConfigureAwait(false)); + logger.LogDebug("Reattach information loaded: {0}", info); + return info; } } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogReattachInformation.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogReattachInformation.cs index f340a4cd8b..91ec990b55 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogReattachInformation.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogReattachInformation.cs @@ -1,4 +1,6 @@ -using Tgstation.Server.Host.Models; +using System; +using System.Globalization; +using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components.Watchdog { @@ -35,5 +37,8 @@ namespace Tgstation.Server.Host.Components.Watchdog if (copy.Bravo != null) Bravo = new ReattachInformation(copy.Bravo, dmbBravo); } + + /// + public override string ToString() => String.Format(CultureInfo.InvariantCulture, "Alpha: {0}, Bravo {1}", Alpha, Bravo); } } diff --git a/src/Tgstation.Server.Host/Models/ReattachInformationBase.cs b/src/Tgstation.Server.Host/Models/ReattachInformationBase.cs index 3d808a7f96..115b0ae6fe 100644 --- a/src/Tgstation.Server.Host/Models/ReattachInformationBase.cs +++ b/src/Tgstation.Server.Host/Models/ReattachInformationBase.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel.DataAnnotations; +using System.Globalization; using Tgstation.Server.Host.Components.Interop; using Tgstation.Server.Host.Components.Watchdog; @@ -34,7 +35,6 @@ namespace Tgstation.Server.Host.Models /// /// The current DreamDaemon reboot state /// - [Required] public RebootState RebootState { get; set; } /// @@ -56,5 +56,8 @@ namespace Tgstation.Server.Host.Models ProcessId = copy.ProcessId; RebootState = copy.RebootState; } + + /// + public override string ToString() => String.Format(CultureInfo.InvariantCulture, "Process ID: {3}, Access Identifier {4}, Primary: {0}, RebootState: {1}, Port: {2}", IsPrimary, RebootState, Port, ProcessId, AccessIdentifier); } } From 1d4c498861f4b14ec4c9acd37e9db4ed747a5bba Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 8 Sep 2018 14:16:00 -0400 Subject: [PATCH 009/230] Add documentation about setting hosting URL and reverse proxying --- README.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/README.md b/README.md index 603fa83bbc..dd2c7379ea 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,8 @@ Create an `appsettings.Production.json` file next to `appsettings.json`. This wi - `Logging:LogLevel:Default`: Can be one of `Trace`, `Debug`, `Information`, `Warning`, `Error`, or `Critical`. Restricts what is put into the log files. Currently `Debug` is reccommended for help with error reporting. +- `Kestrel:Endpoints:Http:Url`: The URL (i.e. interface and ports) your application should listen on. General use case should be `http://localhost:` for restricted local connections. See the Remote Access section for configuring public access to the World Wide Web. + - `Database:DatabaseType`: Can be one of `SqlServer`, `MariaDB`, or `MySql` - `Database:MySqlServerVersion`: The version of MySql/MariaDB the database resides on, can be left as null for attempted auto detection. Used by the MySQL/MariaDB provider for selection of [certain features](https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql/blob/2.1.1/src/EFCore.MySql/Storage/Internal/ServerVersion.cs) ignore at your own risk. A string in the form `..` @@ -79,6 +81,41 @@ A breaking change from V3: tgstation-server 4 now REQUIRES the DMAPI to be integ The DMAPI is fully backwards compatible and should function with any tgstation-server version to date. Updates can be performed in the same manner. Using the `TGS_EXTERNAL_CONFIGURATION` is recommended in order to make the process as easy as replacing `tgs.dm` and the `tgs` folder with a new version +## Remote Access + +tgstation-server is an [ASP.Net Core](https://docs.microsoft.com/en-us/aspnet/core/) based on the Kestrel web server. This section is meant to serve as a general use case overview, but the entire Kestrel configuration can be modified to your liking with the configuration JSON. See [the official documentation](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel) for details. + +Exposing the builtin kestrel server to the internet directly over HTTP is highly not reccommended due to the lack of security. The recommended way to expose tgstation-server to the internet is to host it through a reverse proxy with HTTPS support. Here are some step by step examples to achieve this for major web servers. + +System administrators will most likely have their own configuration plans, but here are some basic guides for beginners. + +Once complete, test that your configuration worked by visiting your proxy site from a different computer. You should recieve a 401 Unauthorized response. + +### IIS (Reccommended for Windows) + +1. Acquire an HTTPS certificate. The easiet free way for Windows is [win-acme](https://github.com/PKISharp/win-acme) (requires you to set up the website first) +2. Install the [Web Platform Installer](https://www.microsoft.com/web/downloads/platform.aspx) +3. Open the web platform installer in the IIS Manager and install the Application Request Routing 3.0 module +4. Create a new website, bind it to HTTPS only with your chosen certificate and exposed port. The physical path won't matter since it won't be used. Use `Require Server Name Indication` if you want to limit requests to a specific URL prefix. +5. Close and reopen the IIS Manager +5. Open the site and navigate to the `URL Rewrite` module +6. In the `Actions` Pane on the right click `Add Rule(s)...` +7. For the rule template, select `Reverse Proxy` under `Inbound and Outbound Rules` and click `OK` +8. You may get a prompt about enabling proxy functionality. Click `OK` +9. In the window that appears set the `Inbound Rules` textbox to the URL of your tgstation-server i.e. `http://localhost:5000`. Ensure `Enable SSL Offloading` is checked, then click `OK` + +### Nginx (Reccommended for Linux) + +TODO + +See https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/ + +### Apache + +TODO + +See https://httpd.apache.org/docs/2.4/howto/reverse_proxy.html + ## Usage tgstation-sever v4 is controlled via a RESTful HTTP json API. Documentation on this API can be found [here](https://tgstation.github.io/tgstation-server/api.html). This section serves to document the concepts of the server. From 4739a03e0a7663f164d8804693f442f1962a509a Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 8 Sep 2018 14:35:28 -0400 Subject: [PATCH 010/230] Fix integration test --- tests/Tgstation.Server.Tests/TestingServer.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/Tgstation.Server.Tests/TestingServer.cs b/tests/Tgstation.Server.Tests/TestingServer.cs index 3e30fbfa06..e583fa5e93 100644 --- a/tests/Tgstation.Server.Tests/TestingServer.cs +++ b/tests/Tgstation.Server.Tests/TestingServer.cs @@ -37,8 +37,7 @@ namespace Tgstation.Server.Tests realServer = new ServerFactory().CreateServer(new string[] { - "--urls", - Url.ToString(), + String.Format(CultureInfo.InvariantCulture, "Kestrel:EndPoints:Http:Url={0}", Url), String.Format(CultureInfo.InvariantCulture, "Database:DatabaseType={0}", databaseType), String.Format(CultureInfo.InvariantCulture, "Database:ConnectionString={0}", connectionString), "Database:DropDatabase=true" From aac185e51b271c0778ca97b864a28d7f8b7b0c99 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 8 Sep 2018 14:43:49 -0400 Subject: [PATCH 011/230] Add restart on config change if possible --- src/Tgstation.Server.Host/Core/Application.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index a397f43b17..17404a3033 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -10,6 +10,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Primitives; using Microsoft.IdentityModel.Tokens; using Newtonsoft.Json; using System; @@ -221,7 +222,8 @@ namespace Tgstation.Server.Host.Core /// /// The to configure /// The for the - public void Configure(IApplicationBuilder applicationBuilder, ILogger logger) + /// The for the application + public void Configure(IApplicationBuilder applicationBuilder, ILogger logger, IServerControl serverControl) { if (applicationBuilder == null) throw new ArgumentNullException(nameof(applicationBuilder)); @@ -229,6 +231,9 @@ namespace Tgstation.Server.Host.Core throw new ArgumentNullException(nameof(logger)); logger.LogInformation(VersionString); + + //attempt to restart the server if the configuration changes + ChangeToken.OnChange(configuration.GetReloadToken, () => serverControl.Restart()); applicationBuilder.UseDeveloperExceptionPage(); //it is not worth it to limit this, you should only ever get it if you're an authorized user From 6c743e06ee5ea95965990e77b0e89846d0c8822b Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 8 Sep 2018 14:44:00 -0400 Subject: [PATCH 012/230] Remove unused using --- src/Tgstation.Server.Host/Core/Application.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 17404a3033..fa6d8264aa 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -3,7 +3,6 @@ using Cyberboss.AspNetCore.AsyncInitializer; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; From d74a5ea80b4a4d3b95f58a42286a0b11f5e98298 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 8 Sep 2018 14:54:40 -0400 Subject: [PATCH 013/230] Fix release build --- src/Tgstation.Server.Host/Components/ReattachInfoHandler.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Tgstation.Server.Host/Components/ReattachInfoHandler.cs b/src/Tgstation.Server.Host/Components/ReattachInfoHandler.cs index 8623cf0ab8..8dc412f10a 100644 --- a/src/Tgstation.Server.Host/Components/ReattachInfoHandler.cs +++ b/src/Tgstation.Server.Host/Components/ReattachInfoHandler.cs @@ -38,6 +38,7 @@ namespace Tgstation.Server.Host.Components /// /// The value of /// The value of + /// The value of /// The value of public ReattachInfoHandler(IDatabaseContextFactory databaseContextFactory, IDmbFactory dmbFactory, ILogger logger, Api.Models.Instance metadata) { From e1fa1d3faf03d551f0e109cf8e25b39a86f1d936 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 8 Sep 2018 15:07:14 -0400 Subject: [PATCH 014/230] Fix more null reattach info errors --- src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index 4d36e949d0..6ce7108c42 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -650,7 +650,7 @@ namespace Tgstation.Server.Host.Components.Watchdog throw new InvalidOperationException("Entered LaunchNoLock with one or more of the servers not being null!"); var reattachInfo = doReattach ? await reattachInfoHandler.Load(cancellationToken).ConfigureAwait(false) : null; - var doesntNeedNewDmb = doReattach && reattachInfo.Alpha != null && reattachInfo.Bravo != null; + var doesntNeedNewDmb = doReattach && reattachInfo?.Alpha != null && reattachInfo?.Bravo != null; var dmbToUse = doesntNeedNewDmb ? null : dmbFactory.LockNextDmb(2); try @@ -705,7 +705,7 @@ namespace Tgstation.Server.Host.Components.Watchdog cancellationToken.ThrowIfCancellationRequested(); //both servers are now running, alpha is the active server, huzzah - AlphaIsActive = doReattach ? reattachInfo.AlphaIsActive : true; + AlphaIsActive = doReattach ? reattachInfo?.AlphaIsActive ?? true : true; LastLaunchResult = alphaLrt.Result; logger.LogInformation("Launched servers successfully"); Running = true; From 554627226fd33e4575d52710fabe27704bdc0588 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 10 Sep 2018 09:31:12 -0400 Subject: [PATCH 015/230] Add GitHubAccessToken general configuration option --- .../Configuration/GeneralConfiguration.cs | 5 +++++ src/Tgstation.Server.Host/appsettings.Docker.json | 4 +++- src/Tgstation.Server.Host/appsettings.json | 3 ++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs b/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs index 6b7884b399..0451038e48 100644 --- a/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs +++ b/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs @@ -24,5 +24,10 @@ /// Minimum length of database user passwords /// public uint MinimumPasswordLength { get; set; } + + /// + /// A GitHub personal access token to use for bypassing rate limits on requests. Requires no scopes + /// + public string GitHubAccessToken { get; set; } } } diff --git a/src/Tgstation.Server.Host/appsettings.Docker.json b/src/Tgstation.Server.Host/appsettings.Docker.json index d90c93f5a5..f7d5860d86 100644 --- a/src/Tgstation.Server.Host/appsettings.Docker.json +++ b/src/Tgstation.Server.Host/appsettings.Docker.json @@ -1,6 +1,8 @@ { "General": { - "LogFileDirectory": "/tgs_logs" + "LogFileDirectory": "/tgs_logs", + "MinimumPasswordLength": 15, + "GitHubAccessToken": null }, "Database": { "DatabaseType": "SqlServer or MySQL or MariaDB", diff --git a/src/Tgstation.Server.Host/appsettings.json b/src/Tgstation.Server.Host/appsettings.json index d16d0bb4f5..6caafa8898 100644 --- a/src/Tgstation.Server.Host/appsettings.json +++ b/src/Tgstation.Server.Host/appsettings.json @@ -2,7 +2,8 @@ "General": { "LogFileDirectory": null, //use the default path "DisableFileLogging": false, - "MinimumPasswordLength": 15 + "MinimumPasswordLength": 15, + "GitHubAccessToken": null }, "Logging": { "IncludeScopes": false, From 992ab646d2419adfd8632a6029f75bc25ab8a0ae Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 10 Sep 2018 09:55:30 -0400 Subject: [PATCH 016/230] Use configured GitHubAccessToken when available for Octokit operations --- .../Controllers/AdministrationController.cs | 19 +++++++++++++++---- .../Controllers/RepositoryController.cs | 12 ++++++++++-- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs index a401011001..f73a6edc84 100644 --- a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs +++ b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs @@ -31,9 +31,9 @@ namespace Tgstation.Server.Host.Controllers const string RestartNotSupportedException = "This deployment of tgstation-server is lacking the Tgstation.Server.Host.Watchdog component. Restarts and version changes cannot be completed!"; /// - /// The for the + /// The for the /// - readonly IGitHubClient gitHubClient; + readonly IGitHubClientFactory gitHubClientFactory; /// /// The for the @@ -55,6 +55,11 @@ namespace Tgstation.Server.Host.Controllers /// readonly UpdatesConfiguration updatesConfiguration; + /// + /// The for the + /// + readonly GeneralConfiguration generalConfiguration; + /// /// Construct an /// @@ -66,13 +71,15 @@ namespace Tgstation.Server.Host.Controllers /// The value of /// The for the /// The containing value of - public AdministrationController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IGitHubClient gitHubClient, IServerControl serverUpdater, IApplication application, IIOManager ioManager, ILogger logger, IOptions updatesConfigurationOptions) : base(databaseContext, authenticationContextFactory, logger, false) + /// The containing value of + public AdministrationController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IGitHubClientFactory gitHubClientFactory, IServerControl serverUpdater, IApplication application, IIOManager ioManager, ILogger logger, IOptions updatesConfigurationOptions, IOptions generalConfigurationOptions) : base(databaseContext, authenticationContextFactory, logger, false) { - this.gitHubClient = gitHubClient ?? throw new ArgumentNullException(nameof(gitHubClient)); + this.gitHubClientFactory = gitHubClientFactory ?? throw new ArgumentNullException(nameof(gitHubClientFactory)); this.serverUpdater = serverUpdater ?? throw new ArgumentNullException(nameof(serverUpdater)); this.application = application ?? throw new ArgumentNullException(nameof(application)); this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); updatesConfiguration = updatesConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(updatesConfigurationOptions)); + generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions)); } StatusCodeResult RateLimit(RateLimitExceededException exception) @@ -83,6 +90,8 @@ namespace Tgstation.Server.Host.Controllers return StatusCode(429); } + IGitHubClient GetGitHubClient() => String.IsNullOrEmpty(generalConfiguration.GitHubAccessToken) ? gitHubClientFactory.CreateClient() : gitHubClientFactory.CreateClient(generalConfiguration.GitHubAccessToken); + /// [TgsAuthorize] public override async Task Read(CancellationToken cancellationToken) @@ -93,6 +102,7 @@ namespace Tgstation.Server.Host.Controllers Uri repoUrl = null; try { + var gitHubClient = GetGitHubClient(); var repositoryTask = gitHubClient.Repository.Get(updatesConfiguration.GitHubRepositoryId); var releases = (await gitHubClient.Repository.Release.GetAll(updatesConfiguration.GitHubRepositoryId).ConfigureAwait(false)).Where(x => x.TagName.StartsWith(updatesConfiguration.GitTagPrefix, StringComparison.InvariantCulture)); @@ -137,6 +147,7 @@ namespace Tgstation.Server.Host.Controllers IEnumerable releases; try { + var gitHubClient = GetGitHubClient(); releases = (await gitHubClient.Repository.Release.GetAll(updatesConfiguration.GitHubRepositoryId).ConfigureAwait(false)).Where(x => x.TagName.StartsWith(updatesConfiguration.GitTagPrefix, StringComparison.InvariantCulture)); } catch (RateLimitExceededException e) diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index 721e8b6bd7..50e95ce65d 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -2,6 +2,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using System; using System.Collections.Generic; using System.Globalization; @@ -15,6 +16,7 @@ using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Components; +using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Core; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; @@ -42,6 +44,11 @@ namespace Tgstation.Server.Host.Controllers /// readonly IJobManager jobManager; + /// + /// The for the + /// + readonly GeneralConfiguration generalConfiguration; + /// /// Construct a /// @@ -51,11 +58,12 @@ namespace Tgstation.Server.Host.Controllers /// The value of /// The value of /// The for the - public RepositoryController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IInstanceManager instanceManager, IGitHubClientFactory gitHubClientFactory, IJobManager jobManager, ILogger logger) : base(databaseContext, authenticationContextFactory, logger, true) + public RepositoryController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IInstanceManager instanceManager, IGitHubClientFactory gitHubClientFactory, IJobManager jobManager, ILogger logger, IOptions generalConfigurationOptions) : base(databaseContext, authenticationContextFactory, logger, true) { this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); this.gitHubClientFactory = gitHubClientFactory ?? throw new ArgumentNullException(nameof(gitHubClientFactory)); this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); + generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions)); } static async Task LoadRevisionInformation(Components.Repository.IRepository repository, IDatabaseContext databaseContext, Models.Instance instance, string lastOriginCommitSha, Action revInfoSink, CancellationToken cancellationToken) @@ -474,7 +482,7 @@ namespace Tgstation.Server.Host.Controllers foreach (var I in model.NewTestMerges.Where(x => String.IsNullOrWhiteSpace(x.PullRequestRevision))) I.PullRequestRevision = null; - var gitHubClient = currentModel.AccessToken != null ? gitHubClientFactory.CreateClient(currentModel.AccessToken) : gitHubClientFactory.CreateClient(); + var gitHubClient = String.IsNullOrEmpty(generalConfiguration.GitHubAccessToken) ? (currentModel.AccessToken != null ? gitHubClientFactory.CreateClient(currentModel.AccessToken) : gitHubClientFactory.CreateClient()) : gitHubClientFactory.CreateClient(generalConfiguration.GitHubAccessToken); var repoOwner = repo.GitHubOwner; var repoName = repo.GitHubRepoName; From b12bc979cdc5516b7883422b44a1c475357a6100 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 10 Sep 2018 09:57:07 -0400 Subject: [PATCH 017/230] Add documentation about General:GitHubAccessToken --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 603fa83bbc..69d6028c32 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,8 @@ Create an `appsettings.Production.json` file next to `appsettings.json`. This wi - `General:MinimumPasswordLength`: Minimum password length requirement for database users +- `General:GitHubAccessToken`: Specify a GitHub personal access token with no scopes here to highly mitigate the possiblity of 429 response codes from GitHub requests + - `Logging:LogLevel:Default`: Can be one of `Trace`, `Debug`, `Information`, `Warning`, `Error`, or `Critical`. Restricts what is put into the log files. Currently `Debug` is reccommended for help with error reporting. - `Database:DatabaseType`: Can be one of `SqlServer`, `MariaDB`, or `MySql` From b2ae012598085ed86961105e461cac59f19d53a8 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 10 Sep 2018 09:59:17 -0400 Subject: [PATCH 018/230] Add GitHubAccessToken support to the tests --- tests/Tgstation.Server.Tests/TestingServer.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/Tgstation.Server.Tests/TestingServer.cs b/tests/Tgstation.Server.Tests/TestingServer.cs index 3e30fbfa06..71cd1195eb 100644 --- a/tests/Tgstation.Server.Tests/TestingServer.cs +++ b/tests/Tgstation.Server.Tests/TestingServer.cs @@ -1,5 +1,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using System; +using System.Collections.Generic; using System.Globalization; using System.IO; using System.Threading; @@ -28,6 +29,7 @@ namespace Tgstation.Server.Tests //we have to rely on env vars var databaseType = Environment.GetEnvironmentVariable("TGS4_TEST_DATABASE_TYPE"); var connectionString = Environment.GetEnvironmentVariable("TGS4_TEST_CONNECTION_STRING"); + var gitHubAccessToken = Environment.GetEnvironmentVariable("TGS4_TEST_GITHUB_TOKEN"); if (String.IsNullOrEmpty(databaseType)) Assert.Fail("No database type configured in env var TGS4_TEST_DATABASE_TYPE!"); @@ -35,14 +37,19 @@ namespace Tgstation.Server.Tests if (String.IsNullOrEmpty(connectionString)) Assert.Fail("No connection string configured in env var TGS4_TEST_CONNECTION_STRING!"); - realServer = new ServerFactory().CreateServer(new string[] + var args = new List() { "--urls", Url.ToString(), String.Format(CultureInfo.InvariantCulture, "Database:DatabaseType={0}", databaseType), String.Format(CultureInfo.InvariantCulture, "Database:ConnectionString={0}", connectionString), "Database:DropDatabase=true" - }, null); + }; + + if (!String.IsNullOrEmpty(gitHubAccessToken)) + args.Add(String.Format(CultureInfo.InvariantCulture, "General:GitHubAccessToken={0}", gitHubAccessToken)); + + realServer = new ServerFactory().CreateServer(args.ToArray(), null); } public void Dispose() From caa5ce9ebccb341046c7f5c267afe21480b1d109 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 10 Sep 2018 10:01:27 -0400 Subject: [PATCH 019/230] Add GitHubAccessToken support to appveyor --- appveyor.yml | 2 +- build/BuildDox.ps1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 9bd18e311d..4874f5497a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,7 +4,7 @@ pull_requests: environment: TGS4_TEST_DATABASE_TYPE: SqlServer TGS4_TEST_CONNECTION_STRING: Server=(local)\SQL2017;Initial Catalog=TGS_Test;User ID=sa;Password=Password12! - repo_token: + TGS4_TEST_GITHUB_TOKEN: secure: lJNGAXwiB5HlWdthz3K4PetqpTG5IEAyRgKaiKxFMQ8HW8CcOjRtB97B05op7BsK branches: only: diff --git a/build/BuildDox.ps1 b/build/BuildDox.ps1 index 81e88b9666..9087e16101 100644 --- a/build/BuildDox.ps1 +++ b/build/BuildDox.ps1 @@ -30,6 +30,6 @@ if($publish_dox){ echo "" > .nojekyll git add --all git commit -m "Deploy code docs to GitHub Pages for Appveyor build $Env:APPVEYOR_BUILD_NUMBER" -m "Commit: $Env:APPVEYOR_REPO_COMMIT" - git push -f "https://$Env:repo_token@$github_url" 2>&1 | out-null + git push -f "https://$Env:TGS4_TEST_GITHUB_TOKEN@$github_url" 2>&1 | out-null cd "$bf" } From e524bcbd36db80b9a52ba374a4c2313aadca310e Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 10 Sep 2018 10:19:56 -0400 Subject: [PATCH 020/230] Fix multiarch builds not running --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 50230a0ab3..ecbd87e6ee 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,8 +32,8 @@ matrix: directories: - $HOME/.nuget/packages: os: - - linux - - osx + - linux + - osx install: - if [ $DMAPI = true ]; then build/install_byond.sh; fi From e70a4d9c0b3e8d68fce413277263dfad7662fb8c Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 10 Sep 2018 10:21:17 -0400 Subject: [PATCH 021/230] Test release mode as well --- .travis.yml | 18 +++++++++++++++++- build/test_core.sh | 13 +++++++------ 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index ecbd87e6ee..eea3fb0b76 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,7 +22,23 @@ matrix: - libstdc++6:i386 - env: - DMAPI=false - name: "Linux Build" + - CONFIG=Debug + name: "Test Server Debug" + language: csharp + mono: none + dotnet: 2.1.300 + services: + - mysql + cache: + directories: + - $HOME/.nuget/packages: + os: + - linux + - osx + - env: + - DMAPI=false + - CONFIG=Release + name: "Test Server Release" language: csharp mono: none dotnet: 2.1.300 diff --git a/build/test_core.sh b/build/test_core.sh index a9477dfb24..849726a149 100755 --- a/build/test_core.sh +++ b/build/test_core.sh @@ -3,26 +3,27 @@ set -e cd tests/Tgstation.Server.Api.Tests -dotnet test +dotnet test -c $CONFIG cd ../Tgstation.Server.Client.Tests -dotnet test +dotnet test -c $CONFIG cd ../Tgstation.Server.Host.Tests -dotnet test +dotnet test -c $CONFIG cd ../Tgstation.Server.Host.Watchdog.Tests -dotnet test +dotnet test -c $CONFIG cd ../Tgstation.Server.Host.Console.Tests -dotnet test +dotnet test -c $CONFIG cd ../Tgstation.Server.Tests export TGS4_TEST_DATABASE_TYPE=MySql export TGS4_TEST_CONNECTION_STRING="server=127.0.0.1;uid=root;pwd=;database=tgs_test" -dotnet test +#token set in CI settings +dotnet test -c $CONFIG From 899c291123a1812a755e8dfc8d063c2b77ac844a Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 10 Sep 2018 10:25:41 -0400 Subject: [PATCH 022/230] Fix doc comments --- .../Controllers/AdministrationController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs index f73a6edc84..e3d8fb5e6a 100644 --- a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs +++ b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs @@ -65,7 +65,7 @@ namespace Tgstation.Server.Host.Controllers /// /// The for the /// The for the - /// The value of + /// The value of /// The value of /// The value of /// The value of From 4377f1b0962c414869a232a17caa2879496bbb12 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 10 Sep 2018 10:26:22 -0400 Subject: [PATCH 023/230] Remove IGitHubClient from dependency container --- src/Tgstation.Server.Host/Core/Application.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index a397f43b17..adccb77f87 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -175,7 +175,6 @@ namespace Tgstation.Server.Host.Core services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(x => x.GetRequiredService().CreateClient()); if (isWindows) { From 4d2f4c1414416de9feaff83f9dbc7c75230385cd Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 10 Sep 2018 10:56:12 -0400 Subject: [PATCH 024/230] Repository API model now contains GitHub repo owner and name --- src/Tgstation.Server.Api/Models/Repository.cs | 9 +++++++-- .../Controllers/RepositoryController.cs | 7 +++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Tgstation.Server.Api/Models/Repository.cs b/src/Tgstation.Server.Api/Models/Repository.cs index 83f610ba5a..ee50701502 100644 --- a/src/Tgstation.Server.Api/Models/Repository.cs +++ b/src/Tgstation.Server.Api/Models/Repository.cs @@ -23,9 +23,14 @@ namespace Tgstation.Server.Api.Models public RevisionInformation RevisionInformation { get; set; } /// - /// If the repository was cloned from GitHub.com. If this enables test merge functionality + /// If the repository was cloned from GitHub.com this will be set with the owner of the repository /// - public bool? IsGitHub { get; set; } + public string GitHubOwner { get; set; } + + /// + /// If the repository was cloned from GitHub.com this will be set with the name of the repository + /// + public string GitHubName { get; set; } /// /// The started by the if any diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index 1d1d26b4e8..358437032b 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -94,7 +94,11 @@ namespace Tgstation.Server.Host.Controllers static async Task PopulateApi(Repository model, Components.Repository.IRepository repository, IDatabaseContext databaseContext, Models.Instance instance, CancellationToken cancellationToken) { - model.IsGitHub = repository.IsGitHubRepository; + if (repository.IsGitHubRepository) + { + model.GitHubOwner = repository.GitHubOwner; + model.GitHubName = repository.GitHubRepoName; + } model.Origin = repository.Origin; model.Reference = repository.Reference; @@ -187,7 +191,6 @@ namespace Tgstation.Server.Host.Controllers api.Origin = model.Origin; api.Reference = model.Reference; - api.IsGitHub = model.Origin.ToUpperInvariant().Contains(uiGitHub); api.ActiveJob = job.ToApi(); return StatusCode((int)HttpStatusCode.Created, api); From 9facbea539565a97289ca6209071d8732fccf22a Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 10 Sep 2018 10:57:32 -0400 Subject: [PATCH 025/230] Bump API preview version --- src/Tgstation.Server.Api/Tgstation.Server.Api.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj index 20370a3c63..6b76bb1d75 100644 --- a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj +++ b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj @@ -17,7 +17,7 @@ 4.0.0.0 json web api tgstation-server tgstation ss13 byond Prototype release - 4.0.0.0-preview6001 + 4.0.0.0-preview6002 From b366ad381fc80b0f9aaadf4c29153ce0b851cd81 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 10 Sep 2018 10:57:56 -0400 Subject: [PATCH 026/230] Bump client preview version --- src/Tgstation.Server.Client/Tgstation.Server.Client.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj index 7e0b60fb65..7b97f583c8 100644 --- a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj +++ b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj @@ -3,7 +3,7 @@ netstandard2.0 Full - 4.0.0.0-preview9102 + 4.0.0.0-preview9103 true Cyberboss /tg/station 13 From d96faf40e5963867d3521523cf8454bd6c655945 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 10 Sep 2018 11:30:21 -0400 Subject: [PATCH 027/230] Give up on multiarch travis builds --- .travis.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index eea3fb0b76..94d49a8e0d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,9 +32,6 @@ matrix: cache: directories: - $HOME/.nuget/packages: - os: - - linux - - osx - env: - DMAPI=false - CONFIG=Release @@ -47,9 +44,6 @@ matrix: cache: directories: - $HOME/.nuget/packages: - os: - - linux - - osx install: - if [ $DMAPI = true ]; then build/install_byond.sh; fi From 93a78ed59cfd7cf2a87c11dd4d141f0e9fea89c8 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 10 Sep 2018 11:42:29 -0400 Subject: [PATCH 028/230] Add missing doc comment --- src/Tgstation.Server.Host/Controllers/RepositoryController.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index 50e95ce65d..fe1040dee3 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -58,6 +58,7 @@ namespace Tgstation.Server.Host.Controllers /// The value of /// The value of /// The for the + /// The containing value of public RepositoryController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IInstanceManager instanceManager, IGitHubClientFactory gitHubClientFactory, IJobManager jobManager, ILogger logger, IOptions generalConfigurationOptions) : base(databaseContext, authenticationContextFactory, logger, true) { this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); From 97011de2fc1f18361a26b9ca2b18950c20f66639 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Tue, 11 Sep 2018 13:32:05 -0400 Subject: [PATCH 029/230] Expose 5000 by default in docker image --- build/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Dockerfile b/build/Dockerfile index e01c962a85..d811f25a1b 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -24,7 +24,7 @@ WORKDIR /src/src/Tgstation.Server.Host RUN dotnet publish -c Release -o /app/lib/Default && mv /app/lib/Default/appsettings* /app FROM microsoft/dotnet:2.1-aspnetcore-runtime -EXPOSE 80 +EXPOSE 5000 WORKDIR /app From 9bcabeb64d3ffe31ff427564361a6222b7887b97 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Tue, 11 Sep 2018 13:42:16 -0400 Subject: [PATCH 030/230] Add kestrel config to docker json --- src/Tgstation.Server.Host/appsettings.Docker.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Tgstation.Server.Host/appsettings.Docker.json b/src/Tgstation.Server.Host/appsettings.Docker.json index f7d5860d86..142c7e376b 100644 --- a/src/Tgstation.Server.Host/appsettings.Docker.json +++ b/src/Tgstation.Server.Host/appsettings.Docker.json @@ -4,6 +4,13 @@ "MinimumPasswordLength": 15, "GitHubAccessToken": null }, + "Kestrel": { + "Endpoints": { + "Http": { + "Url": "http://localhost:5000" + } + } + }, "Database": { "DatabaseType": "SqlServer or MySQL or MariaDB", "ConnectionString": "", From 6254913d4686a6570d928374c9a614efb6baf13f Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 11 Sep 2018 16:20:59 -0400 Subject: [PATCH 031/230] Fix EmptyCommitExceptions --- src/Tgstation.Server.Host/Components/Repository/Repository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs index 0ed716b751..1b704da4f5 100644 --- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs @@ -212,7 +212,7 @@ namespace Tgstation.Server.Host.Components.Repository return false; } - if (commitMessage != null) + if (commitMessage != null && result.Status != MergeStatus.UpToDate) repository.Commit(commitMessage, sig, sig, new CommitOptions { PrettifyMessage = true From e6795dd84cd11d23642afae11058488666097e4f Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 12 Sep 2018 13:35:07 -0400 Subject: [PATCH 032/230] Prevent the same test merge PR# from being merged multiple times in one commit chain. This is necessary to keep a sane database. Workaround is to checkout whatever base you want and then reapply test merges as desired --- src/Tgstation.Server.Host/Controllers/RepositoryController.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index 29bea54022..a5e749c6d4 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -609,6 +609,10 @@ namespace Tgstation.Server.Host.Controllers { Octokit.PullRequest pr = null; string errorMessage = null; + + if (lastRevisionInfo.ActiveTestMerges.Any(x => x.TestMerge.Number == I.Number.Value)) + throw new JobException("Cannot test merge the same PR twice in one HEAD!"); + try { //load from cache if possible From 84dd0643e36716c540fc88792ff7948fa1a9e073 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 12 Sep 2018 16:33:20 -0400 Subject: [PATCH 033/230] Move post write handling from StaticFiles to Core --- src/Tgstation.Server.Host/Core/Application.cs | 1 - .../{Components/StaticFiles => Core}/IPostWriteHandler.cs | 2 +- .../{Components/StaticFiles => Core}/PosixPostWriteHandler.cs | 2 +- .../{Components/StaticFiles => Core}/ServerSideModifications.cs | 0 .../{Components/StaticFiles => Core}/WindowsPostWriteHandler.cs | 2 +- 5 files changed, 3 insertions(+), 4 deletions(-) rename src/Tgstation.Server.Host/{Components/StaticFiles => Core}/IPostWriteHandler.cs (81%) rename src/Tgstation.Server.Host/{Components/StaticFiles => Core}/PosixPostWriteHandler.cs (92%) rename src/Tgstation.Server.Host/{Components/StaticFiles => Core}/ServerSideModifications.cs (100%) rename src/Tgstation.Server.Host/{Components/StaticFiles => Core}/WindowsPostWriteHandler.cs (79%) diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index eaa3e40521..b0218e2dc4 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -21,7 +21,6 @@ using System.Threading.Tasks; using Tgstation.Server.Host.Components; using Tgstation.Server.Host.Components.Byond; using Tgstation.Server.Host.Components.Chat; -using Tgstation.Server.Host.Components.StaticFiles; using Tgstation.Server.Host.Components.Watchdog; using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Controllers; diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/IPostWriteHandler.cs b/src/Tgstation.Server.Host/Core/IPostWriteHandler.cs similarity index 81% rename from src/Tgstation.Server.Host/Components/StaticFiles/IPostWriteHandler.cs rename to src/Tgstation.Server.Host/Core/IPostWriteHandler.cs index a1db4b8d37..7209b44761 100644 --- a/src/Tgstation.Server.Host/Components/StaticFiles/IPostWriteHandler.cs +++ b/src/Tgstation.Server.Host/Core/IPostWriteHandler.cs @@ -1,4 +1,4 @@ -namespace Tgstation.Server.Host.Components.StaticFiles +namespace Tgstation.Server.Host.Core { interface IPostWriteHandler { diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/PosixPostWriteHandler.cs b/src/Tgstation.Server.Host/Core/PosixPostWriteHandler.cs similarity index 92% rename from src/Tgstation.Server.Host/Components/StaticFiles/PosixPostWriteHandler.cs rename to src/Tgstation.Server.Host/Core/PosixPostWriteHandler.cs index a028fe1c03..d121d2bca8 100644 --- a/src/Tgstation.Server.Host/Components/StaticFiles/PosixPostWriteHandler.cs +++ b/src/Tgstation.Server.Host/Core/PosixPostWriteHandler.cs @@ -1,7 +1,7 @@ using Mono.Unix; using Mono.Unix.Native; -namespace Tgstation.Server.Host.Components.StaticFiles +namespace Tgstation.Server.Host.Core { /// /// for POSIX systems diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/ServerSideModifications.cs b/src/Tgstation.Server.Host/Core/ServerSideModifications.cs similarity index 100% rename from src/Tgstation.Server.Host/Components/StaticFiles/ServerSideModifications.cs rename to src/Tgstation.Server.Host/Core/ServerSideModifications.cs diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/WindowsPostWriteHandler.cs b/src/Tgstation.Server.Host/Core/WindowsPostWriteHandler.cs similarity index 79% rename from src/Tgstation.Server.Host/Components/StaticFiles/WindowsPostWriteHandler.cs rename to src/Tgstation.Server.Host/Core/WindowsPostWriteHandler.cs index 61c8431662..42ed2cdf1b 100644 --- a/src/Tgstation.Server.Host/Components/StaticFiles/WindowsPostWriteHandler.cs +++ b/src/Tgstation.Server.Host/Core/WindowsPostWriteHandler.cs @@ -1,4 +1,4 @@ -namespace Tgstation.Server.Host.Components.StaticFiles +namespace Tgstation.Server.Host.Core { /// /// for Windows systems From dbfc3f8188b9b5667dba7e43a14f5a70f93a0019 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 12 Sep 2018 16:33:40 -0400 Subject: [PATCH 034/230] Make ByondManager's BinPath a public constant --- src/Tgstation.Server.Host/Components/Byond/ByondManager.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs b/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs index f051d38dd1..638d6b612c 100644 --- a/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs +++ b/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs @@ -13,11 +13,14 @@ namespace Tgstation.Server.Host.Components.Byond /// sealed class ByondManager : IByondManager { + /// + /// The path to the BYOND bin folder + /// + public const string BinPath = "byond/bin"; + const string VersionFileName = "Version.txt"; const string ActiveVersionFileName = "ActiveVersion.txt"; - const string BinPath = "byond/bin"; - /// public Version ActiveVersion { get; private set; } From 6e9167ee49e9a9b1dcea092b71c3397e892b828b Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 12 Sep 2018 16:40:33 -0400 Subject: [PATCH 035/230] Add scripts to change LD_LIBRARY_PATH for byond on install --- .../Components/Byond/PosixByondInstaller.cs | 35 ++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Byond/PosixByondInstaller.cs b/src/Tgstation.Server.Host/Components/Byond/PosixByondInstaller.cs index ded0d06a1e..19b62b0795 100644 --- a/src/Tgstation.Server.Host/Components/Byond/PosixByondInstaller.cs +++ b/src/Tgstation.Server.Host/Components/Byond/PosixByondInstaller.cs @@ -2,8 +2,10 @@ using System; using System.Globalization; using System.Net; +using System.Text; using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Host.Core; using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host.Components.Byond @@ -23,16 +25,21 @@ namespace Tgstation.Server.Host.Components.Byond const string ByondCachePath = "~/.byond/cache"; /// - public string DreamDaemonName => "DreamDaemon"; + public string DreamDaemonName => "DreamDaemon.sh"; /// - public string DreamMakerName => "DreamMaker"; + public string DreamMakerName => "DreamMaker.sh"; /// /// The for the /// readonly IIOManager ioManager; + /// + /// The for the + /// + readonly IPostWriteHandler postWriteHandler; + /// /// The for the /// @@ -42,10 +49,12 @@ namespace Tgstation.Server.Host.Components.Byond /// Construct a /// /// The value of + /// The value of /// The value of - public PosixByondInstaller(IIOManager ioManager, ILogger logger) + public PosixByondInstaller(IIOManager ioManager, IPostWriteHandler postWriteHandler, ILogger logger) { this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + this.postWriteHandler = postWriteHandler ?? throw new ArgumentNullException(nameof(postWriteHandler)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); } @@ -88,6 +97,24 @@ namespace Tgstation.Server.Host.Components.Byond } /// - public Task InstallByond(string path, Version version, CancellationToken cancellationToken) => Task.CompletedTask; + public Task InstallByond(string path, Version version, CancellationToken cancellationToken) + { + //write the scripts for running the ting + //need to add $ORIGIN to LD_LIBRARY_PATH + const string StandardScript = "#!/bin/sh\nexport LD_LIBRARY_PATH=\"\\$ORIGIN:$LD_LIBRARY_PATH\"\nBASEDIR=$(dirname \"$0\")\nexec \"$BASEDIR/{0}\" \"$@\"\n"; + + var dreamDaemonScript = String.Format(CultureInfo.InvariantCulture, StandardScript, "DreamDaemon"); + var dreamMakerScript = String.Format(CultureInfo.InvariantCulture, StandardScript, "DreamMaker"); + + async Task WriteAndMakeExecutable(string fullPath, string script) + { + await ioManager.WriteAllBytes(fullPath, Encoding.ASCII.GetBytes(script), cancellationToken).ConfigureAwait(false); + postWriteHandler.HandleWrite(fullPath); + } + + var basePath = ioManager.ConcatPath(path, ByondManager.BinPath); + + return Task.WhenAll(WriteAndMakeExecutable(ioManager.ConcatPath(basePath, DreamDaemonName), dreamDaemonScript), WriteAndMakeExecutable(ioManager.ConcatPath(basePath, DreamMakerName), dreamMakerScript)); + } } } From dba2f81d95f2be5298089f14d2caab6b4b667b65 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 13 Sep 2018 10:14:20 -0400 Subject: [PATCH 036/230] RepsoitoryController Update now formats job names better --- .../Controllers/RepositoryController.cs | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index a5e749c6d4..92bff1cf9b 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -381,9 +381,29 @@ namespace Tgstation.Server.Host.Controllers //this is just db stuf so stow it away await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); + //format the job description + string description = null; + if (model.UpdateFromOrigin == true) + if (model.Reference != null) + description = String.Format(CultureInfo.InvariantCulture, "Fetch and hard reset repsitory to origin/{0}", model.Reference); + else if (model.CheckoutSha != null) + description = String.Format(CultureInfo.InvariantCulture, "Fetch and checkout {0} in repository", model.CheckoutSha); + else + description = "Pull current repository reference"; + else if (model.Reference != null || model.CheckoutSha != null) + description = String.Format(CultureInfo.InvariantCulture, "Checkout repository {0} {1}", model.Reference != null ? "reference" : "SHA", model.Reference ?? model.CheckoutSha); + + if (newTestMerges) + description = String.Format(CultureInfo.InvariantCulture, "{0}est merge pull request(s) {1}{2}", + description != null ? String.Format(CultureInfo.InvariantCulture, "{0} and t", description) : "T", + String.Join(", ", model.NewTestMerges.Select(x => + String.Format(CultureInfo.InvariantCulture, "#{0}{1}", x.Number, + x.PullRequestRevision != null ? String.Format(CultureInfo.InvariantCulture, " {0}", x.PullRequestRevision.Substring(0, 7)) : String.Empty))), + description != null ? String.Empty : " in repository"); + var job = new Models.Job { - Description = "Apply repository changes", + Description = description ?? "Apply repository changes", StartedBy = AuthenticationContext.User, Instance = Instance, CancelRightsType = RightsType.Repository, From 89e3141689bc663bb8562f77fbd2f2c39073a679 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 13 Sep 2018 10:14:50 -0400 Subject: [PATCH 037/230] RepositoryController Update now returns OK without ActiveJob field if no git changes to make --- .../Controllers/RepositoryController.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index 92bff1cf9b..3cc31661ca 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -401,9 +401,13 @@ namespace Tgstation.Server.Host.Controllers x.PullRequestRevision != null ? String.Format(CultureInfo.InvariantCulture, " {0}", x.PullRequestRevision.Substring(0, 7)) : String.Empty))), description != null ? String.Empty : " in repository"); + if (description == null) + //no git changes + return Json(api); + var job = new Models.Job { - Description = description ?? "Apply repository changes", + Description = description, StartedBy = AuthenticationContext.User, Instance = Instance, CancelRightsType = RightsType.Repository, From e5d14aaef008649fd210f07a501c86e4e7e9d32e Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 13 Sep 2018 10:16:14 -0400 Subject: [PATCH 038/230] Update repository POST API docs --- docs/API.dox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/API.dox b/docs/API.dox index 49c19ce3ee..759c73ff56 100644 --- a/docs/API.dox +++ b/docs/API.dox @@ -287,7 +287,7 @@ Modifications to the repository are done with the following request: I POST "/Repository" => @ref Tgstation.Server.Api.Models.Repository => @ref Tgstation.Server.Api.Models.Repository -Each update creates a job specified in the @ref Tgstation.Server.Api.Models.Repository.ActiveJob field jobs will be queued in succession. See below for post examples. +Each update that requires git changes creates a job specified in the @ref Tgstation.Server.Api.Models.Repository.ActiveJob field jobs will be queued in succession. See below for POST examples. @subsubsection api_repopost Repository Commands for Git Aliases From de495b1417ad332f7728587c0105d3001aefb554 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 13 Sep 2018 10:31:52 -0400 Subject: [PATCH 039/230] Adds warnings when the repository takes it's HEAD as an OriginCommitSha --- src/Tgstation.Server.Host/Components/Instance.cs | 1 + .../Components/Repository/Repository.cs | 7 ++++++- .../Controllers/DreamMakerController.cs | 2 ++ .../Controllers/RepositoryController.cs | 11 ++++++++--- 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index 080c469aab..21e7edc06d 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -186,6 +186,7 @@ namespace Tgstation.Server.Host.Components Id = metadata.Id } }; + logger.LogWarning(Repository.Repository.OriginTrackingErrorTemplate, currentSha); db.Instances.Attach(revInfo.Instance); } diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs index 1b704da4f5..c29384b083 100644 --- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs @@ -18,13 +18,18 @@ namespace Tgstation.Server.Host.Components.Repository /// public const string GitHubUrl = "://github.com/"; - const string UnknownReference = ""; + /// + /// Template error message for when tracking of the most recent origin commit fails + /// + public const string OriginTrackingErrorTemplate = "Unable to determine most recent origin commit of {0}. Marking it as an origin commit. This may result in invalid git metadata until the next hard reset to an origin reference."; /// /// The branch name used for publishing testmerge commits /// public const string RemoteTemporaryBranchName = "___TGSTempBranch"; + const string UnknownReference = ""; + /// public bool IsGitHubRepository { get; } diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs index 9341fd8dd3..d2cb696917 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Tgstation.Server.Api; using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Components; +using Tgstation.Server.Host.Components.Repository; using Tgstation.Server.Host.Core; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; @@ -179,6 +180,7 @@ namespace Tgstation.Server.Host.Controllers Id = Instance.Id } }; + Logger.LogWarning(Repository.OriginTrackingErrorTemplate, repoSha); databaseContext.Instances.Attach(revInfo.Instance); } diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index 3cc31661ca..fc916acae6 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -67,7 +67,7 @@ namespace Tgstation.Server.Host.Controllers generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions)); } - static async Task LoadRevisionInformation(Components.Repository.IRepository repository, IDatabaseContext databaseContext, Models.Instance instance, string lastOriginCommitSha, Action revInfoSink, CancellationToken cancellationToken) + async Task LoadRevisionInformation(Components.Repository.IRepository repository, IDatabaseContext databaseContext, Models.Instance instance, string lastOriginCommitSha, Action revInfoSink, CancellationToken cancellationToken) { var repoSha = repository.Head; @@ -96,12 +96,17 @@ namespace Tgstation.Server.Host.Controllers lock (databaseContext) //cleaner this way databaseContext.RevisionInformations.Add(revisionInfo); } - revisionInfo.OriginCommitSha = revisionInfo.OriginCommitSha ?? lastOriginCommitSha ?? repository.Head; + revisionInfo.OriginCommitSha = revisionInfo.OriginCommitSha ?? lastOriginCommitSha; + if (revisionInfo.OriginCommitSha == null) + { + revisionInfo.OriginCommitSha = repoSha; + Logger.LogWarning(Components.Repository.Repository.OriginTrackingErrorTemplate, repoSha); + } revInfoSink?.Invoke(revisionInfo); return needsDbUpdate; } - static async Task PopulateApi(Repository model, Components.Repository.IRepository repository, IDatabaseContext databaseContext, Models.Instance instance, CancellationToken cancellationToken) + async Task PopulateApi(Repository model, Components.Repository.IRepository repository, IDatabaseContext databaseContext, Models.Instance instance, CancellationToken cancellationToken) { if (repository.IsGitHubRepository) { From e4245866e8307ce94e9d846674612fe93ad1b73e Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 13 Sep 2018 10:33:47 -0400 Subject: [PATCH 040/230] Do not update OriginCommitSha on an "Up To Date" merge Fixes #627 --- src/Tgstation.Server.Host/Components/Repository/IRepository.cs | 2 +- src/Tgstation.Server.Host/Components/Repository/Repository.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Repository/IRepository.cs b/src/Tgstation.Server.Host/Components/Repository/IRepository.cs index 48f6ff2d06..244ef15f91 100644 --- a/src/Tgstation.Server.Host/Components/Repository/IRepository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/IRepository.cs @@ -105,7 +105,7 @@ namespace Tgstation.Server.Host.Components.Repository /// The name of the merge committer /// The e-mail of the merge committer /// The for the operation - /// A resulting in a representing the merge result that is after a fast forward or up to date, on a merge, on a conflict + /// A resulting in a representing the merge result that is after a fast forward, on a merge or up to date, on a conflict Task MergeOrigin(string committerName, string committerEmail, CancellationToken cancellationToken); /// diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs index c29384b083..e8f54cc2a2 100644 --- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs @@ -371,7 +371,7 @@ namespace Tgstation.Server.Host.Components.Repository return null; } - return result.Status != MergeStatus.NonFastForward; + return result.Status == MergeStatus.FastForward; } /// From 9cc3c97a033049e363701a7bb494c535cd34da27 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 13 Sep 2018 10:48:33 -0400 Subject: [PATCH 041/230] Add gcc-multilib as a necessary dependency for byond to Docker image --- build/Dockerfile | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/build/Dockerfile b/build/Dockerfile index d811f25a1b..ee2bbfe25f 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -1,4 +1,5 @@ FROM microsoft/dotnet:2.1-sdk AS build + WORKDIR /src COPY tgstation-server.sln ./ @@ -26,6 +27,12 @@ RUN dotnet publish -c Release -o /app/lib/Default && mv /app/lib/Default/appsett FROM microsoft/dotnet:2.1-aspnetcore-runtime EXPOSE 5000 + +RUN apt-get update + && apt-get install -y \ + gcc-multilib \ #needed for byond + && rm -rf /var/lib/apt/lists/* + WORKDIR /app COPY --from=build /app . From 271f944f5b6f56767d372a5d9a4316e3b6155aa2 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 13 Sep 2018 10:49:36 -0400 Subject: [PATCH 042/230] Remove remaining test artifacts from Docker build --- .dockerignore | 1 + build/Dockerfile | 5 ----- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.dockerignore b/.dockerignore index 24e31346b4..1dfe466b59 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,3 +6,4 @@ packages */bin */obj +tests diff --git a/build/Dockerfile b/build/Dockerfile index ee2bbfe25f..f4e4d2be7c 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -9,11 +9,6 @@ COPY src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj sr COPY src/Tgstation.Server.Host/Tgstation.Server.Host.csproj src/Tgstation.Server.Host/ COPY src/Tgstation.Server.Api/Tgstation.Server.Api.csproj src/Tgstation.Server.Api/ -COPY tests/Tgstation.Server.Api.Tests/Tgstation.Server.Api.Tests.csproj tests/Tgstation.Server.Api.Tests/ -COPY tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj tests/Tgstation.Server.Host.Tests/ -COPY tests/Tgstation.Server.Host.Watchdog.Tests/Tgstation.Server.Host.Watchdog.Tests.csproj tests/Tgstation.Server.Host.Watchdog.Tests/ -COPY tests/Tgstation.Server.Host.Console.Tests/Tgstation.Server.Host.Console.Tests.csproj tests/Tgstation.Server.Host.Console.Tests/ - RUN dotnet restore -nowarn:MSB3202,nu1503 -p:RestoreUseSkipNonexistentTargets=false COPY . . From f190d4b48f30205a48b4c339859aaf000611a067 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 13 Sep 2018 11:03:11 -0400 Subject: [PATCH 043/230] Add more docker/linux installation documentation --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 52f406aa7e..6f623170ee 100644 --- a/README.md +++ b/README.md @@ -25,9 +25,17 @@ Generally, updates force a live tracking of the configured git repo, resetting l 3. Extract the .zip file to where you want the server to run from. Note the account running the server must have write access to the `lib` subdirectory. 4. If using the ServerService package, run `Tgstation.Server.Host.Service.exe`. It should prompt you to install the service. Click `Yes` and accept a potential UAC elevation prompt. You should now be able to control the service using the Windows service control commandlet. +#### Linux + +The following dependencies are required to run tgstation-server on Linux alongside the .NET Core runtime + +- gcc-multilib (on 64-bit systems for running BYOND) + +Note that tgstation-server has only ever been tested on Linux via it's [docker environment](https://github.com/tgstation/tgstation-server/blob/master/build/Dockerfile#L22). If you are having trouble with something, or figure out a required workaround, please contact project maintainers so this documentation may be better updated. + #### Docker -tgstation-server supports running in a docker container on linux systems and is the preferred deployment method to avoid [native dependency hell with libgit2](https://github.com/libgit2/libgit2sharp/issues/1533). The official image repository is located at https://hub.docker.com/r/tgstation/server it can be built locally, however, by running `docker build . -f build/Dockerfile` in the repository root. +tgstation-server supports running in a docker container on Linux systems. The official image repository is located at https://hub.docker.com/r/tgstation/server. It can also be built locally by running `docker build . -f build/Dockerfile` in the repository root. To create a container run `docker create --restart=always -p :80 -v /path/to/your/appsettings.Production.json:/config_data -v path/to/your/log/folder:/tgs_logs tgstation/server` with any additional options you desire (i.e. You'll have to expose more ports in order to actually host servers, add a volume to create instances on, and create a volume for the SQLite database if that is what you're using). From b245bf00a956dc71aa548aeb57c48b255ce21ae5 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 13 Sep 2018 11:09:03 -0400 Subject: [PATCH 044/230] Add missing escape sequences to Dockerfile --- build/Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/Dockerfile b/build/Dockerfile index f4e4d2be7c..45e06d1d07 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -22,10 +22,10 @@ RUN dotnet publish -c Release -o /app/lib/Default && mv /app/lib/Default/appsett FROM microsoft/dotnet:2.1-aspnetcore-runtime EXPOSE 5000 - -RUN apt-get update +#needed for byond +RUN apt-get update \ && apt-get install -y \ - gcc-multilib \ #needed for byond + gcc-multilib \ && rm -rf /var/lib/apt/lists/* WORKDIR /app From 66dcfb658de5a8fcc105456f80fd0334ff432be9 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 13 Sep 2018 12:23:12 -0400 Subject: [PATCH 045/230] Add missing IByondClient List call --- .../Components/ByondClient.cs | 8 ++++++-- .../Components/IByondClient.cs | 18 +++++++++++++----- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/Tgstation.Server.Client/Components/ByondClient.cs b/src/Tgstation.Server.Client/Components/ByondClient.cs index e75c47110c..92c2f8aba5 100644 --- a/src/Tgstation.Server.Client/Components/ByondClient.cs +++ b/src/Tgstation.Server.Client/Components/ByondClient.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api; @@ -30,9 +31,12 @@ namespace Tgstation.Server.Client.Components } /// - public Task Read(CancellationToken cancellationToken) => apiClient.Read(Routes.Byond, instance.Id, cancellationToken); + public Task ActiveVersion(CancellationToken cancellationToken) => apiClient.Read(Routes.Byond, instance.Id, cancellationToken); /// - public Task Update(Byond byond, CancellationToken cancellationToken) => apiClient.Update(Routes.Byond, byond ?? throw new ArgumentNullException(nameof(byond)), instance.Id, cancellationToken); + public Task> InstalledVersions(CancellationToken cancellationToken) => apiClient.Read>(Routes.List(Routes.Byond), instance.Id, cancellationToken); + + /// + public Task SetActiveVersion(Byond byond, CancellationToken cancellationToken) => apiClient.Update(Routes.Byond, byond ?? throw new ArgumentNullException(nameof(byond)), instance.Id, cancellationToken); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Client/Components/IByondClient.cs b/src/Tgstation.Server.Client/Components/IByondClient.cs index bfe9e8f003..da4f4d45a3 100644 --- a/src/Tgstation.Server.Client/Components/IByondClient.cs +++ b/src/Tgstation.Server.Client/Components/IByondClient.cs @@ -1,4 +1,5 @@ -using System.Threading; +using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; @@ -10,11 +11,18 @@ namespace Tgstation.Server.Client.Components public interface IByondClient { /// - /// Get the information + /// Get the active information /// /// The for the operation - /// A resulting in the information - Task Read(CancellationToken cancellationToken); + /// A resulting in the active information + Task ActiveVersion(CancellationToken cancellationToken); + + /// + /// Get all installed s + /// + /// The for the operation + /// A resulting in an of installed s + Task> InstalledVersions(CancellationToken cancellationToken); /// /// Updates the information @@ -22,6 +30,6 @@ namespace Tgstation.Server.Client.Components /// The information to update /// The for the operation /// A resulting in the updated information - Task Update(Byond byond, CancellationToken cancellationToken); + Task SetActiveVersion(Byond byond, CancellationToken cancellationToken); } } From 4274706b8b6ebd99d89f35aa78d9076f8db05d37 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 13 Sep 2018 12:24:12 -0400 Subject: [PATCH 046/230] Bump client preview version --- src/Tgstation.Server.Client/Tgstation.Server.Client.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj index 7b97f583c8..f5bbc82a26 100644 --- a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj +++ b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj @@ -3,7 +3,7 @@ netstandard2.0 Full - 4.0.0.0-preview9103 + 4.0.0.0-preview9104 true Cyberboss /tg/station 13 From 549d06cc3a0d6b77854c94f463ed07965fafc0e1 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Thu, 13 Sep 2018 15:21:32 -0400 Subject: [PATCH 047/230] SessionControllerFactory now launches with noShellExecute --- .../Components/Watchdog/SessionControllerFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs index d4ce1cdc68..fad899d6e7 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs @@ -186,7 +186,7 @@ namespace Tgstation.Server.Host.Components.Watchdog parameters); //launch dd - var process = processExecutor.LaunchProcess(byondLock.DreamDaemonPath, basePath, arguments); + var process = processExecutor.LaunchProcess(byondLock.DreamDaemonPath, basePath, arguments, noShellExecute: true); try { //return the session controller for it From 8c5703b693a65377d3edffbc38b9a34042f998bf Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Thu, 13 Sep 2018 15:35:53 -0400 Subject: [PATCH 048/230] Minor formatting improvement in ProcessExecutor --- .../Core/ProcessExecutor.cs | 36 ++++++++----------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/src/Tgstation.Server.Host/Core/ProcessExecutor.cs b/src/Tgstation.Server.Host/Core/ProcessExecutor.cs index 32d00b207d..6b8a97490a 100644 --- a/src/Tgstation.Server.Host/Core/ProcessExecutor.cs +++ b/src/Tgstation.Server.Host/Core/ProcessExecutor.cs @@ -85,31 +85,25 @@ namespace Tgstation.Server.Host.Core { outputStringBuilder = new StringBuilder(); handle.StartInfo.RedirectStandardOutput = true; - var eventHandler = new DataReceivedEventHandler( - delegate (object sender, DataReceivedEventArgs e) - { - combinedStringBuilder.Append(Environment.NewLine); - combinedStringBuilder.Append(e.Data); - outputStringBuilder.Append(Environment.NewLine); - outputStringBuilder.Append(e.Data); - } - ); - handle.OutputDataReceived += eventHandler; + handle.OutputDataReceived += (sender, e) => + { + combinedStringBuilder.Append(Environment.NewLine); + combinedStringBuilder.Append(e.Data); + outputStringBuilder.Append(Environment.NewLine); + outputStringBuilder.Append(e.Data); + }; } if (readError) { errorStringBuilder = new StringBuilder(); handle.StartInfo.RedirectStandardError = true; - var eventHandler = new DataReceivedEventHandler( - delegate (object sender, DataReceivedEventArgs e) - { - combinedStringBuilder.Append(Environment.NewLine); - combinedStringBuilder.Append(e.Data); - errorStringBuilder.Append(Environment.NewLine); - errorStringBuilder.Append(e.Data); - } - ); - handle.ErrorDataReceived += eventHandler; + handle.ErrorDataReceived += (sender, e) => + { + combinedStringBuilder.Append(Environment.NewLine); + combinedStringBuilder.Append(e.Data); + errorStringBuilder.Append(Environment.NewLine); + errorStringBuilder.Append(e.Data); + }; } } @@ -138,4 +132,4 @@ namespace Tgstation.Server.Host.Core } } } -} \ No newline at end of file +} From 09166c86f434326e605b89b6dfb07c21e585c4be Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 13 Sep 2018 16:43:01 -0400 Subject: [PATCH 049/230] Refactor ApiClient to take a IHttpClient --- src/Tgstation.Server.Client/ApiClient.cs | 9 +++-- .../ApiClientFactory.cs | 2 +- src/Tgstation.Server.Client/HttpClient.cs | 37 +++++++++++++++++++ src/Tgstation.Server.Client/IHttpClient.cs | 26 +++++++++++++ 4 files changed, 69 insertions(+), 5 deletions(-) create mode 100644 src/Tgstation.Server.Client/HttpClient.cs create mode 100644 src/Tgstation.Server.Client/IHttpClient.cs diff --git a/src/Tgstation.Server.Client/ApiClient.cs b/src/Tgstation.Server.Client/ApiClient.cs index e20a143c47..90a24bd92e 100644 --- a/src/Tgstation.Server.Client/ApiClient.cs +++ b/src/Tgstation.Server.Client/ApiClient.cs @@ -32,7 +32,7 @@ namespace Tgstation.Server.Client /// /// The for the /// - readonly HttpClient httpClient; + readonly IHttpClient httpClient; /// /// The s used by the @@ -42,14 +42,15 @@ namespace Tgstation.Server.Client /// /// Construct an /// + /// The value of /// The value of /// The value of - public ApiClient(Uri url, ApiHeaders apiHeaders) + public ApiClient(IHttpClient httpClient, Uri url, ApiHeaders apiHeaders) { + this.httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); Url = url ?? throw new ArgumentNullException(nameof(url)); Headers = apiHeaders ?? throw new ArgumentNullException(nameof(apiHeaders)); - - httpClient = new HttpClient(); + requestLoggers = new List(); } diff --git a/src/Tgstation.Server.Client/ApiClientFactory.cs b/src/Tgstation.Server.Client/ApiClientFactory.cs index 64dae0469f..0b686b77c2 100644 --- a/src/Tgstation.Server.Client/ApiClientFactory.cs +++ b/src/Tgstation.Server.Client/ApiClientFactory.cs @@ -7,6 +7,6 @@ namespace Tgstation.Server.Client sealed class ApiClientFactory : IApiClientFactory { /// - public IApiClient CreateApiClient(Uri url, ApiHeaders apiHeaders) => new ApiClient(url, apiHeaders); + public IApiClient CreateApiClient(Uri url, ApiHeaders apiHeaders) => new ApiClient(new HttpClient(), url, apiHeaders); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Client/HttpClient.cs b/src/Tgstation.Server.Client/HttpClient.cs new file mode 100644 index 0000000000..c01042ae2d --- /dev/null +++ b/src/Tgstation.Server.Client/HttpClient.cs @@ -0,0 +1,37 @@ +using System; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Client +{ + /// + sealed class HttpClient : IHttpClient + { + /// + public TimeSpan Timeout + { + get => httpClient.Timeout; + set => httpClient.Timeout = value; + } + + /// + /// The real + /// + readonly System.Net.Http.HttpClient httpClient; + + /// + /// Construct an + /// + public HttpClient() + { + httpClient = new System.Net.Http.HttpClient(); + } + + /// + public void Dispose() => httpClient.Dispose(); + + /// + public Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) => httpClient.SendAsync(request, cancellationToken); + } +} diff --git a/src/Tgstation.Server.Client/IHttpClient.cs b/src/Tgstation.Server.Client/IHttpClient.cs new file mode 100644 index 0000000000..a1e146624e --- /dev/null +++ b/src/Tgstation.Server.Client/IHttpClient.cs @@ -0,0 +1,26 @@ +using System; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Client +{ + /// + /// For sending HTTP requests + /// + interface IHttpClient : IDisposable + { + /// + /// The request timeout + /// + TimeSpan Timeout { get; set; } + + /// + /// Send an HTTP request + /// + /// The + /// The for the operation + /// A resulting in the of the request + Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken); + } +} \ No newline at end of file From 123ab7a9a820f55f4d7734c875920c66390a5be7 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 13 Sep 2018 17:07:49 -0400 Subject: [PATCH 050/230] Adds a client test for byond deserialization --- src/Tgstation.Server.Client/AssemblyInfo.cs | 4 ++ .../TestApiClient.cs | 42 +++++++++++++++++++ .../Tgstation.Server.Client.Tests.csproj | 1 + 3 files changed, 47 insertions(+) create mode 100644 src/Tgstation.Server.Client/AssemblyInfo.cs create mode 100644 tests/Tgstation.Server.Client.Tests/TestApiClient.cs diff --git a/src/Tgstation.Server.Client/AssemblyInfo.cs b/src/Tgstation.Server.Client/AssemblyInfo.cs new file mode 100644 index 0000000000..0dffa25c06 --- /dev/null +++ b/src/Tgstation.Server.Client/AssemblyInfo.cs @@ -0,0 +1,4 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Tgstation.Server.Client.Tests")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] diff --git a/tests/Tgstation.Server.Client.Tests/TestApiClient.cs b/tests/Tgstation.Server.Client.Tests/TestApiClient.cs new file mode 100644 index 0000000000..f93f6f2753 --- /dev/null +++ b/tests/Tgstation.Server.Client.Tests/TestApiClient.cs @@ -0,0 +1,42 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Newtonsoft.Json; +using System; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api; +using Tgstation.Server.Api.Models; + +namespace Tgstation.Server.Client.Tests +{ + [TestClass] + public sealed class TestApiClient + { + [TestMethod] + public async Task TestDeserializingByondModelsWork() + { + var sample = new Byond + { + Version = new Version(511, 1385) + }; + + var sampleJson = JsonConvert.SerializeObject(sample); + + var response = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(sampleJson) + }; + + var httpClient = new Mock(); + httpClient.Setup(x => x.SendAsync(It.IsNotNull(), It.IsAny())).Returns(Task.FromResult(response)); + + var client = new ApiClient(httpClient.Object, new Uri("http://fake.com"), new ApiHeaders(new ProductHeaderValue("fake"), "fake")); + + var result = await client.Read(Routes.Byond, default).ConfigureAwait(false); + Assert.AreEqual(sample.Version, result.Version); + } + } +} diff --git a/tests/Tgstation.Server.Client.Tests/Tgstation.Server.Client.Tests.csproj b/tests/Tgstation.Server.Client.Tests/Tgstation.Server.Client.Tests.csproj index caacb5743a..bf6012405c 100644 --- a/tests/Tgstation.Server.Client.Tests/Tgstation.Server.Client.Tests.csproj +++ b/tests/Tgstation.Server.Client.Tests/Tgstation.Server.Client.Tests.csproj @@ -9,6 +9,7 @@ + From 78cd7818f04d679598a3c721eaa7c225f33ce52f Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Thu, 13 Sep 2018 18:40:42 -0400 Subject: [PATCH 051/230] Swallow and log all process termination exceptions. Avoid where possible --- src/Tgstation.Server.Host/Core/Process.cs | 23 ++++++++++++++++--- .../Core/ProcessExecutor.cs | 14 +++++++---- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/Tgstation.Server.Host/Core/Process.cs b/src/Tgstation.Server.Host/Core/Process.cs index 15296ebb2f..513b0bbe71 100644 --- a/src/Tgstation.Server.Host/Core/Process.cs +++ b/src/Tgstation.Server.Host/Core/Process.cs @@ -1,4 +1,5 @@ -using System; +using Microsoft.Extensions.Logging; +using System; using System.Text; using System.Threading.Tasks; @@ -22,7 +23,12 @@ namespace Tgstation.Server.Host.Core readonly StringBuilder errorStringBuilder; readonly StringBuilder combinedStringBuilder; - public Process(System.Diagnostics.Process handle, Task lifetime, StringBuilder outputStringBuilder, StringBuilder errorStringBuilder, StringBuilder combinedStringBuilder) + /// + /// The for the + /// + readonly ILogger logger; + + public Process(System.Diagnostics.Process handle, Task lifetime, StringBuilder outputStringBuilder, StringBuilder errorStringBuilder, StringBuilder combinedStringBuilder, ILogger logger) { this.handle = handle ?? throw new ArgumentNullException(nameof(handle)); Lifetime = lifetime ?? throw new ArgumentNullException(nameof(lifetime)); @@ -31,6 +37,8 @@ namespace Tgstation.Server.Host.Core this.errorStringBuilder = errorStringBuilder; this.combinedStringBuilder = combinedStringBuilder; + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + Id = handle.Id; Startup = Task.Factory.StartNew(() => { @@ -40,6 +48,8 @@ namespace Tgstation.Server.Host.Core } catch (InvalidOperationException) { } }, default, TaskCreationOptions.LongRunning, TaskScheduler.Current); + + logger.LogTrace("Created proces ID: {0}", Id); } /// @@ -72,12 +82,18 @@ namespace Tgstation.Server.Host.Core /// public void Terminate() { + if (handle.HasExited) + return; try { + logger.LogTrace("Terminating process..."); handle.Kill(); handle.WaitForExit(); } - catch (InvalidOperationException) { } + catch (Exception e) + { + logger.LogDebug("Process termination exception: {0}", e); + } } public void SetHighPriority() @@ -85,6 +101,7 @@ namespace Tgstation.Server.Host.Core try { handle.PriorityClass = System.Diagnostics.ProcessPriorityClass.AboveNormal; + logger.LogTrace("Set to above normal priority", handle.Id); } catch (InvalidOperationException) { } } diff --git a/src/Tgstation.Server.Host/Core/ProcessExecutor.cs b/src/Tgstation.Server.Host/Core/ProcessExecutor.cs index 6b8a97490a..5aad8a9e8b 100644 --- a/src/Tgstation.Server.Host/Core/ProcessExecutor.cs +++ b/src/Tgstation.Server.Host/Core/ProcessExecutor.cs @@ -1,6 +1,5 @@ using Microsoft.Extensions.Logging; using System; -using System.Diagnostics; using System.Text; using System.Threading.Tasks; @@ -14,6 +13,11 @@ namespace Tgstation.Server.Host.Core /// readonly ILogger logger; + /// + /// The for the + /// + readonly ILoggerFactory loggerFactory; + /// /// Create a resulting in the exit code of a given /// @@ -43,9 +47,11 @@ namespace Tgstation.Server.Host.Core /// Construct a /// /// The value of - public ProcessExecutor(ILogger logger) + /// The value of + public ProcessExecutor(ILogger logger, ILoggerFactory loggerFactory) { this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); } /// @@ -55,7 +61,7 @@ namespace Tgstation.Server.Host.Core var handle = System.Diagnostics.Process.GetProcessById(id); try { - return new Process(handle, AttachExitHandler(handle), null, null, null); + return new Process(handle, AttachExitHandler(handle), null, null, null, loggerFactory.CreateLogger()); } catch { @@ -123,7 +129,7 @@ namespace Tgstation.Server.Host.Core } catch (InvalidOperationException) { } - return new Process(handle, lifetimeTask, outputStringBuilder, errorStringBuilder, combinedStringBuilder); + return new Process(handle, lifetimeTask, outputStringBuilder, errorStringBuilder, combinedStringBuilder, loggerFactory.CreateLogger()); } catch { From dd6b2855514647ce629f55c9ae39df6768ea1bd4 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Thu, 13 Sep 2018 19:29:13 -0400 Subject: [PATCH 052/230] Don't crash the watchdog if the process priority can't be raised Log a warning instead --- src/Tgstation.Server.Host/Core/Process.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Core/Process.cs b/src/Tgstation.Server.Host/Core/Process.cs index 513b0bbe71..cc6dd66cd7 100644 --- a/src/Tgstation.Server.Host/Core/Process.cs +++ b/src/Tgstation.Server.Host/Core/Process.cs @@ -103,7 +103,10 @@ namespace Tgstation.Server.Host.Core handle.PriorityClass = System.Diagnostics.ProcessPriorityClass.AboveNormal; logger.LogTrace("Set to above normal priority", handle.Id); } - catch (InvalidOperationException) { } + catch (InvalidOperationException e) + { + logger.LogWarning("Unable to raise process priority! Exception: {0}", e); + } } } } From 21b5b5e43572b12b689d1b3f8512b9a6d9bff53d Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Thu, 13 Sep 2018 19:36:08 -0400 Subject: [PATCH 053/230] Send an error message to chat when watchdog startup fails --- src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index 6ce7108c42..939661e74d 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -735,6 +735,13 @@ namespace Tgstation.Server.Host.Components.Watchdog } catch (Exception e) { + var originalChatTask = chatTask; + async Task ChainChatTaskWithErrorMessage() + { + await originalChatTask.ConfigureAwait(false); + await chat.SendWatchdogMessage("Startup failed!", cancellationToken).ConfigureAwait(false); + } + chatTask = ChainChatTaskWithErrorMessage(); logger.LogWarning("Failed to start watchdog: {0}", e.ToString()); throw; } From 8d49d830e074bae9fb32240afd05660693abfb9d Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Thu, 13 Sep 2018 19:40:07 -0400 Subject: [PATCH 054/230] Add API validation port to DreamMaker read --- .../Controllers/DreamMakerController.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs index d2cb696917..33b8549e47 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs @@ -51,11 +51,12 @@ namespace Tgstation.Server.Host.Controllers public override async Task Read(CancellationToken cancellationToken) { var instance = instanceManager.GetInstance(Instance); - var projectName = await DatabaseContext.DreamMakerSettings.Where(x => x.InstanceId == Instance.Id).Select(x => x.ProjectName).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); + var dreamMakerSettings = await DatabaseContext.DreamMakerSettings.Where(x => x.InstanceId == Instance.Id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); return Json(new Api.Models.DreamMaker { - ProjectName = projectName, - Status = instance.DreamMaker.Status + ProjectName = dreamMakerSettings.ProjectName, + Status = instance.DreamMaker.Status, + ApiValidationPort = dreamMakerSettings.ApiValidationPort }); } From e36a5ec71ee951f0514472c1f529da5344724ff0 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Thu, 13 Sep 2018 19:49:35 -0400 Subject: [PATCH 055/230] Removes CompilerStatus Eventually we will replace it with a progress heuristic as outlined in #614. Until then, it's pretty much useless. This also marks the death of Api.Models.Internal.DreamMakerSettings as it is now elevated into the DreamMaker model itself. As a result, it is now unsealed. --- .../Models/CompilerStatus.cs | 51 ------------------- src/Tgstation.Server.Api/Models/DreamMaker.cs | 14 +++-- .../Models/Internal/DreamMakerSettings.cs | 21 -------- .../Rights/DreamMakerRights.cs | 4 +- .../Components/Compiler/DreamMaker.cs | 39 +++++--------- .../Components/Compiler/IDreamMaker.cs | 10 +--- .../Controllers/DreamMakerController.cs | 1 - .../Models/DreamMakerSettings.cs | 2 +- 8 files changed, 28 insertions(+), 114 deletions(-) delete mode 100644 src/Tgstation.Server.Api/Models/CompilerStatus.cs delete mode 100644 src/Tgstation.Server.Api/Models/Internal/DreamMakerSettings.cs diff --git a/src/Tgstation.Server.Api/Models/CompilerStatus.cs b/src/Tgstation.Server.Api/Models/CompilerStatus.cs deleted file mode 100644 index 9cb0a7a5ed..0000000000 --- a/src/Tgstation.Server.Api/Models/CompilerStatus.cs +++ /dev/null @@ -1,51 +0,0 @@ -namespace Tgstation.Server.Api.Models -{ - /// - /// Status of the for an - /// -#pragma warning disable CA1717 // Only FlagsAttribute enums should have plural names - public enum CompilerStatus -#pragma warning restore CA1717 // Only FlagsAttribute enums should have plural names - { - /// - /// The is idle - /// - Idle, - /// - /// The is being copied - /// - Copying, - /// - /// Pre-compile scripts are running - /// - PreCompile, - /// - /// The .dme is having it's server side modifications applied - /// - Modifying, - /// - /// DreamMaker is running - /// - Compiling, - /// - /// The DMAPI is being verified - /// - Verifying, - /// - /// Post-compile scripts are running - /// - PostCompile, - /// - /// The compile results are being duplicated - /// - Duplicating, - /// - /// The configuration is being linked to the compile results - /// - Symlinking, - /// - /// A failed compile job is being erased - /// - Cleanup - } -} \ No newline at end of file diff --git a/src/Tgstation.Server.Api/Models/DreamMaker.cs b/src/Tgstation.Server.Api/Models/DreamMaker.cs index 93c44bf71e..690cc90e09 100644 --- a/src/Tgstation.Server.Api/Models/DreamMaker.cs +++ b/src/Tgstation.Server.Api/Models/DreamMaker.cs @@ -1,15 +1,21 @@ -using Tgstation.Server.Api.Models.Internal; +using System.ComponentModel.DataAnnotations; namespace Tgstation.Server.Api.Models { /// /// Represents the state of the DreamMaker compiler. Create action starts a new compile. Delete action cancels the current compile /// - public sealed class DreamMaker : DreamMakerSettings + public class DreamMaker { /// - /// The of the compiler + /// The .dme file tries to compile with without the extension /// - public CompilerStatus Status { get; set; } + public string ProjectName { get; set; } + + /// + /// The port used during compilation to validate the DMAPI + /// + [Required] + public ushort? ApiValidationPort { get; set; } } } diff --git a/src/Tgstation.Server.Api/Models/Internal/DreamMakerSettings.cs b/src/Tgstation.Server.Api/Models/Internal/DreamMakerSettings.cs deleted file mode 100644 index a7b649d80c..0000000000 --- a/src/Tgstation.Server.Api/Models/Internal/DreamMakerSettings.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Tgstation.Server.Api.Models.Internal -{ - /// - /// Configurable settings for - /// - public class DreamMakerSettings - { - /// - /// The .dme file tries to compile with without the extension - /// - public string ProjectName { get; set; } - - /// - /// The port used during compilation to validate the DMAPI - /// - [Required] - public ushort? ApiValidationPort { get; set; } - } -} diff --git a/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs b/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs index 439a13d81c..0c4612b834 100644 --- a/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs +++ b/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs @@ -25,11 +25,11 @@ namespace Tgstation.Server.Api.Rights /// CancelCompile = 4, /// - /// User may modify + /// User may modify /// SetDme = 8, /// - /// User may modify + /// User may modify /// SetApiValidationPort = 16, /// diff --git a/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs b/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs index 1088833d24..104f541931 100644 --- a/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs @@ -37,9 +37,6 @@ namespace Tgstation.Server.Host.Components.Compiler /// const string DmeExtension = "dme"; - /// - public CompilerStatus Status { get; private set; } - /// /// The for /// @@ -85,6 +82,11 @@ namespace Tgstation.Server.Host.Components.Compiler /// readonly ILogger logger; + /// + /// If a compile job is running + /// + bool compiling; + /// /// Construct /// @@ -241,7 +243,7 @@ namespace Tgstation.Server.Host.Components.Compiler } /// - public async Task Compile(Models.RevisionInformation revisionInformation, DreamMakerSettings dreamMakerSettings, DreamDaemonSecurity securityLevel, uint apiValidateTimeout, IRepository repository, CancellationToken cancellationToken) + public async Task Compile(Models.RevisionInformation revisionInformation, Api.Models.DreamMaker dreamMakerSettings, DreamDaemonSecurity securityLevel, uint apiValidateTimeout, IRepository repository, CancellationToken cancellationToken) { if (revisionInformation == null) throw new ArgumentNullException(nameof(revisionInformation)); @@ -268,10 +270,9 @@ namespace Tgstation.Server.Host.Components.Compiler lock (this) { - if (Status != CompilerStatus.Idle) - throw new JobException("There is already a compile in progress!"); - - Status = CompilerStatus.Copying; + if (compiling) + throw new JobException("There is already a compile job in progress!"); + compiling = true; } try @@ -302,7 +303,6 @@ namespace Tgstation.Server.Host.Components.Compiler async Task CleanupFailedCompile(bool cancelled) { logger.LogTrace("Cleaning compile directory..."); - Status = CompilerStatus.Cleanup; var chatTask = chat.SendUpdateMessage(cancelled ? "Deploy cancelled!" : "Deploy failed!", cancellationToken); try { @@ -330,13 +330,11 @@ namespace Tgstation.Server.Host.Components.Compiler using (repository) await repository.CopyTo(fullDirA, cancellationToken).ConfigureAwait(false); - Status = CompilerStatus.PreCompile; - + //run precompile scripts var resolvedGameDirectory = ioManager.ResolvePath(ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName)); await eventConsumer.HandleEvent(EventType.CompileStart, new List { resolvedGameDirectory, repoOrigin }, cancellationToken).ConfigureAwait(false); - Status = CompilerStatus.Modifying; - + //determine the dme if (job.DmeName == null) { logger.LogTrace("Searching for available .dmes..."); @@ -351,20 +349,12 @@ namespace Tgstation.Server.Host.Components.Compiler await ModifyDme(job, cancellationToken).ConfigureAwait(false); - Status = CompilerStatus.Compiling; - //run compiler, verify api job.ByondVersion = byondLock.Version.ToString(); var exitCode = await RunDreamMaker(byondLock.DreamMakerPath, job, cancellationToken).ConfigureAwait(false); - var apiValidated = false; - if (exitCode == 0) - { - Status = CompilerStatus.Verifying; - - apiValidated = await VerifyApi(apiValidateTimeout, securityLevel, job, byondLock, dreamMakerSettings.ApiValidationPort.Value, cancellationToken).ConfigureAwait(false); - } + var apiValidated = exitCode == 0 && await VerifyApi(apiValidateTimeout, securityLevel, job, byondLock, dreamMakerSettings.ApiValidationPort.Value, cancellationToken).ConfigureAwait(false); if (!apiValidated) { @@ -374,17 +364,14 @@ namespace Tgstation.Server.Host.Components.Compiler } logger.LogTrace("Running post compile event..."); - Status = CompilerStatus.PostCompile; await eventConsumer.HandleEvent(EventType.CompileComplete, new List { ioManager.ResolvePath(ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName)) }, cancellationToken).ConfigureAwait(false); logger.LogTrace("Duplicating compiled game..."); - Status = CompilerStatus.Duplicating; //duplicate the dmb et al await ioManager.CopyDirectory(dirA, dirB, null, cancellationToken).ConfigureAwait(false); logger.LogTrace("Applying static game file symlinks..."); - Status = CompilerStatus.Symlinking; //symlink in the static data var symATask = configuration.SymlinkStaticFilesTo(fullDirA, cancellationToken); @@ -411,7 +398,7 @@ namespace Tgstation.Server.Host.Components.Compiler } finally { - Status = CompilerStatus.Idle; + compiling = false; } } } diff --git a/src/Tgstation.Server.Host/Components/Compiler/IDreamMaker.cs b/src/Tgstation.Server.Host/Components/Compiler/IDreamMaker.cs index c2d1f3fb6c..dce21e8194 100644 --- a/src/Tgstation.Server.Host/Components/Compiler/IDreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/Compiler/IDreamMaker.cs @@ -1,7 +1,6 @@ using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; -using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Components.Repository; namespace Tgstation.Server.Host.Components.Compiler @@ -11,21 +10,16 @@ namespace Tgstation.Server.Host.Components.Compiler /// public interface IDreamMaker { - /// - /// The of - /// - CompilerStatus Status { get; } - /// /// Starts a compile /// /// The being compiled from the - /// The for the compile + /// The for the compile /// The level allowed for API validation /// The time in seconds to wait while validating the API /// The to copy from /// The for the operation /// A resulting in the partially populated for the operation. In particular, note the field will only have it's field populated - Task Compile(Models.RevisionInformation revisionInformation, DreamMakerSettings dreamMakerSettings, DreamDaemonSecurity securityLevel, uint apiValidateTimeout, IRepository repository, CancellationToken cancellationToken); + Task Compile(Models.RevisionInformation revisionInformation, Api.Models.DreamMaker dreamMakerSettings, DreamDaemonSecurity securityLevel, uint apiValidateTimeout, IRepository repository, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs index 33b8549e47..823a3700da 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs @@ -55,7 +55,6 @@ namespace Tgstation.Server.Host.Controllers return Json(new Api.Models.DreamMaker { ProjectName = dreamMakerSettings.ProjectName, - Status = instance.DreamMaker.Status, ApiValidationPort = dreamMakerSettings.ApiValidationPort }); } diff --git a/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs b/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs index 81f8312926..6520d92f52 100644 --- a/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs +++ b/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs @@ -3,7 +3,7 @@ namespace Tgstation.Server.Host.Models { /// - public sealed class DreamMakerSettings : Api.Models.Internal.DreamMakerSettings + public sealed class DreamMakerSettings : Api.Models.DreamMaker { /// /// The row Id From df6285def9b88e04a70a839c590b9bb157e3a85d Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Thu, 13 Sep 2018 19:50:59 -0400 Subject: [PATCH 056/230] Bump client and API preview versions --- src/Tgstation.Server.Api/Tgstation.Server.Api.csproj | 2 +- src/Tgstation.Server.Client/Tgstation.Server.Client.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj index 6b76bb1d75..ea5523602b 100644 --- a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj +++ b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj @@ -17,7 +17,7 @@ 4.0.0.0 json web api tgstation-server tgstation ss13 byond Prototype release - 4.0.0.0-preview6002 + 4.0.0.0-preview6003 diff --git a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj index f5bbc82a26..5499a146d7 100644 --- a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj +++ b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj @@ -3,7 +3,7 @@ netstandard2.0 Full - 4.0.0.0-preview9104 + 4.0.0.0-preview9105 true Cyberboss /tg/station 13 From 4637d3013e3e8de3b33d78dc25af247af87c7b87 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Thu, 13 Sep 2018 20:16:25 -0400 Subject: [PATCH 057/230] Catch ALL exceptions in SetHighPriority, not just InvalidOperationExceptions --- src/Tgstation.Server.Host/Core/Process.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Core/Process.cs b/src/Tgstation.Server.Host/Core/Process.cs index cc6dd66cd7..7ea9434c74 100644 --- a/src/Tgstation.Server.Host/Core/Process.cs +++ b/src/Tgstation.Server.Host/Core/Process.cs @@ -103,7 +103,7 @@ namespace Tgstation.Server.Host.Core handle.PriorityClass = System.Diagnostics.ProcessPriorityClass.AboveNormal; logger.LogTrace("Set to above normal priority", handle.Id); } - catch (InvalidOperationException e) + catch (Exception e) { logger.LogWarning("Unable to raise process priority! Exception: {0}", e); } From 2948ad57481b9d212e6168837b6224b376d3b8d2 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 14 Sep 2018 09:48:00 -0400 Subject: [PATCH 058/230] Use VersionConverters on both sides of the pipeline --- src/Tgstation.Server.Client/ApiClient.cs | 4 +++- src/Tgstation.Server.Host/Core/Application.cs | 2 ++ tests/Tgstation.Server.Client.Tests/TestApiClient.cs | 8 +++++++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Client/ApiClient.cs b/src/Tgstation.Server.Client/ApiClient.cs index 90a24bd92e..b7df25aa7c 100644 --- a/src/Tgstation.Server.Client/ApiClient.cs +++ b/src/Tgstation.Server.Client/ApiClient.cs @@ -1,4 +1,5 @@ using Newtonsoft.Json; +using Newtonsoft.Json.Converters; using Newtonsoft.Json.Serialization; using System; using System.Collections.Generic; @@ -81,7 +82,8 @@ namespace Tgstation.Server.Client var serializerSettings = new JsonSerializerSettings { - ContractResolver = new CamelCasePropertyNamesContractResolver() + ContractResolver = new CamelCasePropertyNamesContractResolver(), + Converters = new[] { new VersionConverter() } }; if (body != null) diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index b0218e2dc4..5a97b6c573 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -12,6 +12,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; using Microsoft.IdentityModel.Tokens; using Newtonsoft.Json; +using Newtonsoft.Json.Converters; using System; using System.Globalization; using System.IdentityModel.Tokens.Jwt; @@ -139,6 +140,7 @@ namespace Tgstation.Server.Host.Core options.SerializerSettings.CheckAdditionalContent = true; options.SerializerSettings.MissingMemberHandling = MissingMemberHandling.Error; options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; + options.SerializerSettings.Converters = new[] { new VersionConverter() }; }); var databaseConfiguration = databaseConfigurationSection.Get(); diff --git a/tests/Tgstation.Server.Client.Tests/TestApiClient.cs b/tests/Tgstation.Server.Client.Tests/TestApiClient.cs index f93f6f2753..4882fae2cc 100644 --- a/tests/Tgstation.Server.Client.Tests/TestApiClient.cs +++ b/tests/Tgstation.Server.Client.Tests/TestApiClient.cs @@ -1,6 +1,8 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Serialization; using System; using System.Net; using System.Net.Http; @@ -23,7 +25,11 @@ namespace Tgstation.Server.Client.Tests Version = new Version(511, 1385) }; - var sampleJson = JsonConvert.SerializeObject(sample); + var sampleJson = JsonConvert.SerializeObject(sample, new JsonSerializerSettings + { + ContractResolver = new CamelCasePropertyNamesContractResolver(), + Converters = new[] { new VersionConverter() } + }); var response = new HttpResponseMessage(HttpStatusCode.OK) { From 16b66ec9db1f65ce3f3c4bea632a7149ad9fc3ea Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 14 Sep 2018 10:02:09 -0400 Subject: [PATCH 059/230] Adds some byond integration tests --- .../Instance/ByondTest.cs | 69 +++++++++++++++++++ .../Instance/InstanceTest.cs | 6 +- 2 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 tests/Tgstation.Server.Tests/Instance/ByondTest.cs diff --git a/tests/Tgstation.Server.Tests/Instance/ByondTest.cs b/tests/Tgstation.Server.Tests/Instance/ByondTest.cs new file mode 100644 index 0000000000..248a7c4f79 --- /dev/null +++ b/tests/Tgstation.Server.Tests/Instance/ByondTest.cs @@ -0,0 +1,69 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Client.Components; + +namespace Tgstation.Server.Tests.Instance +{ + sealed class ByondTest + { + readonly IByondClient byondClient; + readonly IJobsClient jobsClient; + + public ByondTest(IByondClient byondClient, IJobsClient jobsClient) + { + this.byondClient = byondClient ?? throw new ArgumentNullException(nameof(byondClient)); + this.jobsClient = jobsClient ?? throw new ArgumentNullException(nameof(jobsClient)); + } + + public async Task Run(CancellationToken cancellationToken) + { + await TestNoVersion(cancellationToken).ConfigureAwait(false); + await TestInstall511(cancellationToken).ConfigureAwait(false); + } + + async Task TestInstall511(CancellationToken cancellationToken) + { + var newModel = new Api.Models.Byond + { + Version = new Version(511, 1385) + }; + var test = await byondClient.SetActiveVersion(newModel, cancellationToken).ConfigureAwait(false); + Assert.IsNotNull(test.InstallJob); + Assert.IsNull(test.Version); + var job = test.InstallJob; + var maxWait = 60; //it's 10MB max give me a break + do + { + await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false); + job = await jobsClient.GetId(job, cancellationToken).ConfigureAwait(false); + --maxWait; + } + while (!job.StoppedAt.HasValue && maxWait > 0); + if (!job.StoppedAt.HasValue) + { + await jobsClient.Cancel(job, cancellationToken).ConfigureAwait(false); + Assert.Fail("Byond installation job timed out!"); + } + + if (job.ExceptionDetails != null) + Assert.Fail(job.ExceptionDetails); + + var currentShit = await byondClient.ActiveVersion(cancellationToken).ConfigureAwait(false); + Assert.AreEqual(newModel.Version, currentShit.Version); + } + + async Task TestNoVersion(CancellationToken cancellationToken) + { + var allVersionsTask = byondClient.InstalledVersions(cancellationToken); + var currentShit = await byondClient.ActiveVersion(cancellationToken).ConfigureAwait(false); + Assert.IsNotNull(currentShit); + Assert.IsNull(currentShit.InstallJob); + Assert.IsNull(currentShit.Version); + var otherShit = await allVersionsTask.ConfigureAwait(false); + Assert.IsNotNull(otherShit); + Assert.AreEqual(0, otherShit.Count); + } + } +} diff --git a/tests/Tgstation.Server.Tests/Instance/InstanceTest.cs b/tests/Tgstation.Server.Tests/Instance/InstanceTest.cs index cd65f015f2..8ca9076a30 100644 --- a/tests/Tgstation.Server.Tests/Instance/InstanceTest.cs +++ b/tests/Tgstation.Server.Tests/Instance/InstanceTest.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Text; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Client.Components; @@ -18,8 +16,12 @@ namespace Tgstation.Server.Tests.Instance public async Task RunTests(CancellationToken cancellationToken) { + var byondTests = new ByondTest(instanceClient.Byond, instanceClient.Jobs); var configTests = new ConfigurationTest(instanceClient.Configuration, instanceClient.Metadata); + + var byondTest = byondTests.Run(cancellationToken); await configTests.Run(cancellationToken).ConfigureAwait(false); + await byondTest.ConfigureAwait(false); } } } From 05a0f60bb861664f936f7ddd290eb7f7d14fefca Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 14 Sep 2018 10:21:41 -0400 Subject: [PATCH 060/230] Minor touch of code cleanup --- src/Tgstation.Server.Host/Core/Application.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index b0218e2dc4..7d263ec110 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -149,17 +149,21 @@ namespace Tgstation.Server.Host.Core builder.EnableSensitiveDataLogging(); }; + void AddTypedContext() where TContext : DatabaseContext + { + services.AddDbContext(ConfigureDatabase); + services.AddScoped(x => x.GetRequiredService()); + } + var dbType = databaseConfiguration?.DatabaseType; - switch (dbType) + switch (databaseConfiguration?.DatabaseType) { case DatabaseType.MySql: case DatabaseType.MariaDB: - services.AddDbContext(ConfigureDatabase); - services.AddScoped(x => x.GetRequiredService()); + AddTypedContext(); break; case DatabaseType.SqlServer: - services.AddDbContext(ConfigureDatabase); - services.AddScoped(x => x.GetRequiredService()); + AddTypedContext(); break; default: throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid {0}: {1}!", nameof(DatabaseType), dbType)); From 651aadb98b2ee01d45c45b53c21de07e9a379010 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 14 Sep 2018 10:25:43 -0400 Subject: [PATCH 061/230] Update most nuget packages --- .../Tgstation.Server.Host.csproj | 12 ++++++------ .../Tgstation.Server.Api.Tests.csproj | 6 +++--- .../Tgstation.Server.Client.Tests.csproj | 6 +++--- .../Tgstation.Server.Host.Console.Tests.csproj | 8 ++++---- .../Tgstation.Server.Host.Tests.csproj | 8 ++++---- .../Tgstation.Server.Host.Watchdog.Tests.csproj | 8 ++++---- .../Tgstation.Server.Tests.csproj | 6 +++--- 7 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index fe16fe434e..995a9d2978 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -35,13 +35,13 @@ all compile; build; native; contentfiles; analyzers - - - - + + + + - - + + diff --git a/tests/Tgstation.Server.Api.Tests/Tgstation.Server.Api.Tests.csproj b/tests/Tgstation.Server.Api.Tests/Tgstation.Server.Api.Tests.csproj index e4e77805e2..68b24d8e0d 100644 --- a/tests/Tgstation.Server.Api.Tests/Tgstation.Server.Api.Tests.csproj +++ b/tests/Tgstation.Server.Api.Tests/Tgstation.Server.Api.Tests.csproj @@ -8,9 +8,9 @@ - - - + + + diff --git a/tests/Tgstation.Server.Client.Tests/Tgstation.Server.Client.Tests.csproj b/tests/Tgstation.Server.Client.Tests/Tgstation.Server.Client.Tests.csproj index caacb5743a..2618872bf7 100644 --- a/tests/Tgstation.Server.Client.Tests/Tgstation.Server.Client.Tests.csproj +++ b/tests/Tgstation.Server.Client.Tests/Tgstation.Server.Client.Tests.csproj @@ -8,9 +8,9 @@ - - - + + + diff --git a/tests/Tgstation.Server.Host.Console.Tests/Tgstation.Server.Host.Console.Tests.csproj b/tests/Tgstation.Server.Host.Console.Tests/Tgstation.Server.Host.Console.Tests.csproj index 2e6e921228..9c47b270e9 100644 --- a/tests/Tgstation.Server.Host.Console.Tests/Tgstation.Server.Host.Console.Tests.csproj +++ b/tests/Tgstation.Server.Host.Console.Tests/Tgstation.Server.Host.Console.Tests.csproj @@ -8,10 +8,10 @@ - - - - + + + + diff --git a/tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj b/tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj index 6481ec2802..18580c143d 100644 --- a/tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj +++ b/tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj @@ -8,10 +8,10 @@ - - - - + + + + diff --git a/tests/Tgstation.Server.Host.Watchdog.Tests/Tgstation.Server.Host.Watchdog.Tests.csproj b/tests/Tgstation.Server.Host.Watchdog.Tests/Tgstation.Server.Host.Watchdog.Tests.csproj index d69d3cbdf8..472ff291ce 100644 --- a/tests/Tgstation.Server.Host.Watchdog.Tests/Tgstation.Server.Host.Watchdog.Tests.csproj +++ b/tests/Tgstation.Server.Host.Watchdog.Tests/Tgstation.Server.Host.Watchdog.Tests.csproj @@ -18,10 +18,10 @@ - - - - + + + + diff --git a/tests/Tgstation.Server.Tests/Tgstation.Server.Tests.csproj b/tests/Tgstation.Server.Tests/Tgstation.Server.Tests.csproj index 1a89f581ca..53e7e98c4d 100644 --- a/tests/Tgstation.Server.Tests/Tgstation.Server.Tests.csproj +++ b/tests/Tgstation.Server.Tests/Tgstation.Server.Tests.csproj @@ -15,9 +15,9 @@ - - - + + + From 08494d83d95dfc45fcdb31fcec160181da75176d Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 14 Sep 2018 10:30:28 -0400 Subject: [PATCH 062/230] Migrate service project to PackageReferences --- ...Tgstation.Server.Host.Service.Tests.csproj | 96 +++---------------- .../packages.config | 26 ----- 2 files changed, 14 insertions(+), 108 deletions(-) delete mode 100644 tests/Tgstation.Server.Host.Service.Tests/packages.config diff --git a/tests/Tgstation.Server.Host.Service.Tests/Tgstation.Server.Host.Service.Tests.csproj b/tests/Tgstation.Server.Host.Service.Tests/Tgstation.Server.Host.Service.Tests.csproj index 6baddb56cb..b2b93b20f3 100644 --- a/tests/Tgstation.Server.Host.Service.Tests/Tgstation.Server.Host.Service.Tests.csproj +++ b/tests/Tgstation.Server.Host.Service.Tests/Tgstation.Server.Host.Service.Tests.csproj @@ -1,6 +1,5 @@  - Debug AnyCPU @@ -40,94 +39,21 @@ 7.1 - - ..\..\packages\Castle.Core.4.2.1\lib\net45\Castle.Core.dll - - - ..\..\packages\Microsoft.Extensions.Configuration.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Configuration.dll - - - ..\..\packages\Microsoft.Extensions.Configuration.Abstractions.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Configuration.Abstractions.dll - - - ..\..\packages\Microsoft.Extensions.Configuration.Binder.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Configuration.Binder.dll - - - ..\..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.2.1.1\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll - - - ..\..\packages\Microsoft.Extensions.Logging.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Logging.dll - - - ..\..\packages\Microsoft.Extensions.Logging.Abstractions.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Logging.Abstractions.dll - - - ..\..\packages\Microsoft.Extensions.Logging.EventLog.2.1.1\lib\net461\Microsoft.Extensions.Logging.EventLog.dll - - - ..\..\packages\Microsoft.Extensions.Options.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Options.dll - - - ..\..\packages\Microsoft.Extensions.Primitives.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Primitives.dll - - - ..\..\packages\MSTest.TestFramework.1.2.1\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll - - - ..\..\packages\MSTest.TestFramework.1.2.1\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll - - - ..\..\packages\Moq.4.8.2\lib\net45\Moq.dll - - - ..\..\packages\System.Buffers.4.4.0\lib\netstandard2.0\System.Buffers.dll - - - ..\..\packages\System.Diagnostics.EventLog.4.5.0\lib\net461\System.Diagnostics.EventLog.dll - - - ..\..\packages\System.Memory.4.5.1\lib\netstandard2.0\System.Memory.dll - - - ..\..\packages\System.Numerics.Vectors.4.4.0\lib\net46\System.Numerics.Vectors.dll - - - ..\..\packages\System.Runtime.CompilerServices.Unsafe.4.5.1\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll - - - ..\..\packages\System.Security.AccessControl.4.5.0\lib\net461\System.Security.AccessControl.dll - - - ..\..\packages\System.Security.Permissions.4.5.0\lib\net461\System.Security.Permissions.dll - - - ..\..\packages\System.Security.Principal.Windows.4.5.0\lib\net461\System.Security.Principal.Windows.dll - - - ..\..\packages\System.Threading.Tasks.Extensions.4.3.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll - - - ..\..\packages\System.ValueTuple.4.4.0\lib\net47\System.ValueTuple.dll - True - - - - {29927416-3b78-49a7-a560-5ccaa638b6b4} @@ -138,14 +64,20 @@ Tgstation.Server.Host.Watchdog + + + 2.1.1 + + + 4.10.0 + + + 1.3.2 + + + 1.3.2 + + - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - \ No newline at end of file diff --git a/tests/Tgstation.Server.Host.Service.Tests/packages.config b/tests/Tgstation.Server.Host.Service.Tests/packages.config deleted file mode 100644 index 8a479af186..0000000000 --- a/tests/Tgstation.Server.Host.Service.Tests/packages.config +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From 52994030cb9b17cfe87693b7d5174bf97d46bcee Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 14 Sep 2018 10:36:04 -0400 Subject: [PATCH 063/230] Exclude migrations from code coverage --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 4874f5497a..a44341a0dd 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -43,7 +43,7 @@ test_script: - OpenCover.Console.exe -returntargetcode -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx /p:DebugType=full tests/Tgstation.Server.Client.Tests/Tgstation.Server.Client.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Client.Tests*]*" -output:".\client_coverage.xml" -oldstyle - ps: $wc = New-Object 'System.Net.WebClient' - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\tests\Tgstation.Server.Client.Tests\TestResults\results.trx)) - - OpenCover.Console.exe -returntargetcode -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx /p:DebugType=full tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Host.Tests*]*" -output:".\host_coverage.xml" -oldstyle + - OpenCover.Console.exe -returntargetcode -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx /p:DebugType=full tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Host.Tests*]* -[Tgstation.Server.Host]Tgstation.Server.Host.Models.Migrations*" -output:".\host_coverage.xml" -oldstyle - ps: $wc = New-Object 'System.Net.WebClient' - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\tests\Tgstation.Server.Host.Tests\TestResults\results.trx)) - OpenCover.Console.exe -returntargetcode -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx /p:DebugType=full tests/Tgstation.Server.Host.Console.Tests/Tgstation.Server.Host.Console.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Host.Console.Tests*]*" -output:".\console_coverage.xml" -oldstyle @@ -57,7 +57,7 @@ test_script: - OpenCover.Console.exe -returntargetcode -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx /p:DebugType=full tests/Tgstation.Server.Host.Watchdog.Tests/Tgstation.Server.Host.Watchdog.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Host.Watchdog.Tests*]*" -output:".\watchdog_coverage.xml" -oldstyle - ps: $wc = New-Object 'System.Net.WebClient' - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\tests\Tgstation.Server.Host.Watchdog.Tests\TestResults\results.trx)) - - OpenCover.Console.exe -returntargetcode -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx /p:DebugType=full tests/Tgstation.Server.Tests/Tgstation.Server.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Tests*]*" -output:".\server_coverage.xml" -oldstyle + - OpenCover.Console.exe -returntargetcode -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx /p:DebugType=full tests/Tgstation.Server.Tests/Tgstation.Server.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Tests*]* -[Tgstation.Server.Host]Tgstation.Server.Host.Models.Migrations..*" -output:".\server_coverage.xml" -oldstyle - ps: $wc = New-Object 'System.Net.WebClient' - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\tests\Tgstation.Server.Tests\TestResults\results.trx)) after_test: From 6623c26cdfdee72f58d1f694c7912351eab2f4e6 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 14 Sep 2018 10:54:43 -0400 Subject: [PATCH 065/230] Attempt to add linux code coverage --- build/test_core.sh | 49 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/build/test_core.sh b/build/test_core.sh index 849726a149..23b7d2e23a 100755 --- a/build/test_core.sh +++ b/build/test_core.sh @@ -1,29 +1,62 @@ #!/bin/bash set -e +OpenCover.Console.exe -returntargetcode -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx /p:DebugType=full tests/Tgstation.Server.Api.Tests/Tgstation.Server.Api.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Api.Tests*]*" -output:".\api_coverage.xml" -oldstyle + - ps: $wc = New-Object 'System.Net.WebClient' + - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\tests\Tgstation.Server.Api.Tests\TestResults\results.trx)) + - OpenCover.Console.exe -returntargetcode -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx /p:DebugType=full tests/Tgstation.Server.Client.Tests/Tgstation.Server.Client.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Client.Tests*]*" -output:".\client_coverage.xml" -oldstyle + - ps: $wc = New-Object 'System.Net.WebClient' + - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\tests\Tgstation.Server.Client.Tests\TestResults\results.trx)) + - OpenCover.Console.exe -returntargetcode -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx /p:DebugType=full tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Host.Tests*]* -[Tgstation.Server.Host]Tgstation.Server.Host.Models.Migrations*" -output:".\host_coverage.xml" -oldstyle + - ps: $wc = New-Object 'System.Net.WebClient' + - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\tests\Tgstation.Server.Host.Tests\TestResults\results.trx)) + - OpenCover.Console.exe -returntargetcode -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx /p:DebugType=full tests/Tgstation.Server.Host.Console.Tests/Tgstation.Server.Host.Console.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Host.Console.Tests*]*" -output:".\console_coverage.xml" -oldstyle + - ps: $wc = New-Object 'System.Net.WebClient' + - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\tests\Tgstation.Server.Host.Console.Tests\TestResults\results.trx)) + - set path=%ProgramFiles(x86)%\Microsoft Visual Studio\2017\TestAgent\Common7\IDE\CommonExtensions\Microsoft\TestWindow;%path% + - copy "%ProgramFiles(x86)%\Microsoft Visual Studio\2017\Community\Common7\IDE\CommonExtensions\Microsoft\TestWindow\Extensions\appveyor.*" "%ProgramFiles(x86)%\Microsoft Visual Studio\2017\TestAgent\Common7\IDE\CommonExtensions\Microsoft\TestWindow\Extensions" /y + - vstest.console /logger:trx;LogFileName=results.trx "tests\Tgstation.Server.Host.Service.Tests\bin\%CONFIGURATION%\Tgstation.Server.Host.Service.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 + - ps: $wc = New-Object 'System.Net.WebClient' + - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\TestResults\results.trx)) + - OpenCover.Console.exe -returntargetcode -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx /p:DebugType=full tests/Tgstation.Server.Host.Watchdog.Tests/Tgstation.Server.Host.Watchdog.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Host.Watchdog.Tests*]*" -output:".\watchdog_coverage.xml" -oldstyle + - ps: $wc = New-Object 'System.Net.WebClient' + - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\tests\Tgstation.Server.Host.Watchdog.Tests\TestResults\results.trx)) + - OpenCover.Console.exe -returntargetcode -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx /p:DebugType=full tests/Tgstation.Server.Tests/Tgstation.Server.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Tests*]* -[Tgstation.Server.Host]Tgstation.Server.Host.Models.Migrations..*" -output:".\server_coverage.xml" -oldstyle + +dotnet tool install --global coverlet.console + +mkdir TestResults + cd tests/Tgstation.Server.Api.Tests -dotnet test -c $CONFIG +coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Api.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG" --format opencover --output "../../TestResults/api.xml" cd ../Tgstation.Server.Client.Tests -dotnet test -c $CONFIG +coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Client.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG" --format opencover --output "../../TestResults/client.xml" cd ../Tgstation.Server.Host.Tests -dotnet test -c $CONFIG +coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Host.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG" --format opencover --output "../../TestResults/host.xml" cd ../Tgstation.Server.Host.Watchdog.Tests -dotnet test -c $CONFIG +coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Host.Watchdog.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG" --format opencover --output "../../TestResults/watchdog.xml" cd ../Tgstation.Server.Host.Console.Tests -dotnet test -c $CONFIG - -cd ../Tgstation.Server.Tests +coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Host.Console.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG" --format opencover --output "../../TestResults/console.xml" export TGS4_TEST_DATABASE_TYPE=MySql export TGS4_TEST_CONNECTION_STRING="server=127.0.0.1;uid=root;pwd=;database=tgs_test" #token set in CI settings -dotnet test -c $CONFIG +coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG" --format opencover --output "../../TestResults/server.xml" + +cd ../../TestResults + +bash <(curl -s https://codecov.io/bash) -f api.xml -F unittests +bash <(curl -s https://codecov.io/bash) -f client.xml -F unittests +bash <(curl -s https://codecov.io/bash) -f host.xml -F unittests +bash <(curl -s https://codecov.io/bash) -f watchdog.xml -F unittests +bash <(curl -s https://codecov.io/bash) -f console.xml -F unittests +bash <(curl -s https://codecov.io/bash) -f server.xml -F integration From 8c14c1f52c703183696fa982a8c89089db29568c Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 14 Sep 2018 10:59:54 -0400 Subject: [PATCH 066/230] Add Linux coverage exclusions --- build/test_core.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/build/test_core.sh b/build/test_core.sh index 23b7d2e23a..a20c267896 100755 --- a/build/test_core.sh +++ b/build/test_core.sh @@ -29,28 +29,28 @@ mkdir TestResults cd tests/Tgstation.Server.Api.Tests -coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Api.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG" --format opencover --output "../../TestResults/api.xml" +coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Api.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG" --format opencover --output "../../TestResults/api.xml" --exclude "[Tgstation.Server.Api.Tests*]*" cd ../Tgstation.Server.Client.Tests -coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Client.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG" --format opencover --output "../../TestResults/client.xml" +coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Client.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG" --format opencover --output "../../TestResults/client.xml" --exclude "[Tgstation.Server.Client.Tests*]*" cd ../Tgstation.Server.Host.Tests -coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Host.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG" --format opencover --output "../../TestResults/host.xml" +coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Host.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG" --format opencover --output "../../TestResults/host.xml" --exclude "[Tgstation.Server.Host.Tests*]*" --exclude "[Tgstation.Server.Host]Tgstation.Server.Host.Models.Migrations.*" cd ../Tgstation.Server.Host.Watchdog.Tests -coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Host.Watchdog.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG" --format opencover --output "../../TestResults/watchdog.xml" +coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Host.Watchdog.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG" --format opencover --output "../../TestResults/watchdog.xml" --exclude "[Tgstation.Server.Host.Watchdog.Tests*]*" cd ../Tgstation.Server.Host.Console.Tests -coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Host.Console.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG" --format opencover --output "../../TestResults/console.xml" +coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Host.Console.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG" --format opencover --output "../../TestResults/console.xml" --exclude "[Tgstation.Server.Host.Console.Tests*]*" export TGS4_TEST_DATABASE_TYPE=MySql export TGS4_TEST_CONNECTION_STRING="server=127.0.0.1;uid=root;pwd=;database=tgs_test" #token set in CI settings -coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG" --format opencover --output "../../TestResults/server.xml" +coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG" --format opencover --output "../../TestResults/server.xml" --exclude "[Tgstation.Server.Tests*]*" --exclude "[Tgstation.Server.Host]Tgstation.Server.Host.Models.Migrations.*" cd ../../TestResults From f2100345f48a0a74e03581fbb985a529856a01b3 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 14 Sep 2018 11:00:48 -0400 Subject: [PATCH 067/230] Remove leftovers --- build/test_core.sh | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/build/test_core.sh b/build/test_core.sh index a20c267896..47ecb47b38 100755 --- a/build/test_core.sh +++ b/build/test_core.sh @@ -1,28 +1,6 @@ #!/bin/bash set -e -OpenCover.Console.exe -returntargetcode -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx /p:DebugType=full tests/Tgstation.Server.Api.Tests/Tgstation.Server.Api.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Api.Tests*]*" -output:".\api_coverage.xml" -oldstyle - - ps: $wc = New-Object 'System.Net.WebClient' - - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\tests\Tgstation.Server.Api.Tests\TestResults\results.trx)) - - OpenCover.Console.exe -returntargetcode -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx /p:DebugType=full tests/Tgstation.Server.Client.Tests/Tgstation.Server.Client.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Client.Tests*]*" -output:".\client_coverage.xml" -oldstyle - - ps: $wc = New-Object 'System.Net.WebClient' - - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\tests\Tgstation.Server.Client.Tests\TestResults\results.trx)) - - OpenCover.Console.exe -returntargetcode -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx /p:DebugType=full tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Host.Tests*]* -[Tgstation.Server.Host]Tgstation.Server.Host.Models.Migrations*" -output:".\host_coverage.xml" -oldstyle - - ps: $wc = New-Object 'System.Net.WebClient' - - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\tests\Tgstation.Server.Host.Tests\TestResults\results.trx)) - - OpenCover.Console.exe -returntargetcode -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx /p:DebugType=full tests/Tgstation.Server.Host.Console.Tests/Tgstation.Server.Host.Console.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Host.Console.Tests*]*" -output:".\console_coverage.xml" -oldstyle - - ps: $wc = New-Object 'System.Net.WebClient' - - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\tests\Tgstation.Server.Host.Console.Tests\TestResults\results.trx)) - - set path=%ProgramFiles(x86)%\Microsoft Visual Studio\2017\TestAgent\Common7\IDE\CommonExtensions\Microsoft\TestWindow;%path% - - copy "%ProgramFiles(x86)%\Microsoft Visual Studio\2017\Community\Common7\IDE\CommonExtensions\Microsoft\TestWindow\Extensions\appveyor.*" "%ProgramFiles(x86)%\Microsoft Visual Studio\2017\TestAgent\Common7\IDE\CommonExtensions\Microsoft\TestWindow\Extensions" /y - - vstest.console /logger:trx;LogFileName=results.trx "tests\Tgstation.Server.Host.Service.Tests\bin\%CONFIGURATION%\Tgstation.Server.Host.Service.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 - - ps: $wc = New-Object 'System.Net.WebClient' - - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\TestResults\results.trx)) - - OpenCover.Console.exe -returntargetcode -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx /p:DebugType=full tests/Tgstation.Server.Host.Watchdog.Tests/Tgstation.Server.Host.Watchdog.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Host.Watchdog.Tests*]*" -output:".\watchdog_coverage.xml" -oldstyle - - ps: $wc = New-Object 'System.Net.WebClient' - - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\tests\Tgstation.Server.Host.Watchdog.Tests\TestResults\results.trx)) - - OpenCover.Console.exe -returntargetcode -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx /p:DebugType=full tests/Tgstation.Server.Tests/Tgstation.Server.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Tests*]* -[Tgstation.Server.Host]Tgstation.Server.Host.Models.Migrations..*" -output:".\server_coverage.xml" -oldstyle - dotnet tool install --global coverlet.console mkdir TestResults From 8625ffa17192f1769799e7be1927c5a50aed6fe4 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 14 Sep 2018 11:05:03 -0400 Subject: [PATCH 068/230] Try to fix linux coverage --- build/test_core.sh | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/build/test_core.sh b/build/test_core.sh index 47ecb47b38..20b49c5715 100755 --- a/build/test_core.sh +++ b/build/test_core.sh @@ -7,28 +7,30 @@ mkdir TestResults cd tests/Tgstation.Server.Api.Tests -coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Api.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG" --format opencover --output "../../TestResults/api.xml" --exclude "[Tgstation.Server.Api.Tests*]*" +ls -al $HOME/.dotnet/tools + +$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Api.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG" --format opencover --output "../../TestResults/api.xml" --exclude "[Tgstation.Server.Api.Tests*]*" cd ../Tgstation.Server.Client.Tests -coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Client.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG" --format opencover --output "../../TestResults/client.xml" --exclude "[Tgstation.Server.Client.Tests*]*" +$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Client.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG" --format opencover --output "../../TestResults/client.xml" --exclude "[Tgstation.Server.Client.Tests*]*" cd ../Tgstation.Server.Host.Tests -coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Host.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG" --format opencover --output "../../TestResults/host.xml" --exclude "[Tgstation.Server.Host.Tests*]*" --exclude "[Tgstation.Server.Host]Tgstation.Server.Host.Models.Migrations.*" +$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Host.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG" --format opencover --output "../../TestResults/host.xml" --exclude "[Tgstation.Server.Host.Tests*]*" --exclude "[Tgstation.Server.Host]Tgstation.Server.Host.Models.Migrations.*" cd ../Tgstation.Server.Host.Watchdog.Tests -coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Host.Watchdog.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG" --format opencover --output "../../TestResults/watchdog.xml" --exclude "[Tgstation.Server.Host.Watchdog.Tests*]*" +$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Host.Watchdog.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG" --format opencover --output "../../TestResults/watchdog.xml" --exclude "[Tgstation.Server.Host.Watchdog.Tests*]*" cd ../Tgstation.Server.Host.Console.Tests -coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Host.Console.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG" --format opencover --output "../../TestResults/console.xml" --exclude "[Tgstation.Server.Host.Console.Tests*]*" +$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Host.Console.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG" --format opencover --output "../../TestResults/console.xml" --exclude "[Tgstation.Server.Host.Console.Tests*]*" export TGS4_TEST_DATABASE_TYPE=MySql export TGS4_TEST_CONNECTION_STRING="server=127.0.0.1;uid=root;pwd=;database=tgs_test" #token set in CI settings -coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG" --format opencover --output "../../TestResults/server.xml" --exclude "[Tgstation.Server.Tests*]*" --exclude "[Tgstation.Server.Host]Tgstation.Server.Host.Models.Migrations.*" +$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG" --format opencover --output "../../TestResults/server.xml" --exclude "[Tgstation.Server.Tests*]*" --exclude "[Tgstation.Server.Host]Tgstation.Server.Host.Models.Migrations.*" cd ../../TestResults From b329311ed2eb6f1634f7c3c368d7c1c33674966b Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 14 Sep 2018 11:08:29 -0400 Subject: [PATCH 069/230] Remove debug listing, use build then --no-build --- build/test_core.sh | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/build/test_core.sh b/build/test_core.sh index 20b49c5715..0366440e30 100755 --- a/build/test_core.sh +++ b/build/test_core.sh @@ -7,30 +7,34 @@ mkdir TestResults cd tests/Tgstation.Server.Api.Tests -ls -al $HOME/.dotnet/tools - -$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Api.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG" --format opencover --output "../../TestResults/api.xml" --exclude "[Tgstation.Server.Api.Tests*]*" +dotnet build -c $CONFIG +$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Api.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG --no-build" --format opencover --output "../../TestResults/api.xml" --exclude "[Tgstation.Server.Api.Tests*]*" cd ../Tgstation.Server.Client.Tests -$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Client.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG" --format opencover --output "../../TestResults/client.xml" --exclude "[Tgstation.Server.Client.Tests*]*" +dotnet build -c $CONFIG +$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Client.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG --no-build" --format opencover --output "../../TestResults/client.xml" --exclude "[Tgstation.Server.Client.Tests*]*" cd ../Tgstation.Server.Host.Tests -$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Host.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG" --format opencover --output "../../TestResults/host.xml" --exclude "[Tgstation.Server.Host.Tests*]*" --exclude "[Tgstation.Server.Host]Tgstation.Server.Host.Models.Migrations.*" +dotnet build -c $CONFIG +$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Host.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG --no-build" --format opencover --output "../../TestResults/host.xml" --exclude "[Tgstation.Server.Host.Tests*]*" --exclude "[Tgstation.Server.Host]Tgstation.Server.Host.Models.Migrations.*" cd ../Tgstation.Server.Host.Watchdog.Tests -$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Host.Watchdog.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG" --format opencover --output "../../TestResults/watchdog.xml" --exclude "[Tgstation.Server.Host.Watchdog.Tests*]*" +dotnet build -c $CONFIG +$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Host.Watchdog.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG --no-build" --format opencover --output "../../TestResults/watchdog.xml" --exclude "[Tgstation.Server.Host.Watchdog.Tests*]*" cd ../Tgstation.Server.Host.Console.Tests -$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Host.Console.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG" --format opencover --output "../../TestResults/console.xml" --exclude "[Tgstation.Server.Host.Console.Tests*]*" +dotnet build -c $CONFIG +$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Host.Console.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG --no-build" --format opencover --output "../../TestResults/console.xml" --exclude "[Tgstation.Server.Host.Console.Tests*]*" export TGS4_TEST_DATABASE_TYPE=MySql export TGS4_TEST_CONNECTION_STRING="server=127.0.0.1;uid=root;pwd=;database=tgs_test" #token set in CI settings -$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG" --format opencover --output "../../TestResults/server.xml" --exclude "[Tgstation.Server.Tests*]*" --exclude "[Tgstation.Server.Host]Tgstation.Server.Host.Models.Migrations.*" +dotnet build -c $CONFIG +$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG --no-build" --format opencover --output "../../TestResults/server.xml" --exclude "[Tgstation.Server.Tests*]*" --exclude "[Tgstation.Server.Host]Tgstation.Server.Host.Models.Migrations.*" cd ../../TestResults From 982700c2de805854abdb55a007c5ede88c0a3723 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 14 Sep 2018 11:17:06 -0400 Subject: [PATCH 070/230] Set inclusion filters for Linux code coverage --- build/test_core.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/build/test_core.sh b/build/test_core.sh index 0366440e30..7043e0dde6 100755 --- a/build/test_core.sh +++ b/build/test_core.sh @@ -8,33 +8,33 @@ mkdir TestResults cd tests/Tgstation.Server.Api.Tests dotnet build -c $CONFIG -$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Api.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG --no-build" --format opencover --output "../../TestResults/api.xml" --exclude "[Tgstation.Server.Api.Tests*]*" +$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Api.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG --no-build" --format opencover --output "../../TestResults/api.xml" --include "[Tgstation.Server*]*" --exclude "[Tgstation.Server.Api.Tests*]*" cd ../Tgstation.Server.Client.Tests dotnet build -c $CONFIG -$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Client.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG --no-build" --format opencover --output "../../TestResults/client.xml" --exclude "[Tgstation.Server.Client.Tests*]*" +$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Client.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG --no-build" --format opencover --output "../../TestResults/client.xml" --include "[Tgstation.Server*]*" --exclude "[Tgstation.Server.Client.Tests*]*" cd ../Tgstation.Server.Host.Tests dotnet build -c $CONFIG -$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Host.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG --no-build" --format opencover --output "../../TestResults/host.xml" --exclude "[Tgstation.Server.Host.Tests*]*" --exclude "[Tgstation.Server.Host]Tgstation.Server.Host.Models.Migrations.*" +$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Host.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG --no-build" --format opencover --output "../../TestResults/host.xml" --include "[Tgstation.Server*]*" --exclude "[Tgstation.Server.Host.Tests*]*" --exclude "[Tgstation.Server.Host]Tgstation.Server.Host.Models.Migrations.*" cd ../Tgstation.Server.Host.Watchdog.Tests dotnet build -c $CONFIG -$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Host.Watchdog.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG --no-build" --format opencover --output "../../TestResults/watchdog.xml" --exclude "[Tgstation.Server.Host.Watchdog.Tests*]*" +$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Host.Watchdog.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG --no-build" --format opencover --output "../../TestResults/watchdog.xml" --include "[Tgstation.Server*]*" --exclude "[Tgstation.Server.Host.Watchdog.Tests*]*" cd ../Tgstation.Server.Host.Console.Tests dotnet build -c $CONFIG -$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Host.Console.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG --no-build" --format opencover --output "../../TestResults/console.xml" --exclude "[Tgstation.Server.Host.Console.Tests*]*" +$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Host.Console.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG --no-build" --format opencover --output "../../TestResults/console.xml" --include "[Tgstation.Server*]*" --exclude "[Tgstation.Server.Host.Console.Tests*]*" export TGS4_TEST_DATABASE_TYPE=MySql export TGS4_TEST_CONNECTION_STRING="server=127.0.0.1;uid=root;pwd=;database=tgs_test" #token set in CI settings dotnet build -c $CONFIG -$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG --no-build" --format opencover --output "../../TestResults/server.xml" --exclude "[Tgstation.Server.Tests*]*" --exclude "[Tgstation.Server.Host]Tgstation.Server.Host.Models.Migrations.*" +$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG --no-build" --format opencover --output "../../TestResults/server.xml" --include "[Tgstation.Server*]*" --exclude "[Tgstation.Server.Tests*]*" --exclude "[Tgstation.Server.Host]Tgstation.Server.Host.Models.Migrations.*" cd ../../TestResults From fbf475346ba99201f9eef467463c9ba4cd12b79b Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 14 Sep 2018 11:25:53 -0400 Subject: [PATCH 071/230] Add missing server tests back to travis --- build/test_core.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/build/test_core.sh b/build/test_core.sh index 7043e0dde6..69257a6a81 100755 --- a/build/test_core.sh +++ b/build/test_core.sh @@ -30,6 +30,7 @@ cd ../Tgstation.Server.Host.Console.Tests dotnet build -c $CONFIG $HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Host.Console.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG --no-build" --format opencover --output "../../TestResults/console.xml" --include "[Tgstation.Server*]*" --exclude "[Tgstation.Server.Host.Console.Tests*]*" +cd ../Tgstation.Server.Tests export TGS4_TEST_DATABASE_TYPE=MySql export TGS4_TEST_CONNECTION_STRING="server=127.0.0.1;uid=root;pwd=;database=tgs_test" #token set in CI settings From 579c6053de535090b8baa7222177890726a9f9c4 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 14 Sep 2018 12:51:35 -0400 Subject: [PATCH 072/230] Throw a JobException when there are issues downloading a BYOND version --- src/Tgstation.Server.Host/Components/Byond/ByondManager.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs b/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs index 638d6b612c..1623f115cb 100644 --- a/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs +++ b/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs @@ -1,7 +1,9 @@ using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; +using System.Net; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -123,6 +125,11 @@ namespace Tgstation.Server.Host.Components.Byond //make sure to do this last because this is what tells us we have a valid version in the future await ioManager.WriteAllBytes(ioManager.ConcatPath(versionKey, VersionFileName), Encoding.UTF8.GetBytes(version.ToString()), cancellationToken).ConfigureAwait(false); } + catch (WebException e) + { + //since the user can easily provide non-exitent version numbers, we'll turn this into a JobException + throw new JobException(String.Format(CultureInfo.InvariantCulture, "Error downloading BYOND version: {0}", e.Message)); + } catch (OperationCanceledException) { throw; From bad36be94b437b45c586b703bc54bbcb12531d2c Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 14 Sep 2018 17:14:32 -0400 Subject: [PATCH 073/230] Implement missing IDreamMakerClient functions --- .../Components/DreamMakerClient.cs | 12 ++++++++++-- .../Components/IDreamMakerClient.cs | 18 +++++++++++++++++- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/Tgstation.Server.Client/Components/DreamMakerClient.cs b/src/Tgstation.Server.Client/Components/DreamMakerClient.cs index 5e8d2af1b9..942d252812 100644 --- a/src/Tgstation.Server.Client/Components/DreamMakerClient.cs +++ b/src/Tgstation.Server.Client/Components/DreamMakerClient.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api; @@ -12,11 +13,12 @@ namespace Tgstation.Server.Client.Components /// /// The for the /// - private IApiClient apiClient; + readonly IApiClient apiClient; + /// /// The for the /// - private Instance instance; + readonly Instance instance; /// /// Construct a @@ -32,6 +34,12 @@ namespace Tgstation.Server.Client.Components /// public Task Compile(CancellationToken cancellationToken) => apiClient.Create(Routes.DreamMaker, instance.Id, cancellationToken); + /// + public Task GetCompileJob(CompileJob compileJob, CancellationToken cancellationToken) => apiClient.Read(Routes.SetID(Routes.DreamMaker, compileJob?.Id ?? throw new ArgumentNullException(nameof(compileJob))), instance.Id, cancellationToken); + + /// + public Task> GetJobIds(CancellationToken cancellationToken) => apiClient.Read>(Routes.List(Routes.DreamMaker), instance.Id, cancellationToken); + /// public Task Read(CancellationToken cancellationToken) => apiClient.Read(Routes.DreamMaker, instance.Id, cancellationToken); diff --git a/src/Tgstation.Server.Client/Components/IDreamMakerClient.cs b/src/Tgstation.Server.Client/Components/IDreamMakerClient.cs index dd18119024..011475d5a5 100644 --- a/src/Tgstation.Server.Client/Components/IDreamMakerClient.cs +++ b/src/Tgstation.Server.Client/Components/IDreamMakerClient.cs @@ -1,4 +1,5 @@ -using System.Threading; +using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; @@ -30,5 +31,20 @@ namespace Tgstation.Server.Client.Components /// The for the operation /// A resulting in the for the compile Task Compile(CancellationToken cancellationToken); + + /// + /// Gets the s of all s for the instance + /// + /// The for the operation + /// A resulting in a of s with only the field populated + Task> GetJobIds(CancellationToken cancellationToken); + + /// + /// Get a + /// + /// The to get + /// The for the operation + /// A resulting in the + Task GetCompileJob(CompileJob compileJob, CancellationToken cancellationToken); } } From 88ba49b8756a4c6af339129de8eb554269579a8e Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 14 Sep 2018 17:14:56 -0400 Subject: [PATCH 074/230] Bump client preview version --- src/Tgstation.Server.Client/Tgstation.Server.Client.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj index 5499a146d7..b3ad661fa0 100644 --- a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj +++ b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj @@ -3,7 +3,7 @@ netstandard2.0 Full - 4.0.0.0-preview9105 + 4.0.0.0-preview9106 true Cyberboss /tg/station 13 From bdd3557ec35caef1df8a5bbb3016330c1571108d Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 15 Sep 2018 11:58:58 -0400 Subject: [PATCH 076/230] Fix test merging Job Description grammar --- src/Tgstation.Server.Host/Controllers/RepositoryController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index fc916acae6..ce94498dab 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -403,7 +403,7 @@ namespace Tgstation.Server.Host.Controllers description != null ? String.Format(CultureInfo.InvariantCulture, "{0} and t", description) : "T", String.Join(", ", model.NewTestMerges.Select(x => String.Format(CultureInfo.InvariantCulture, "#{0}{1}", x.Number, - x.PullRequestRevision != null ? String.Format(CultureInfo.InvariantCulture, " {0}", x.PullRequestRevision.Substring(0, 7)) : String.Empty))), + x.PullRequestRevision != null ? String.Format(CultureInfo.InvariantCulture, "at {0}", x.PullRequestRevision.Substring(0, 7)) : String.Empty))), description != null ? String.Empty : " in repository"); if (description == null) From 86b59072737b897a58a54d7fd477fd47c5188b34 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 15 Sep 2018 13:05:53 -0400 Subject: [PATCH 077/230] Adds the PostComplete Job step --- src/Tgstation.Server.Host/Core/JobManager.cs | 52 ++++++++++++++------ src/Tgstation.Server.Host/Models/Job.cs | 13 ++++- 2 files changed, 50 insertions(+), 15 deletions(-) diff --git a/src/Tgstation.Server.Host/Core/JobManager.cs b/src/Tgstation.Server.Host/Core/JobManager.cs index 47ac244289..1876ecda4e 100644 --- a/src/Tgstation.Server.Host/Core/JobManager.cs +++ b/src/Tgstation.Server.Host/Core/JobManager.cs @@ -70,13 +70,35 @@ namespace Tgstation.Server.Host.Core /// The for the operation /// A representing the running operation async Task RunJob(Job job, Func operation, CancellationToken cancellationToken) - { + { try { using (var scope = serviceProvider.CreateScope()) { + async Task HandleExceptions(Task task) + { + try + { + await task.ConfigureAwait(false); + } + catch (OperationCanceledException) + { + logger.LogDebug("Job {0} cancelled!", job.Id); + job.Cancelled = true; + } + catch (Exception e) + { + job.ExceptionDetails = e is JobException ? e.Message : e.ToString(); + logger.LogDebug("Job {0} exited with error! Exception: {1}", job.Id, job.ExceptionDetails); + } + finally + { + job.StoppedAt = DateTimeOffset.Now; + } + } + IDatabaseContext databaseContext = null; - try + async Task RunJobInternal() { var oldJob = job; job = new Job { Id = oldJob.Id }; @@ -86,19 +108,21 @@ namespace Tgstation.Server.Host.Core await operation(job, scope.ServiceProvider, cancellationToken).ConfigureAwait(false); logger.LogDebug("Job {0} completed!", job.Id); - } - catch (OperationCanceledException) - { - logger.LogDebug("Job {0} cancelled!", job.Id); - job.Cancelled = true; - } - catch (Exception e) - { - job.ExceptionDetails = e is JobException ? e.Message : e.ToString(); - logger.LogDebug("Job {0} exited with error! Exception: {1}", job.Id, job.ExceptionDetails); - } - job.StoppedAt = DateTimeOffset.Now; + }; + + await HandleExceptions(RunJobInternal()).ConfigureAwait(false); + await databaseContext.Save(default).ConfigureAwait(false); + + bool JobErroredOrCancelled() => job.ExceptionDetails != null || job.Cancelled.Value; + + //ok so, now it's time for the post commit step if it exists + if (!JobErroredOrCancelled() && job.PostComplete != null) + { + await HandleExceptions(job.PostComplete(cancellationToken)).ConfigureAwait(false); + if (JobErroredOrCancelled()) + await databaseContext.Save(default).ConfigureAwait(false); + } } } finally diff --git a/src/Tgstation.Server.Host/Models/Job.cs b/src/Tgstation.Server.Host/Models/Job.cs index aeb31c0d0e..b601ecc46b 100644 --- a/src/Tgstation.Server.Host/Models/Job.cs +++ b/src/Tgstation.Server.Host/Models/Job.cs @@ -1,4 +1,8 @@ -using System.ComponentModel.DataAnnotations; +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Threading; +using System.Threading.Tasks; namespace Tgstation.Server.Host.Models { @@ -22,6 +26,13 @@ namespace Tgstation.Server.Host.Models [Required] public Instance Instance { get; set; } + /// + /// A to run after the job completes. This will not affect the time, unless it is cancelled or errors + /// + /// This should only be used where there are database dependencies that also rely on the Job itself completing A.K.A. manually initiated s + [NotMapped] + public Func PostComplete { get; set; } + /// /// Convert the to it's API form /// From a956ac239aef71de06f8eeb24bb6f201127a03f6 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 15 Sep 2018 13:15:45 -0400 Subject: [PATCH 078/230] Make DreamMakerController use the PostCompile system --- .../Controllers/DreamMakerController.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs index 823a3700da..746f04157b 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs @@ -189,10 +189,9 @@ namespace Tgstation.Server.Host.Controllers compileJob.Job = job; - databaseContext.CompileJobs.Add(compileJob); - await databaseContext.Save(cancellationToken).ConfigureAwait(false); + databaseContext.CompileJobs.Add(compileJob); //will be saved by job context - await instance.CompileJobConsumer.LoadCompileJob(compileJob, cancellationToken).ConfigureAwait(false); + job.PostComplete = ct => instance.CompileJobConsumer.LoadCompileJob(compileJob, ct); } } } From 3ab97ef6e1f78cc229307a100657863d3cc8e61c Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 15 Sep 2018 13:20:05 -0400 Subject: [PATCH 079/230] Inject IJobManager into the Instance component --- src/Tgstation.Server.Host/Components/Instance.cs | 9 ++++++++- .../Components/InstanceFactory.cs | 12 +++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index 21e7edc06d..46b5dc3541 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -48,6 +48,11 @@ namespace Tgstation.Server.Host.Components /// readonly IDmbFactory dmbFactory; + /// + /// The for the + /// + readonly IJobManager jobManager; + /// /// The for the /// @@ -80,8 +85,9 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// The value of + /// The value of /// The value of - public Instance(Api.Models.Instance metadata, IRepositoryManager repositoryManager, IByondManager byondManager, IDreamMaker dreamMaker, IWatchdog watchdog, IChat chat, StaticFiles.IConfiguration configuration, ICompileJobConsumer compileJobConsumer, IDatabaseContextFactory databaseContextFactory, IDmbFactory dmbFactory, ILogger logger) + public Instance(Api.Models.Instance metadata, IRepositoryManager repositoryManager, IByondManager byondManager, IDreamMaker dreamMaker, IWatchdog watchdog, IChat chat, StaticFiles.IConfiguration configuration, ICompileJobConsumer compileJobConsumer, IDatabaseContextFactory databaseContextFactory, IDmbFactory dmbFactory, IJobManager jobManager, ILogger logger) { this.metadata = metadata ?? throw new ArgumentNullException(nameof(metadata)); RepositoryManager = repositoryManager ?? throw new ArgumentNullException(nameof(repositoryManager)); @@ -93,6 +99,7 @@ namespace Tgstation.Server.Host.Components CompileJobConsumer = compileJobConsumer ?? throw new ArgumentNullException(nameof(compileJobConsumer)); this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); this.dmbFactory = dmbFactory ?? throw new ArgumentNullException(nameof(dmbFactory)); + this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); } diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index 11c9b9a197..35c0fd23df 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -8,7 +8,6 @@ using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Components.Chat.Commands; using Tgstation.Server.Host.Components.Compiler; using Tgstation.Server.Host.Components.Repository; -using Tgstation.Server.Host.Components.StaticFiles; using Tgstation.Server.Host.Components.Watchdog; using Tgstation.Server.Host.Core; using Tgstation.Server.Host.IO; @@ -89,6 +88,11 @@ namespace Tgstation.Server.Host.Components /// readonly IWatchdogFactory watchdogFactory; + /// + /// The for the + /// + readonly IJobManager jobManager; + /// /// Construct an /// @@ -106,7 +110,8 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// The value of - public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IByondTopicSender byondTopicSender, IServerControl serverUpdater, ICryptographySuite cryptographySuite, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IByondInstaller byondInstaller, IProviderFactory providerFactory, IProcessExecutor processExecutor, IPostWriteHandler postWriteHandler, IWatchdogFactory watchdogFactory) + /// The value of + public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IByondTopicSender byondTopicSender, IServerControl serverUpdater, ICryptographySuite cryptographySuite, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IByondInstaller byondInstaller, IProviderFactory providerFactory, IProcessExecutor processExecutor, IPostWriteHandler postWriteHandler, IWatchdogFactory watchdogFactory, IJobManager jobManager) { this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); @@ -122,6 +127,7 @@ namespace Tgstation.Server.Host.Components this.processExecutor = processExecutor ?? throw new ArgumentNullException(nameof(processExecutor)); this.postWriteHandler = postWriteHandler ?? throw new ArgumentNullException(nameof(postWriteHandler)); this.watchdogFactory = watchdogFactory ?? throw new ArgumentNullException(nameof(watchdogFactory)); + this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); } /// @@ -162,7 +168,7 @@ namespace Tgstation.Server.Host.Components { var dreamMaker = new DreamMaker(byond, gameIoManager, configuration, sessionControllerFactory, dmbFactory, application, eventConsumer, chat, processExecutor, watchdog, loggerFactory.CreateLogger()); - return new Instance(metadata.CloneMetadata(), repoManager, byond, dreamMaker, watchdog, chat, configuration, dmbFactory, databaseContextFactory, dmbFactory, loggerFactory.CreateLogger()); + return new Instance(metadata.CloneMetadata(), repoManager, byond, dreamMaker, watchdog, chat, configuration, dmbFactory, databaseContextFactory, dmbFactory, jobManager, loggerFactory.CreateLogger()); } catch { From a374651e38afc11552058ac47a9a329875919572 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 15 Sep 2018 13:56:29 -0400 Subject: [PATCH 080/230] Add IJobManager.WaitForCompletion --- src/Tgstation.Server.Host/Core/IJobManager.cs | 12 ++++++++++++ src/Tgstation.Server.Host/Core/JobManager.cs | 17 +++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/Tgstation.Server.Host/Core/IJobManager.cs b/src/Tgstation.Server.Host/Core/IJobManager.cs index 0d5a16842e..7389ce78d5 100644 --- a/src/Tgstation.Server.Host/Core/IJobManager.cs +++ b/src/Tgstation.Server.Host/Core/IJobManager.cs @@ -25,6 +25,18 @@ namespace Tgstation.Server.Host.Core /// A representing a running operation Task RegisterOperation(Job job, Func, CancellationToken, Task> operation, CancellationToken cancellationToken); + /// + /// Wait for a given to complete + /// + /// The to wait for + /// The to cancel the + /// A that will cancel the + /// The for the operation + /// A representing the +#pragma warning disable CA1068 // CancellationToken parameters must come last https://github.com/dotnet/roslyn-analyzers/issues/1816 + Task WaitForJobCompletion(Job job, User canceller, CancellationToken jobCancellationToken, CancellationToken cancellationToken); +#pragma warning restore CA1068 // CancellationToken parameters must come last + /// /// Cancels a give /// diff --git a/src/Tgstation.Server.Host/Core/JobManager.cs b/src/Tgstation.Server.Host/Core/JobManager.cs index 1876ecda4e..b9b8ba4b68 100644 --- a/src/Tgstation.Server.Host/Core/JobManager.cs +++ b/src/Tgstation.Server.Host/Core/JobManager.cs @@ -254,5 +254,22 @@ namespace Tgstation.Server.Host.Core return handler.Progress; } } + + /// + public async Task WaitForJobCompletion(Job job, User canceller, CancellationToken jobCancellationToken, CancellationToken cancellationToken) + { + JobHandler handler; + lock (this) + { + if (!jobs.TryGetValue(job.Id, out handler)) + return; + } + Task cancelTask = null; + using (jobCancellationToken.Register(() => cancelTask = CancelJob(job, canceller, true, cancellationToken))) + await handler.Wait(cancellationToken).ConfigureAwait(false); + + if (cancelTask != null) + await cancelTask.ConfigureAwait(false); + } } } From cad7ddc10f58e667e81dfc47139716ee8a8b7720 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 15 Sep 2018 14:03:44 -0400 Subject: [PATCH 081/230] Merge DreamMakerController and Instance auto update compile functions. Instance auto update now properly specifies it as a job --- .../Components/IInstance.cs | 11 ++ .../Components/Instance.cs | 130 ++++++++++++------ .../Controllers/DreamMakerController.cs | 68 +-------- 3 files changed, 99 insertions(+), 110 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/IInstance.cs b/src/Tgstation.Server.Host/Components/IInstance.cs index 8e5b2edf80..cf2c527a8a 100644 --- a/src/Tgstation.Server.Host/Components/IInstance.cs +++ b/src/Tgstation.Server.Host/Components/IInstance.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.Hosting; using System; +using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Host.Components.Byond; using Tgstation.Server.Host.Components.Chat; @@ -75,5 +76,15 @@ namespace Tgstation.Server.Host.Components /// The new auto update inteval /// A representing the running operation Task SetAutoUpdateInterval(uint newInterval); + + /// + /// Run the compile job and insert it into the database. Meant to be called by a + /// + /// The running + /// The for the operation + /// The to report compilation progress + /// The for the operation + /// A representing the running operation + Task CompileProcess(Job job, IServiceProvider serviceProvider, Action progressReporter, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index 46b5dc3541..1e99e0b49a 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -1,9 +1,11 @@ using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using System; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Components.Byond; using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Components.Compiler; @@ -114,6 +116,59 @@ namespace Tgstation.Server.Host.Components RepositoryManager.Dispose(); } + /// + public async Task CompileProcess(Job job, IServiceProvider serviceProvider, Action progressReporter, CancellationToken cancellationToken) + { + var databaseContext = serviceProvider.GetRequiredService(); + + var ddSettingsTask = databaseContext.DreamDaemonSettings.Where(x => x.InstanceId == metadata.Id).Select(x => new DreamDaemonSettings + { + StartupTimeout = x.StartupTimeout, + SecurityLevel = x.SecurityLevel + }).FirstOrDefaultAsync(cancellationToken); + + var dreamMakerSettings = await databaseContext.DreamMakerSettings.Where(x => x.InstanceId == metadata.Id).FirstAsync(cancellationToken).ConfigureAwait(false); + if (dreamMakerSettings == default) + throw new JobException("Missing DreamMakerSettings in DB!"); + var ddSettings = await ddSettingsTask.ConfigureAwait(false); + if (ddSettings == default) + throw new JobException("Missing DreamDaemonSettings in DB!"); + + CompileJob compileJob; + RevisionInformation revInfo; + using (var repo = await RepositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false)) + { + if (repo == null) + throw new JobException("Missing Repository!"); + + var repoSha = repo.Head; + revInfo = await databaseContext.RevisionInformations.Where(x => x.CommitSha == repoSha).Include(x => x.ActiveTestMerges).ThenInclude(x => x.TestMerge).FirstOrDefaultAsync().ConfigureAwait(false); + + if (revInfo == default) + { + revInfo = new RevisionInformation + { + CommitSha = repoSha, + OriginCommitSha = repoSha, + Instance = new Models.Instance + { + Id = metadata.Id + } + }; + logger.LogWarning(Repository.Repository.OriginTrackingErrorTemplate, repoSha); + databaseContext.Instances.Attach(revInfo.Instance); + } + + compileJob = await DreamMaker.Compile(revInfo, dreamMakerSettings, ddSettings.SecurityLevel.Value, ddSettings.StartupTimeout.Value, repo, cancellationToken).ConfigureAwait(false); + } + + compileJob.Job = job; + + databaseContext.CompileJobs.Add(compileJob); //will be saved by job context + + job.PostComplete = ct => CompileJobConsumer.LoadCompileJob(compileJob, ct); + } + /// /// Pull the repository and compile for every set of given /// @@ -129,33 +184,32 @@ namespace Tgstation.Server.Host.Components try { - CompileJob job = null; + Job job = null; + Models.User user = null; //need this the whole time + var noRepo = false; await databaseContextFactory.UseContext(async (db) => { - //start up queries we'll need in the future - var instanceQuery = db.Instances.Where(x => x.Id == metadata.Id); - var ddSettingsTask = instanceQuery.Select(x => x.DreamDaemonSettings).Select(x => new DreamDaemonSettings - { - StartupTimeout = x.StartupTimeout, - SecurityLevel = x.SecurityLevel - }).FirstAsync(cancellationToken); - var dmSettingsTask = instanceQuery.Select(x => x.DreamMakerSettings).FirstAsync(cancellationToken); - var repositorySettingsTask = instanceQuery.Select(x => x.RepositorySettings).FirstAsync(cancellationToken); + var userTask = db.Users.FirstAsync(cancellationToken); + + var repositorySettingsTask = db.RepositorySettings.Where(x => x.InstanceId == metadata.Id).FirstAsync(cancellationToken); using (var repo = await RepositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false)) { if (repo == null) + { + //no repo, no auto updates + noRepo = true; return; + } - //start the rev info query - var startSha = repo.Head; - var revInfoTask = instanceQuery.SelectMany(x => x.RevisionInformations).Where(x => x.CommitSha == startSha).FirstOrDefaultAsync(cancellationToken); - - //need repo setting to fetch var repositorySettings = await repositorySettingsTask.ConfigureAwait(false); + + //the main point of auto update is to pull the remote await repo.FetchOrigin(repositorySettings.AccessUser, repositorySettings.AccessToken, null, cancellationToken).ConfigureAwait(false); + var startSha = repo.Head; + //take appropriate auto update actions bool shouldSyncTracked; if (repositorySettings.AutoUpdatesKeepTestMerges.Value) @@ -174,38 +228,28 @@ namespace Tgstation.Server.Host.Components //synch if necessary if (repositorySettings.AutoUpdatesSynchronize.Value && startSha != repo.Head) await repo.Sychronize(repositorySettings.AccessUser, repositorySettings.AccessToken, shouldSyncTracked, cancellationToken).ConfigureAwait(false); - - //finish other queries - var dmSettings = await dmSettingsTask.ConfigureAwait(false); - var ddSettings = await ddSettingsTask.ConfigureAwait(false); - var revInfo = await revInfoTask.ConfigureAwait(false); - - //null rev info handling - if (revInfo == default) - { - var currentSha = repo.Head; - revInfo = new RevisionInformation - { - CommitSha = currentSha, - OriginCommitSha = currentSha, - Instance = new Models.Instance - { - Id = metadata.Id - } - }; - logger.LogWarning(Repository.Repository.OriginTrackingErrorTemplate, currentSha); - db.Instances.Attach(revInfo.Instance); - } - - //finally start compile - job = await DreamMaker.Compile(revInfo, dmSettings, ddSettings.SecurityLevel.Value, ddSettings.StartupTimeout.Value, repo, cancellationToken).ConfigureAwait(false); } - db.CompileJobs.Add(job); - await db.Save(cancellationToken).ConfigureAwait(false); + user = await userTask.ConfigureAwait(false); + + //finally set up the job + job = new Job + { + StartedBy = user, + Instance = new Models.Instance + { + Id = metadata.Id + }, + Description = "Scheduled code deployment", + CancelRightsType = RightsType.DreamMaker, + CancelRight = (ulong)DreamMakerRights.CancelCompile + }; + + await jobManager.RegisterOperation(job, CompileProcess, cancellationToken).ConfigureAwait(false); }).ConfigureAwait(false); - await CompileJobConsumer.LoadCompileJob(job, cancellationToken).ConfigureAwait(false); + if(!noRepo) + await jobManager.WaitForJobCompletion(job, user, cancellationToken, default).ConfigureAwait(false); } catch (OperationCanceledException) { diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs index 746f04157b..3c12cdc2bf 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs @@ -1,6 +1,5 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using System; using System.Linq; @@ -10,7 +9,6 @@ using System.Threading.Tasks; using Tgstation.Server.Api; using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Components; -using Tgstation.Server.Host.Components.Repository; using Tgstation.Server.Host.Core; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; @@ -97,7 +95,7 @@ namespace Tgstation.Server.Host.Controllers CancelRight = (ulong)DreamMakerRights.CancelCompile, Instance = Instance }; - await jobManager.RegisterOperation(job, (paramJob, serviceProvider, progressReporter, ct) => RunCompile(paramJob, serviceProvider, Instance, ct), cancellationToken).ConfigureAwait(false); + await jobManager.RegisterOperation(job, instanceManager.GetInstance(Instance).CompileProcess, cancellationToken).ConfigureAwait(false); return Accepted(job.ToApi()); } @@ -129,69 +127,5 @@ namespace Tgstation.Server.Host.Controllers await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); return await Read(cancellationToken).ConfigureAwait(false); } - - /// - /// Run the compile job and insert it into the database - /// - /// The running - /// The for the operation - /// The for the operation - /// The for the operation - /// A representing the running operation - async Task RunCompile(Job job, IServiceProvider serviceProvider, Models.Instance instanceModel, CancellationToken cancellationToken) - { - var instanceManager = serviceProvider.GetRequiredService(); - var databaseContext = serviceProvider.GetRequiredService(); - - var ddSettingsTask = databaseContext.DreamDaemonSettings.Where(x => x.InstanceId == instanceModel.Id).Select(x => new DreamDaemonSettings - { - StartupTimeout = x.StartupTimeout, - SecurityLevel = x.SecurityLevel - }).FirstOrDefaultAsync(cancellationToken); - - - var dreamMakerSettings = await databaseContext.DreamMakerSettings.Where(x => x.InstanceId == instanceModel.Id).FirstAsync(cancellationToken).ConfigureAwait(false); - if (dreamMakerSettings == default) - throw new JobException("Missing DreamMakerSettings in DB!"); - var ddSettings = await ddSettingsTask.ConfigureAwait(false); - if (ddSettings == default) - throw new JobException("Missing DreamDaemonSettings in DB!"); - - var instance = instanceManager.GetInstance(instanceModel); - - CompileJob compileJob; - RevisionInformation revInfo; - using (var repo = await instance.RepositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false)) - { - if (repo == null) - throw new JobException("Missing Repository!"); - - var repoSha = repo.Head; - revInfo = await databaseContext.RevisionInformations.Where(x => x.CommitSha == repoSha).Include(x => x.ActiveTestMerges).ThenInclude(x => x.TestMerge).FirstOrDefaultAsync().ConfigureAwait(false); - - if (revInfo == default) - { - revInfo = new RevisionInformation - { - CommitSha = repoSha, - OriginCommitSha = repoSha, - Instance = new Models.Instance - { - Id = Instance.Id - } - }; - Logger.LogWarning(Repository.OriginTrackingErrorTemplate, repoSha); - databaseContext.Instances.Attach(revInfo.Instance); - } - - compileJob = await instance.DreamMaker.Compile(revInfo, dreamMakerSettings, ddSettings.SecurityLevel.Value, ddSettings.StartupTimeout.Value, repo, cancellationToken).ConfigureAwait(false); - } - - compileJob.Job = job; - - databaseContext.CompileJobs.Add(compileJob); //will be saved by job context - - job.PostComplete = ct => instance.CompileJobConsumer.LoadCompileJob(compileJob, ct); - } } } From b6b8d8e06b2b697c6896863d36f0902c18b5a8b3 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 15 Sep 2018 14:05:26 -0400 Subject: [PATCH 082/230] Remove unused IInstance properties --- .../Components/IInstance.cs | 18 +----------------- .../Components/Instance.cs | 5 +---- 2 files changed, 2 insertions(+), 21 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/IInstance.cs b/src/Tgstation.Server.Host/Components/IInstance.cs index cf2c527a8a..921a408266 100644 --- a/src/Tgstation.Server.Host/Components/IInstance.cs +++ b/src/Tgstation.Server.Host/Components/IInstance.cs @@ -26,12 +26,7 @@ namespace Tgstation.Server.Host.Components /// The for the /// IByondManager ByondManager { get; } - - /// - /// The for the - /// - IDreamMaker DreamMaker { get; } - + /// /// The for the /// @@ -42,11 +37,6 @@ namespace Tgstation.Server.Host.Components /// IChat Chat { get; } - /// - /// The for the - /// - ICompileJobConsumer CompileJobConsumer { get; } - /// /// The for the /// @@ -58,12 +48,6 @@ namespace Tgstation.Server.Host.Components /// The latest if it exists CompileJob LatestCompileJob(); - /// - /// Get the associated with the - /// - /// The associated with the - Api.Models.Instance GetMetadata(); - /// /// Rename the /// diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index 1e99e0b49a..d904aadfa4 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -266,10 +266,7 @@ namespace Tgstation.Server.Host.Components break; } } - - /// - public Api.Models.Instance GetMetadata() => metadata.CloneMetadata(); - + /// public void Rename(string newName) { From 9222031ba116c77973d872a0624684315c2bbf7a Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 15 Sep 2018 14:07:20 -0400 Subject: [PATCH 083/230] Make use of IInstance.Rename --- src/Tgstation.Server.Host/Controllers/InstanceController.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs index 277677d774..3545f1ce8a 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs @@ -295,6 +295,7 @@ namespace Tgstation.Server.Host.Controllers } var originalOnline = originalModel.Online.Value; + var renamed = model.Name != null && originalModel.Name != model.Name; if (CheckModified(x => x.AutoUpdateInterval, InstanceManagerRights.SetAutoUpdate) || CheckModified(x => x.ConfigurationType, InstanceManagerRights.SetConfiguration) @@ -311,6 +312,9 @@ namespace Tgstation.Server.Host.Controllers await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); + if (renamed) + instanceManager.GetInstance(originalModel).Rename(originalModel.Name); + var oldAutoStart = originalModel.DreamDaemonSettings.AutoStart; try { From fee1e4a6070c46d593b455a35164b9b296537e3e Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 15 Sep 2018 14:11:43 -0400 Subject: [PATCH 084/230] Clean up queries involving CompileJobs Repeat after me: CompileJobs that didn't succeed will not be in the database CompileJobs that didn't succeed will not be in the database CompileJobs that didn't succeed will not be in the database CompileJobs that didn't succeed will not be in the database CompileJobs that didn't succeed will not be in the database CompileJobs that didn't succeed will not be in the database CompileJobs that didn't succeed will not be in the database CompileJobs that didn't succeed will not be in the database CompileJobs that didn't succeed will not be in the database CompileJobs that didn't succeed will not be in the database --- src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs | 4 ++-- src/Tgstation.Server.Host/Components/Instance.cs | 2 +- src/Tgstation.Server.Host/Controllers/DreamMakerController.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs b/src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs index 045e495fa0..c31ab7e326 100644 --- a/src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs +++ b/src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs @@ -169,7 +169,7 @@ namespace Tgstation.Server.Host.Components.Compiler public Task StartAsync(CancellationToken cancellationToken) => databaseContextFactory.UseContext(async (db) => { //where complete clause not necessary, only successful COMPILEjobs get in the db - var cj = await db.CompileJobs.Where(x => x.Job.Instance.Id == instance.Id && !x.Job.Cancelled.Value && x.Job.ExceptionDetails == null && x.Job.StoppedAt != null) + var cj = await db.CompileJobs.Where(x => x.Job.Instance.Id == instance.Id) .Include(x => x.Job).ThenInclude(x => x.StartedBy) .Include(x => x.RevisionInformation).ThenInclude(x => x.PrimaryTestMerge).ThenInclude(x => x.MergedBy) .Include(x => x.RevisionInformation).ThenInclude(x => x.ActiveTestMerges).ThenInclude(x => x.TestMerge).ThenInclude(x => x.MergedBy) @@ -245,7 +245,7 @@ namespace Tgstation.Server.Host.Components.Compiler //find the uids of locked directories await databaseContextFactory.UseContext(async db => { - jobUidsToNotErase = await db.CompileJobs.Where(x => x.Job.Instance.Id == instance.Id && jobIdsToSkip.Contains(x.Id) && x.DirectoryName.HasValue).Select(x => x.DirectoryName.Value.ToString().ToUpperInvariant()).ToListAsync(cancellationToken).ConfigureAwait(false); + jobUidsToNotErase = await db.CompileJobs.Where(x => x.Job.Instance.Id == instance.Id && jobIdsToSkip.Contains(x.Id)).Select(x => x.DirectoryName.Value.ToString().ToUpperInvariant()).ToListAsync(cancellationToken).ConfigureAwait(false); }).ConfigureAwait(false); //add the other exemption diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index d904aadfa4..fc9c3234cf 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -286,7 +286,7 @@ namespace Tgstation.Server.Host.Components CompileJob latestCompileJob = null; await databaseContextFactory.UseContext(async db => { - latestCompileJob = await db.CompileJobs.Where(x => x.Job.Instance.Id == metadata.Id && x.Job.ExceptionDetails == null).OrderByDescending(x => x.Job.StoppedAt).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); + latestCompileJob = await db.CompileJobs.Where(x => x.Job.Instance.Id == metadata.Id).OrderByDescending(x => x.Job.StoppedAt).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); }).ConfigureAwait(false); await dmbFactory.CleanUnusedCompileJobs(latestCompileJob, cancellationToken).ConfigureAwait(false); } diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs index 3c12cdc2bf..b847fef9e2 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs @@ -76,7 +76,7 @@ namespace Tgstation.Server.Host.Controllers [TgsAuthorize(DreamMakerRights.CompileJobs)] public override async Task List(CancellationToken cancellationToken) { - var compileJobs = await DatabaseContext.CompileJobs.Where(x => x.Job.Instance.Id == Instance.Id).OrderByDescending(x => x.Job.StartedAt).Select(x => new Api.Models.CompileJob + var compileJobs = await DatabaseContext.CompileJobs.Where(x => x.Job.Instance.Id == Instance.Id).OrderByDescending(x => x.Job.StoppedAt).Select(x => new Api.Models.CompileJob { Id = x.Id }).ToListAsync(cancellationToken).ConfigureAwait(false); From 1c0007398a93de11d2edb1c9d46670897cec3f37 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 15 Sep 2018 14:31:03 -0400 Subject: [PATCH 085/230] Move auto update repository changes into it's own job. Add some logging --- .../Components/Instance.cs | 82 ++++++++++++------- 1 file changed, 53 insertions(+), 29 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index fc9c3234cf..f178039f9e 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -184,17 +184,31 @@ namespace Tgstation.Server.Host.Components try { - Job job = null; Models.User user = null; - //need this the whole time - var noRepo = false; - await databaseContextFactory.UseContext(async (db) => + await databaseContextFactory.UseContext(async (db) => user = await db.Users.FirstAsync(cancellationToken).ConfigureAwait(false)).ConfigureAwait(false); + var repositoryUpdateJob = new Job { - var userTask = db.Users.FirstAsync(cancellationToken); + Instance = new Models.Instance + { + Id = metadata.Id + }, + Description = "Scheduled repository update", + CancelRightsType = RightsType.Repository, + CancelRight = (ulong)RepositoryRights.CancelPendingChanges + }; - var repositorySettingsTask = db.RepositorySettings.Where(x => x.InstanceId == metadata.Id).FirstAsync(cancellationToken); + var noRepo = false; + await jobManager.RegisterOperation(repositoryUpdateJob, async (paramJob, serviceProvider, progressReporter, jobCancellationToken) => + { + var db = serviceProvider.GetRequiredService(); + var repositorySettingsTask = db.RepositorySettings.Where(x => x.InstanceId == metadata.Id).FirstAsync(jobCancellationToken); - using (var repo = await RepositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false)) + //assume 5 steps with synchronize + const int ProgressSections = 5; + const int ProgressStep = 100 / ProgressSections; + progressReporter(0 * ProgressStep); + + using (var repo = await RepositoryManager.LoadRepository(jobCancellationToken).ConfigureAwait(false)) { if (repo == null) { @@ -202,11 +216,17 @@ namespace Tgstation.Server.Host.Components noRepo = true; return; } + progressReporter(1 * ProgressStep); var repositorySettings = await repositorySettingsTask.ConfigureAwait(false); - + + const int SecondStepProgress = 2 * ProgressStep; + progressReporter(SecondStepProgress); + //the main point of auto update is to pull the remote - await repo.FetchOrigin(repositorySettings.AccessUser, repositorySettings.AccessToken, null, cancellationToken).ConfigureAwait(false); + await repo.FetchOrigin(repositorySettings.AccessUser, repositorySettings.AccessToken, x => progressReporter(SecondStepProgress + (x / ProgressSections)), jobCancellationToken).ConfigureAwait(false); + + progressReporter(3 * ProgressStep); var startSha = repo.Head; @@ -214,45 +234,48 @@ namespace Tgstation.Server.Host.Components bool shouldSyncTracked; if (repositorySettings.AutoUpdatesKeepTestMerges.Value) { - var result = await repo.MergeOrigin(repositorySettings.CommitterName, repositorySettings.CommitterEmail, cancellationToken).ConfigureAwait(false); + var result = await repo.MergeOrigin(repositorySettings.CommitterName, repositorySettings.CommitterEmail, jobCancellationToken).ConfigureAwait(false); if (!result.HasValue) return; shouldSyncTracked = result.Value; } else { - await repo.ResetToOrigin(cancellationToken).ConfigureAwait(false); + await repo.ResetToOrigin(jobCancellationToken).ConfigureAwait(false); shouldSyncTracked = true; } + progressReporter(4 * ProgressStep); //synch if necessary if (repositorySettings.AutoUpdatesSynchronize.Value && startSha != repo.Head) - await repo.Sychronize(repositorySettings.AccessUser, repositorySettings.AccessToken, shouldSyncTracked, cancellationToken).ConfigureAwait(false); + await repo.Sychronize(repositorySettings.AccessUser, repositorySettings.AccessToken, shouldSyncTracked, jobCancellationToken).ConfigureAwait(false); + + progressReporter(5 * ProgressStep); } + }, cancellationToken).ConfigureAwait(false); - user = await userTask.ConfigureAwait(false); + await jobManager.WaitForJobCompletion(repositoryUpdateJob, user, cancellationToken, default).ConfigureAwait(false); - //finally set up the job - job = new Job - { - StartedBy = user, - Instance = new Models.Instance - { - Id = metadata.Id - }, - Description = "Scheduled code deployment", - CancelRightsType = RightsType.DreamMaker, - CancelRight = (ulong)DreamMakerRights.CancelCompile - }; + if (noRepo) + continue; - await jobManager.RegisterOperation(job, CompileProcess, cancellationToken).ConfigureAwait(false); - }).ConfigureAwait(false); + //finally set up the job + var compileProcessJob = new Job + { + StartedBy = user, + Instance = repositoryUpdateJob.Instance, + Description = "Scheduled code deployment", + CancelRightsType = RightsType.DreamMaker, + CancelRight = (ulong)DreamMakerRights.CancelCompile + }; - if(!noRepo) - await jobManager.WaitForJobCompletion(job, user, cancellationToken, default).ConfigureAwait(false); + await jobManager.RegisterOperation(compileProcessJob, CompileProcess, cancellationToken).ConfigureAwait(false); + + await jobManager.WaitForJobCompletion(compileProcessJob, user, cancellationToken, default).ConfigureAwait(false); } catch (OperationCanceledException) { + logger.LogDebug("Cancelled auto update job!"); throw; } catch (Exception e) @@ -265,6 +288,7 @@ namespace Tgstation.Server.Host.Components { break; } + logger.LogTrace("Leaving auto update loop..."); } /// From 4eb0b89b0836303b42ab64c1b388fc334d11c5b2 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 15 Sep 2018 14:33:58 -0400 Subject: [PATCH 086/230] Stops assuming the admin user's ID --- src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index 939661e74d..e51e28e9a2 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -1,4 +1,5 @@ using Byond.TopicSender; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; @@ -807,11 +808,14 @@ namespace Tgstation.Server.Host.Components.Watchdog if (!autoStart) return; + long? adminUserId = null; + + await databaseContextFactory.UseContext(async db => adminUserId = await db.Users.Select(x => x.Id).FirstAsync(cancellationToken).ConfigureAwait(false)).ConfigureAwait(false); var job = new Models.Job { StartedBy = new Models.User { - Id = 1 //just use admin for this cause whatever + Id = adminUserId.Value }, Instance = new Models.Instance { From a8ae7efcafd1c63c997c0abac57088aba80bfa4c Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 15 Sep 2018 15:16:38 -0400 Subject: [PATCH 087/230] Sanitize your public API functions --- src/Tgstation.Server.Host/Components/Instance.cs | 8 ++++++++ src/Tgstation.Server.Host/Core/JobManager.cs | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index f178039f9e..8f7b0e7700 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -119,6 +119,14 @@ namespace Tgstation.Server.Host.Components /// public async Task CompileProcess(Job job, IServiceProvider serviceProvider, Action progressReporter, CancellationToken cancellationToken) { + //DO NOT FOLLOW THE SUGGESTION FOR A THROW EXPRESSION HERE + if (job == null) + throw new ArgumentNullException(nameof(job)); + if (serviceProvider == null) + throw new ArgumentNullException(nameof(serviceProvider)); + if (progressReporter == null) + throw new ArgumentNullException(nameof(progressReporter)); + var databaseContext = serviceProvider.GetRequiredService(); var ddSettingsTask = databaseContext.DreamDaemonSettings.Where(x => x.InstanceId == metadata.Id).Select(x => new DreamDaemonSettings diff --git a/src/Tgstation.Server.Host/Core/JobManager.cs b/src/Tgstation.Server.Host/Core/JobManager.cs index b9b8ba4b68..cb034f445a 100644 --- a/src/Tgstation.Server.Host/Core/JobManager.cs +++ b/src/Tgstation.Server.Host/Core/JobManager.cs @@ -247,6 +247,8 @@ namespace Tgstation.Server.Host.Core /// public int? JobProgress(Job job) { + if (job == null) + throw new ArgumentNullException(nameof(job)); lock (this) { if (!jobs.TryGetValue(job.Id, out var handler)) @@ -258,6 +260,10 @@ namespace Tgstation.Server.Host.Core /// public async Task WaitForJobCompletion(Job job, User canceller, CancellationToken jobCancellationToken, CancellationToken cancellationToken) { + if (job == null) + throw new ArgumentNullException(nameof(job)); + if (canceller == null) + throw new ArgumentNullException(nameof(canceller)); JobHandler handler; lock (this) { From 1944565a5394c4447546640e66d35693ff709c49 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 15 Sep 2018 15:29:23 -0400 Subject: [PATCH 088/230] Throw a cleaner exception when the specified .dme doesn't exist --- src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs b/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs index 104f541931..56836577f6 100644 --- a/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs @@ -344,6 +344,8 @@ namespace Tgstation.Server.Host.Components.Compiler var dmeWithExtension = ioManager.GetFileName(path); job.DmeName = dmeWithExtension.Substring(0, dmeWithExtension.Length - DmeExtension.Length - 1); } + else if (!await ioManager.FileExists(ioManager.ConcatPath(dirA, String.Join('.', job.DmeName, DmeExtension)), cancellationToken).ConfigureAwait(false)) + throw new JobException("Unable to locate specified .dme!"); logger.LogDebug("Selected {0}.dme for compilation!", job.DmeName); From eb09bfc94e59f246875f50e69c0c894c37051129 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 15 Sep 2018 17:19:59 -0400 Subject: [PATCH 089/230] Add UnrecognizedResponseException --- .../UnrecognizedResponseException.cs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/Tgstation.Server.Client/UnrecognizedResponseException.cs diff --git a/src/Tgstation.Server.Client/UnrecognizedResponseException.cs b/src/Tgstation.Server.Client/UnrecognizedResponseException.cs new file mode 100644 index 0000000000..24dfa9227d --- /dev/null +++ b/src/Tgstation.Server.Client/UnrecognizedResponseException.cs @@ -0,0 +1,40 @@ +using System; +using System.Globalization; +using System.Net; +using Tgstation.Server.Api.Models; + +namespace Tgstation.Server.Client +{ + sealed class UnrecognizedResponseException : ClientException + { + /// + /// Construct an with the of a response body and the + /// + /// The body of the response + /// The for the + public UnrecognizedResponseException(string data, HttpStatusCode statusCode) : base(new ErrorMessage + { + Message = String.Format(CultureInfo.InvariantCulture, "Unrecognized response body: {0}", data), + SeverApiVersion = null + }, statusCode) + { } + + /// + /// Construct a + /// + public UnrecognizedResponseException() { } + + /// + /// Construct an with a + /// + /// The message for the + public UnrecognizedResponseException(string message) : base(message) { } + + /// + /// Construct an with a and + /// + /// The message for the + /// The inner for the base + public UnrecognizedResponseException(string message, Exception innerException) : base(message, innerException) { } + } +} From 06fb98aaca625bc3e75cb156ae5387021f0822d2 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 15 Sep 2018 17:27:03 -0400 Subject: [PATCH 090/230] Throw UnrecognizedResponseExceptions from ApiClient --- src/Tgstation.Server.Client/ApiClient.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Client/ApiClient.cs b/src/Tgstation.Server.Client/ApiClient.cs index b7df25aa7c..66a2609031 100644 --- a/src/Tgstation.Server.Client/ApiClient.cs +++ b/src/Tgstation.Server.Client/ApiClient.cs @@ -164,7 +164,14 @@ namespace Tgstation.Server.Client if (String.IsNullOrWhiteSpace(json)) json = JsonConvert.SerializeObject(new object()); - return JsonConvert.DeserializeObject(json, serializerSettings); + try + { + return JsonConvert.DeserializeObject(json, serializerSettings); + } + catch (JsonException) + { + throw new UnrecognizedResponseException(json, response.StatusCode); + } } /// From 537805f86d18783e81c509acddf59e1a8abcb06e Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 15 Sep 2018 17:30:10 -0400 Subject: [PATCH 091/230] Add TestUnrecognizedResponse Client test --- .../TestApiClient.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/Tgstation.Server.Client.Tests/TestApiClient.cs b/tests/Tgstation.Server.Client.Tests/TestApiClient.cs index 4882fae2cc..2a544b5863 100644 --- a/tests/Tgstation.Server.Client.Tests/TestApiClient.cs +++ b/tests/Tgstation.Server.Client.Tests/TestApiClient.cs @@ -44,5 +44,28 @@ namespace Tgstation.Server.Client.Tests var result = await client.Read(Routes.Byond, default).ConfigureAwait(false); Assert.AreEqual(sample.Version, result.Version); } + + [TestMethod] + public async Task TestUnrecognizedResponse() + { + var sample = new Byond + { + Version = new Version(511, 1385) + }; + + var fakeJson = "asdfasd <>F#(*)U*#JLI"; + + var response = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(fakeJson) + }; + + var httpClient = new Mock(); + httpClient.Setup(x => x.SendAsync(It.IsNotNull(), It.IsAny())).Returns(Task.FromResult(response)); + + var client = new ApiClient(httpClient.Object, new Uri("http://fake.com"), new ApiHeaders(new ProductHeaderValue("fake"), "fake")); + + await Assert.ThrowsExceptionAsync(() => client.Read(Routes.Byond, default)).ConfigureAwait(false); + } } } From 7d192b3a6e8992c82492e3bdf3e860888418c993 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 15 Sep 2018 17:30:39 -0400 Subject: [PATCH 092/230] Bump client preview version --- src/Tgstation.Server.Client/Tgstation.Server.Client.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj index b3ad661fa0..34cf1a89d8 100644 --- a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj +++ b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj @@ -3,7 +3,7 @@ netstandard2.0 Full - 4.0.0.0-preview9106 + 4.0.0.0-preview9107 true Cyberboss /tg/station 13 From b521ffd7e31b0d4aa1e132138d78f21b4aeb0419 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 15 Sep 2018 17:35:49 -0400 Subject: [PATCH 093/230] Mark /app/lib as an initial Docker volume --- build/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Dockerfile b/build/Dockerfile index 45e06d1d07..270755a249 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -35,6 +35,6 @@ COPY --from=build /src/build/tgs.docker.sh tgs.sh COPY --from=build /src/src/Tgstation.Server.Host/appsettings.Docker.json . RUN mkdir /config_data && mv appsettings.Docker.json /config_data/appsettings.Production.json -VOLUME ["/config_data", "/tgs_logs"] +VOLUME ["/config_data", "/tgs_logs", "/app/lib"] ENTRYPOINT ["./tgs.sh"] From ebefd65908346825897a616052a8f7c80ceb0218 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 15 Sep 2018 17:58:39 -0400 Subject: [PATCH 094/230] Update readme, reccommend docker for linux --- README.md | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6f623170ee..902400ef1e 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,8 @@ Generally, updates force a live tracking of the configured git repo, resetting l #### Linux +[We reccommend using Docker for Linux installations](https://github.com/tgstation/tgstation-server#docker). The content of this parent section may be skipped if you choose to do so + The following dependencies are required to run tgstation-server on Linux alongside the .NET Core runtime - gcc-multilib (on 64-bit systems for running BYOND) @@ -35,11 +37,23 @@ Note that tgstation-server has only ever been tested on Linux via it's [docker e #### Docker -tgstation-server supports running in a docker container on Linux systems. The official image repository is located at https://hub.docker.com/r/tgstation/server. It can also be built locally by running `docker build . -f build/Dockerfile` in the repository root. +tgstation-server supports running in a docker container and is the recommended deployment method for Linux systems due being the only tested environment. The official image repository is located at https://hub.docker.com/r/tgstation/server. It can also be built locally by running `docker build . -f build/Dockerfile` in the repository root. -To create a container run `docker create --restart=always -p :80 -v /path/to/your/appsettings.Production.json:/config_data -v path/to/your/log/folder:/tgs_logs tgstation/server` with any additional options you desire (i.e. You'll have to expose more ports in order to actually host servers, add a volume to create instances on, and create a volume for the SQLite database if that is what you're using). +To create a container run +``` +docker create \ + --restart=always \ #if you want maximum uptime + --network="host" \ #if your sql server is on the same machine + -p :80 \ + -p 0.0.0.0:: \ + -v /path/to/store/instances:/tgs4_instances \ + -v /path/to/your/appsettings.Production.json:/config_data \ + -v path/to/your/log/folder:/tgs_logs \ + tgstation/server +``` +with any additional options you desire (i.e. You'll have to expose more game ports in order to host more than one instance). -Note that due to the nature of docker. If the container restarts, you will be sent back to the version of TGS you installed with the initial command. Server updates will have to be reapplied. For this reason it is NOT RECOMMENDED to use the live update feature with a docker host. +Note although `/app/lib` is specified as a volume mount point in the `Dockerfile`, unless you REALLY know what you're doing. Do not mount any volumes over this for fear of breaking your container. ### Configuring From 721afc34b2b88bc79158d21ce3a6774d93435f3f Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 15 Sep 2018 18:01:19 -0400 Subject: [PATCH 096/230] Not how you spell recommend --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 902400ef1e..a03c2d0a2d 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Generally, updates force a live tracking of the configured git repo, resetting l #### Linux -[We reccommend using Docker for Linux installations](https://github.com/tgstation/tgstation-server#docker). The content of this parent section may be skipped if you choose to do so +[We recommend using Docker for Linux installations](https://github.com/tgstation/tgstation-server#docker). The content of this parent section may be skipped if you choose to do so The following dependencies are required to run tgstation-server on Linux alongside the .NET Core runtime From 41e38211cf897a8530c38544b28b9fd792bfbe0d Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 16 Sep 2018 10:27:28 -0400 Subject: [PATCH 097/230] Adds IWatchdog.ResetRebootState --- .../Components/Watchdog/IWatchdog.cs | 7 +++++++ .../Components/Watchdog/Watchdog.cs | 15 +++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs index 21a45d3e60..6c8b65b05f 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs @@ -76,5 +76,12 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The for the operation /// A representing the running operation Task Terminate(bool graceful, CancellationToken cancellationToken); + + /// + /// Cancels pending graceful actions + /// + /// The for the operation + /// A representing the running operation + Task ResetRebootState(CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index e51e28e9a2..bbd159f5a4 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -764,6 +764,19 @@ namespace Tgstation.Server.Host.Components.Watchdog return await LaunchNoLock(true, true, false, cancellationToken).ConfigureAwait(false); } + /// + public async Task ResetRebootState(CancellationToken cancellationToken) + { + using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) + { + if (!Running) + return; + var toClear = AlphaIsActive ? alphaServer : bravoServer; + if (toClear != null) + toClear.ResetRebootState(); + } + } + /// public async Task Restart(bool graceful, CancellationToken cancellationToken) { @@ -784,12 +797,10 @@ namespace Tgstation.Server.Host.Components.Watchdog return result; } var toReboot = AlphaIsActive ? alphaServer : bravoServer; - var other = AlphaIsActive ? bravoServer : alphaServer; if (toReboot != null) { if (!await toReboot.SetRebootState(Components.Watchdog.RebootState.Restart, cancellationToken).ConfigureAwait(false)) logger.LogWarning("Unable to send reboot state change event!"); - } return null; } From 5042cb28e7848d1794fca3f0db3c0cba9b1b4a65 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 16 Sep 2018 10:27:54 -0400 Subject: [PATCH 098/230] Allows resetting reboot state with the DreamDaemonController --- .../Controllers/DreamDaemonController.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs index 1100022216..e7cd7ac52e 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs @@ -192,13 +192,13 @@ namespace Tgstation.Server.Host.Controllers //run these in parallel because they are equally as important await Task.WhenAll(DatabaseContext.Save(cancellationToken), wd.ChangeSettings(current, cancellationToken)).ConfigureAwait(false); - - //soft shutdown/restart can't be cancelled because of how many things rely on them - //They can be alternated though + if (!oldSoftRestart.Value && current.SoftRestart.Value) await wd.Restart(true, cancellationToken).ConfigureAwait(false); else if (!oldSoftShutdown.Value && current.SoftShutdown.Value) await wd.Terminate(true, cancellationToken).ConfigureAwait(false); + else if ((oldSoftRestart.Value && !current.SoftRestart.Value) || (oldSoftShutdown.Value && !current.SoftShutdown.Value)) + await wd.ResetRebootState(cancellationToken).ConfigureAwait(false); return await ReadImpl(current, cancellationToken).ConfigureAwait(false); } From 3e5e6a0e24d01ef6e373171fe9952a3efb2d95fa Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 16 Sep 2018 11:19:10 -0400 Subject: [PATCH 099/230] Corrects IDreamDaemonClient.Start()'s return type --- src/Tgstation.Server.Client/Components/DreamDaemonClient.cs | 2 +- src/Tgstation.Server.Client/Components/IDreamDaemonClient.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Client/Components/DreamDaemonClient.cs b/src/Tgstation.Server.Client/Components/DreamDaemonClient.cs index e0815a873b..10f72d0ffe 100644 --- a/src/Tgstation.Server.Client/Components/DreamDaemonClient.cs +++ b/src/Tgstation.Server.Client/Components/DreamDaemonClient.cs @@ -33,7 +33,7 @@ namespace Tgstation.Server.Client.Components public Task Shutdown(CancellationToken cancellationToken) => apiClient.Delete(Routes.DreamDaemon, instance.Id, cancellationToken); /// - public Task Start(CancellationToken cancellationToken) => apiClient.Create(Routes.DreamDaemon, instance.Id, cancellationToken); + public Task Start(CancellationToken cancellationToken) => apiClient.Create(Routes.DreamDaemon, instance.Id, cancellationToken); /// public Task Read(CancellationToken cancellationToken) => apiClient.Read(Routes.DreamDaemon, instance.Id, cancellationToken); diff --git a/src/Tgstation.Server.Client/Components/IDreamDaemonClient.cs b/src/Tgstation.Server.Client/Components/IDreamDaemonClient.cs index 0bb8a1d72b..764ddfbc23 100644 --- a/src/Tgstation.Server.Client/Components/IDreamDaemonClient.cs +++ b/src/Tgstation.Server.Client/Components/IDreamDaemonClient.cs @@ -21,7 +21,7 @@ namespace Tgstation.Server.Client.Components /// /// The for the operation /// A resulting in the information - Task Start(CancellationToken cancellationToken); + Task Start(CancellationToken cancellationToken); /// /// Shutdown From 76ebfd105e7f5e1339ed01ff3fb88f1c94ac4ae1 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 16 Sep 2018 11:19:38 -0400 Subject: [PATCH 100/230] Bump client preview version --- src/Tgstation.Server.Client/Tgstation.Server.Client.csproj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj index 34cf1a89d8..054656e7df 100644 --- a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj +++ b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj @@ -3,7 +3,7 @@ netstandard2.0 Full - 4.0.0.0-preview9107 + 4.0.0.0-preview9108 true Cyberboss /tg/station 13 @@ -24,6 +24,7 @@ true bin\Release\netstandard2.0\Tgstation.Server.Client.xml + 0 From 676486683cb0f221ccb9cf0304bf4b82ffa3e43f Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 16 Sep 2018 11:25:38 -0400 Subject: [PATCH 101/230] Add another client test --- .../Components/TestDreamDaemonClient.cs | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 tests/Tgstation.Server.Client.Tests/Components/TestDreamDaemonClient.cs diff --git a/tests/Tgstation.Server.Client.Tests/Components/TestDreamDaemonClient.cs b/tests/Tgstation.Server.Client.Tests/Components/TestDreamDaemonClient.cs new file mode 100644 index 0000000000..a70423e97c --- /dev/null +++ b/tests/Tgstation.Server.Client.Tests/Components/TestDreamDaemonClient.cs @@ -0,0 +1,39 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api; +using Tgstation.Server.Api.Models; + +namespace Tgstation.Server.Client.Components.Tests +{ + [TestClass] + public sealed class TestDreamDaemonClient + { + [TestMethod] + public async Task TestStart() + { + var example = new Job + { + Id = 347, + StartedAt = DateTimeOffset.Now + }; + + var inst = new Instance + { + Id = 4958 + }; + + var mockApiClient = new Mock(); + mockApiClient.Setup(x => x.Create(Routes.DreamDaemon, inst.Id, It.IsAny())).Returns(Task.FromResult(example)); + + var client = new DreamDaemonClient(mockApiClient.Object, inst); + + var result = await client.Start(default).ConfigureAwait(false); + Assert.AreSame(example, result); + } + } +} From 77aa4a5b4f3bfadc5f4bd449bd61722efa113724 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 16 Sep 2018 12:10:31 -0400 Subject: [PATCH 102/230] Adds some additional validation to port changes --- .../Controllers/DreamDaemonController.cs | 10 ++++++++-- .../Controllers/DreamMakerController.cs | 10 +++++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs index e7cd7ac52e..8070da2382 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs @@ -149,6 +149,12 @@ namespace Tgstation.Server.Host.Controllers if (model == null) throw new ArgumentNullException(nameof(model)); + if (model.PrimaryPort == 0) + return BadRequest(new ErrorMessage { Message = "Primary port cannot be 0!" }); + + if (model.SecurityLevel == DreamDaemonSecurity.Ultrasafe) + return BadRequest(new ErrorMessage { Message = "This version of TGS does not support the ultrasafe DreamDaemon configuration!" }); + //alias for changing DD settings var current = await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).Select(x => x.DreamDaemonSettings).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); @@ -185,8 +191,8 @@ namespace Tgstation.Server.Host.Controllers || CheckModified(x => x.StartupTimeout, DreamDaemonRights.SetStartupTimeout)) return Forbid(); - if (current.SecurityLevel == DreamDaemonSecurity.Ultrasafe) - return BadRequest(new ErrorMessage { Message = "This version of TGS does not support the ultrasafe DreamDaemon configuration!" }); + if (current.PrimaryPort == current.SecondaryPort) + return BadRequest(new ErrorMessage { Message = "Primary port and secondary port cannot be the same!" }); var wd = instanceManager.GetInstance(Instance).Watchdog; diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs index b847fef9e2..1f4a9bb81c 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs @@ -7,6 +7,7 @@ using System.Net; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api; +using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Components; using Tgstation.Server.Host.Core; @@ -85,9 +86,9 @@ namespace Tgstation.Server.Host.Controllers /// [TgsAuthorize(DreamMakerRights.Compile)] - public override async Task Create([FromBody] Api.Models.DreamMaker model, CancellationToken cancellationToken) + public override async Task Create([FromBody] DreamMaker model, CancellationToken cancellationToken) { - var job = new Job + var job = new Models.Job { Description = "Compile active repository code", StartedBy = AuthenticationContext.User, @@ -101,8 +102,11 @@ namespace Tgstation.Server.Host.Controllers /// [TgsAuthorize(DreamMakerRights.SetDme | DreamMakerRights.SetApiValidationPort)] - public override async Task Update([FromBody] Api.Models.DreamMaker model, CancellationToken cancellationToken) + public override async Task Update([FromBody] DreamMaker model, CancellationToken cancellationToken) { + if (model.ApiValidationPort == 0) + return BadRequest(new ErrorMessage { Message = "API Validation port cannot be 0!" }); + var hostModel = await DatabaseContext.DreamMakerSettings.Where(x => x.InstanceId == Instance.Id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); if (hostModel == null) return StatusCode((int)HttpStatusCode.Gone); From 0398091da0336fc9c8fdc5f0afdea6b23144ea08 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 16 Sep 2018 12:51:34 -0400 Subject: [PATCH 103/230] Adds missing StartupTimeout to DreamDaemon responses --- src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs index 8070da2382..5d06822f1f 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs @@ -119,6 +119,7 @@ namespace Tgstation.Server.Host.Controllers result.SecurityLevel = settings.SecurityLevel; result.SoftRestart = rstate == RebootState.Restart; result.SoftShutdown = rstate == RebootState.Shutdown; + result.StartupTimeout = settings.StartupTimeout; }; if (revision) From aac1194f74f001eff4b49715e1e7ab9c3689a9dc Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 16 Sep 2018 13:07:16 -0400 Subject: [PATCH 104/230] Adds endpoint to restart the watchdog --- .../Controllers/DreamDaemonController.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs index 8070da2382..704515c4b9 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs @@ -208,5 +208,29 @@ namespace Tgstation.Server.Host.Controllers return await ReadImpl(current, cancellationToken).ConfigureAwait(false); } + + /// + /// Handle a HTTP PATCH to the + /// + /// The for the operation + /// A resulting in the of the request + [HttpPatch] + [TgsAuthorize(DreamDaemonRights.Restart)] + public async Task Restart(CancellationToken cancellationToken) + { + var job = new Models.Job + { + Instance = Instance, + CancelRightsType = RightsType.DreamDaemon, + CancelRight = (ulong)DreamDaemonRights.Shutdown, + StartedBy = AuthenticationContext.User, + Description = "Restart Watchdog" + }; + + var watchdog = instanceManager.GetInstance(Instance).Watchdog; + + await jobManager.RegisterOperation(job, (paramJob, serviceProvider, progressReporter, ct) => watchdog.Restart(false, ct), cancellationToken).ConfigureAwait(false); + return Accepted(job.ToApi()); + } } } From 297c28b263bdffa45387a777f1aca6f708869033 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 16 Sep 2018 13:11:36 -0400 Subject: [PATCH 105/230] Add HTTP PATCH support to client --- src/Tgstation.Server.Client/ApiClient.cs | 3 +++ src/Tgstation.Server.Client/IApiClient.cs | 1 + 2 files changed, 4 insertions(+) diff --git a/src/Tgstation.Server.Client/ApiClient.cs b/src/Tgstation.Server.Client/ApiClient.cs index 66a2609031..8107fc1913 100644 --- a/src/Tgstation.Server.Client/ApiClient.cs +++ b/src/Tgstation.Server.Client/ApiClient.cs @@ -216,6 +216,9 @@ namespace Tgstation.Server.Client /// public Task Create(string route, long instanceId, CancellationToken cancellationToken) => RunRequest(route, new object(), HttpMethod.Put, instanceId, cancellationToken); + /// + public Task Patch(string route, long instanceId, CancellationToken cancellationToken) => RunRequest(route, new object(), new HttpMethod("PATCH"), instanceId, cancellationToken); + /// public void AddRequestLogger(IRequestLogger requestLogger) => requestLoggers.Add(requestLogger ?? throw new ArgumentNullException(nameof(requestLogger))); } diff --git a/src/Tgstation.Server.Client/IApiClient.cs b/src/Tgstation.Server.Client/IApiClient.cs index 82b1298122..48c7d93234 100644 --- a/src/Tgstation.Server.Client/IApiClient.cs +++ b/src/Tgstation.Server.Client/IApiClient.cs @@ -32,6 +32,7 @@ namespace Tgstation.Server.Client Task Create(string route, TBody body, long instanceId, CancellationToken cancellationToken); Task Create(string route, long instanceId, CancellationToken cancellationToken); + Task Patch(string route, long instanceId, CancellationToken cancellationToken); Task Read(string route, long instanceId, CancellationToken cancellationToken); Task Update(string route, TBody body, long instanceId, CancellationToken cancellationToken); Task Delete(string route, long instanceId, CancellationToken cancellationToken); From 8b6fac7537676facabf3dcdd7276bdeccae800b8 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 16 Sep 2018 13:11:57 -0400 Subject: [PATCH 106/230] Add IDreamDaemonClient.Restart --- .../Components/DreamDaemonClient.cs | 3 +++ .../Components/IDreamDaemonClient.cs | 9 ++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Client/Components/DreamDaemonClient.cs b/src/Tgstation.Server.Client/Components/DreamDaemonClient.cs index 10f72d0ffe..e077ea142d 100644 --- a/src/Tgstation.Server.Client/Components/DreamDaemonClient.cs +++ b/src/Tgstation.Server.Client/Components/DreamDaemonClient.cs @@ -35,6 +35,9 @@ namespace Tgstation.Server.Client.Components /// public Task Start(CancellationToken cancellationToken) => apiClient.Create(Routes.DreamDaemon, instance.Id, cancellationToken); + /// + public Task Restart(CancellationToken cancellationToken) => apiClient.Patch(Routes.DreamDaemon, instance.Id, cancellationToken); + /// public Task Read(CancellationToken cancellationToken) => apiClient.Read(Routes.DreamDaemon, instance.Id, cancellationToken); diff --git a/src/Tgstation.Server.Client/Components/IDreamDaemonClient.cs b/src/Tgstation.Server.Client/Components/IDreamDaemonClient.cs index 764ddfbc23..7b858cba6b 100644 --- a/src/Tgstation.Server.Client/Components/IDreamDaemonClient.cs +++ b/src/Tgstation.Server.Client/Components/IDreamDaemonClient.cs @@ -20,9 +20,16 @@ namespace Tgstation.Server.Client.Components /// Start /// /// The for the operation - /// A resulting in the information + /// A resulting in the of the running operation Task Start(CancellationToken cancellationToken); + /// + /// Restart + /// + /// The for the operation + /// A resulting in the of the running operation + Task Restart(CancellationToken cancellationToken); + /// /// Shutdown /// From 1a1d23ac623084b8a07cfdc881a8d6281dd86b38 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 16 Sep 2018 13:12:21 -0400 Subject: [PATCH 107/230] Bump client preview version --- src/Tgstation.Server.Client/Tgstation.Server.Client.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj index 054656e7df..6bb5253884 100644 --- a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj +++ b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj @@ -3,7 +3,7 @@ netstandard2.0 Full - 4.0.0.0-preview9108 + 4.0.0.0-preview9109 true Cyberboss /tg/station 13 From bbd5a97582837ee7f5116ebe76c67a6c95641677 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 16 Sep 2018 13:13:40 -0400 Subject: [PATCH 108/230] Update API documentation --- docs/API.dox | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/API.dox b/docs/API.dox index 759c73ff56..c05ca4243d 100644 --- a/docs/API.dox +++ b/docs/API.dox @@ -24,7 +24,6 @@ Last off, if anything in this API doesn't seem to hold true when tested against This document will reference the canonical C# models in the @ref Tgstation.Server.Api.Models namespace. Note that these models are built to mirror the JSON requests and responses with a couple caveats. - The first letter of every field name will/must be lowercase in JSON models -- Fields in the C# model marked with "DenyWrite = true" cannot be changed using POST requests - Fields marked 'Required' should be ignored, this is a semantic for the backing SQL database - Id fields must be specified when making POST requests - All other fields are optional and may be absent from responses or requests unless otherwise specified @@ -37,7 +36,7 @@ TGS4 expects this set of headers. Failure to provide them may result in 400 erro - Accept: application/json - Api: Another product header value representing the version of the API to use. Currently this must be: Tgstation.Server.Api/4.0.0.0 -For POST and PUT requests you must also include the content type. Currently only json is supported +For POST, PATCH, and PUT requests you must also include the content type. Currently only json is supported - Content-Type: application/json @@ -448,7 +447,11 @@ To start the watchdog use the following request: I PUT "/DreamDaemon" Empty => @ref Tgstation.Server.Api.Models.Job -The returned job represents the startup process for the watchdog +To restart the watchdog use the following request: + +I PATCH "/DreamDaemon" Empty => @ref Tgstation.Server.Api.Models.Job + +The returned jobs represents the startup process for the watchdog To stop the watchdog use the following request: From 08aed5d904938ca1489d1180c8b6e748ea44d13e Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 16 Sep 2018 13:40:50 -0400 Subject: [PATCH 109/230] Remove redundant call to StopMonitor() --- src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index bbd159f5a4..6507c386f1 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -713,7 +713,6 @@ namespace Tgstation.Server.Host.Components.Watchdog if (startMonitor) { - await StopMonitor().ConfigureAwait(false); monitorCts = new CancellationTokenSource(); monitorTask = MonitorLifetimes(monitorCts.Token); } From f91fc8e982d0313a34267258db7b18ceeba04f5f Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 16 Sep 2018 13:41:26 -0400 Subject: [PATCH 110/230] Clear LastLaunchParameters on Watchdog termination --- src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index 6507c386f1..0a852be342 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -215,6 +215,7 @@ namespace Tgstation.Server.Host.Components.Watchdog var chatTask = announce ? chat.SendWatchdogMessage("Terminating...", cancellationToken) : Task.CompletedTask; await StopMonitor().ConfigureAwait(false); DisposeAndNullControllers(); + LastLaunchParameters = null; await chatTask.ConfigureAwait(false); return; } From 7db74b01fba147402abe0193fc87f3af2c3cabcc Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 16 Sep 2018 21:37:09 -0400 Subject: [PATCH 111/230] Fix executables not having +x set on Linux --- .../Components/Byond/PosixByondInstaller.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Byond/PosixByondInstaller.cs b/src/Tgstation.Server.Host/Components/Byond/PosixByondInstaller.cs index 19b62b0795..e26aab8da4 100644 --- a/src/Tgstation.Server.Host/Components/Byond/PosixByondInstaller.cs +++ b/src/Tgstation.Server.Host/Components/Byond/PosixByondInstaller.cs @@ -103,8 +103,11 @@ namespace Tgstation.Server.Host.Components.Byond //need to add $ORIGIN to LD_LIBRARY_PATH const string StandardScript = "#!/bin/sh\nexport LD_LIBRARY_PATH=\"\\$ORIGIN:$LD_LIBRARY_PATH\"\nBASEDIR=$(dirname \"$0\")\nexec \"$BASEDIR/{0}\" \"$@\"\n"; - var dreamDaemonScript = String.Format(CultureInfo.InvariantCulture, StandardScript, "DreamDaemon"); - var dreamMakerScript = String.Format(CultureInfo.InvariantCulture, StandardScript, "DreamMaker"); + const string DreamDaemonExecutableName = "DreamDaemon"; + const string DreamMakerExecutableName = "DreamMaker"; + + var dreamDaemonScript = String.Format(CultureInfo.InvariantCulture, StandardScript, DreamDaemonExecutableName); + var dreamMakerScript = String.Format(CultureInfo.InvariantCulture, StandardScript, DreamMakerExecutableName); async Task WriteAndMakeExecutable(string fullPath, string script) { @@ -114,7 +117,12 @@ namespace Tgstation.Server.Host.Components.Byond var basePath = ioManager.ConcatPath(path, ByondManager.BinPath); - return Task.WhenAll(WriteAndMakeExecutable(ioManager.ConcatPath(basePath, DreamDaemonName), dreamDaemonScript), WriteAndMakeExecutable(ioManager.ConcatPath(basePath, DreamMakerName), dreamMakerScript)); + var task = Task.WhenAll(WriteAndMakeExecutable(ioManager.ConcatPath(basePath, DreamDaemonName), dreamDaemonScript), WriteAndMakeExecutable(ioManager.ConcatPath(basePath, DreamMakerName), dreamMakerScript)); + + postWriteHandler.HandleWrite(ioManager.ConcatPath(basePath, DreamDaemonExecutableName)); + postWriteHandler.HandleWrite(ioManager.ConcatPath(basePath, DreamMakerExecutableName)); + + return task; } } } From e3701368907f6315e38493d17dcf7fc2761c22c1 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 16 Sep 2018 22:15:38 -0400 Subject: [PATCH 112/230] Adds logging when a CompileJob is loaded into the DmbFactory --- src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs b/src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs index c31ab7e326..a9fe99a2f8 100644 --- a/src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs +++ b/src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs @@ -125,6 +125,8 @@ namespace Tgstation.Server.Host.Components.Compiler if (job == null) throw new ArgumentNullException(nameof(job)); + logger.LogTrace("Loading compile job {0}...", job.Id); + CompileJob finalCompileJob = null; //now load the entire compile job tree await databaseContextFactory.UseContext(async db => finalCompileJob = await db.CompileJobs.Where(x => x.Id == job.Id) From 53af76cab08539ef02f30219b2bb3e8a8826ec77 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 16 Sep 2018 22:17:03 -0400 Subject: [PATCH 113/230] A compile job should always exist --- src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs b/src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs index a9fe99a2f8..0625499ac9 100644 --- a/src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs +++ b/src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs @@ -133,11 +133,7 @@ namespace Tgstation.Server.Host.Components.Compiler .Include(x => x.Job).ThenInclude(x => x.StartedBy) .Include(x => x.RevisionInformation).ThenInclude(x => x.PrimaryTestMerge).ThenInclude(x => x.MergedBy) .Include(x => x.RevisionInformation).ThenInclude(x => x.ActiveTestMerges).ThenInclude(x => x.TestMerge).ThenInclude(x => x.MergedBy) - .FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false)).ConfigureAwait(false); //can't wait to see that query - - if (finalCompileJob == null) - //lol git fucked - return; + .FirstAsync(cancellationToken).ConfigureAwait(false)).ConfigureAwait(false); //can't wait to see that query var newProvider = await FromCompileJob(finalCompileJob, cancellationToken).ConfigureAwait(false); if (newProvider == null) From 8fb0f3c35742e74d5fa056e47fc3b5792d5a1bf5 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 16 Sep 2018 22:32:02 -0400 Subject: [PATCH 114/230] Fix PostComplete jobs --- src/Tgstation.Server.Host/Core/JobManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Core/JobManager.cs b/src/Tgstation.Server.Host/Core/JobManager.cs index cb034f445a..304f2d0acd 100644 --- a/src/Tgstation.Server.Host/Core/JobManager.cs +++ b/src/Tgstation.Server.Host/Core/JobManager.cs @@ -114,7 +114,7 @@ namespace Tgstation.Server.Host.Core await databaseContext.Save(default).ConfigureAwait(false); - bool JobErroredOrCancelled() => job.ExceptionDetails != null || job.Cancelled.Value; + bool JobErroredOrCancelled() => job.ExceptionDetails != null || job.Cancelled == true; //ok so, now it's time for the post commit step if it exists if (!JobErroredOrCancelled() && job.PostComplete != null) From 0ae9825a247ee001c6f0c8308443f015e9510d2a Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 17 Sep 2018 10:46:09 -0400 Subject: [PATCH 115/230] Fix test merge job grammar --- src/Tgstation.Server.Host/Controllers/RepositoryController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index ce94498dab..4a2cbd5801 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -403,7 +403,7 @@ namespace Tgstation.Server.Host.Controllers description != null ? String.Format(CultureInfo.InvariantCulture, "{0} and t", description) : "T", String.Join(", ", model.NewTestMerges.Select(x => String.Format(CultureInfo.InvariantCulture, "#{0}{1}", x.Number, - x.PullRequestRevision != null ? String.Format(CultureInfo.InvariantCulture, "at {0}", x.PullRequestRevision.Substring(0, 7)) : String.Empty))), + x.PullRequestRevision != null ? String.Format(CultureInfo.InvariantCulture, " at {0}", x.PullRequestRevision.Substring(0, 7)) : String.Empty))), description != null ? String.Empty : " in repository"); if (description == null) From 8cd9b4d8e33d778cab4fe7ec4605f0dc48e3fb74 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 17 Sep 2018 10:47:26 -0400 Subject: [PATCH 116/230] Update Z.EntityFramework.Plus.EFCore to 1.8.10 --- src/Tgstation.Server.Host/Tgstation.Server.Host.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index 995a9d2978..cac195f632 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -46,7 +46,7 @@ - + From 5f5cd5cea425e0206c395ee695c9667505183eeb Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 17 Sep 2018 10:51:50 -0400 Subject: [PATCH 117/230] Bump project files properly to net core 2.1 --- build/test_core.sh | 12 ++++++------ .../Properties/launchSettings.json | 2 +- .../Tgstation.Server.Host.Console.csproj | 4 ++-- src/Tgstation.Server.Host.Watchdog/Watchdog.cs | 2 +- .../Tgstation.Server.Host.csproj | 4 ++-- .../Tgstation.Server.Api.Tests.csproj | 2 +- .../Tgstation.Server.Client.Tests.csproj | 2 +- .../Tgstation.Server.Host.Console.Tests.csproj | 2 +- .../Tgstation.Server.Host.Tests.csproj | 2 +- .../Tgstation.Server.Host.Watchdog.Tests.csproj | 2 +- .../Tgstation.Server.Tests.csproj | 2 +- 11 files changed, 18 insertions(+), 18 deletions(-) diff --git a/build/test_core.sh b/build/test_core.sh index 69257a6a81..071071c3ca 100755 --- a/build/test_core.sh +++ b/build/test_core.sh @@ -8,34 +8,34 @@ mkdir TestResults cd tests/Tgstation.Server.Api.Tests dotnet build -c $CONFIG -$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Api.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG --no-build" --format opencover --output "../../TestResults/api.xml" --include "[Tgstation.Server*]*" --exclude "[Tgstation.Server.Api.Tests*]*" +$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.1/Tgstation.Server.Api.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG --no-build" --format opencover --output "../../TestResults/api.xml" --include "[Tgstation.Server*]*" --exclude "[Tgstation.Server.Api.Tests*]*" cd ../Tgstation.Server.Client.Tests dotnet build -c $CONFIG -$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Client.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG --no-build" --format opencover --output "../../TestResults/client.xml" --include "[Tgstation.Server*]*" --exclude "[Tgstation.Server.Client.Tests*]*" +$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.1/Tgstation.Server.Client.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG --no-build" --format opencover --output "../../TestResults/client.xml" --include "[Tgstation.Server*]*" --exclude "[Tgstation.Server.Client.Tests*]*" cd ../Tgstation.Server.Host.Tests dotnet build -c $CONFIG -$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Host.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG --no-build" --format opencover --output "../../TestResults/host.xml" --include "[Tgstation.Server*]*" --exclude "[Tgstation.Server.Host.Tests*]*" --exclude "[Tgstation.Server.Host]Tgstation.Server.Host.Models.Migrations.*" +$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.1/Tgstation.Server.Host.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG --no-build" --format opencover --output "../../TestResults/host.xml" --include "[Tgstation.Server*]*" --exclude "[Tgstation.Server.Host.Tests*]*" --exclude "[Tgstation.Server.Host]Tgstation.Server.Host.Models.Migrations.*" cd ../Tgstation.Server.Host.Watchdog.Tests dotnet build -c $CONFIG -$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Host.Watchdog.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG --no-build" --format opencover --output "../../TestResults/watchdog.xml" --include "[Tgstation.Server*]*" --exclude "[Tgstation.Server.Host.Watchdog.Tests*]*" +$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.1/Tgstation.Server.Host.Watchdog.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG --no-build" --format opencover --output "../../TestResults/watchdog.xml" --include "[Tgstation.Server*]*" --exclude "[Tgstation.Server.Host.Watchdog.Tests*]*" cd ../Tgstation.Server.Host.Console.Tests dotnet build -c $CONFIG -$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Host.Console.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG --no-build" --format opencover --output "../../TestResults/console.xml" --include "[Tgstation.Server*]*" --exclude "[Tgstation.Server.Host.Console.Tests*]*" +$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.1/Tgstation.Server.Host.Console.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG --no-build" --format opencover --output "../../TestResults/console.xml" --include "[Tgstation.Server*]*" --exclude "[Tgstation.Server.Host.Console.Tests*]*" cd ../Tgstation.Server.Tests export TGS4_TEST_DATABASE_TYPE=MySql export TGS4_TEST_CONNECTION_STRING="server=127.0.0.1;uid=root;pwd=;database=tgs_test" #token set in CI settings dotnet build -c $CONFIG -$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.0/Tgstation.Server.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG --no-build" --format opencover --output "../../TestResults/server.xml" --include "[Tgstation.Server*]*" --exclude "[Tgstation.Server.Tests*]*" --exclude "[Tgstation.Server.Host]Tgstation.Server.Host.Models.Migrations.*" +$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.1/Tgstation.Server.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG --no-build" --format opencover --output "../../TestResults/server.xml" --include "[Tgstation.Server*]*" --exclude "[Tgstation.Server.Tests*]*" --exclude "[Tgstation.Server.Host]Tgstation.Server.Host.Models.Migrations.*" cd ../../TestResults diff --git a/src/Tgstation.Server.Host.Console/Properties/launchSettings.json b/src/Tgstation.Server.Host.Console/Properties/launchSettings.json index 5991274947..e88685dcca 100644 --- a/src/Tgstation.Server.Host.Console/Properties/launchSettings.json +++ b/src/Tgstation.Server.Host.Console/Properties/launchSettings.json @@ -10,7 +10,7 @@ "profiles": { "Tgstation.Server.Host.Console": { "commandName": "Project", - "workingDirectory": "bin\\Debug\\netcoreapp2.0", + "workingDirectory": "bin\\Debug\\netcoreapp2.1", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" diff --git a/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj b/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj index dbaae93de8..99e069d7fb 100644 --- a/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj +++ b/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp2.0 + netcoreapp2.1 Full 4.0.0.0 @@ -11,7 +11,7 @@ latest true - bin\Release\netcoreapp2.0\Tgstation.Server.Host.Console.xml + bin\Release\netcoreapp2.1\Tgstation.Server.Host.Console.xml diff --git a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs index bc81c91573..d69ff7ee72 100644 --- a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs @@ -75,7 +75,7 @@ namespace Tgstation.Server.Host.Watchdog Directory.Delete(assemblyStoragePath, true); Directory.CreateDirectory(defaultAssemblyPath); - var sourcePath = "../../../../Tgstation.Server.Host/bin/Debug/netcoreapp2.0"; + var sourcePath = "../../../../Tgstation.Server.Host/bin/Debug/netcoreapp2.1"; foreach (string dirPath in Directory.GetDirectories(sourcePath, "*", SearchOption.AllDirectories)) Directory.CreateDirectory(dirPath.Replace(sourcePath, defaultAssemblyPath)); diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index cac195f632..3c20773be9 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -1,7 +1,7 @@ - netcoreapp2.0 + netcoreapp2.1 Full 4.0.0.0 4.0.0.0 @@ -12,7 +12,7 @@ latest true - bin\Release\netstandard2.0\Tgstation.Server.Host.xml + bin\Release\netcoreapp2.1\Tgstation.Server.Host.xml 1701;1702;1705;CA2227 diff --git a/tests/Tgstation.Server.Api.Tests/Tgstation.Server.Api.Tests.csproj b/tests/Tgstation.Server.Api.Tests/Tgstation.Server.Api.Tests.csproj index 68b24d8e0d..c395583e37 100644 --- a/tests/Tgstation.Server.Api.Tests/Tgstation.Server.Api.Tests.csproj +++ b/tests/Tgstation.Server.Api.Tests/Tgstation.Server.Api.Tests.csproj @@ -1,7 +1,7 @@ - netcoreapp2.0 + netcoreapp2.1 false latest diff --git a/tests/Tgstation.Server.Client.Tests/Tgstation.Server.Client.Tests.csproj b/tests/Tgstation.Server.Client.Tests/Tgstation.Server.Client.Tests.csproj index b80fdac622..6eaed1daed 100644 --- a/tests/Tgstation.Server.Client.Tests/Tgstation.Server.Client.Tests.csproj +++ b/tests/Tgstation.Server.Client.Tests/Tgstation.Server.Client.Tests.csproj @@ -1,7 +1,7 @@ - netcoreapp2.0 + netcoreapp2.1 false latest diff --git a/tests/Tgstation.Server.Host.Console.Tests/Tgstation.Server.Host.Console.Tests.csproj b/tests/Tgstation.Server.Host.Console.Tests/Tgstation.Server.Host.Console.Tests.csproj index 9c47b270e9..1648915ec8 100644 --- a/tests/Tgstation.Server.Host.Console.Tests/Tgstation.Server.Host.Console.Tests.csproj +++ b/tests/Tgstation.Server.Host.Console.Tests/Tgstation.Server.Host.Console.Tests.csproj @@ -1,7 +1,7 @@ - netcoreapp2.0 + netcoreapp2.1 false latest diff --git a/tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj b/tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj index 18580c143d..3226a9a749 100644 --- a/tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj +++ b/tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj @@ -1,7 +1,7 @@ - netcoreapp2.0 + netcoreapp2.1 false latest diff --git a/tests/Tgstation.Server.Host.Watchdog.Tests/Tgstation.Server.Host.Watchdog.Tests.csproj b/tests/Tgstation.Server.Host.Watchdog.Tests/Tgstation.Server.Host.Watchdog.Tests.csproj index 472ff291ce..d2102ff8cf 100644 --- a/tests/Tgstation.Server.Host.Watchdog.Tests/Tgstation.Server.Host.Watchdog.Tests.csproj +++ b/tests/Tgstation.Server.Host.Watchdog.Tests/Tgstation.Server.Host.Watchdog.Tests.csproj @@ -1,7 +1,7 @@ - netcoreapp2.0 + netcoreapp2.1 false diff --git a/tests/Tgstation.Server.Tests/Tgstation.Server.Tests.csproj b/tests/Tgstation.Server.Tests/Tgstation.Server.Tests.csproj index 53e7e98c4d..044446ae80 100644 --- a/tests/Tgstation.Server.Tests/Tgstation.Server.Tests.csproj +++ b/tests/Tgstation.Server.Tests/Tgstation.Server.Tests.csproj @@ -1,7 +1,7 @@ - netcoreapp2.0 + netcoreapp2.1 false From 2ec0903150f94a7da79873fa3287506244751e5e Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 17 Sep 2018 15:06:21 -0400 Subject: [PATCH 118/230] Fix warnings --- .../Components/Chat/Providers/IrcProvider.cs | 4 ++-- src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs | 4 ++-- src/Tgstation.Server.Host/Components/Repository/Repository.cs | 2 +- src/Tgstation.Server.Host/Controllers/RepositoryController.cs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs index f8f6197396..fa7555c7de 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs @@ -258,9 +258,9 @@ namespace Tgstation.Server.Host.Components.Chat.Providers var recievedPlus = false; client.OnReadLine += (sender, e) => { - if (e.Line.Contains("ACK :sasl")) + if (e.Line.Contains("ACK :sasl", StringComparison.Ordinal)) recievedAck = true; - else if (e.Line.Contains("AUTHENTICATE +")) + else if (e.Line.Contains("AUTHENTICATE +", StringComparison.Ordinal)) recievedPlus = true; }; diff --git a/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs b/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs index 56836577f6..15c46067f8 100644 --- a/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs @@ -226,12 +226,12 @@ namespace Tgstation.Server.Host.Components.Compiler for (var I = 0; I < dmeLines.Count; ++I) { var line = dmeLines[I]; - if (line.Contains("BEGIN_INCLUDE") && dmeModifications.HeadIncludeLine != null) + if (line.Contains("BEGIN_INCLUDE", StringComparison.Ordinal) && dmeModifications.HeadIncludeLine != null) { dmeLines.Insert(I + 1, dmeModifications.HeadIncludeLine); ++I; } - else if (line.Contains("END_INCLUDE") && dmeModifications.TailIncludeLine != null) + else if (line.Contains("END_INCLUDE", StringComparison.Ordinal) && dmeModifications.TailIncludeLine != null) { dmeLines.Insert(I, dmeModifications.TailIncludeLine); break; diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs index e8f54cc2a2..afa1e36c5c 100644 --- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs @@ -97,7 +97,7 @@ namespace Tgstation.Server.Host.Components.Repository this.ioMananger = ioMananger ?? throw new ArgumentNullException(nameof(ioMananger)); this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer)); this.onDispose = onDispose ?? throw new ArgumentNullException(nameof(onDispose)); - IsGitHubRepository = Origin.ToUpperInvariant().Contains(GitHubUrl.ToUpperInvariant()); + IsGitHubRepository = Origin.Contains(GitHubUrl, StringComparison.InvariantCultureIgnoreCase); if (IsGitHubRepository) { GetRepositoryOwnerName(Origin, out var owner, out var name); diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index 4a2cbd5801..20f87f6cd1 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -146,7 +146,7 @@ namespace Tgstation.Server.Host.Controllers var uiOrigin = model.Origin.ToUpperInvariant(); var uiBad = BadGitHubUrl.ToUpperInvariant(); var uiGitHub = Components.Repository.Repository.GitHubUrl.ToUpperInvariant(); - if (uiOrigin.Contains(uiBad)) + if (uiOrigin.Contains(uiBad, StringComparison.Ordinal)) model.Origin = uiOrigin.Replace(uiBad, uiGitHub, StringComparison.Ordinal); currentModel.AccessToken = model.AccessToken; From 49658a64e8f9af2b283ccc4d5970a6e93ea72d47 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 17 Sep 2018 15:45:52 -0400 Subject: [PATCH 119/230] Adds security level stuff to the DMAPI --- src/DMAPI/tgs.dm | 11 ++++++++++- src/DMAPI/tgs/core/core.dm | 9 +++++++-- src/DMAPI/tgs/core/datum.dm | 5 ++++- src/DMAPI/tgs/v3210/api.dm | 5 ++++- src/DMAPI/tgs/v4/api.dm | 6 +++--- 5 files changed, 28 insertions(+), 8 deletions(-) diff --git a/src/DMAPI/tgs.dm b/src/DMAPI/tgs.dm index 6e55567544..7571d7f9a9 100644 --- a/src/DMAPI/tgs.dm +++ b/src/DMAPI/tgs.dm @@ -55,11 +55,16 @@ #define TGS_REBOOT_MODE_SHUTDOWN 1 #define TGS_REBOOT_MODE_RESTART 2 +#define TGS_SECURTY_TRUSTED 0 +#define TGS_SECURITY_SAFE 1 +#define TGS_SECURITY_ULTRASAFE 2 + //REQUIRED HOOKS //Call this somewhere in /world/New() that is always run //event_handler: optional user defined event handler. The default behaviour is to broadcast the event in english to all connected admin channels -/world/proc/TgsNew(datum/tgs_event_handler/event_handler) +//minimum_required_security_level: The minimum required security level to run the game in which the DMAPI is integrated +/world/proc/TgsNew(datum/tgs_event_handler/event_handler, minimum_required_security_level = TGS_SERCURITY_ULTRASAFE) return //Call this when your initializations are complete and your game is ready to play before any player interactions happen @@ -155,6 +160,10 @@ /world/proc/TgsRevision() return +//Get the current BYOND security level +/world/proc/TgsSecurityLevel() + return + //Gets a list of active `/datum/tgs_revision_information/test_merge`s /world/proc/TgsTestMerges() return diff --git a/src/DMAPI/tgs/core/core.dm b/src/DMAPI/tgs/core/core.dm index 1158fdbd34..e0495aba4e 100644 --- a/src/DMAPI/tgs/core/core.dm +++ b/src/DMAPI/tgs/core/core.dm @@ -1,4 +1,4 @@ -/world/TgsNew(datum/tgs_event_handler/event_handler) +/world/TgsNew(datum/tgs_event_handler/event_handler, minimum_required_security_level = TGS_SECURITY_ULTRASAFE) var/current_api = TGS_READ_GLOBAL(tgs) if(current_api) TGS_ERROR_LOG("TgsNew(): TGS API datum already set ([current_api])!") @@ -18,7 +18,7 @@ TGS_WRITE_GLOBAL(tgs, new_api) - var/result = new_api.OnWorldNew(event_handler ? event_handler : new /datum/tgs_event_handler/tgs_default) + var/result = new_api.OnWorldNew(event_handler ? event_handler : new /datum/tgs_event_handler/tgs_default, minimum_required_security_level) if(!result || result == TGS_UNIMPLEMENTED) TGS_WRITE_GLOBAL(tgs, null) TGS_ERROR_LOG("Failed to activate API!") @@ -127,6 +127,11 @@ if(api) api.ChatPrivateMessage(message, user) +/world/TgsSecurityLevel() + var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) + if(api) + api.SecurityLevel() + /* The MIT License diff --git a/src/DMAPI/tgs/core/datum.dm b/src/DMAPI/tgs/core/datum.dm index f81569136c..9ae5247989 100644 --- a/src/DMAPI/tgs/core/datum.dm +++ b/src/DMAPI/tgs/core/datum.dm @@ -42,10 +42,13 @@ TGS_PROTECT_DATUM(/datum/tgs_api) /datum/tgs_api/proc/ChatTargetedBroadcast(message, admin_only) return TGS_UNIMPLEMENTED - + /datum/tgs_api/proc/ChatPrivateMessage(message, admin_only) return TGS_UNIMPLEMENTED +/datum/tgs_api/proc/SecurityLevel() + return TGS_UNIMPLEMENTED + /* The MIT License diff --git a/src/DMAPI/tgs/v3210/api.dm b/src/DMAPI/tgs/v3210/api.dm index 1c04be9212..63bc0beb2b 100644 --- a/src/DMAPI/tgs/v3210/api.dm +++ b/src/DMAPI/tgs/v3210/api.dm @@ -56,7 +56,7 @@ /datum/tgs_api/v3210/proc/file2list(filename) return splittext(trim_left(trim_right(file2text(filename))), "\n") -/datum/tgs_api/v3210/OnWorldNew(datum/tgs_event_handler/event_handler) //don't use event handling in this version +/datum/tgs_api/v3210/OnWorldNew(datum/tgs_event_handler/event_handler, minimum_required_security_level) //don't use event handling in this version . = FALSE comms_key = world.params[SERVICE_WORLD_PARAM] @@ -191,6 +191,9 @@ /datum/tgs_api/v3210/ChatPrivateMessage(message, datum/tgs_chat_user/user) return TGS_UNIMPLEMENTED +/datum/tgs_api/v3210/SecurityLevel() + return TGS_SECURITY_TRUSTED + #undef REBOOT_MODE_NORMAL #undef REBOOT_MODE_HARD #undef REBOOT_MODE_SHUTDOWN diff --git a/src/DMAPI/tgs/v4/api.dm b/src/DMAPI/tgs/v4/api.dm index 92251d1e43..4fb8afc385 100644 --- a/src/DMAPI/tgs/v4/api.dm +++ b/src/DMAPI/tgs/v4/api.dm @@ -48,7 +48,7 @@ /datum/tgs_api/v4/ApiVersion() return "4.0.0.0" -/datum/tgs_api/v4/OnWorldNew(datum/tgs_event_handler/event_handler) +/datum/tgs_api/v4/OnWorldNew(datum/tgs_event_handler/event_handler, minimum_required_security_level) json_path = world.params[TGS4_PARAM_INFO_JSON] if(!json_path) TGS_ERROR_LOG("Missing [TGS4_PARAM_INFO_JSON] world parameter!") @@ -68,7 +68,7 @@ if(cached_json["apiValidateOnly"]) TGS_INFO_LOG("Validating API and exiting...") - Export(TGS4_COMM_VALIDATE) + Export(TGS4_COMM_VALIDATE, list(TGS4_PARAMETER_DATA = minimum_required_security_level)) del(world) chat_channels_json_path = cached_json["chatChannelsJson"] @@ -187,7 +187,7 @@ //request a new port export_lock = FALSE - var/list/new_port_json = Export(TGS4_COMM_NEW_PORT, list("current_port" = "[world.port]")) //stringify this on purpose + var/list/new_port_json = Export(TGS4_COMM_NEW_PORT, list(TGS4_PARAMETER_DATA = "[world.port]")) //stringify this on purpose if(!new_port_json) TGS_ERROR_LOG("No new port response from server![TGS4_PORT_CRITFAIL_MESSAGE]") From 5a0acf7266cff7a40c731bdbd1189feb0126db4c Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 17 Sep 2018 16:21:43 -0400 Subject: [PATCH 120/230] Add minimum security level validation to the compile step. Only serves as a one off warning at the moment. To be integrated with compile jobs and expanded upon at a later date. At the moment, this also allows for more verbosity during DMAPI validation. --- src/DMAPI/tgs/v4/api.dm | 2 +- .../Components/Compiler/DreamMaker.cs | 39 ++++++++++++---- .../Watchdog/ApiValidationStatus.cs | 33 +++++++++++++ .../Components/Watchdog/ISessionController.cs | 4 +- .../Components/Watchdog/SessionController.cs | 46 ++++++++++++++++--- .../Watchdog/SessionControllerFactory.cs | 4 +- 6 files changed, 106 insertions(+), 22 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/Watchdog/ApiValidationStatus.cs diff --git a/src/DMAPI/tgs/v4/api.dm b/src/DMAPI/tgs/v4/api.dm index 4fb8afc385..bf1f506f0a 100644 --- a/src/DMAPI/tgs/v4/api.dm +++ b/src/DMAPI/tgs/v4/api.dm @@ -68,7 +68,7 @@ if(cached_json["apiValidateOnly"]) TGS_INFO_LOG("Validating API and exiting...") - Export(TGS4_COMM_VALIDATE, list(TGS4_PARAMETER_DATA = minimum_required_security_level)) + Export(TGS4_COMM_VALIDATE, list(TGS4_PARAMETER_DATA = "[minimum_required_security_level]")) del(world) chat_channels_json_path = cached_json["chatChannelsJson"] diff --git a/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs b/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs index 15c46067f8..e8dd38d401 100644 --- a/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs @@ -125,8 +125,8 @@ namespace Tgstation.Server.Host.Components.Compiler /// The current /// The port to use for API validation /// The for the operation - /// A resulting in if the DMAPI was successfully validated, otherwise - async Task VerifyApi(uint timeout, DreamDaemonSecurity securityLevel, Models.CompileJob job, IByondExecutableLock byondLock, ushort portToUse, CancellationToken cancellationToken) + /// A representing the running operation + async Task VerifyApi(uint timeout, DreamDaemonSecurity securityLevel, Models.CompileJob job, IByondExecutableLock byondLock, ushort portToUse, CancellationToken cancellationToken) { logger.LogTrace("Verifying DMAPI..."); var launchParameters = new DreamDaemonLaunchParameters @@ -156,13 +156,27 @@ namespace Tgstation.Server.Host.Components.Compiler if (!controller.Lifetime.IsCompleted) { - logger.LogDebug("API validation timed out!"); - return false; + var validationStatus = controller.ApiValidationStatus; + logger.LogTrace("API validation status: {0}", validationStatus); + switch (validationStatus) + { + case ApiValidationStatus.Validated: + return; + case ApiValidationStatus.NeverValidated: + break; + case ApiValidationStatus.BadValidationRequest: + throw new JobException("Recieved an unrecognized API validation request from DreamDaemon!"); + case ApiValidationStatus.RequiresSafe: + throw new JobException("This game must be run with at least the 'Safe' DreamDaemon security level!"); + case ApiValidationStatus.RequiresTrusted: + throw new JobException("This game must be run with at least the 'Trusted' DreamDaemon security level!"); + case ApiValidationStatus.UnaskedValidationRequest: + default: + throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Session controller returned unexpected ApiValidationStatus: {0}", validationStatus)); + } } - var validated = controller.ApiValidated; - logger.LogTrace("API valid: {0}", validated); - return validated; + throw new JobException("DMAPI validation timed out!"); } } @@ -356,13 +370,18 @@ namespace Tgstation.Server.Host.Components.Compiler var exitCode = await RunDreamMaker(byondLock.DreamMakerPath, job, cancellationToken).ConfigureAwait(false); - var apiValidated = exitCode == 0 && await VerifyApi(apiValidateTimeout, securityLevel, job, byondLock, dreamMakerSettings.ApiValidationPort.Value, cancellationToken).ConfigureAwait(false); + try + { + if (exitCode != 0) + throw new JobException(String.Format(CultureInfo.InvariantCulture, "DM exited with a non-zero code: {0}{1}{2}", exitCode, Environment.NewLine, job.Output)); - if (!apiValidated) + await VerifyApi(apiValidateTimeout, securityLevel, job, byondLock, dreamMakerSettings.ApiValidationPort.Value, cancellationToken).ConfigureAwait(false); + } + catch (JobException) { //server never validated or compile failed await eventConsumer.HandleEvent(EventType.CompileFailure, new List { resolvedGameDirectory, exitCode == 0 ? "1" : "0" }, cancellationToken).ConfigureAwait(false); - throw new JobException(exitCode == 0 ? "Validation of the TGS api failed!" : String.Format(CultureInfo.InvariantCulture, "DM exited with a non-zero code: {0}{1}{2}", exitCode, Environment.NewLine, job.Output)); + throw; } logger.LogTrace("Running post compile event..."); diff --git a/src/Tgstation.Server.Host/Components/Watchdog/ApiValidationStatus.cs b/src/Tgstation.Server.Host/Components/Watchdog/ApiValidationStatus.cs new file mode 100644 index 0000000000..cc179c5271 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Watchdog/ApiValidationStatus.cs @@ -0,0 +1,33 @@ +namespace Tgstation.Server.Host.Components.Watchdog +{ + /// + /// Status of DMAPI validation + /// + enum ApiValidationStatus + { + /// + /// The DMAPI never contacted the server for validation + /// + NeverValidated, + /// + /// The server was contacted for validation but it was never requested + /// + UnaskedValidationRequest, + /// + /// The game must be run with a minimum security level of + /// + RequiresSafe, + /// + /// The game must be run with a security level of + /// + RequiresTrusted, + /// + /// The validation request was malformed + /// + BadValidationRequest, + /// + /// The DMAPI validated successfully + /// + Validated + } +} diff --git a/src/Tgstation.Server.Host/Components/Watchdog/ISessionController.cs b/src/Tgstation.Server.Host/Components/Watchdog/ISessionController.cs index 3f2e245eba..97e19b271d 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/ISessionController.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/ISessionController.cs @@ -25,9 +25,9 @@ namespace Tgstation.Server.Host.Components.Watchdog bool TerminationWasRequested { get; } /// - /// If the DMAPI was validated. This field may only be access once completes + /// The DMAPI /// - bool ApiValidated { get; } + ApiValidationStatus ApiValidationStatus { get; } /// /// The being used diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs index 20b1115531..969aed7866 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs @@ -29,13 +29,13 @@ namespace Tgstation.Server.Host.Components.Watchdog } /// - public bool ApiValidated + public ApiValidationStatus ApiValidationStatus { get { if (!Lifetime.IsCompleted) throw new InvalidOperationException("ApiValidated cannot be checked while Lifetime is incomplete!"); - return apiValidated; + return apiValidationStatus; } } @@ -126,6 +126,11 @@ namespace Tgstation.Server.Host.Components.Watchdog /// readonly ILogger logger; + /// + /// The level the was launched with + /// + readonly DreamDaemonSecurity? launchSecurityLevel; + /// /// The waits on when DreamDaemon currently has it's ports closed /// @@ -151,9 +156,9 @@ namespace Tgstation.Server.Host.Components.Watchdog bool disposed; /// - /// If the DMAPI was validated + /// The for the /// - bool apiValidated; + ApiValidationStatus apiValidationStatus; /// /// If should be kept alive instead @@ -171,8 +176,9 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The value of /// The value of /// The value of + /// The value of /// The optional time to wait before failing the - public SessionController(ReattachInformation reattachInformation, IProcess process, IByondExecutableLock byondLock, IByondTopicSender byondTopicSender, IJsonTrackingContext chatJsonTrackingContext, ICommContext interopContext, IChat chat, ILogger logger, uint? startupTimeout) + public SessionController(ReattachInformation reattachInformation, IProcess process, IByondExecutableLock byondLock, IByondTopicSender byondTopicSender, IJsonTrackingContext chatJsonTrackingContext, ICommContext interopContext, IChat chat, ILogger logger, DreamDaemonSecurity? launchSecurityLevel, uint? startupTimeout) { this.chatJsonTrackingContext = chatJsonTrackingContext; //null valid this.reattachInformation = reattachInformation ?? throw new ArgumentNullException(nameof(reattachInformation)); @@ -183,11 +189,13 @@ namespace Tgstation.Server.Host.Components.Watchdog this.chat = chat ?? throw new ArgumentNullException(nameof(chat)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + this.launchSecurityLevel = launchSecurityLevel; + interopContext.RegisterHandler(this); portClosedForReboot = false; disposed = false; - apiValidated = false; + apiValidationStatus = ApiValidationStatus.NeverValidated; released = false; rebootTcs = new TaskCompletionSource(); @@ -289,6 +297,7 @@ namespace Tgstation.Server.Host.Components.Watchdog { /////UHHHH logger.LogWarning("DreamDaemon sent new port command without providing it's own!"); + content = new ErrorMessage { Message = "Missing stringified port as data parameter!" }; break; } @@ -314,7 +323,30 @@ namespace Tgstation.Server.Host.Components.Watchdog } break; case Constants.DMCommandApiValidate: - apiValidated = true; + if (!launchSecurityLevel.HasValue) + { + logger.LogWarning("DreamDaemon requested API validation but no intial security level was passed to the session controller!"); + apiValidationStatus = ApiValidationStatus.UnaskedValidationRequest; + content = new ErrorMessage { Message = "Invalid API validation request!" }; + break; + } + if (!query.TryGetValue(Constants.DMParameterData, out var stringMinimumSecurityLevel) || !Enum.TryParse(stringMinimumSecurityLevel, out var minimumSecurityLevel)) + apiValidationStatus = ApiValidationStatus.BadValidationRequest; + else + switch (minimumSecurityLevel) + { + case DreamDaemonSecurity.Safe: + apiValidationStatus = launchSecurityLevel == DreamDaemonSecurity.Ultrasafe ? ApiValidationStatus.RequiresSafe : ApiValidationStatus.Validated; + break; + case DreamDaemonSecurity.Ultrasafe: + apiValidationStatus = ApiValidationStatus.Validated; + break; + case DreamDaemonSecurity.Trusted: + apiValidationStatus = launchSecurityLevel == DreamDaemonSecurity.Trusted ? ApiValidationStatus.Validated : ApiValidationStatus.RequiresTrusted; + break; + default: + throw new InvalidOperationException("Enum.TryParse failed to validate the DreamDaemonSecurity range!"); + } break; case Constants.DMCommandWorldReboot: if (ClosePortOnReboot) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs index fad899d6e7..b2f4399c13 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs @@ -200,7 +200,7 @@ namespace Tgstation.Server.Host.Components.Watchdog ChatChannelsJson = interopInfo.ChatChannelsJson, ChatCommandsJson = interopInfo.ChatCommandsJson, ServerCommandsJson = interopInfo.ServerCommandsJson, - }, process, byondLock, byondTopicSender, chatJsonTrackingContext, context, chat, loggerFactory.CreateLogger(), launchParameters.StartupTimeout); + }, process, byondLock, byondTopicSender, chatJsonTrackingContext, context, chat, loggerFactory.CreateLogger(), launchParameters.SecurityLevel, launchParameters.StartupTimeout); } catch { @@ -247,7 +247,7 @@ namespace Tgstation.Server.Host.Components.Watchdog var process = processExecutor.GetProcess(reattachInformation.ProcessId); try { - return new SessionController(reattachInformation, process, byondLock, byondTopicSender, chatJsonTrackingContext, context, chat, loggerFactory.CreateLogger(), null); + return new SessionController(reattachInformation, process, byondLock, byondTopicSender, chatJsonTrackingContext, context, chat, loggerFactory.CreateLogger(), null, null); } catch { From f3c8a91f5e6104852dacbd06f74004c5cf6877be Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 17 Sep 2018 16:24:06 -0400 Subject: [PATCH 121/230] Remove stray whitespace --- src/DMAPI/tgs/core/datum.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DMAPI/tgs/core/datum.dm b/src/DMAPI/tgs/core/datum.dm index 9ae5247989..b2f9b19cdd 100644 --- a/src/DMAPI/tgs/core/datum.dm +++ b/src/DMAPI/tgs/core/datum.dm @@ -42,7 +42,7 @@ TGS_PROTECT_DATUM(/datum/tgs_api) /datum/tgs_api/proc/ChatTargetedBroadcast(message, admin_only) return TGS_UNIMPLEMENTED - + /datum/tgs_api/proc/ChatPrivateMessage(message, admin_only) return TGS_UNIMPLEMENTED From ad9667bcc371b29431224efc580fffb8159ecb0b Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 17 Sep 2018 18:03:25 -0400 Subject: [PATCH 122/230] Fix misspelling of TGS_SECURITY_ULTRASAFE --- src/DMAPI/tgs.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DMAPI/tgs.dm b/src/DMAPI/tgs.dm index 7571d7f9a9..c3a4937495 100644 --- a/src/DMAPI/tgs.dm +++ b/src/DMAPI/tgs.dm @@ -64,7 +64,7 @@ //Call this somewhere in /world/New() that is always run //event_handler: optional user defined event handler. The default behaviour is to broadcast the event in english to all connected admin channels //minimum_required_security_level: The minimum required security level to run the game in which the DMAPI is integrated -/world/proc/TgsNew(datum/tgs_event_handler/event_handler, minimum_required_security_level = TGS_SERCURITY_ULTRASAFE) +/world/proc/TgsNew(datum/tgs_event_handler/event_handler, minimum_required_security_level = TGS_SECURITY_ULTRASAFE) return //Call this when your initializations are complete and your game is ready to play before any player interactions happen From 23956b7bdead8cb3b0b0901b045432110232550c Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 17 Sep 2018 18:11:03 -0400 Subject: [PATCH 123/230] Fix misspelling of TGS_SECURITY_TRUSTED --- src/DMAPI/tgs.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DMAPI/tgs.dm b/src/DMAPI/tgs.dm index c3a4937495..6645ce51ff 100644 --- a/src/DMAPI/tgs.dm +++ b/src/DMAPI/tgs.dm @@ -55,7 +55,7 @@ #define TGS_REBOOT_MODE_SHUTDOWN 1 #define TGS_REBOOT_MODE_RESTART 2 -#define TGS_SECURTY_TRUSTED 0 +#define TGS_SECURITY_TRUSTED 0 #define TGS_SECURITY_SAFE 1 #define TGS_SECURITY_ULTRASAFE 2 From 965ce32064f18912642935f39c626fa203e24c74 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 17 Sep 2018 21:40:41 -0400 Subject: [PATCH 124/230] Set minimum security level in CompileJobs --- src/Tgstation.Server.Api/Models/DreamMaker.cs | 6 +++++ .../Models/Internal/CompileJob.cs | 7 +++++ .../Components/Compiler/DreamMaker.cs | 27 ++++++++++++------- .../Components/Compiler/IDreamMaker.cs | 3 +-- .../Components/Instance.cs | 3 +-- .../Watchdog/ApiValidationStatus.cs | 20 +++++++------- .../Components/Watchdog/SessionController.cs | 6 ++--- .../Controllers/InstanceController.cs | 3 ++- 8 files changed, 47 insertions(+), 28 deletions(-) diff --git a/src/Tgstation.Server.Api/Models/DreamMaker.cs b/src/Tgstation.Server.Api/Models/DreamMaker.cs index 690cc90e09..bb821fdc63 100644 --- a/src/Tgstation.Server.Api/Models/DreamMaker.cs +++ b/src/Tgstation.Server.Api/Models/DreamMaker.cs @@ -17,5 +17,11 @@ namespace Tgstation.Server.Api.Models /// [Required] public ushort? ApiValidationPort { get; set; } + + /// + /// The level used to validate the DMAPI + /// + [Required] + public DreamDaemonSecurity? ApiValidationSecurityLevel { get; set; } } } diff --git a/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs b/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs index bcd2801a51..e101ff7f23 100644 --- a/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs +++ b/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel.DataAnnotations; namespace Tgstation.Server.Api.Models.Internal { @@ -26,5 +27,11 @@ namespace Tgstation.Server.Api.Models.Internal /// The Game folder the results were compiled into /// public Guid? DirectoryName { get; set; } + + /// + /// The minimum required to run the 's output + /// + [Required] + public DreamDaemonSecurity? MinimumSecurityLevel { get; set; } } } diff --git a/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs b/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs index e8dd38d401..8b0304625f 100644 --- a/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs @@ -160,22 +160,29 @@ namespace Tgstation.Server.Host.Components.Compiler logger.LogTrace("API validation status: {0}", validationStatus); switch (validationStatus) { - case ApiValidationStatus.Validated: + case ApiValidationStatus.RequiresUltrasafe: + job.MinimumSecurityLevel = DreamDaemonSecurity.Ultrasafe; + return; + case ApiValidationStatus.RequiresSafe: + if (securityLevel == DreamDaemonSecurity.Ultrasafe) + throw new JobException("This game must be run with at least the 'Safe' DreamDaemon security level!"); + job.MinimumSecurityLevel = DreamDaemonSecurity.Safe; + return; + case ApiValidationStatus.RequiresTrusted: + if (securityLevel != DreamDaemonSecurity.Trusted) + throw new JobException("This game must be run with at least the 'Trusted' DreamDaemon security level!"); + job.MinimumSecurityLevel = DreamDaemonSecurity.Trusted; return; case ApiValidationStatus.NeverValidated: break; case ApiValidationStatus.BadValidationRequest: throw new JobException("Recieved an unrecognized API validation request from DreamDaemon!"); - case ApiValidationStatus.RequiresSafe: - throw new JobException("This game must be run with at least the 'Safe' DreamDaemon security level!"); - case ApiValidationStatus.RequiresTrusted: - throw new JobException("This game must be run with at least the 'Trusted' DreamDaemon security level!"); case ApiValidationStatus.UnaskedValidationRequest: default: throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Session controller returned unexpected ApiValidationStatus: {0}", validationStatus)); } } - + throw new JobException("DMAPI validation timed out!"); } } @@ -257,7 +264,7 @@ namespace Tgstation.Server.Host.Components.Compiler } /// - public async Task Compile(Models.RevisionInformation revisionInformation, Api.Models.DreamMaker dreamMakerSettings, DreamDaemonSecurity securityLevel, uint apiValidateTimeout, IRepository repository, CancellationToken cancellationToken) + public async Task Compile(Models.RevisionInformation revisionInformation, Api.Models.DreamMaker dreamMakerSettings, uint apiValidateTimeout, IRepository repository, CancellationToken cancellationToken) { if (revisionInformation == null) throw new ArgumentNullException(nameof(revisionInformation)); @@ -268,8 +275,8 @@ namespace Tgstation.Server.Host.Components.Compiler if (repository == null) throw new ArgumentNullException(nameof(repository)); - if (securityLevel == DreamDaemonSecurity.Ultrasafe) - throw new ArgumentOutOfRangeException(nameof(securityLevel), securityLevel, "Cannot compile with ultrasafe security!"); + if (dreamMakerSettings.ApiValidationSecurityLevel == DreamDaemonSecurity.Ultrasafe) + throw new ArgumentOutOfRangeException(nameof(dreamMakerSettings), dreamMakerSettings, "Cannot compile with ultrasafe security!"); logger.LogTrace("Begin Compile"); @@ -375,7 +382,7 @@ namespace Tgstation.Server.Host.Components.Compiler if (exitCode != 0) throw new JobException(String.Format(CultureInfo.InvariantCulture, "DM exited with a non-zero code: {0}{1}{2}", exitCode, Environment.NewLine, job.Output)); - await VerifyApi(apiValidateTimeout, securityLevel, job, byondLock, dreamMakerSettings.ApiValidationPort.Value, cancellationToken).ConfigureAwait(false); + await VerifyApi(apiValidateTimeout, dreamMakerSettings.ApiValidationSecurityLevel.Value, job, byondLock, dreamMakerSettings.ApiValidationPort.Value, cancellationToken).ConfigureAwait(false); } catch (JobException) { diff --git a/src/Tgstation.Server.Host/Components/Compiler/IDreamMaker.cs b/src/Tgstation.Server.Host/Components/Compiler/IDreamMaker.cs index dce21e8194..83f83849fa 100644 --- a/src/Tgstation.Server.Host/Components/Compiler/IDreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/Compiler/IDreamMaker.cs @@ -15,11 +15,10 @@ namespace Tgstation.Server.Host.Components.Compiler /// /// The being compiled from the /// The for the compile - /// The level allowed for API validation /// The time in seconds to wait while validating the API /// The to copy from /// The for the operation /// A resulting in the partially populated for the operation. In particular, note the field will only have it's field populated - Task Compile(Models.RevisionInformation revisionInformation, Api.Models.DreamMaker dreamMakerSettings, DreamDaemonSecurity securityLevel, uint apiValidateTimeout, IRepository repository, CancellationToken cancellationToken); + Task Compile(Models.RevisionInformation revisionInformation, Api.Models.DreamMaker dreamMakerSettings, uint apiValidateTimeout, IRepository repository, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index 8f7b0e7700..79faad2b45 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -132,7 +132,6 @@ namespace Tgstation.Server.Host.Components var ddSettingsTask = databaseContext.DreamDaemonSettings.Where(x => x.InstanceId == metadata.Id).Select(x => new DreamDaemonSettings { StartupTimeout = x.StartupTimeout, - SecurityLevel = x.SecurityLevel }).FirstOrDefaultAsync(cancellationToken); var dreamMakerSettings = await databaseContext.DreamMakerSettings.Where(x => x.InstanceId == metadata.Id).FirstAsync(cancellationToken).ConfigureAwait(false); @@ -167,7 +166,7 @@ namespace Tgstation.Server.Host.Components databaseContext.Instances.Attach(revInfo.Instance); } - compileJob = await DreamMaker.Compile(revInfo, dreamMakerSettings, ddSettings.SecurityLevel.Value, ddSettings.StartupTimeout.Value, repo, cancellationToken).ConfigureAwait(false); + compileJob = await DreamMaker.Compile(revInfo, dreamMakerSettings, ddSettings.StartupTimeout.Value, repo, cancellationToken).ConfigureAwait(false); } compileJob.Job = job; diff --git a/src/Tgstation.Server.Host/Components/Watchdog/ApiValidationStatus.cs b/src/Tgstation.Server.Host/Components/Watchdog/ApiValidationStatus.cs index cc179c5271..46e282802d 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/ApiValidationStatus.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/ApiValidationStatus.cs @@ -14,20 +14,20 @@ /// UnaskedValidationRequest, /// - /// The game must be run with a minimum security level of - /// - RequiresSafe, - /// - /// The game must be run with a security level of - /// - RequiresTrusted, - /// /// The validation request was malformed /// BadValidationRequest, /// - /// The DMAPI validated successfully + /// Valid API. The game must be run with a minimum security level of /// - Validated + RequiresSafe, + /// + /// Valid API. The game must be run with a security level of + /// + RequiresTrusted, + /// + /// Valid API. The game must be run with a minimum security level of + /// + RequiresUltrasafe } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs index 969aed7866..b71d7f0c62 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs @@ -336,13 +336,13 @@ namespace Tgstation.Server.Host.Components.Watchdog switch (minimumSecurityLevel) { case DreamDaemonSecurity.Safe: - apiValidationStatus = launchSecurityLevel == DreamDaemonSecurity.Ultrasafe ? ApiValidationStatus.RequiresSafe : ApiValidationStatus.Validated; + apiValidationStatus = ApiValidationStatus.RequiresSafe; break; case DreamDaemonSecurity.Ultrasafe: - apiValidationStatus = ApiValidationStatus.Validated; + apiValidationStatus = ApiValidationStatus.RequiresUltrasafe; break; case DreamDaemonSecurity.Trusted: - apiValidationStatus = launchSecurityLevel == DreamDaemonSecurity.Trusted ? ApiValidationStatus.Validated : ApiValidationStatus.RequiresTrusted; + apiValidationStatus = ApiValidationStatus.RequiresTrusted; break; default: throw new InvalidOperationException("Enum.TryParse failed to validate the DreamDaemonSecurity range!"); diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs index 3545f1ce8a..aafd0da61e 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs @@ -136,7 +136,8 @@ namespace Tgstation.Server.Host.Controllers }, DreamMakerSettings = new DreamMakerSettings { - ApiValidationPort = 1339 + ApiValidationPort = 1339, + ApiValidationSecurityLevel = DreamDaemonSecurity.Safe }, Name = model.Name, Online = false, From 11c594843ddb343b9140e1f13a7f3c3479a311a8 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 17 Sep 2018 21:59:26 -0400 Subject: [PATCH 125/230] Limit the maximum DreamDaemonSecurity level to that of the CompileJob's in SessionControllerFactory --- .../Components/Compiler/DreamMaker.cs | 2 ++ .../Watchdog/SessionControllerFactory.cs | 18 +++++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs b/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs index 8b0304625f..97cdcc8519 100644 --- a/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs @@ -138,6 +138,8 @@ namespace Tgstation.Server.Host.Components.Compiler }; var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName); + + job.MinimumSecurityLevel = securityLevel; //needed for the TempDmbProvider var provider = new TemporaryDmbProvider(ioManager.ResolvePath(dirA), String.Concat(job.DmeName, DmbExtension), job); var timeoutAt = DateTimeOffset.Now.AddSeconds(timeout); diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs index b2f4399c13..b5ec781ba0 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs @@ -177,12 +177,28 @@ namespace Tgstation.Server.Host.Components.Watchdog //more sanitization here cause it uses the same scheme var parameters = String.Format(CultureInfo.InvariantCulture, "{2}={0}&{3}={1}", byondTopicSender.SanitizeString(application.Version.ToString()), byondTopicSender.SanitizeString(interopJsonFile), byondTopicSender.SanitizeString(Constants.DMParamHostVersion), byondTopicSender.SanitizeString(Constants.DMParamInfoJson)); + var securityLevelToUse = launchParameters.SecurityLevel.Value; + switch (dmbProvider.CompileJob.MinimumSecurityLevel) + { + case DreamDaemonSecurity.Ultrasafe: + break; + case DreamDaemonSecurity.Safe: + if (securityLevelToUse == DreamDaemonSecurity.Ultrasafe) + securityLevelToUse = DreamDaemonSecurity.Safe; + break; + case DreamDaemonSecurity.Trusted: + securityLevelToUse = DreamDaemonSecurity.Trusted; + break; + default: + throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid DreamDaemonSecurity value: {0}", dmbProvider.CompileJob.MinimumSecurityLevel)); + } + //important to run on all ports to allow port changing var arguments = String.Format(CultureInfo.InvariantCulture, "{0} -port {1} -ports 1-65535 {2}-close -{3} -verbose -public -params \"{4}\"", dmbProvider.DmbName, primaryPort ? launchParameters.PrimaryPort : launchParameters.SecondaryPort, launchParameters.AllowWebClient.Value ? "-webclient " : String.Empty, - SecurityWord(launchParameters.SecurityLevel.Value), + SecurityWord(securityLevelToUse), parameters); //launch dd From 4d89dcdbfee290f97bde099f8ea3f7de758cbfef Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 17 Sep 2018 22:15:52 -0400 Subject: [PATCH 126/230] Adds AddMinimumSecurity migration --- ...20180906143029_MYInitialCreate.Designer.cs | 1 - ...918020726_MYAddMinimumSecurity.Designer.cs | 648 +++++++++++++++++ .../20180918020726_MYAddMinimumSecurity.cs | 45 ++ ...918021228_MSAddMinimumSecurity.Designer.cs | 676 ++++++++++++++++++ .../20180918021228_MSAddMinimumSecurity.cs | 45 ++ .../MySqlDatabaseContextModelSnapshot.cs | 7 +- .../SqlServerDatabaseContextModelSnapshot.cs | 6 +- 7 files changed, 1424 insertions(+), 4 deletions(-) create mode 100644 src/Tgstation.Server.Host/Models/Migrations/20180918020726_MYAddMinimumSecurity.Designer.cs create mode 100644 src/Tgstation.Server.Host/Models/Migrations/20180918020726_MYAddMinimumSecurity.cs create mode 100644 src/Tgstation.Server.Host/Models/Migrations/20180918021228_MSAddMinimumSecurity.Designer.cs create mode 100644 src/Tgstation.Server.Host/Models/Migrations/20180918021228_MSAddMinimumSecurity.cs diff --git a/src/Tgstation.Server.Host/Models/Migrations/20180906143029_MYInitialCreate.Designer.cs b/src/Tgstation.Server.Host/Models/Migrations/20180906143029_MYInitialCreate.Designer.cs index 73566bba2d..3c1a424266 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/20180906143029_MYInitialCreate.Designer.cs +++ b/src/Tgstation.Server.Host/Models/Migrations/20180906143029_MYInitialCreate.Designer.cs @@ -3,7 +3,6 @@ using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Tgstation.Server.Host.Models.Migrations { diff --git a/src/Tgstation.Server.Host/Models/Migrations/20180918020726_MYAddMinimumSecurity.Designer.cs b/src/Tgstation.Server.Host/Models/Migrations/20180918020726_MYAddMinimumSecurity.Designer.cs new file mode 100644 index 0000000000..bf112bf7b2 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/Migrations/20180918020726_MYAddMinimumSecurity.Designer.cs @@ -0,0 +1,648 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Tgstation.Server.Host.Models; + +namespace Tgstation.Server.Host.Migrations +{ + [DbContext(typeof(MySqlDatabaseContext))] + [Migration("20180918020726_MYAddMinimumSecurity")] + partial class MYAddMinimumSecurity + { + /// + /// Builds the target model + /// + /// The to use + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.3-rtm-32065") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConnectionString") + .IsRequired(); + + b.Property("Enabled"); + + b.Property("InstanceId"); + + b.Property("Name") + .IsRequired(); + + b.Property("Provider"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("ChatBots"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChatSettingsId"); + + b.Property("DiscordChannelId"); + + b.Property("IrcChannel"); + + b.Property("IsAdminChannel") + .IsRequired(); + + b.Property("IsUpdatesChannel") + .IsRequired(); + + b.Property("IsWatchdogChannel") + .IsRequired(); + + b.Property("Tag"); + + b.HasKey("Id"); + + b.HasIndex("ChatSettingsId", "DiscordChannelId") + .IsUnique(); + + b.HasIndex("ChatSettingsId", "IrcChannel") + .IsUnique(); + + b.ToTable("ChatChannels"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ByondVersion") + .IsRequired(); + + b.Property("DirectoryName"); + + b.Property("DmeName"); + + b.Property("JobId"); + + b.Property("MinimumSecurityLevel"); + + b.Property("Output"); + + b.Property("RevisionInformationId"); + + b.HasKey("Id"); + + b.HasIndex("DirectoryName"); + + b.HasIndex("JobId"); + + b.HasIndex("RevisionInformationId"); + + b.ToTable("CompileJobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessToken"); + + b.Property("AllowWebClient") + .IsRequired(); + + b.Property("AutoStart") + .IsRequired(); + + b.Property("InstanceId"); + + b.Property("PrimaryPort") + .IsRequired(); + + b.Property("ProcessId"); + + b.Property("SecondaryPort") + .IsRequired(); + + b.Property("SecurityLevel"); + + b.Property("SoftRestart") + .IsRequired(); + + b.Property("SoftShutdown") + .IsRequired(); + + b.Property("StartupTimeout") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamDaemonSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ApiValidationPort") + .IsRequired(); + + b.Property("ApiValidationSecurityLevel"); + + b.Property("InstanceId"); + + b.Property("ProjectName"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamMakerSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoUpdateInterval") + .IsRequired(); + + b.Property("ConfigurationType"); + + b.Property("Name") + .IsRequired(); + + b.Property("Online") + .IsRequired(); + + b.Property("Path") + .IsRequired(); + + b.Property("WatchdogReattachInformationId"); + + b.HasKey("Id"); + + b.HasIndex("Path") + .IsUnique(); + + b.HasIndex("WatchdogReattachInformationId"); + + b.ToTable("Instances"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ByondRights"); + + b.Property("ChatBotRights"); + + b.Property("ConfigurationRights"); + + b.Property("DreamDaemonRights"); + + b.Property("DreamMakerRights"); + + b.Property("InstanceId"); + + b.Property("InstanceUserRights"); + + b.Property("RepositoryRights"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("UserId", "InstanceId") + .IsUnique(); + + b.ToTable("InstanceUsers"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CancelRight"); + + b.Property("CancelRightsType"); + + b.Property("Cancelled") + .IsRequired(); + + b.Property("CancelledById"); + + b.Property("Description") + .IsRequired(); + + b.Property("ExceptionDetails"); + + b.Property("InstanceId"); + + b.Property("StartedAt") + .IsRequired(); + + b.Property("StartedById"); + + b.Property("StoppedAt"); + + b.HasKey("Id"); + + b.HasIndex("CancelledById"); + + b.HasIndex("InstanceId"); + + b.HasIndex("StartedById"); + + b.ToTable("Jobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessIdentifier") + .IsRequired(); + + b.Property("ChatChannelsJson") + .IsRequired(); + + b.Property("ChatCommandsJson") + .IsRequired(); + + b.Property("CompileJobId"); + + b.Property("IsPrimary"); + + b.Property("Port"); + + b.Property("ProcessId"); + + b.Property("RebootState"); + + b.Property("ServerCommandsJson") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("CompileJobId"); + + b.ToTable("ReattachInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessToken"); + + b.Property("AccessUser"); + + b.Property("AutoUpdatesKeepTestMerges") + .IsRequired(); + + b.Property("AutoUpdatesSynchronize") + .IsRequired(); + + b.Property("CommitterEmail") + .IsRequired(); + + b.Property("CommitterName") + .IsRequired(); + + b.Property("InstanceId"); + + b.Property("PushTestMergeCommits") + .IsRequired(); + + b.Property("ShowTestMergeCommitters") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("RepositorySettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("RevisionInformationId"); + + b.Property("TestMergeId"); + + b.HasKey("Id"); + + b.HasIndex("RevisionInformationId"); + + b.HasIndex("TestMergeId"); + + b.ToTable("RevInfoTestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CommitSha") + .IsRequired() + .HasMaxLength(40); + + b.Property("InstanceId"); + + b.Property("OriginCommitSha") + .IsRequired() + .HasMaxLength(40); + + b.HasKey("Id"); + + b.HasIndex("CommitSha") + .IsUnique(); + + b.HasIndex("InstanceId"); + + b.ToTable("RevisionInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Author") + .IsRequired(); + + b.Property("BodyAtMerge") + .IsRequired(); + + b.Property("Comment"); + + b.Property("MergedAt"); + + b.Property("MergedById"); + + b.Property("Number") + .IsRequired(); + + b.Property("PrimaryRevisionInformationId"); + + b.Property("PullRequestRevision") + .IsRequired(); + + b.Property("TitleAtMerge") + .IsRequired(); + + b.Property("Url") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("MergedById"); + + b.HasIndex("PrimaryRevisionInformationId") + .IsUnique(); + + b.ToTable("TestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AdministrationRights"); + + b.Property("CanonicalName") + .IsRequired(); + + b.Property("CreatedAt") + .IsRequired(); + + b.Property("CreatedById"); + + b.Property("Enabled") + .IsRequired(); + + b.Property("InstanceManagerRights"); + + b.Property("LastPasswordUpdate"); + + b.Property("Name") + .IsRequired(); + + b.Property("PasswordHash"); + + b.Property("SystemIdentifier"); + + b.HasKey("Id"); + + b.HasIndex("CanonicalName") + .IsUnique(); + + b.HasIndex("CreatedById"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.WatchdogReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AlphaId"); + + b.Property("AlphaIsActive"); + + b.Property("BravoId"); + + b.HasKey("Id"); + + b.HasIndex("AlphaId"); + + b.HasIndex("BravoId"); + + b.ToTable("WatchdogReattachInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("ChatSettings") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings") + .WithMany("Channels") + .HasForeignKey("ChatSettingsId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.HasOne("Tgstation.Server.Host.Models.Job", "Job") + .WithMany() + .HasForeignKey("JobId"); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("CompileJobs") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamDaemonSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamMakerSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.HasOne("Tgstation.Server.Host.Models.WatchdogReattachInformation", "WatchdogReattachInformation") + .WithMany() + .HasForeignKey("WatchdogReattachInformationId"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("InstanceUsers") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User") + .WithMany("InstanceUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CancelledBy") + .WithMany() + .HasForeignKey("CancelledById"); + + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("Jobs") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy") + .WithMany() + .HasForeignKey("StartedById") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") + .WithMany() + .HasForeignKey("CompileJobId"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("RepositorySettings") + .HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("ActiveTestMerges") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge") + .WithMany("RevisonInformations") + .HasForeignKey("TestMergeId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("RevisionInformations") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy") + .WithMany("TestMerges") + .HasForeignKey("MergedById") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation") + .WithOne("PrimaryTestMerge") + .HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId") + .OnDelete(DeleteBehavior.SetNull); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy") + .WithMany("CreatedUsers") + .HasForeignKey("CreatedById"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.WatchdogReattachInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.ReattachInformation", "Alpha") + .WithMany() + .HasForeignKey("AlphaId"); + + b.HasOne("Tgstation.Server.Host.Models.ReattachInformation", "Bravo") + .WithMany() + .HasForeignKey("BravoId"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Tgstation.Server.Host/Models/Migrations/20180918020726_MYAddMinimumSecurity.cs b/src/Tgstation.Server.Host/Models/Migrations/20180918020726_MYAddMinimumSecurity.cs new file mode 100644 index 0000000000..13a103ac32 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/Migrations/20180918020726_MYAddMinimumSecurity.cs @@ -0,0 +1,45 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Tgstation.Server.Api.Models; + +namespace Tgstation.Server.Host.Migrations +{ + /// + /// Add the and columns for MySQL/MariaDB + /// + public partial class MYAddMinimumSecurity : Migration + { + /// + /// Applies the migration + /// + /// The to use + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ApiValidationSecurityLevel", + table: "DreamMakerSettings", + nullable: false, + defaultValue: (int)DreamDaemonSecurity.Safe); + + migrationBuilder.AddColumn( + name: "MinimumSecurityLevel", + table: "CompileJobs", + nullable: false, + defaultValue: (int)DreamDaemonSecurity.Safe); + } + + /// + /// Unapplies the migration + /// + /// The to use + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "ApiValidationSecurityLevel", + table: "DreamMakerSettings"); + + migrationBuilder.DropColumn( + name: "MinimumSecurityLevel", + table: "CompileJobs"); + } + } +} diff --git a/src/Tgstation.Server.Host/Models/Migrations/20180918021228_MSAddMinimumSecurity.Designer.cs b/src/Tgstation.Server.Host/Models/Migrations/20180918021228_MSAddMinimumSecurity.Designer.cs new file mode 100644 index 0000000000..0abec67697 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/Migrations/20180918021228_MSAddMinimumSecurity.Designer.cs @@ -0,0 +1,676 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Tgstation.Server.Host.Models; + +namespace Tgstation.Server.Host.Migrations +{ + [DbContext(typeof(SqlServerDatabaseContext))] + [Migration("20180918021228_MSAddMinimumSecurity")] + partial class MSAddMinimumSecurity + { + /// + /// Builds the target model + /// + /// The to use + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.3-rtm-32065") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ConnectionString") + .IsRequired(); + + b.Property("Enabled"); + + b.Property("InstanceId"); + + b.Property("Name") + .IsRequired(); + + b.Property("Provider"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("ChatBots"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ChatSettingsId"); + + b.Property("DiscordChannelId") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("IrcChannel"); + + b.Property("IsAdminChannel") + .IsRequired(); + + b.Property("IsUpdatesChannel") + .IsRequired(); + + b.Property("IsWatchdogChannel") + .IsRequired(); + + b.Property("Tag"); + + b.HasKey("Id"); + + b.HasIndex("ChatSettingsId", "DiscordChannelId") + .IsUnique() + .HasFilter("[DiscordChannelId] IS NOT NULL"); + + b.HasIndex("ChatSettingsId", "IrcChannel") + .IsUnique() + .HasFilter("[IrcChannel] IS NOT NULL"); + + b.ToTable("ChatChannels"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ByondVersion") + .IsRequired(); + + b.Property("DirectoryName"); + + b.Property("DmeName"); + + b.Property("JobId"); + + b.Property("MinimumSecurityLevel"); + + b.Property("Output"); + + b.Property("RevisionInformationId"); + + b.HasKey("Id"); + + b.HasIndex("DirectoryName"); + + b.HasIndex("JobId"); + + b.HasIndex("RevisionInformationId"); + + b.ToTable("CompileJobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AccessToken"); + + b.Property("AllowWebClient") + .IsRequired(); + + b.Property("AutoStart") + .IsRequired(); + + b.Property("InstanceId"); + + b.Property("PrimaryPort"); + + b.Property("ProcessId"); + + b.Property("SecondaryPort"); + + b.Property("SecurityLevel"); + + b.Property("SoftRestart") + .IsRequired(); + + b.Property("SoftShutdown") + .IsRequired(); + + b.Property("StartupTimeout"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamDaemonSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ApiValidationPort"); + + b.Property("ApiValidationSecurityLevel"); + + b.Property("InstanceId"); + + b.Property("ProjectName"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamMakerSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AutoUpdateInterval"); + + b.Property("ConfigurationType"); + + b.Property("Name") + .IsRequired(); + + b.Property("Online") + .IsRequired(); + + b.Property("Path") + .IsRequired(); + + b.Property("WatchdogReattachInformationId"); + + b.HasKey("Id"); + + b.HasIndex("Path") + .IsUnique(); + + b.HasIndex("WatchdogReattachInformationId"); + + b.ToTable("Instances"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ByondRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("ChatBotRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("ConfigurationRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("DreamDaemonRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("DreamMakerRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("InstanceId"); + + b.Property("InstanceUserRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("RepositoryRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("UserId", "InstanceId") + .IsUnique(); + + b.ToTable("InstanceUsers"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CancelRight") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("CancelRightsType") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("Cancelled") + .IsRequired(); + + b.Property("CancelledById"); + + b.Property("Description") + .IsRequired(); + + b.Property("ExceptionDetails"); + + b.Property("InstanceId"); + + b.Property("StartedAt") + .IsRequired(); + + b.Property("StartedById"); + + b.Property("StoppedAt"); + + b.HasKey("Id"); + + b.HasIndex("CancelledById"); + + b.HasIndex("InstanceId"); + + b.HasIndex("StartedById"); + + b.ToTable("Jobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AccessIdentifier") + .IsRequired(); + + b.Property("ChatChannelsJson") + .IsRequired(); + + b.Property("ChatCommandsJson") + .IsRequired(); + + b.Property("CompileJobId"); + + b.Property("IsPrimary"); + + b.Property("Port"); + + b.Property("ProcessId"); + + b.Property("RebootState"); + + b.Property("ServerCommandsJson") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("CompileJobId"); + + b.ToTable("ReattachInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AccessToken"); + + b.Property("AccessUser"); + + b.Property("AutoUpdatesKeepTestMerges") + .IsRequired(); + + b.Property("AutoUpdatesSynchronize") + .IsRequired(); + + b.Property("CommitterEmail") + .IsRequired(); + + b.Property("CommitterName") + .IsRequired(); + + b.Property("InstanceId"); + + b.Property("PushTestMergeCommits") + .IsRequired(); + + b.Property("ShowTestMergeCommitters") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("RepositorySettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("RevisionInformationId"); + + b.Property("TestMergeId"); + + b.HasKey("Id"); + + b.HasIndex("RevisionInformationId"); + + b.HasIndex("TestMergeId"); + + b.ToTable("RevInfoTestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CommitSha") + .IsRequired() + .HasMaxLength(40); + + b.Property("InstanceId"); + + b.Property("OriginCommitSha") + .IsRequired() + .HasMaxLength(40); + + b.HasKey("Id"); + + b.HasIndex("CommitSha") + .IsUnique(); + + b.HasIndex("InstanceId"); + + b.ToTable("RevisionInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Author") + .IsRequired(); + + b.Property("BodyAtMerge") + .IsRequired(); + + b.Property("Comment"); + + b.Property("MergedAt"); + + b.Property("MergedById"); + + b.Property("Number") + .IsRequired(); + + b.Property("PrimaryRevisionInformationId"); + + b.Property("PullRequestRevision") + .IsRequired(); + + b.Property("TitleAtMerge") + .IsRequired(); + + b.Property("Url") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("MergedById"); + + b.HasIndex("PrimaryRevisionInformationId") + .IsUnique() + .HasFilter("[PrimaryRevisionInformationId] IS NOT NULL"); + + b.ToTable("TestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AdministrationRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("CanonicalName") + .IsRequired(); + + b.Property("CreatedAt") + .IsRequired(); + + b.Property("CreatedById"); + + b.Property("Enabled") + .IsRequired(); + + b.Property("InstanceManagerRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("LastPasswordUpdate"); + + b.Property("Name") + .IsRequired(); + + b.Property("PasswordHash"); + + b.Property("SystemIdentifier"); + + b.HasKey("Id"); + + b.HasIndex("CanonicalName") + .IsUnique(); + + b.HasIndex("CreatedById"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.WatchdogReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AlphaId"); + + b.Property("AlphaIsActive"); + + b.Property("BravoId"); + + b.HasKey("Id"); + + b.HasIndex("AlphaId"); + + b.HasIndex("BravoId"); + + b.ToTable("WatchdogReattachInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("ChatSettings") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings") + .WithMany("Channels") + .HasForeignKey("ChatSettingsId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.HasOne("Tgstation.Server.Host.Models.Job", "Job") + .WithMany() + .HasForeignKey("JobId"); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("CompileJobs") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamDaemonSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamMakerSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.HasOne("Tgstation.Server.Host.Models.WatchdogReattachInformation", "WatchdogReattachInformation") + .WithMany() + .HasForeignKey("WatchdogReattachInformationId"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("InstanceUsers") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User") + .WithMany("InstanceUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CancelledBy") + .WithMany() + .HasForeignKey("CancelledById"); + + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("Jobs") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy") + .WithMany() + .HasForeignKey("StartedById") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") + .WithMany() + .HasForeignKey("CompileJobId"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("RepositorySettings") + .HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("ActiveTestMerges") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge") + .WithMany("RevisonInformations") + .HasForeignKey("TestMergeId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("RevisionInformations") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy") + .WithMany("TestMerges") + .HasForeignKey("MergedById") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation") + .WithOne("PrimaryTestMerge") + .HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId") + .OnDelete(DeleteBehavior.SetNull); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy") + .WithMany("CreatedUsers") + .HasForeignKey("CreatedById"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.WatchdogReattachInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.ReattachInformation", "Alpha") + .WithMany() + .HasForeignKey("AlphaId"); + + b.HasOne("Tgstation.Server.Host.Models.ReattachInformation", "Bravo") + .WithMany() + .HasForeignKey("BravoId"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Tgstation.Server.Host/Models/Migrations/20180918021228_MSAddMinimumSecurity.cs b/src/Tgstation.Server.Host/Models/Migrations/20180918021228_MSAddMinimumSecurity.cs new file mode 100644 index 0000000000..78c8da9d83 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/Migrations/20180918021228_MSAddMinimumSecurity.cs @@ -0,0 +1,45 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Tgstation.Server.Api.Models; + +namespace Tgstation.Server.Host.Migrations +{ + /// + /// Add the and columns for MSSQL + /// + public partial class MSAddMinimumSecurity : Migration + { + /// + /// Applies the migration + /// + /// The to use + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ApiValidationSecurityLevel", + table: "DreamMakerSettings", + nullable: false, + defaultValue: (int)DreamDaemonSecurity.Safe); + + migrationBuilder.AddColumn( + name: "MinimumSecurityLevel", + table: "CompileJobs", + nullable: false, + defaultValue: (int)DreamDaemonSecurity.Safe); + } + + /// + /// Unapplies the migration + /// + /// The to use + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "ApiValidationSecurityLevel", + table: "DreamMakerSettings"); + + migrationBuilder.DropColumn( + name: "MinimumSecurityLevel", + table: "CompileJobs"); + } + } +} diff --git a/src/Tgstation.Server.Host/Models/Migrations/MySqlDatabaseContextModelSnapshot.cs b/src/Tgstation.Server.Host/Models/Migrations/MySqlDatabaseContextModelSnapshot.cs index 7c82e23050..7658150957 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/MySqlDatabaseContextModelSnapshot.cs +++ b/src/Tgstation.Server.Host/Models/Migrations/MySqlDatabaseContextModelSnapshot.cs @@ -2,7 +2,6 @@ using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Tgstation.Server.Host.Models.Migrations { @@ -13,7 +12,7 @@ namespace Tgstation.Server.Host.Models.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "2.1.2-rtm-30932") + .HasAnnotation("ProductVersion", "2.1.3-rtm-32065") .HasAnnotation("Relational:MaxIdentifierLength", 64); modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => @@ -90,6 +89,8 @@ namespace Tgstation.Server.Host.Models.Migrations b.Property("JobId"); + b.Property("MinimumSecurityLevel"); + b.Property("Output"); b.Property("RevisionInformationId"); @@ -155,6 +156,8 @@ namespace Tgstation.Server.Host.Models.Migrations b.Property("ApiValidationPort") .IsRequired(); + b.Property("ApiValidationSecurityLevel"); + b.Property("InstanceId"); b.Property("ProjectName"); diff --git a/src/Tgstation.Server.Host/Models/Migrations/SqlServerDatabaseContextModelSnapshot.cs b/src/Tgstation.Server.Host/Models/Migrations/SqlServerDatabaseContextModelSnapshot.cs index a17096d672..d8ea3e4706 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/SqlServerDatabaseContextModelSnapshot.cs +++ b/src/Tgstation.Server.Host/Models/Migrations/SqlServerDatabaseContextModelSnapshot.cs @@ -14,7 +14,7 @@ namespace Tgstation.Server.Host.Models.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "2.1.2-rtm-30932") + .HasAnnotation("ProductVersion", "2.1.3-rtm-32065") .HasAnnotation("Relational:MaxIdentifierLength", 128) .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); @@ -98,6 +98,8 @@ namespace Tgstation.Server.Host.Models.Migrations b.Property("JobId"); + b.Property("MinimumSecurityLevel"); + b.Property("Output"); b.Property("RevisionInformationId"); @@ -161,6 +163,8 @@ namespace Tgstation.Server.Host.Models.Migrations b.Property("ApiValidationPort"); + b.Property("ApiValidationSecurityLevel"); + b.Property("InstanceId"); b.Property("ProjectName"); From b8f797b8e51e710b83ebb5768c2faf649b17bd83 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 17 Sep 2018 22:22:19 -0400 Subject: [PATCH 127/230] Handle pushing clamped DreamDaemonSecurity levels back to the user without modifying the db --- src/Tgstation.Server.Api/Models/DreamDaemon.cs | 2 +- .../Components/Watchdog/ISessionControllerFactory.cs | 2 +- src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs | 2 +- .../Components/Watchdog/SessionControllerFactory.cs | 7 ++++++- src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs | 2 +- .../Controllers/DreamDaemonController.cs | 7 ++++--- 6 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/Tgstation.Server.Api/Models/DreamDaemon.cs b/src/Tgstation.Server.Api/Models/DreamDaemon.cs index f78ffd2f15..ac982cf3d0 100644 --- a/src/Tgstation.Server.Api/Models/DreamDaemon.cs +++ b/src/Tgstation.Server.Api/Models/DreamDaemon.cs @@ -23,7 +23,7 @@ namespace Tgstation.Server.Api.Models public bool? Running { get; set; } /// - /// The current of + /// The current of . May be downgraded due to requirements of /// public DreamDaemonSecurity? CurrentSecurity { get; set; } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/ISessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/ISessionControllerFactory.cs index 645af10171..4873ce5b21 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/ISessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/ISessionControllerFactory.cs @@ -13,7 +13,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// /// Create a from a freshly launch DreamDaemon instance /// - /// The to use + /// The to use. will be updated with the minumum required security level for the launch /// The to use /// The current if any /// If the of should be used diff --git a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs index 6c8b65b05f..2002597284 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs @@ -56,7 +56,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// /// Changes the . If currently triggers a graceful restart /// - /// The new + /// The new . May be modified /// The for the operation /// A representing the running operation Task ChangeSettings(DreamDaemonLaunchParameters launchParameters, CancellationToken cancellationToken); diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs index b5ec781ba0..dae993db92 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs @@ -206,7 +206,7 @@ namespace Tgstation.Server.Host.Components.Watchdog try { //return the session controller for it - return new SessionController(new ReattachInformation + var result = new SessionController(new ReattachInformation { AccessIdentifier = accessIdentifier, Dmb = dmbProvider, @@ -217,6 +217,11 @@ namespace Tgstation.Server.Host.Components.Watchdog ChatCommandsJson = interopInfo.ChatCommandsJson, ServerCommandsJson = interopInfo.ServerCommandsJson, }, process, byondLock, byondTopicSender, chatJsonTrackingContext, context, chat, loggerFactory.CreateLogger(), launchParameters.SecurityLevel, launchParameters.StartupTimeout); + + //writeback launch parameter's fixed security level + launchParameters.SecurityLevel = securityLevelToUse; + + return result; } catch { diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index 0a852be342..28a1eeb60c 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -150,7 +150,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The value of /// The value of /// The value of - /// The initial value of + /// The initial value of . May be modified /// The value of /// The value of public Watchdog(IChat chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, IServerControl serverUpdater, ILogger logger, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IEventConsumer eventConsumer, IJobManager jobManager, DreamDaemonLaunchParameters initialLaunchParameters, Api.Models.Instance instance, bool autoStart) diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs index 34d8158a05..85d7841d8c 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs @@ -196,10 +196,11 @@ namespace Tgstation.Server.Host.Controllers return BadRequest(new ErrorMessage { Message = "Primary port and secondary port cannot be the same!" }); var wd = instanceManager.GetInstance(Instance).Watchdog; - - //run these in parallel because they are equally as important - await Task.WhenAll(DatabaseContext.Save(cancellationToken), wd.ChangeSettings(current, cancellationToken)).ConfigureAwait(false); + await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); + //run this second because current may be modified by it + await wd.ChangeSettings(current, cancellationToken).ConfigureAwait(false); + if (!oldSoftRestart.Value && current.SoftRestart.Value) await wd.Restart(true, cancellationToken).ConfigureAwait(false); else if (!oldSoftShutdown.Value && current.SoftShutdown.Value) From 486008cc4e6b433fd42b678ef600b80203a10afc Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 17 Sep 2018 22:26:44 -0400 Subject: [PATCH 128/230] Add launch security level to interop JSON --- .../Components/Interop/JsonFile.cs | 8 ++++- .../Watchdog/SessionControllerFactory.cs | 33 ++++++++++--------- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Interop/JsonFile.cs b/src/Tgstation.Server.Host/Components/Interop/JsonFile.cs index 4c318c21c3..b7e5da7d3a 100644 --- a/src/Tgstation.Server.Host/Components/Interop/JsonFile.cs +++ b/src/Tgstation.Server.Host/Components/Interop/JsonFile.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Host.Components.Interop @@ -41,7 +42,12 @@ namespace Tgstation.Server.Host.Components.Interop /// /// The of the launch /// - public RevisionInformation Revision { get; set; } + public Api.Models.Internal.RevisionInformation Revision { get; set; } + + /// + /// The level of the launch + /// + public DreamDaemonSecurity SecurityLevel { get; set; } /// /// The s in the launch diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs index dae993db92..9e4601e314 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs @@ -131,6 +131,22 @@ namespace Tgstation.Server.Host.Components.Watchdog //i changed this back from guids, hopefully i don't regret that string JsonFile(string name) => String.Format(CultureInfo.InvariantCulture, "{0}.{1}", name, JsonPostfix); + var securityLevelToUse = launchParameters.SecurityLevel.Value; + switch (dmbProvider.CompileJob.MinimumSecurityLevel) + { + case DreamDaemonSecurity.Ultrasafe: + break; + case DreamDaemonSecurity.Safe: + if (securityLevelToUse == DreamDaemonSecurity.Ultrasafe) + securityLevelToUse = DreamDaemonSecurity.Safe; + break; + case DreamDaemonSecurity.Trusted: + securityLevelToUse = DreamDaemonSecurity.Trusted; + break; + default: + throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid DreamDaemonSecurity value: {0}", dmbProvider.CompileJob.MinimumSecurityLevel)); + } + //setup interop files var interopInfo = new JsonFile { @@ -140,6 +156,7 @@ namespace Tgstation.Server.Host.Components.Watchdog ChatCommandsJson = JsonFile("chat_commands"), ServerCommandsJson = JsonFile("server_commands"), InstanceName = instance.Name, + SecurityLevel = securityLevelToUse, Revision = new Api.Models.Internal.RevisionInformation { CommitSha = dmbProvider.CompileJob.RevisionInformation.CommitSha, @@ -177,22 +194,6 @@ namespace Tgstation.Server.Host.Components.Watchdog //more sanitization here cause it uses the same scheme var parameters = String.Format(CultureInfo.InvariantCulture, "{2}={0}&{3}={1}", byondTopicSender.SanitizeString(application.Version.ToString()), byondTopicSender.SanitizeString(interopJsonFile), byondTopicSender.SanitizeString(Constants.DMParamHostVersion), byondTopicSender.SanitizeString(Constants.DMParamInfoJson)); - var securityLevelToUse = launchParameters.SecurityLevel.Value; - switch (dmbProvider.CompileJob.MinimumSecurityLevel) - { - case DreamDaemonSecurity.Ultrasafe: - break; - case DreamDaemonSecurity.Safe: - if (securityLevelToUse == DreamDaemonSecurity.Ultrasafe) - securityLevelToUse = DreamDaemonSecurity.Safe; - break; - case DreamDaemonSecurity.Trusted: - securityLevelToUse = DreamDaemonSecurity.Trusted; - break; - default: - throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid DreamDaemonSecurity value: {0}", dmbProvider.CompileJob.MinimumSecurityLevel)); - } - //important to run on all ports to allow port changing var arguments = String.Format(CultureInfo.InvariantCulture, "{0} -port {1} -ports 1-65535 {2}-close -{3} -verbose -public -params \"{4}\"", dmbProvider.DmbName, From db186b4457591ed9503b2b4031732e1898f7a5ce Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 17 Sep 2018 22:28:50 -0400 Subject: [PATCH 129/230] Add security level support to the V4 DMAPI --- src/DMAPI/tgs/v4/api.dm | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/DMAPI/tgs/v4/api.dm b/src/DMAPI/tgs/v4/api.dm index bf1f506f0a..b5bc692f11 100644 --- a/src/DMAPI/tgs/v4/api.dm +++ b/src/DMAPI/tgs/v4/api.dm @@ -32,6 +32,7 @@ var/chat_commands_json_path var/server_commands_json_path var/reboot_mode = TGS_REBOOT_MODE_NORMAL + var/security_level var/list/intercepted_message_queue @@ -63,7 +64,6 @@ return access_identifier = cached_json["accessIdentifier"] - instance_name = text2num(cached_json["instanceName"]) server_commands_json_path = cached_json["serverCommandsJson"] if(cached_json["apiValidateOnly"]) @@ -71,6 +71,7 @@ Export(TGS4_COMM_VALIDATE, list(TGS4_PARAMETER_DATA = "[minimum_required_security_level]")) del(world) + security_level = cached_json["securityLevel"] chat_channels_json_path = cached_json["chatChannelsJson"] chat_commands_json_path = cached_json["chatCommandsJson"] src.event_handler = event_handler @@ -295,6 +296,9 @@ channel.custom_tag = channel_json["tag"] return channel +/datum/tgs_api/v4/SecurityLevel() + return security_level + /* The MIT License From 3b4aa9a162faea58023c1130e691236b3660bca1 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 17 Sep 2018 23:07:16 -0400 Subject: [PATCH 130/230] Fix off by shebang --- src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs b/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs index 97cdcc8519..adaf2a55ef 100644 --- a/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs @@ -156,7 +156,7 @@ namespace Tgstation.Server.Host.Components.Compiler cancellationToken.ThrowIfCancellationRequested(); } - if (!controller.Lifetime.IsCompleted) + if (controller.Lifetime.IsCompleted) { var validationStatus = controller.ApiValidationStatus; logger.LogTrace("API validation status: {0}", validationStatus); From cd658c7b291063dc0b802f916015c3b8a5de6993 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 17 Sep 2018 23:08:06 -0400 Subject: [PATCH 131/230] Remove redundant log --- src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs b/src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs index 0625499ac9..1c3a909b98 100644 --- a/src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs +++ b/src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs @@ -125,8 +125,6 @@ namespace Tgstation.Server.Host.Components.Compiler if (job == null) throw new ArgumentNullException(nameof(job)); - logger.LogTrace("Loading compile job {0}...", job.Id); - CompileJob finalCompileJob = null; //now load the entire compile job tree await databaseContextFactory.UseContext(async db => finalCompileJob = await db.CompileJobs.Where(x => x.Id == job.Id) From ce42715f22179f27b39bec064b0d41eec83169d0 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 17 Sep 2018 23:11:57 -0400 Subject: [PATCH 132/230] Buff nuget preview versions --- src/Tgstation.Server.Api/Tgstation.Server.Api.csproj | 2 +- src/Tgstation.Server.Client/Tgstation.Server.Client.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj index ea5523602b..d3a57d45bd 100644 --- a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj +++ b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj @@ -17,7 +17,7 @@ 4.0.0.0 json web api tgstation-server tgstation ss13 byond Prototype release - 4.0.0.0-preview6003 + 4.0.0.0-preview6004 diff --git a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj index 6bb5253884..97763f5f38 100644 --- a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj +++ b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj @@ -3,7 +3,7 @@ netstandard2.0 Full - 4.0.0.0-preview9109 + 4.0.0.0-preview9110 true Cyberboss /tg/station 13 From ce70e8bcabe8121633a8b627e1c1604add48dc61 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 17 Sep 2018 23:22:36 -0400 Subject: [PATCH 133/230] Add SetSecurityLevel right and prevent setting ultrasafe mode --- src/Tgstation.Server.Api/Rights/DreamMakerRights.cs | 6 +++++- .../Controllers/DreamMakerController.cs | 12 +++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs b/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs index 0c4612b834..e9b04645f4 100644 --- a/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs +++ b/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs @@ -35,6 +35,10 @@ namespace Tgstation.Server.Api.Rights /// /// User may list and read all s /// - CompileJobs = 32 + CompileJobs = 32, + /// + /// User may modify + /// + SetSecurityLevel = 64 } } diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs index 1f4a9bb81c..cb147a5e79 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs @@ -101,12 +101,15 @@ namespace Tgstation.Server.Host.Controllers } /// - [TgsAuthorize(DreamMakerRights.SetDme | DreamMakerRights.SetApiValidationPort)] + [TgsAuthorize(DreamMakerRights.SetDme | DreamMakerRights.SetApiValidationPort | DreamMakerRights.SetApiValidationPort)] public override async Task Update([FromBody] DreamMaker model, CancellationToken cancellationToken) { if (model.ApiValidationPort == 0) return BadRequest(new ErrorMessage { Message = "API Validation port cannot be 0!" }); + if (model.ApiValidationSecurityLevel == DreamDaemonSecurity.Ultrasafe) + return BadRequest(new ErrorMessage { Message = "This version of TGS does not support the ultrasafe DreamDaemon configuration!" }); + var hostModel = await DatabaseContext.DreamMakerSettings.Where(x => x.InstanceId == Instance.Id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); if (hostModel == null) return StatusCode((int)HttpStatusCode.Gone); @@ -128,6 +131,13 @@ namespace Tgstation.Server.Host.Controllers hostModel.ApiValidationPort = model.ApiValidationPort; } + if (model.ApiValidationSecurityLevel.HasValue) + { + if (!AuthenticationContext.InstanceUser.DreamMakerRights.Value.HasFlag(DreamMakerRights.SetSecurityLevel)) + return Forbid(); + hostModel.ApiValidationSecurityLevel = model.ApiValidationSecurityLevel; + } + await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); return await Read(cancellationToken).ConfigureAwait(false); } From 5fe97a9a06da256268f3e60c49bdd50b9961b280 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 17 Sep 2018 23:35:34 -0400 Subject: [PATCH 134/230] Fix build --- src/Tgstation.Server.Host/Components/Interop/JsonFile.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Interop/JsonFile.cs b/src/Tgstation.Server.Host/Components/Interop/JsonFile.cs index b7e5da7d3a..10eebe5c13 100644 --- a/src/Tgstation.Server.Host/Components/Interop/JsonFile.cs +++ b/src/Tgstation.Server.Host/Components/Interop/JsonFile.cs @@ -40,7 +40,7 @@ namespace Tgstation.Server.Host.Components.Interop public string ServerCommandsJson { get; set; } /// - /// The of the launch + /// The of the launch /// public Api.Models.Internal.RevisionInformation Revision { get; set; } From 4f99568d412d8b965fcfbce71fcb29b1d3a8a42f Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 18 Sep 2018 10:40:14 -0400 Subject: [PATCH 135/230] Replace IServiceProvider parameter in job execution with IDatabaseContext --- .../Components/IInstance.cs | 5 +- .../Components/Instance.cs | 14 ++- .../Components/Watchdog/Watchdog.cs | 2 +- .../Controllers/ByondController.cs | 2 +- .../Controllers/DreamDaemonController.cs | 4 +- .../Controllers/InstanceController.cs | 2 +- .../Controllers/RepositoryController.cs | 17 ++-- src/Tgstation.Server.Host/Core/IJobManager.cs | 4 +- src/Tgstation.Server.Host/Core/JobManager.cs | 86 ++++++++----------- 9 files changed, 59 insertions(+), 77 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/IInstance.cs b/src/Tgstation.Server.Host/Components/IInstance.cs index 921a408266..d90826f9b4 100644 --- a/src/Tgstation.Server.Host/Components/IInstance.cs +++ b/src/Tgstation.Server.Host/Components/IInstance.cs @@ -4,7 +4,6 @@ using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Host.Components.Byond; using Tgstation.Server.Host.Components.Chat; -using Tgstation.Server.Host.Components.Compiler; using Tgstation.Server.Host.Components.Repository; using Tgstation.Server.Host.Components.StaticFiles; using Tgstation.Server.Host.Components.Watchdog; @@ -65,10 +64,10 @@ namespace Tgstation.Server.Host.Components /// Run the compile job and insert it into the database. Meant to be called by a /// /// The running - /// The for the operation + /// The for the operation /// The to report compilation progress /// The for the operation /// A representing the running operation - Task CompileProcess(Job job, IServiceProvider serviceProvider, Action progressReporter, CancellationToken cancellationToken); + Task CompileProcess(Job job, IDatabaseContext databaseContext, Action progressReporter, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index 79faad2b45..708725c349 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -1,5 +1,4 @@ using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using System; using System.Linq; @@ -117,18 +116,16 @@ namespace Tgstation.Server.Host.Components } /// - public async Task CompileProcess(Job job, IServiceProvider serviceProvider, Action progressReporter, CancellationToken cancellationToken) + public async Task CompileProcess(Job job, IDatabaseContext databaseContext, Action progressReporter, CancellationToken cancellationToken) { //DO NOT FOLLOW THE SUGGESTION FOR A THROW EXPRESSION HERE if (job == null) throw new ArgumentNullException(nameof(job)); - if (serviceProvider == null) - throw new ArgumentNullException(nameof(serviceProvider)); + if (databaseContext == null) + throw new ArgumentNullException(nameof(databaseContext)); if (progressReporter == null) throw new ArgumentNullException(nameof(progressReporter)); - var databaseContext = serviceProvider.GetRequiredService(); - var ddSettingsTask = databaseContext.DreamDaemonSettings.Where(x => x.InstanceId == metadata.Id).Select(x => new DreamDaemonSettings { StartupTimeout = x.StartupTimeout, @@ -205,10 +202,9 @@ namespace Tgstation.Server.Host.Components }; var noRepo = false; - await jobManager.RegisterOperation(repositoryUpdateJob, async (paramJob, serviceProvider, progressReporter, jobCancellationToken) => + await jobManager.RegisterOperation(repositoryUpdateJob, async (paramJob, databaseContext, progressReporter, jobCancellationToken) => { - var db = serviceProvider.GetRequiredService(); - var repositorySettingsTask = db.RepositorySettings.Where(x => x.InstanceId == metadata.Id).FirstAsync(jobCancellationToken); + var repositorySettingsTask = databaseContext.RepositorySettings.Where(x => x.InstanceId == metadata.Id).FirstAsync(jobCancellationToken); //assume 5 steps with synchronize const int ProgressSections = 5; diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index 28a1eeb60c..412612e49c 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -836,7 +836,7 @@ namespace Tgstation.Server.Host.Components.Watchdog CancelRight = (ulong)DreamDaemonRights.Shutdown, CancelRightsType = RightsType.DreamDaemon }; - await jobManager.RegisterOperation(job, (j, serviceProvider, progressFunction, ct) => Launch(ct), cancellationToken).ConfigureAwait(false); + await jobManager.RegisterOperation(job, (j, databaseContext, progressFunction, ct) => Launch(ct), cancellationToken).ConfigureAwait(false); } /// diff --git a/src/Tgstation.Server.Host/Controllers/ByondController.cs b/src/Tgstation.Server.Host/Controllers/ByondController.cs index 566aaae2b0..f8be5a0174 100644 --- a/src/Tgstation.Server.Host/Controllers/ByondController.cs +++ b/src/Tgstation.Server.Host/Controllers/ByondController.cs @@ -95,7 +95,7 @@ namespace Tgstation.Server.Host.Controllers CancelRight = (ulong)ByondRights.CancelInstall, Instance = Instance }; - await jobManager.RegisterOperation(job, (paramJob, serviceProvicer, progressHandler, ct) => byondManager.ChangeVersion(installingVersion, ct), cancellationToken).ConfigureAwait(false); + await jobManager.RegisterOperation(job, (paramJob, databaseContext, progressHandler, ct) => byondManager.ChangeVersion(installingVersion, ct), cancellationToken).ConfigureAwait(false); result.InstallJob = job.ToApi(); } result.Version = byondManager.ActiveVersion; diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs index 85d7841d8c..c13ec7e820 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs @@ -67,7 +67,7 @@ namespace Tgstation.Server.Host.Controllers StartedBy = AuthenticationContext.User }; await jobManager.RegisterOperation(job, - async (paramJob, serviceProvider, progressHandler, innerCt) => + async (paramJob, databaseContext, progressHandler, innerCt) => { var result = await instance.Watchdog.Launch(innerCt).ConfigureAwait(false); if (result == null) @@ -231,7 +231,7 @@ namespace Tgstation.Server.Host.Controllers var watchdog = instanceManager.GetInstance(Instance).Watchdog; - await jobManager.RegisterOperation(job, (paramJob, serviceProvider, progressReporter, ct) => watchdog.Restart(false, ct), cancellationToken).ConfigureAwait(false); + await jobManager.RegisterOperation(job, (paramJob, databaseContext, progressReporter, ct) => watchdog.Restart(false, ct), cancellationToken).ConfigureAwait(false); return Accepted(job.ToApi()); } } diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs index aafd0da61e..0c6801cbf2 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs @@ -352,7 +352,7 @@ namespace Tgstation.Server.Host.Controllers StartedBy = AuthenticationContext.User }; - await jobManager.RegisterOperation(job, (paramJob, serviceProvider, progressHandler, ct) => instanceManager.MoveInstance(originalModel, rawPath, ct), cancellationToken).ConfigureAwait(false); + await jobManager.RegisterOperation(job, (paramJob, databaseContext, progressHandler, ct) => instanceManager.MoveInstance(originalModel, rawPath, ct), cancellationToken).ConfigureAwait(false); api.MoveJob = job.ToApi(); } diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index 20f87f6cd1..240227aaaf 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -1,6 +1,5 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System; @@ -186,20 +185,19 @@ namespace Tgstation.Server.Host.Controllers Instance = Instance }; var api = currentModel.ToApi(); - await jobManager.RegisterOperation(job, async (paramJob, serviceProvider, progressReporter, ct) => + await jobManager.RegisterOperation(job, async (paramJob, databaseContext, progressReporter, ct) => { using (var repos = await repoManager.CloneRepository(new Uri(origin), cloneBranch, currentModel.AccessUser, currentModel.AccessToken, progressReporter, ct).ConfigureAwait(false)) { if (repos == null) throw new JobException("Filesystem conflict while cloning repository!"); - var db = serviceProvider.GetRequiredService(); var instance = new Models.Instance { Id = Instance.Id }; - db.Instances.Attach(instance); - if (await PopulateApi(api, repos, db, instance, ct).ConfigureAwait(false)) - await db.Save(ct).ConfigureAwait(false); + databaseContext.Instances.Attach(instance); + if (await PopulateApi(api, repos, databaseContext, instance, ct).ConfigureAwait(false)) + await databaseContext.Save(ct).ConfigureAwait(false); } }, cancellationToken).ConfigureAwait(false); @@ -238,7 +236,7 @@ namespace Tgstation.Server.Host.Controllers Instance = Instance }; var api = currentModel.ToApi(); - await jobManager.RegisterOperation(job, (paramJob, serviceProvider, progressReporter, ct) => instanceManager.GetInstance(Instance).RepositoryManager.DeleteRepository(cancellationToken), cancellationToken).ConfigureAwait(false); + await jobManager.RegisterOperation(job, (paramJob, databaseContext, progressReporter, ct) => instanceManager.GetInstance(Instance).RepositoryManager.DeleteRepository(cancellationToken), cancellationToken).ConfigureAwait(false); api.ActiveJob = job.ToApi(); return Accepted(api); } @@ -419,7 +417,7 @@ namespace Tgstation.Server.Host.Controllers CancelRight = (ulong)RepositoryRights.CancelPendingChanges, }; - await jobManager.RegisterOperation(job, async (paramJob, serviceProvider, progressReporter, ct) => + await jobManager.RegisterOperation(job, async (paramJob, databaseContext, progressReporter, ct) => { using (var repo = await repoManager.LoadRepository(ct).ConfigureAwait(false)) { @@ -443,8 +441,7 @@ namespace Tgstation.Server.Host.Controllers //get a base line for where we are Models.RevisionInformation lastRevisionInfo = null; - - var databaseContext = serviceProvider.GetRequiredService(); + var attachedInstance = new Models.Instance { Id = Instance.Id diff --git a/src/Tgstation.Server.Host/Core/IJobManager.cs b/src/Tgstation.Server.Host/Core/IJobManager.cs index 7389ce78d5..12ecf7666f 100644 --- a/src/Tgstation.Server.Host/Core/IJobManager.cs +++ b/src/Tgstation.Server.Host/Core/IJobManager.cs @@ -20,10 +20,10 @@ namespace Tgstation.Server.Host.Core /// Registers a given and begins running it /// /// The - /// The operation to run taking the started , a progress reporter and a + /// The operation to run taking the started , a , progress reporter and a /// The for the operation /// A representing a running operation - Task RegisterOperation(Job job, Func, CancellationToken, Task> operation, CancellationToken cancellationToken); + Task RegisterOperation(Job job, Func, CancellationToken, Task> operation, CancellationToken cancellationToken); /// /// Wait for a given to complete diff --git a/src/Tgstation.Server.Host/Core/JobManager.cs b/src/Tgstation.Server.Host/Core/JobManager.cs index 304f2d0acd..2900dc28f4 100644 --- a/src/Tgstation.Server.Host/Core/JobManager.cs +++ b/src/Tgstation.Server.Host/Core/JobManager.cs @@ -1,5 +1,4 @@ using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; @@ -16,7 +15,7 @@ namespace Tgstation.Server.Host.Core /// /// The for the /// - readonly IServiceProvider serviceProvider; + readonly IDatabaseContextFactory databaseContextFactory; /// /// The for the @@ -31,11 +30,11 @@ namespace Tgstation.Server.Host.Core /// /// Construct a /// - /// The value of + /// The value of /// The value of - public JobManager(IServiceProvider serviceProvider, ILogger logger) + public JobManager(IDatabaseContextFactory databaseContextFactory, ILogger logger) { - this.serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); + this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); jobs = new Dictionary(); } @@ -69,11 +68,11 @@ namespace Tgstation.Server.Host.Core /// The operation for the /// The for the operation /// A representing the running operation - async Task RunJob(Job job, Func operation, CancellationToken cancellationToken) + async Task RunJob(Job job, Func operation, CancellationToken cancellationToken) { try { - using (var scope = serviceProvider.CreateScope()) + await databaseContextFactory.UseContext(async databaseContext => { async Task HandleExceptions(Task task) { @@ -97,15 +96,13 @@ namespace Tgstation.Server.Host.Core } } - IDatabaseContext databaseContext = null; async Task RunJobInternal() { var oldJob = job; job = new Job { Id = oldJob.Id }; - databaseContext = scope.ServiceProvider.GetRequiredService(); databaseContext.Jobs.Attach(job); - await operation(job, scope.ServiceProvider, cancellationToken).ConfigureAwait(false); + await operation(job, databaseContext, cancellationToken).ConfigureAwait(false); logger.LogDebug("Job {0} completed!", job.Id); }; @@ -123,7 +120,7 @@ namespace Tgstation.Server.Host.Core if (JobErroredOrCancelled()) await databaseContext.Save(default).ConfigureAwait(false); } - } + }).ConfigureAwait(false); } finally { @@ -137,50 +134,44 @@ namespace Tgstation.Server.Host.Core } /// - public async Task RegisterOperation(Job job, Func, CancellationToken, Task> operation, CancellationToken cancellationToken) + public Task RegisterOperation(Job job, Func, CancellationToken, Task> operation, CancellationToken cancellationToken) => databaseContextFactory.UseContext(async databaseContext => { - using (var scope = serviceProvider.CreateScope()) + job.StartedAt = DateTimeOffset.Now; + job.Cancelled = false; + job.Instance = new Instance { - var databaseContext = scope.ServiceProvider.GetRequiredService(); - job.StartedAt = DateTimeOffset.Now; - job.Cancelled = false; - job.Instance = new Instance + Id = job.Instance.Id + }; + databaseContext.Instances.Attach(job.Instance); + if (job.StartedBy != null) + { + job.StartedBy = new User { - Id = job.Instance.Id + Id = job.StartedBy.Id }; - databaseContext.Instances.Attach(job.Instance); - if (job.StartedBy != null) - { - job.StartedBy = new User - { - Id = job.StartedBy.Id - }; - databaseContext.Users.Attach(job.StartedBy); - } - databaseContext.Jobs.Add(job); - await databaseContext.Save(cancellationToken).ConfigureAwait(false); - logger.LogDebug("Starting job {0}: {1}...", job.Id, job.Description); - var jobHandler = JobHandler.Create(x => RunJob(job, (jobParam, serviceProvider, ct) => - operation(jobParam, serviceProvider, y => - { - lock (this) - if (jobs.TryGetValue(job.Id, out var handler)) - handler.Progress = y; - }, ct), - x)); - lock (this) - jobs.Add(job.Id, jobHandler); + databaseContext.Users.Attach(job.StartedBy); } - } + databaseContext.Jobs.Add(job); + await databaseContext.Save(cancellationToken).ConfigureAwait(false); + logger.LogDebug("Starting job {0}: {1}...", job.Id, job.Description); + var jobHandler = JobHandler.Create(x => RunJob(job, (jobParam, serviceProvider, ct) => + operation(jobParam, serviceProvider, y => + { + lock (this) + if (jobs.TryGetValue(job.Id, out var handler)) + handler.Progress = y; + }, ct), + x)); + lock (this) + jobs.Add(job.Id, jobHandler); + }); /// public async Task StartAsync(CancellationToken cancellationToken) { logger.LogTrace("Starting job manager..."); - using (var scope = serviceProvider.CreateScope()) + await databaseContextFactory.UseContext(async databaseContext => { - var databaseContext = scope.ServiceProvider.GetRequiredService(); - //mark all jobs as cancelled var badJobs = await databaseContext.Jobs.Where(y => !y.StoppedAt.HasValue).Select(y => y.Id).ToListAsync(cancellationToken).ConfigureAwait(false); if (badJobs.Count > 0) @@ -195,7 +186,7 @@ namespace Tgstation.Server.Host.Core } await databaseContext.Save(cancellationToken).ConfigureAwait(false); } - } + }).ConfigureAwait(false); logger.LogDebug("Job manager started!"); } @@ -228,9 +219,8 @@ namespace Tgstation.Server.Host.Core return false; } handler.Cancel(); //this will ensure the db update is only done once - using (var scope = serviceProvider.CreateScope()) + await databaseContextFactory.UseContext(async databaseContext => { - var databaseContext = scope.ServiceProvider.GetRequiredService(); job = new Job { Id = job.Id }; databaseContext.Jobs.Attach(job); user = new User { Id = user.Id }; @@ -238,7 +228,7 @@ namespace Tgstation.Server.Host.Core job.CancelledBy = user; //let either startup or cancellation set job.cancelled await databaseContext.Save(cancellationToken).ConfigureAwait(false); - } + }).ConfigureAwait(false); if (blocking) await handler.Wait(cancellationToken).ConfigureAwait(false); return true; From 16c6d41e09f33d833e6238066c3ab6f952215620 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 18 Sep 2018 11:09:23 -0400 Subject: [PATCH 136/230] Change DatabaseContextFactory to use IServiceScopeFactory. Perform some constructor time validation --- .../Core/DatabaseContextFactory.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Tgstation.Server.Host/Core/DatabaseContextFactory.cs b/src/Tgstation.Server.Host/Core/DatabaseContextFactory.cs index f3d64f355c..69c5287aa9 100644 --- a/src/Tgstation.Server.Host/Core/DatabaseContextFactory.cs +++ b/src/Tgstation.Server.Host/Core/DatabaseContextFactory.cs @@ -9,20 +9,26 @@ namespace Tgstation.Server.Host.Core sealed class DatabaseContextFactory : IDatabaseContextFactory { /// - /// The for the + /// The for the /// - readonly IServiceProvider serviceProvider; + readonly IServiceScopeFactory scopeFactory; /// /// Construct a /// - /// The value of - public DatabaseContextFactory(IServiceProvider serviceProvider) => this.serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); + /// The value of . Created scopes must be able to provide instances of + public DatabaseContextFactory(IServiceScopeFactory scopeFactory) + { + this.scopeFactory = scopeFactory ?? throw new ArgumentNullException(nameof(scopeFactory)); + + using (var scope = scopeFactory.CreateScope()) + scope.ServiceProvider.GetRequiredService(); + } /// public async Task UseContext(Func operation) { - using (var scope = serviceProvider.CreateScope()) + using (var scope = scopeFactory.CreateScope()) await operation(scope.ServiceProvider.GetRequiredService()).ConfigureAwait(false); } } From cd0df312cbc7c81af3b487ab6762c9b2ee8c11a2 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 18 Sep 2018 11:40:13 -0400 Subject: [PATCH 137/230] Create a custom injected class for claims injection --- .../Controllers/ApiController.cs | 66 +------------ src/Tgstation.Server.Host/Core/Application.cs | 6 +- .../Security/ClaimsInjector.cs | 95 +++++++++++++++++++ .../Security/IClaimsInjector.cs | 20 ++++ 4 files changed, 121 insertions(+), 66 deletions(-) create mode 100644 src/Tgstation.Server.Host/Security/ClaimsInjector.cs create mode 100644 src/Tgstation.Server.Host/Security/IClaimsInjector.cs diff --git a/src/Tgstation.Server.Host/Controllers/ApiController.cs b/src/Tgstation.Server.Host/Controllers/ApiController.cs index 16fa1704dd..0fc660be6c 100644 --- a/src/Tgstation.Server.Host/Controllers/ApiController.cs +++ b/src/Tgstation.Server.Host/Controllers/ApiController.cs @@ -1,20 +1,14 @@ -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using System; -using System.Collections.Generic; using System.Globalization; -using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Net; -using System.Security.Claims; using System.Threading.Tasks; using Tgstation.Server.Api; using Tgstation.Server.Api.Models; -using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; @@ -57,64 +51,6 @@ namespace Tgstation.Server.Host.Controllers /// readonly bool requireInstance; - /// - /// Runs after a has been validated. Creates the for the - /// - /// The for the operation - /// A representing the running operation - public static async Task OnTokenValidated(TokenValidatedContext context) - { - var databaseContext = context.HttpContext.RequestServices.GetRequiredService(); - var authenticationContextFactory = context.HttpContext.RequestServices.GetRequiredService(); - - var userIdClaim = context.Principal.FindFirst(JwtRegisteredClaimNames.Sub); - - if (userIdClaim == default(Claim)) - throw new InvalidOperationException("Missing required claim!"); - - long userId; - try - { - userId = Int64.Parse(userIdClaim.Value, CultureInfo.InvariantCulture); - } - catch (Exception e) - { - throw new InvalidOperationException("Failed to parse user ID!", e); - } - - ApiHeaders apiHeaders; - try - { - apiHeaders = new ApiHeaders(context.HttpContext.Request.GetTypedHeaders()); - } - catch - { - //let OnActionExecutionAsync handle the reponse - return; - } - - await authenticationContextFactory.CreateAuthenticationContext(userId, apiHeaders.InstanceId, context.SecurityToken.ValidFrom, context.HttpContext.RequestAborted).ConfigureAwait(false); - - var authenticationContext = authenticationContextFactory.CurrentAuthenticationContext; - - var enumerator = Enum.GetValues(typeof(RightsType)); - var claims = new List(); - foreach (RightsType I in enumerator) - { - //if there's no instance user, do a weird thing and add all the instance roles - //we need it so we can get to OnActionExecutionAsync where we can properly decide between BadRequest and Forbid - //if user is null that means they got the token with an expired password - var rightInt = authenticationContext.User == null || (RightsHelper.IsInstanceRight(I) && authenticationContext.InstanceUser == null) ? ~0U : authenticationContext.GetRight(I); - var rightEnum = RightsHelper.RightToType(I); - var right = (Enum)Enum.ToObject(rightEnum, rightInt); - foreach (Enum J in Enum.GetValues(rightEnum)) - if (right.HasFlag(J)) - claims.Add(new Claim(ClaimTypes.Role, RightsHelper.RoleName(I, J))); - } - - context.Principal.AddIdentity(new ClaimsIdentity(claims)); - } - /// /// Construct an /// diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 86475ae07c..6233129ca1 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -101,6 +101,8 @@ namespace Tgstation.Server.Host.Core services.AddOptions(); + services.AddScoped(); + const string scheme = "JwtBearer"; services.AddAuthentication((options) => { @@ -128,9 +130,11 @@ namespace Tgstation.Server.Host.Core }; jwtBearerOptions.Events = new JwtBearerEvents { - OnTokenValidated = ApiController.OnTokenValidated + //Application is our composition root so this monstrosity of a line is okay + OnTokenValidated = ctx => ctx.HttpContext.RequestServices.GetRequiredService().InjectClaimsIntoContext(ctx, ctx.HttpContext.RequestAborted) }; }); + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); //fucking converts 'sub' to M$ bs services.AddMvc().AddJsonOptions(options => diff --git a/src/Tgstation.Server.Host/Security/ClaimsInjector.cs b/src/Tgstation.Server.Host/Security/ClaimsInjector.cs new file mode 100644 index 0000000000..3e7f202b2e --- /dev/null +++ b/src/Tgstation.Server.Host/Security/ClaimsInjector.cs @@ -0,0 +1,95 @@ +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Http; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api; +using Tgstation.Server.Api.Rights; +using Tgstation.Server.Host.Models; + +namespace Tgstation.Server.Host.Security +{ + /// + sealed class ClaimsInjector : IClaimsInjector + { + /// + /// The for the + /// + readonly IDatabaseContext databaseContext; + + /// + /// The for the + /// + readonly IAuthenticationContextFactory authenticationContextFactory; + + /// + /// Construct a + /// + /// The value of + /// The value of + public ClaimsInjector(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory) + { + this.databaseContext = databaseContext ?? throw new ArgumentNullException(nameof(databaseContext)); + this.authenticationContextFactory = authenticationContextFactory ?? throw new ArgumentNullException(nameof(authenticationContextFactory)); + } + + /// + public async Task InjectClaimsIntoContext(TokenValidatedContext tokenValidatedContext, CancellationToken cancellationToken) + { + if (tokenValidatedContext == null) + throw new ArgumentNullException(nameof(tokenValidatedContext)); + + //Find the user id in the token + var userIdClaim = tokenValidatedContext.Principal.FindFirst(JwtRegisteredClaimNames.Sub); + if (userIdClaim == default) + throw new InvalidOperationException("Missing required claim!"); + + long userId; + try + { + userId = Int64.Parse(userIdClaim.Value, CultureInfo.InvariantCulture); + } + catch (Exception e) + { + throw new InvalidOperationException("Failed to parse user ID!", e); + } + + ApiHeaders apiHeaders; + try + { + apiHeaders = new ApiHeaders(tokenValidatedContext.HttpContext.Request.GetTypedHeaders()); + } + catch (InvalidOperationException) + { + //we are not responsible for handling header validation issues + return; + } + + //This populates the CurrentAuthenticationContext field for use by us and subsequent controllers + await authenticationContextFactory.CreateAuthenticationContext(userId, apiHeaders.InstanceId, tokenValidatedContext.SecurityToken.ValidFrom, cancellationToken).ConfigureAwait(false); + + var authenticationContext = authenticationContextFactory.CurrentAuthenticationContext; + + var enumerator = Enum.GetValues(typeof(RightsType)); + var claims = new List(); + foreach (RightsType I in enumerator) + { + //if there's no instance user, do a weird thing and add all the instance roles + //we need it so we can get to OnActionExecutionAsync where we can properly decide between BadRequest and Forbid + //if user is null that means they got the token with an expired password + var rightInt = authenticationContext.User == null || (RightsHelper.IsInstanceRight(I) && authenticationContext.InstanceUser == null) ? ~0U : authenticationContext.GetRight(I); + var rightEnum = RightsHelper.RightToType(I); + var right = (Enum)Enum.ToObject(rightEnum, rightInt); + foreach (Enum J in Enum.GetValues(rightEnum)) + if (right.HasFlag(J)) + claims.Add(new Claim(ClaimTypes.Role, RightsHelper.RoleName(I, J))); + } + + tokenValidatedContext.Principal.AddIdentity(new ClaimsIdentity(claims)); + } + } +} diff --git a/src/Tgstation.Server.Host/Security/IClaimsInjector.cs b/src/Tgstation.Server.Host/Security/IClaimsInjector.cs new file mode 100644 index 0000000000..216b03b0f0 --- /dev/null +++ b/src/Tgstation.Server.Host/Security/IClaimsInjector.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNetCore.Authentication.JwtBearer; +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Security +{ + /// + /// For injecting s that can look for + /// + interface IClaimsInjector + { + /// + /// Setup the s for a given + /// + /// The containing the and of the request and the to add s to + /// The for the operation + /// A representing the running operation + Task InjectClaimsIntoContext(TokenValidatedContext tokenValidatedContext, CancellationToken cancellationToken); + } +} \ No newline at end of file From c235500c02901749081dc3081101252d25a24fac Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 18 Sep 2018 12:26:46 -0400 Subject: [PATCH 138/230] Remove unecessary specificity --- src/Tgstation.Server.Host/Controllers/DreamMakerController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs index cb147a5e79..48843b419d 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs @@ -51,7 +51,7 @@ namespace Tgstation.Server.Host.Controllers { var instance = instanceManager.GetInstance(Instance); var dreamMakerSettings = await DatabaseContext.DreamMakerSettings.Where(x => x.InstanceId == Instance.Id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); - return Json(new Api.Models.DreamMaker + return Json(new DreamMaker { ProjectName = dreamMakerSettings.ProjectName, ApiValidationPort = dreamMakerSettings.ApiValidationPort From 1745d2c5cb1c36617584fd377e5d3da6287b6e6f Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 18 Sep 2018 12:27:22 -0400 Subject: [PATCH 139/230] Include MinimumSecurityLevel in CompileJob.ToApi() --- src/Tgstation.Server.Host/Models/CompileJob.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Models/CompileJob.cs b/src/Tgstation.Server.Host/Models/CompileJob.cs index 22e97759dd..e372065af0 100644 --- a/src/Tgstation.Server.Host/Models/CompileJob.cs +++ b/src/Tgstation.Server.Host/Models/CompileJob.cs @@ -35,7 +35,8 @@ namespace Tgstation.Server.Host.Models Job = Job.ToApi(), Output = Output, RevisionInformation = RevisionInformation.ToApi(), - ByondVersion = Version.Parse(ByondVersion) + ByondVersion = Version.Parse(ByondVersion), + MinimumSecurityLevel = MinimumSecurityLevel }; } } From 75ea9cda6ca5a441e328e6654be39a37f7875eef Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 18 Sep 2018 12:28:38 -0400 Subject: [PATCH 140/230] Marks required CompileJob fields as required --- src/Tgstation.Server.Api/Models/Internal/CompileJob.cs | 3 +++ src/Tgstation.Server.Host/Models/CompileJob.cs | 1 + 2 files changed, 4 insertions(+) diff --git a/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs b/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs index e101ff7f23..c659c49737 100644 --- a/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs +++ b/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs @@ -16,16 +16,19 @@ namespace Tgstation.Server.Api.Models.Internal /// /// The .dme file used for compilation /// + [Required] public string DmeName { get; set; } /// /// Textual output of DM /// + [Required] public string Output { get; set; } /// /// The Game folder the results were compiled into /// + [Required] public Guid? DirectoryName { get; set; } /// diff --git a/src/Tgstation.Server.Host/Models/CompileJob.cs b/src/Tgstation.Server.Host/Models/CompileJob.cs index e372065af0..ffcdd186a4 100644 --- a/src/Tgstation.Server.Host/Models/CompileJob.cs +++ b/src/Tgstation.Server.Host/Models/CompileJob.cs @@ -9,6 +9,7 @@ namespace Tgstation.Server.Host.Models /// /// See /// + [Required] public Job Job { get; set; } /// From 0a792c3873d640de82c92c41dfa0c0de2caa9019 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 18 Sep 2018 12:31:06 -0400 Subject: [PATCH 141/230] Adds a note about sharing databases to the README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index a03c2d0a2d..10e2440f64 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,8 @@ If using MySQL, our provider library [recommends you set 'utf8mb4' as your defau The user created for the application will need the privilege to create databases on the first run. Once the initial set of migrations is run, the create right may be revoked. The user should maintain DDL rights though for applying future migrations +Note that the ratio of application installations to databases is 1:1. Do not attempt to share a database amongst multiple TGS installations. + ### Starting For the Windows service version start the `tgstation-server-4` service From 2c462c3b2173e484ec3f97273dc74ac9ef45e856 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 18 Sep 2018 12:50:36 -0400 Subject: [PATCH 142/230] Tabify existing migrations --- ...918020726_MYAddMinimumSecurity.Designer.cs | 900 ++++++++--------- .../20180918020726_MYAddMinimumSecurity.cs | 42 +- ...918021228_MSAddMinimumSecurity.Designer.cs | 952 +++++++++--------- .../20180918021228_MSAddMinimumSecurity.cs | 42 +- 4 files changed, 968 insertions(+), 968 deletions(-) diff --git a/src/Tgstation.Server.Host/Models/Migrations/20180918020726_MYAddMinimumSecurity.Designer.cs b/src/Tgstation.Server.Host/Models/Migrations/20180918020726_MYAddMinimumSecurity.Designer.cs index bf112bf7b2..3387ebe8bd 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/20180918020726_MYAddMinimumSecurity.Designer.cs +++ b/src/Tgstation.Server.Host/Models/Migrations/20180918020726_MYAddMinimumSecurity.Designer.cs @@ -7,642 +7,642 @@ using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Migrations { - [DbContext(typeof(MySqlDatabaseContext))] - [Migration("20180918020726_MYAddMinimumSecurity")] - partial class MYAddMinimumSecurity + [DbContext(typeof(MySqlDatabaseContext))] + [Migration("20180918020726_MYAddMinimumSecurity")] + partial class MYAddMinimumSecurity { /// /// Builds the target model /// /// The to use protected override void BuildTargetModel(ModelBuilder modelBuilder) - { + { #pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.1.3-rtm-32065") - .HasAnnotation("Relational:MaxIdentifierLength", 64); + modelBuilder + .HasAnnotation("ProductVersion", "2.1.3-rtm-32065") + .HasAnnotation("Relational:MaxIdentifierLength", 64); - modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); - b.Property("ConnectionString") - .IsRequired(); + b.Property("ConnectionString") + .IsRequired(); - b.Property("Enabled"); + b.Property("Enabled"); - b.Property("InstanceId"); + b.Property("InstanceId"); - b.Property("Name") - .IsRequired(); + b.Property("Name") + .IsRequired(); - b.Property("Provider"); + b.Property("Provider"); - b.HasKey("Id"); + b.HasKey("Id"); - b.HasIndex("InstanceId"); + b.HasIndex("InstanceId"); - b.HasIndex("Name") - .IsUnique(); + b.HasIndex("Name") + .IsUnique(); - b.ToTable("ChatBots"); - }); + b.ToTable("ChatBots"); + }); - modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); - b.Property("ChatSettingsId"); + b.Property("ChatSettingsId"); - b.Property("DiscordChannelId"); + b.Property("DiscordChannelId"); - b.Property("IrcChannel"); + b.Property("IrcChannel"); - b.Property("IsAdminChannel") - .IsRequired(); + b.Property("IsAdminChannel") + .IsRequired(); - b.Property("IsUpdatesChannel") - .IsRequired(); + b.Property("IsUpdatesChannel") + .IsRequired(); - b.Property("IsWatchdogChannel") - .IsRequired(); + b.Property("IsWatchdogChannel") + .IsRequired(); - b.Property("Tag"); + b.Property("Tag"); - b.HasKey("Id"); + b.HasKey("Id"); - b.HasIndex("ChatSettingsId", "DiscordChannelId") - .IsUnique(); + b.HasIndex("ChatSettingsId", "DiscordChannelId") + .IsUnique(); - b.HasIndex("ChatSettingsId", "IrcChannel") - .IsUnique(); + b.HasIndex("ChatSettingsId", "IrcChannel") + .IsUnique(); - b.ToTable("ChatChannels"); - }); + b.ToTable("ChatChannels"); + }); - modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); - b.Property("ByondVersion") - .IsRequired(); + b.Property("ByondVersion") + .IsRequired(); - b.Property("DirectoryName"); + b.Property("DirectoryName"); - b.Property("DmeName"); + b.Property("DmeName"); - b.Property("JobId"); + b.Property("JobId"); - b.Property("MinimumSecurityLevel"); + b.Property("MinimumSecurityLevel"); - b.Property("Output"); + b.Property("Output"); - b.Property("RevisionInformationId"); + b.Property("RevisionInformationId"); - b.HasKey("Id"); + b.HasKey("Id"); - b.HasIndex("DirectoryName"); + b.HasIndex("DirectoryName"); - b.HasIndex("JobId"); + b.HasIndex("JobId"); - b.HasIndex("RevisionInformationId"); + b.HasIndex("RevisionInformationId"); - b.ToTable("CompileJobs"); - }); + b.ToTable("CompileJobs"); + }); - modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); - b.Property("AccessToken"); + b.Property("AccessToken"); - b.Property("AllowWebClient") - .IsRequired(); + b.Property("AllowWebClient") + .IsRequired(); - b.Property("AutoStart") - .IsRequired(); + b.Property("AutoStart") + .IsRequired(); - b.Property("InstanceId"); + b.Property("InstanceId"); - b.Property("PrimaryPort") - .IsRequired(); + b.Property("PrimaryPort") + .IsRequired(); - b.Property("ProcessId"); + b.Property("ProcessId"); - b.Property("SecondaryPort") - .IsRequired(); + b.Property("SecondaryPort") + .IsRequired(); - b.Property("SecurityLevel"); + b.Property("SecurityLevel"); - b.Property("SoftRestart") - .IsRequired(); + b.Property("SoftRestart") + .IsRequired(); - b.Property("SoftShutdown") - .IsRequired(); + b.Property("SoftShutdown") + .IsRequired(); - b.Property("StartupTimeout") - .IsRequired(); + b.Property("StartupTimeout") + .IsRequired(); - b.HasKey("Id"); + b.HasKey("Id"); - b.HasIndex("InstanceId") - .IsUnique(); + b.HasIndex("InstanceId") + .IsUnique(); - b.ToTable("DreamDaemonSettings"); - }); + b.ToTable("DreamDaemonSettings"); + }); - modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); - b.Property("ApiValidationPort") - .IsRequired(); + b.Property("ApiValidationPort") + .IsRequired(); - b.Property("ApiValidationSecurityLevel"); + b.Property("ApiValidationSecurityLevel"); - b.Property("InstanceId"); + b.Property("InstanceId"); - b.Property("ProjectName"); + b.Property("ProjectName"); - b.HasKey("Id"); + b.HasKey("Id"); - b.HasIndex("InstanceId") - .IsUnique(); + b.HasIndex("InstanceId") + .IsUnique(); - b.ToTable("DreamMakerSettings"); - }); + b.ToTable("DreamMakerSettings"); + }); - modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); - b.Property("AutoUpdateInterval") - .IsRequired(); + b.Property("AutoUpdateInterval") + .IsRequired(); - b.Property("ConfigurationType"); + b.Property("ConfigurationType"); - b.Property("Name") - .IsRequired(); + b.Property("Name") + .IsRequired(); - b.Property("Online") - .IsRequired(); + b.Property("Online") + .IsRequired(); - b.Property("Path") - .IsRequired(); + b.Property("Path") + .IsRequired(); - b.Property("WatchdogReattachInformationId"); + b.Property("WatchdogReattachInformationId"); - b.HasKey("Id"); + b.HasKey("Id"); - b.HasIndex("Path") - .IsUnique(); + b.HasIndex("Path") + .IsUnique(); - b.HasIndex("WatchdogReattachInformationId"); + b.HasIndex("WatchdogReattachInformationId"); - b.ToTable("Instances"); - }); + b.ToTable("Instances"); + }); - modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); + modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); - b.Property("ByondRights"); + b.Property("ByondRights"); - b.Property("ChatBotRights"); + b.Property("ChatBotRights"); - b.Property("ConfigurationRights"); + b.Property("ConfigurationRights"); - b.Property("DreamDaemonRights"); + b.Property("DreamDaemonRights"); - b.Property("DreamMakerRights"); + b.Property("DreamMakerRights"); - b.Property("InstanceId"); + b.Property("InstanceId"); - b.Property("InstanceUserRights"); + b.Property("InstanceUserRights"); - b.Property("RepositoryRights"); + b.Property("RepositoryRights"); - b.Property("UserId") - .IsRequired(); + b.Property("UserId") + .IsRequired(); - b.HasKey("Id"); + b.HasKey("Id"); - b.HasIndex("InstanceId"); + b.HasIndex("InstanceId"); - b.HasIndex("UserId", "InstanceId") - .IsUnique(); + b.HasIndex("UserId", "InstanceId") + .IsUnique(); - b.ToTable("InstanceUsers"); - }); + b.ToTable("InstanceUsers"); + }); - modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); - b.Property("CancelRight"); + b.Property("CancelRight"); - b.Property("CancelRightsType"); + b.Property("CancelRightsType"); - b.Property("Cancelled") - .IsRequired(); + b.Property("Cancelled") + .IsRequired(); - b.Property("CancelledById"); + b.Property("CancelledById"); - b.Property("Description") - .IsRequired(); + b.Property("Description") + .IsRequired(); - b.Property("ExceptionDetails"); + b.Property("ExceptionDetails"); - b.Property("InstanceId"); + b.Property("InstanceId"); - b.Property("StartedAt") - .IsRequired(); + b.Property("StartedAt") + .IsRequired(); - b.Property("StartedById"); + b.Property("StartedById"); - b.Property("StoppedAt"); + b.Property("StoppedAt"); - b.HasKey("Id"); + b.HasKey("Id"); - b.HasIndex("CancelledById"); + b.HasIndex("CancelledById"); - b.HasIndex("InstanceId"); + b.HasIndex("InstanceId"); - b.HasIndex("StartedById"); + b.HasIndex("StartedById"); - b.ToTable("Jobs"); - }); + b.ToTable("Jobs"); + }); - modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); - b.Property("AccessIdentifier") - .IsRequired(); + b.Property("AccessIdentifier") + .IsRequired(); - b.Property("ChatChannelsJson") - .IsRequired(); + b.Property("ChatChannelsJson") + .IsRequired(); - b.Property("ChatCommandsJson") - .IsRequired(); + b.Property("ChatCommandsJson") + .IsRequired(); - b.Property("CompileJobId"); + b.Property("CompileJobId"); - b.Property("IsPrimary"); + b.Property("IsPrimary"); - b.Property("Port"); + b.Property("Port"); - b.Property("ProcessId"); + b.Property("ProcessId"); - b.Property("RebootState"); + b.Property("RebootState"); - b.Property("ServerCommandsJson") - .IsRequired(); + b.Property("ServerCommandsJson") + .IsRequired(); - b.HasKey("Id"); + b.HasKey("Id"); - b.HasIndex("CompileJobId"); + b.HasIndex("CompileJobId"); - b.ToTable("ReattachInformations"); - }); + b.ToTable("ReattachInformations"); + }); - modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); - b.Property("AccessToken"); + b.Property("AccessToken"); - b.Property("AccessUser"); + b.Property("AccessUser"); - b.Property("AutoUpdatesKeepTestMerges") - .IsRequired(); + b.Property("AutoUpdatesKeepTestMerges") + .IsRequired(); - b.Property("AutoUpdatesSynchronize") - .IsRequired(); + b.Property("AutoUpdatesSynchronize") + .IsRequired(); - b.Property("CommitterEmail") - .IsRequired(); + b.Property("CommitterEmail") + .IsRequired(); - b.Property("CommitterName") - .IsRequired(); + b.Property("CommitterName") + .IsRequired(); - b.Property("InstanceId"); + b.Property("InstanceId"); - b.Property("PushTestMergeCommits") - .IsRequired(); + b.Property("PushTestMergeCommits") + .IsRequired(); - b.Property("ShowTestMergeCommitters") - .IsRequired(); + b.Property("ShowTestMergeCommitters") + .IsRequired(); - b.HasKey("Id"); + b.HasKey("Id"); - b.HasIndex("InstanceId") - .IsUnique(); + b.HasIndex("InstanceId") + .IsUnique(); - b.ToTable("RepositorySettings"); - }); + b.ToTable("RepositorySettings"); + }); - modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); - b.Property("RevisionInformationId"); + b.Property("RevisionInformationId"); - b.Property("TestMergeId"); + b.Property("TestMergeId"); - b.HasKey("Id"); + b.HasKey("Id"); - b.HasIndex("RevisionInformationId"); + b.HasIndex("RevisionInformationId"); - b.HasIndex("TestMergeId"); + b.HasIndex("TestMergeId"); - b.ToTable("RevInfoTestMerges"); - }); + b.ToTable("RevInfoTestMerges"); + }); - modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); - b.Property("CommitSha") - .IsRequired() - .HasMaxLength(40); + b.Property("CommitSha") + .IsRequired() + .HasMaxLength(40); - b.Property("InstanceId"); + b.Property("InstanceId"); - b.Property("OriginCommitSha") - .IsRequired() - .HasMaxLength(40); + b.Property("OriginCommitSha") + .IsRequired() + .HasMaxLength(40); - b.HasKey("Id"); + b.HasKey("Id"); - b.HasIndex("CommitSha") - .IsUnique(); + b.HasIndex("CommitSha") + .IsUnique(); - b.HasIndex("InstanceId"); + b.HasIndex("InstanceId"); - b.ToTable("RevisionInformations"); - }); + b.ToTable("RevisionInformations"); + }); - modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); - b.Property("Author") - .IsRequired(); + b.Property("Author") + .IsRequired(); - b.Property("BodyAtMerge") - .IsRequired(); + b.Property("BodyAtMerge") + .IsRequired(); - b.Property("Comment"); + b.Property("Comment"); - b.Property("MergedAt"); + b.Property("MergedAt"); - b.Property("MergedById"); + b.Property("MergedById"); - b.Property("Number") - .IsRequired(); + b.Property("Number") + .IsRequired(); - b.Property("PrimaryRevisionInformationId"); + b.Property("PrimaryRevisionInformationId"); - b.Property("PullRequestRevision") - .IsRequired(); + b.Property("PullRequestRevision") + .IsRequired(); - b.Property("TitleAtMerge") - .IsRequired(); + b.Property("TitleAtMerge") + .IsRequired(); - b.Property("Url") - .IsRequired(); + b.Property("Url") + .IsRequired(); - b.HasKey("Id"); + b.HasKey("Id"); - b.HasIndex("MergedById"); + b.HasIndex("MergedById"); - b.HasIndex("PrimaryRevisionInformationId") - .IsUnique(); + b.HasIndex("PrimaryRevisionInformationId") + .IsUnique(); - b.ToTable("TestMerges"); - }); + b.ToTable("TestMerges"); + }); - modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); - b.Property("AdministrationRights"); + b.Property("AdministrationRights"); - b.Property("CanonicalName") - .IsRequired(); + b.Property("CanonicalName") + .IsRequired(); - b.Property("CreatedAt") - .IsRequired(); + b.Property("CreatedAt") + .IsRequired(); - b.Property("CreatedById"); + b.Property("CreatedById"); - b.Property("Enabled") - .IsRequired(); + b.Property("Enabled") + .IsRequired(); - b.Property("InstanceManagerRights"); + b.Property("InstanceManagerRights"); - b.Property("LastPasswordUpdate"); + b.Property("LastPasswordUpdate"); - b.Property("Name") - .IsRequired(); + b.Property("Name") + .IsRequired(); - b.Property("PasswordHash"); + b.Property("PasswordHash"); - b.Property("SystemIdentifier"); + b.Property("SystemIdentifier"); - b.HasKey("Id"); + b.HasKey("Id"); - b.HasIndex("CanonicalName") - .IsUnique(); + b.HasIndex("CanonicalName") + .IsUnique(); - b.HasIndex("CreatedById"); + b.HasIndex("CreatedById"); - b.ToTable("Users"); - }); + b.ToTable("Users"); + }); - modelBuilder.Entity("Tgstation.Server.Host.Models.WatchdogReattachInformation", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); + modelBuilder.Entity("Tgstation.Server.Host.Models.WatchdogReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); - b.Property("AlphaId"); + b.Property("AlphaId"); - b.Property("AlphaIsActive"); + b.Property("AlphaIsActive"); - b.Property("BravoId"); + b.Property("BravoId"); - b.HasKey("Id"); + b.HasKey("Id"); - b.HasIndex("AlphaId"); + b.HasIndex("AlphaId"); - b.HasIndex("BravoId"); + b.HasIndex("BravoId"); - b.ToTable("WatchdogReattachInformations"); - }); + b.ToTable("WatchdogReattachInformations"); + }); - modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => - { - b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") - .WithMany("ChatSettings") - .HasForeignKey("InstanceId") - .OnDelete(DeleteBehavior.Cascade); - }); + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("ChatSettings") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); - modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => - { - b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings") - .WithMany("Channels") - .HasForeignKey("ChatSettingsId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => - { - b.HasOne("Tgstation.Server.Host.Models.Job", "Job") - .WithMany() - .HasForeignKey("JobId"); - - b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") - .WithMany("CompileJobs") - .HasForeignKey("RevisionInformationId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => - { - b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") - .WithOne("DreamDaemonSettings") - .HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => - { - b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") - .WithOne("DreamMakerSettings") - .HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => - { - b.HasOne("Tgstation.Server.Host.Models.WatchdogReattachInformation", "WatchdogReattachInformation") - .WithMany() - .HasForeignKey("WatchdogReattachInformationId"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => - { - b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") - .WithMany("InstanceUsers") - .HasForeignKey("InstanceId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Tgstation.Server.Host.Models.User") - .WithMany("InstanceUsers") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => - { - b.HasOne("Tgstation.Server.Host.Models.User", "CancelledBy") - .WithMany() - .HasForeignKey("CancelledById"); - - b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") - .WithMany("Jobs") - .HasForeignKey("InstanceId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy") - .WithMany() - .HasForeignKey("StartedById") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => - { - b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") - .WithMany() - .HasForeignKey("CompileJobId"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => - { - b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") - .WithOne("RepositorySettings") - .HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => - { - b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") - .WithMany("ActiveTestMerges") - .HasForeignKey("RevisionInformationId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge") - .WithMany("RevisonInformations") - .HasForeignKey("TestMergeId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => - { - b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") - .WithMany("RevisionInformations") - .HasForeignKey("InstanceId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => - { - b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy") - .WithMany("TestMerges") - .HasForeignKey("MergedById") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation") - .WithOne("PrimaryTestMerge") - .HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId") - .OnDelete(DeleteBehavior.SetNull); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => - { - b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy") - .WithMany("CreatedUsers") - .HasForeignKey("CreatedById"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.WatchdogReattachInformation", b => - { - b.HasOne("Tgstation.Server.Host.Models.ReattachInformation", "Alpha") - .WithMany() - .HasForeignKey("AlphaId"); - - b.HasOne("Tgstation.Server.Host.Models.ReattachInformation", "Bravo") - .WithMany() - .HasForeignKey("BravoId"); - }); + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings") + .WithMany("Channels") + .HasForeignKey("ChatSettingsId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.HasOne("Tgstation.Server.Host.Models.Job", "Job") + .WithMany() + .HasForeignKey("JobId"); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("CompileJobs") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamDaemonSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamMakerSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.HasOne("Tgstation.Server.Host.Models.WatchdogReattachInformation", "WatchdogReattachInformation") + .WithMany() + .HasForeignKey("WatchdogReattachInformationId"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("InstanceUsers") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User") + .WithMany("InstanceUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CancelledBy") + .WithMany() + .HasForeignKey("CancelledById"); + + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("Jobs") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy") + .WithMany() + .HasForeignKey("StartedById") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") + .WithMany() + .HasForeignKey("CompileJobId"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("RepositorySettings") + .HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("ActiveTestMerges") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge") + .WithMany("RevisonInformations") + .HasForeignKey("TestMergeId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("RevisionInformations") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy") + .WithMany("TestMerges") + .HasForeignKey("MergedById") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation") + .WithOne("PrimaryTestMerge") + .HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId") + .OnDelete(DeleteBehavior.SetNull); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy") + .WithMany("CreatedUsers") + .HasForeignKey("CreatedById"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.WatchdogReattachInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.ReattachInformation", "Alpha") + .WithMany() + .HasForeignKey("AlphaId"); + + b.HasOne("Tgstation.Server.Host.Models.ReattachInformation", "Bravo") + .WithMany() + .HasForeignKey("BravoId"); + }); #pragma warning restore 612, 618 - } - } + } + } } diff --git a/src/Tgstation.Server.Host/Models/Migrations/20180918020726_MYAddMinimumSecurity.cs b/src/Tgstation.Server.Host/Models/Migrations/20180918020726_MYAddMinimumSecurity.cs index 13a103ac32..6d47a3801a 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/20180918020726_MYAddMinimumSecurity.cs +++ b/src/Tgstation.Server.Host/Models/Migrations/20180918020726_MYAddMinimumSecurity.cs @@ -13,33 +13,33 @@ namespace Tgstation.Server.Host.Migrations /// /// The to use protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "ApiValidationSecurityLevel", - table: "DreamMakerSettings", - nullable: false, - defaultValue: (int)DreamDaemonSecurity.Safe); + { + migrationBuilder.AddColumn( + name: "ApiValidationSecurityLevel", + table: "DreamMakerSettings", + nullable: false, + defaultValue: (int)DreamDaemonSecurity.Safe); - migrationBuilder.AddColumn( - name: "MinimumSecurityLevel", - table: "CompileJobs", - nullable: false, - defaultValue: (int)DreamDaemonSecurity.Safe); - } + migrationBuilder.AddColumn( + name: "MinimumSecurityLevel", + table: "CompileJobs", + nullable: false, + defaultValue: (int)DreamDaemonSecurity.Safe); + } /// /// Unapplies the migration /// /// The to use protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "ApiValidationSecurityLevel", - table: "DreamMakerSettings"); + { + migrationBuilder.DropColumn( + name: "ApiValidationSecurityLevel", + table: "DreamMakerSettings"); - migrationBuilder.DropColumn( - name: "MinimumSecurityLevel", - table: "CompileJobs"); - } - } + migrationBuilder.DropColumn( + name: "MinimumSecurityLevel", + table: "CompileJobs"); + } + } } diff --git a/src/Tgstation.Server.Host/Models/Migrations/20180918021228_MSAddMinimumSecurity.Designer.cs b/src/Tgstation.Server.Host/Models/Migrations/20180918021228_MSAddMinimumSecurity.Designer.cs index 0abec67697..5d669e5b4b 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/20180918021228_MSAddMinimumSecurity.Designer.cs +++ b/src/Tgstation.Server.Host/Models/Migrations/20180918021228_MSAddMinimumSecurity.Designer.cs @@ -9,668 +9,668 @@ using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Migrations { - [DbContext(typeof(SqlServerDatabaseContext))] - [Migration("20180918021228_MSAddMinimumSecurity")] - partial class MSAddMinimumSecurity + [DbContext(typeof(SqlServerDatabaseContext))] + [Migration("20180918021228_MSAddMinimumSecurity")] + partial class MSAddMinimumSecurity { /// /// Builds the target model /// /// The to use protected override void BuildTargetModel(ModelBuilder modelBuilder) - { + { #pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.1.3-rtm-32065") - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + modelBuilder + .HasAnnotation("ProductVersion", "2.1.3-rtm-32065") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - b.Property("ConnectionString") - .IsRequired(); + b.Property("ConnectionString") + .IsRequired(); - b.Property("Enabled"); + b.Property("Enabled"); - b.Property("InstanceId"); + b.Property("InstanceId"); - b.Property("Name") - .IsRequired(); + b.Property("Name") + .IsRequired(); - b.Property("Provider"); + b.Property("Provider"); - b.HasKey("Id"); + b.HasKey("Id"); - b.HasIndex("InstanceId"); + b.HasIndex("InstanceId"); - b.HasIndex("Name") - .IsUnique(); + b.HasIndex("Name") + .IsUnique(); - b.ToTable("ChatBots"); - }); + b.ToTable("ChatBots"); + }); - modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - b.Property("ChatSettingsId"); + b.Property("ChatSettingsId"); - b.Property("DiscordChannelId") - .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + b.Property("DiscordChannelId") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); - b.Property("IrcChannel"); + b.Property("IrcChannel"); - b.Property("IsAdminChannel") - .IsRequired(); + b.Property("IsAdminChannel") + .IsRequired(); - b.Property("IsUpdatesChannel") - .IsRequired(); + b.Property("IsUpdatesChannel") + .IsRequired(); - b.Property("IsWatchdogChannel") - .IsRequired(); + b.Property("IsWatchdogChannel") + .IsRequired(); - b.Property("Tag"); + b.Property("Tag"); - b.HasKey("Id"); + b.HasKey("Id"); - b.HasIndex("ChatSettingsId", "DiscordChannelId") - .IsUnique() - .HasFilter("[DiscordChannelId] IS NOT NULL"); + b.HasIndex("ChatSettingsId", "DiscordChannelId") + .IsUnique() + .HasFilter("[DiscordChannelId] IS NOT NULL"); - b.HasIndex("ChatSettingsId", "IrcChannel") - .IsUnique() - .HasFilter("[IrcChannel] IS NOT NULL"); + b.HasIndex("ChatSettingsId", "IrcChannel") + .IsUnique() + .HasFilter("[IrcChannel] IS NOT NULL"); - b.ToTable("ChatChannels"); - }); + b.ToTable("ChatChannels"); + }); - modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - b.Property("ByondVersion") - .IsRequired(); + b.Property("ByondVersion") + .IsRequired(); - b.Property("DirectoryName"); + b.Property("DirectoryName"); - b.Property("DmeName"); + b.Property("DmeName"); - b.Property("JobId"); + b.Property("JobId"); - b.Property("MinimumSecurityLevel"); + b.Property("MinimumSecurityLevel"); - b.Property("Output"); + b.Property("Output"); - b.Property("RevisionInformationId"); + b.Property("RevisionInformationId"); - b.HasKey("Id"); + b.HasKey("Id"); - b.HasIndex("DirectoryName"); + b.HasIndex("DirectoryName"); - b.HasIndex("JobId"); + b.HasIndex("JobId"); - b.HasIndex("RevisionInformationId"); + b.HasIndex("RevisionInformationId"); - b.ToTable("CompileJobs"); - }); + b.ToTable("CompileJobs"); + }); - modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - b.Property("AccessToken"); + b.Property("AccessToken"); - b.Property("AllowWebClient") - .IsRequired(); + b.Property("AllowWebClient") + .IsRequired(); - b.Property("AutoStart") - .IsRequired(); + b.Property("AutoStart") + .IsRequired(); - b.Property("InstanceId"); + b.Property("InstanceId"); - b.Property("PrimaryPort"); + b.Property("PrimaryPort"); - b.Property("ProcessId"); + b.Property("ProcessId"); - b.Property("SecondaryPort"); + b.Property("SecondaryPort"); - b.Property("SecurityLevel"); + b.Property("SecurityLevel"); - b.Property("SoftRestart") - .IsRequired(); + b.Property("SoftRestart") + .IsRequired(); - b.Property("SoftShutdown") - .IsRequired(); + b.Property("SoftShutdown") + .IsRequired(); - b.Property("StartupTimeout"); + b.Property("StartupTimeout"); - b.HasKey("Id"); + b.HasKey("Id"); - b.HasIndex("InstanceId") - .IsUnique(); + b.HasIndex("InstanceId") + .IsUnique(); - b.ToTable("DreamDaemonSettings"); - }); + b.ToTable("DreamDaemonSettings"); + }); - modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - b.Property("ApiValidationPort"); + b.Property("ApiValidationPort"); - b.Property("ApiValidationSecurityLevel"); + b.Property("ApiValidationSecurityLevel"); - b.Property("InstanceId"); + b.Property("InstanceId"); - b.Property("ProjectName"); + b.Property("ProjectName"); - b.HasKey("Id"); + b.HasKey("Id"); - b.HasIndex("InstanceId") - .IsUnique(); + b.HasIndex("InstanceId") + .IsUnique(); - b.ToTable("DreamMakerSettings"); - }); + b.ToTable("DreamMakerSettings"); + }); - modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - b.Property("AutoUpdateInterval"); + b.Property("AutoUpdateInterval"); - b.Property("ConfigurationType"); + b.Property("ConfigurationType"); - b.Property("Name") - .IsRequired(); + b.Property("Name") + .IsRequired(); - b.Property("Online") - .IsRequired(); + b.Property("Online") + .IsRequired(); - b.Property("Path") - .IsRequired(); + b.Property("Path") + .IsRequired(); - b.Property("WatchdogReattachInformationId"); + b.Property("WatchdogReattachInformationId"); - b.HasKey("Id"); + b.HasKey("Id"); - b.HasIndex("Path") - .IsUnique(); + b.HasIndex("Path") + .IsUnique(); - b.HasIndex("WatchdogReattachInformationId"); + b.HasIndex("WatchdogReattachInformationId"); - b.ToTable("Instances"); - }); + b.ToTable("Instances"); + }); - modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - b.Property("ByondRights") - .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + b.Property("ByondRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); - b.Property("ChatBotRights") - .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + b.Property("ChatBotRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); - b.Property("ConfigurationRights") - .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + b.Property("ConfigurationRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); - b.Property("DreamDaemonRights") - .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + b.Property("DreamDaemonRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); - b.Property("DreamMakerRights") - .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + b.Property("DreamMakerRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); - b.Property("InstanceId"); + b.Property("InstanceId"); - b.Property("InstanceUserRights") - .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + b.Property("InstanceUserRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); - b.Property("RepositoryRights") - .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + b.Property("RepositoryRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); - b.Property("UserId") - .IsRequired(); + b.Property("UserId") + .IsRequired(); - b.HasKey("Id"); + b.HasKey("Id"); - b.HasIndex("InstanceId"); + b.HasIndex("InstanceId"); - b.HasIndex("UserId", "InstanceId") - .IsUnique(); + b.HasIndex("UserId", "InstanceId") + .IsUnique(); - b.ToTable("InstanceUsers"); - }); + b.ToTable("InstanceUsers"); + }); - modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - b.Property("CancelRight") - .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + b.Property("CancelRight") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); - b.Property("CancelRightsType") - .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + b.Property("CancelRightsType") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); - b.Property("Cancelled") - .IsRequired(); + b.Property("Cancelled") + .IsRequired(); - b.Property("CancelledById"); + b.Property("CancelledById"); - b.Property("Description") - .IsRequired(); + b.Property("Description") + .IsRequired(); - b.Property("ExceptionDetails"); + b.Property("ExceptionDetails"); - b.Property("InstanceId"); + b.Property("InstanceId"); - b.Property("StartedAt") - .IsRequired(); + b.Property("StartedAt") + .IsRequired(); - b.Property("StartedById"); + b.Property("StartedById"); - b.Property("StoppedAt"); + b.Property("StoppedAt"); - b.HasKey("Id"); + b.HasKey("Id"); - b.HasIndex("CancelledById"); + b.HasIndex("CancelledById"); - b.HasIndex("InstanceId"); + b.HasIndex("InstanceId"); - b.HasIndex("StartedById"); + b.HasIndex("StartedById"); - b.ToTable("Jobs"); - }); + b.ToTable("Jobs"); + }); - modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - b.Property("AccessIdentifier") - .IsRequired(); + b.Property("AccessIdentifier") + .IsRequired(); - b.Property("ChatChannelsJson") - .IsRequired(); + b.Property("ChatChannelsJson") + .IsRequired(); - b.Property("ChatCommandsJson") - .IsRequired(); + b.Property("ChatCommandsJson") + .IsRequired(); - b.Property("CompileJobId"); + b.Property("CompileJobId"); - b.Property("IsPrimary"); + b.Property("IsPrimary"); - b.Property("Port"); + b.Property("Port"); - b.Property("ProcessId"); + b.Property("ProcessId"); - b.Property("RebootState"); + b.Property("RebootState"); - b.Property("ServerCommandsJson") - .IsRequired(); + b.Property("ServerCommandsJson") + .IsRequired(); - b.HasKey("Id"); + b.HasKey("Id"); - b.HasIndex("CompileJobId"); + b.HasIndex("CompileJobId"); - b.ToTable("ReattachInformations"); - }); + b.ToTable("ReattachInformations"); + }); - modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - b.Property("AccessToken"); + b.Property("AccessToken"); - b.Property("AccessUser"); + b.Property("AccessUser"); - b.Property("AutoUpdatesKeepTestMerges") - .IsRequired(); + b.Property("AutoUpdatesKeepTestMerges") + .IsRequired(); - b.Property("AutoUpdatesSynchronize") - .IsRequired(); + b.Property("AutoUpdatesSynchronize") + .IsRequired(); - b.Property("CommitterEmail") - .IsRequired(); + b.Property("CommitterEmail") + .IsRequired(); - b.Property("CommitterName") - .IsRequired(); + b.Property("CommitterName") + .IsRequired(); - b.Property("InstanceId"); + b.Property("InstanceId"); - b.Property("PushTestMergeCommits") - .IsRequired(); + b.Property("PushTestMergeCommits") + .IsRequired(); - b.Property("ShowTestMergeCommitters") - .IsRequired(); + b.Property("ShowTestMergeCommitters") + .IsRequired(); - b.HasKey("Id"); + b.HasKey("Id"); - b.HasIndex("InstanceId") - .IsUnique(); + b.HasIndex("InstanceId") + .IsUnique(); - b.ToTable("RepositorySettings"); - }); + b.ToTable("RepositorySettings"); + }); - modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - b.Property("RevisionInformationId"); + b.Property("RevisionInformationId"); - b.Property("TestMergeId"); + b.Property("TestMergeId"); - b.HasKey("Id"); + b.HasKey("Id"); - b.HasIndex("RevisionInformationId"); + b.HasIndex("RevisionInformationId"); - b.HasIndex("TestMergeId"); + b.HasIndex("TestMergeId"); - b.ToTable("RevInfoTestMerges"); - }); + b.ToTable("RevInfoTestMerges"); + }); - modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - b.Property("CommitSha") - .IsRequired() - .HasMaxLength(40); + b.Property("CommitSha") + .IsRequired() + .HasMaxLength(40); - b.Property("InstanceId"); + b.Property("InstanceId"); - b.Property("OriginCommitSha") - .IsRequired() - .HasMaxLength(40); + b.Property("OriginCommitSha") + .IsRequired() + .HasMaxLength(40); - b.HasKey("Id"); + b.HasKey("Id"); - b.HasIndex("CommitSha") - .IsUnique(); + b.HasIndex("CommitSha") + .IsUnique(); - b.HasIndex("InstanceId"); + b.HasIndex("InstanceId"); - b.ToTable("RevisionInformations"); - }); + b.ToTable("RevisionInformations"); + }); - modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - b.Property("Author") - .IsRequired(); + b.Property("Author") + .IsRequired(); - b.Property("BodyAtMerge") - .IsRequired(); + b.Property("BodyAtMerge") + .IsRequired(); - b.Property("Comment"); + b.Property("Comment"); - b.Property("MergedAt"); + b.Property("MergedAt"); - b.Property("MergedById"); + b.Property("MergedById"); - b.Property("Number") - .IsRequired(); + b.Property("Number") + .IsRequired(); - b.Property("PrimaryRevisionInformationId"); + b.Property("PrimaryRevisionInformationId"); - b.Property("PullRequestRevision") - .IsRequired(); + b.Property("PullRequestRevision") + .IsRequired(); - b.Property("TitleAtMerge") - .IsRequired(); + b.Property("TitleAtMerge") + .IsRequired(); - b.Property("Url") - .IsRequired(); + b.Property("Url") + .IsRequired(); - b.HasKey("Id"); + b.HasKey("Id"); - b.HasIndex("MergedById"); + b.HasIndex("MergedById"); - b.HasIndex("PrimaryRevisionInformationId") - .IsUnique() - .HasFilter("[PrimaryRevisionInformationId] IS NOT NULL"); + b.HasIndex("PrimaryRevisionInformationId") + .IsUnique() + .HasFilter("[PrimaryRevisionInformationId] IS NOT NULL"); - b.ToTable("TestMerges"); - }); + b.ToTable("TestMerges"); + }); - modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - b.Property("AdministrationRights") - .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + b.Property("AdministrationRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); - b.Property("CanonicalName") - .IsRequired(); + b.Property("CanonicalName") + .IsRequired(); - b.Property("CreatedAt") - .IsRequired(); + b.Property("CreatedAt") + .IsRequired(); - b.Property("CreatedById"); + b.Property("CreatedById"); - b.Property("Enabled") - .IsRequired(); + b.Property("Enabled") + .IsRequired(); - b.Property("InstanceManagerRights") - .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + b.Property("InstanceManagerRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); - b.Property("LastPasswordUpdate"); + b.Property("LastPasswordUpdate"); - b.Property("Name") - .IsRequired(); + b.Property("Name") + .IsRequired(); - b.Property("PasswordHash"); + b.Property("PasswordHash"); - b.Property("SystemIdentifier"); + b.Property("SystemIdentifier"); - b.HasKey("Id"); + b.HasKey("Id"); - b.HasIndex("CanonicalName") - .IsUnique(); + b.HasIndex("CanonicalName") + .IsUnique(); - b.HasIndex("CreatedById"); + b.HasIndex("CreatedById"); - b.ToTable("Users"); - }); + b.ToTable("Users"); + }); - modelBuilder.Entity("Tgstation.Server.Host.Models.WatchdogReattachInformation", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + modelBuilder.Entity("Tgstation.Server.Host.Models.WatchdogReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - b.Property("AlphaId"); + b.Property("AlphaId"); - b.Property("AlphaIsActive"); + b.Property("AlphaIsActive"); - b.Property("BravoId"); + b.Property("BravoId"); - b.HasKey("Id"); + b.HasKey("Id"); - b.HasIndex("AlphaId"); + b.HasIndex("AlphaId"); - b.HasIndex("BravoId"); + b.HasIndex("BravoId"); - b.ToTable("WatchdogReattachInformations"); - }); + b.ToTable("WatchdogReattachInformations"); + }); - modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => - { - b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") - .WithMany("ChatSettings") - .HasForeignKey("InstanceId") - .OnDelete(DeleteBehavior.Cascade); - }); + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("ChatSettings") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); - modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => - { - b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings") - .WithMany("Channels") - .HasForeignKey("ChatSettingsId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => - { - b.HasOne("Tgstation.Server.Host.Models.Job", "Job") - .WithMany() - .HasForeignKey("JobId"); - - b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") - .WithMany("CompileJobs") - .HasForeignKey("RevisionInformationId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => - { - b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") - .WithOne("DreamDaemonSettings") - .HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => - { - b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") - .WithOne("DreamMakerSettings") - .HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => - { - b.HasOne("Tgstation.Server.Host.Models.WatchdogReattachInformation", "WatchdogReattachInformation") - .WithMany() - .HasForeignKey("WatchdogReattachInformationId"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => - { - b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") - .WithMany("InstanceUsers") - .HasForeignKey("InstanceId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Tgstation.Server.Host.Models.User") - .WithMany("InstanceUsers") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => - { - b.HasOne("Tgstation.Server.Host.Models.User", "CancelledBy") - .WithMany() - .HasForeignKey("CancelledById"); - - b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") - .WithMany("Jobs") - .HasForeignKey("InstanceId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy") - .WithMany() - .HasForeignKey("StartedById") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => - { - b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") - .WithMany() - .HasForeignKey("CompileJobId"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => - { - b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") - .WithOne("RepositorySettings") - .HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => - { - b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") - .WithMany("ActiveTestMerges") - .HasForeignKey("RevisionInformationId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge") - .WithMany("RevisonInformations") - .HasForeignKey("TestMergeId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => - { - b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") - .WithMany("RevisionInformations") - .HasForeignKey("InstanceId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => - { - b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy") - .WithMany("TestMerges") - .HasForeignKey("MergedById") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation") - .WithOne("PrimaryTestMerge") - .HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId") - .OnDelete(DeleteBehavior.SetNull); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => - { - b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy") - .WithMany("CreatedUsers") - .HasForeignKey("CreatedById"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.WatchdogReattachInformation", b => - { - b.HasOne("Tgstation.Server.Host.Models.ReattachInformation", "Alpha") - .WithMany() - .HasForeignKey("AlphaId"); - - b.HasOne("Tgstation.Server.Host.Models.ReattachInformation", "Bravo") - .WithMany() - .HasForeignKey("BravoId"); - }); + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings") + .WithMany("Channels") + .HasForeignKey("ChatSettingsId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.HasOne("Tgstation.Server.Host.Models.Job", "Job") + .WithMany() + .HasForeignKey("JobId"); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("CompileJobs") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamDaemonSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamMakerSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.HasOne("Tgstation.Server.Host.Models.WatchdogReattachInformation", "WatchdogReattachInformation") + .WithMany() + .HasForeignKey("WatchdogReattachInformationId"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("InstanceUsers") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User") + .WithMany("InstanceUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CancelledBy") + .WithMany() + .HasForeignKey("CancelledById"); + + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("Jobs") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy") + .WithMany() + .HasForeignKey("StartedById") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") + .WithMany() + .HasForeignKey("CompileJobId"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("RepositorySettings") + .HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("ActiveTestMerges") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge") + .WithMany("RevisonInformations") + .HasForeignKey("TestMergeId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("RevisionInformations") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy") + .WithMany("TestMerges") + .HasForeignKey("MergedById") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation") + .WithOne("PrimaryTestMerge") + .HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId") + .OnDelete(DeleteBehavior.SetNull); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy") + .WithMany("CreatedUsers") + .HasForeignKey("CreatedById"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.WatchdogReattachInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.ReattachInformation", "Alpha") + .WithMany() + .HasForeignKey("AlphaId"); + + b.HasOne("Tgstation.Server.Host.Models.ReattachInformation", "Bravo") + .WithMany() + .HasForeignKey("BravoId"); + }); #pragma warning restore 612, 618 - } - } + } + } } diff --git a/src/Tgstation.Server.Host/Models/Migrations/20180918021228_MSAddMinimumSecurity.cs b/src/Tgstation.Server.Host/Models/Migrations/20180918021228_MSAddMinimumSecurity.cs index 78c8da9d83..9cf5da54c7 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/20180918021228_MSAddMinimumSecurity.cs +++ b/src/Tgstation.Server.Host/Models/Migrations/20180918021228_MSAddMinimumSecurity.cs @@ -13,33 +13,33 @@ namespace Tgstation.Server.Host.Migrations /// /// The to use protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "ApiValidationSecurityLevel", - table: "DreamMakerSettings", - nullable: false, - defaultValue: (int)DreamDaemonSecurity.Safe); + { + migrationBuilder.AddColumn( + name: "ApiValidationSecurityLevel", + table: "DreamMakerSettings", + nullable: false, + defaultValue: (int)DreamDaemonSecurity.Safe); - migrationBuilder.AddColumn( - name: "MinimumSecurityLevel", - table: "CompileJobs", - nullable: false, - defaultValue: (int)DreamDaemonSecurity.Safe); - } + migrationBuilder.AddColumn( + name: "MinimumSecurityLevel", + table: "CompileJobs", + nullable: false, + defaultValue: (int)DreamDaemonSecurity.Safe); + } /// /// Unapplies the migration /// /// The to use protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "ApiValidationSecurityLevel", - table: "DreamMakerSettings"); + { + migrationBuilder.DropColumn( + name: "ApiValidationSecurityLevel", + table: "DreamMakerSettings"); - migrationBuilder.DropColumn( - name: "MinimumSecurityLevel", - table: "CompileJobs"); - } - } + migrationBuilder.DropColumn( + name: "MinimumSecurityLevel", + table: "CompileJobs"); + } + } } From 7566c62befe10a20f4770ad74b9fb042e56ec599 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 18 Sep 2018 12:57:33 -0400 Subject: [PATCH 143/230] Fix migrations namespaces --- .../Migrations/20180918020726_MYAddMinimumSecurity.Designer.cs | 3 +-- .../Models/Migrations/20180918020726_MYAddMinimumSecurity.cs | 2 +- .../Migrations/20180918021228_MSAddMinimumSecurity.Designer.cs | 3 +-- .../Models/Migrations/20180918021228_MSAddMinimumSecurity.cs | 2 +- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Tgstation.Server.Host/Models/Migrations/20180918020726_MYAddMinimumSecurity.Designer.cs b/src/Tgstation.Server.Host/Models/Migrations/20180918020726_MYAddMinimumSecurity.Designer.cs index 3387ebe8bd..0c4538790b 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/20180918020726_MYAddMinimumSecurity.Designer.cs +++ b/src/Tgstation.Server.Host/Models/Migrations/20180918020726_MYAddMinimumSecurity.Designer.cs @@ -3,9 +3,8 @@ using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; -using Tgstation.Server.Host.Models; -namespace Tgstation.Server.Host.Migrations +namespace Tgstation.Server.Host.Models.Migrations { [DbContext(typeof(MySqlDatabaseContext))] [Migration("20180918020726_MYAddMinimumSecurity")] diff --git a/src/Tgstation.Server.Host/Models/Migrations/20180918020726_MYAddMinimumSecurity.cs b/src/Tgstation.Server.Host/Models/Migrations/20180918020726_MYAddMinimumSecurity.cs index 6d47a3801a..95950e251f 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/20180918020726_MYAddMinimumSecurity.cs +++ b/src/Tgstation.Server.Host/Models/Migrations/20180918020726_MYAddMinimumSecurity.cs @@ -1,7 +1,7 @@ using Microsoft.EntityFrameworkCore.Migrations; using Tgstation.Server.Api.Models; -namespace Tgstation.Server.Host.Migrations +namespace Tgstation.Server.Host.Models.Migrations { /// /// Add the and columns for MySQL/MariaDB diff --git a/src/Tgstation.Server.Host/Models/Migrations/20180918021228_MSAddMinimumSecurity.Designer.cs b/src/Tgstation.Server.Host/Models/Migrations/20180918021228_MSAddMinimumSecurity.Designer.cs index 5d669e5b4b..6223542618 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/20180918021228_MSAddMinimumSecurity.Designer.cs +++ b/src/Tgstation.Server.Host/Models/Migrations/20180918021228_MSAddMinimumSecurity.Designer.cs @@ -5,9 +5,8 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Tgstation.Server.Host.Models; -namespace Tgstation.Server.Host.Migrations +namespace Tgstation.Server.Host.Models.Migrations { [DbContext(typeof(SqlServerDatabaseContext))] [Migration("20180918021228_MSAddMinimumSecurity")] diff --git a/src/Tgstation.Server.Host/Models/Migrations/20180918021228_MSAddMinimumSecurity.cs b/src/Tgstation.Server.Host/Models/Migrations/20180918021228_MSAddMinimumSecurity.cs index 9cf5da54c7..9ac5b22b9f 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/20180918021228_MSAddMinimumSecurity.cs +++ b/src/Tgstation.Server.Host/Models/Migrations/20180918021228_MSAddMinimumSecurity.cs @@ -1,7 +1,7 @@ using Microsoft.EntityFrameworkCore.Migrations; using Tgstation.Server.Api.Models; -namespace Tgstation.Server.Host.Migrations +namespace Tgstation.Server.Host.Models.Migrations { /// /// Add the and columns for MSSQL From 71fe411c680b0650511afc27c82f29ee595ab545 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 18 Sep 2018 13:11:59 -0400 Subject: [PATCH 144/230] I've struggled with this before but it seems multiple cascade paths will absolutely not allow us to create CompileJobs with Required jobs --- src/Tgstation.Server.Host/Models/CompileJob.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Models/CompileJob.cs b/src/Tgstation.Server.Host/Models/CompileJob.cs index ffcdd186a4..e372065af0 100644 --- a/src/Tgstation.Server.Host/Models/CompileJob.cs +++ b/src/Tgstation.Server.Host/Models/CompileJob.cs @@ -9,7 +9,6 @@ namespace Tgstation.Server.Host.Models /// /// See /// - [Required] public Job Job { get; set; } /// From 56d7f38eeb2aa0e277bb58f5ced8a80cc4672117 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 18 Sep 2018 15:05:11 -0400 Subject: [PATCH 145/230] EF Model integrity fixups --- src/Tgstation.Server.Host/Models/CompileJob.cs | 5 +++++ src/Tgstation.Server.Host/Models/DatabaseContext.cs | 2 ++ 2 files changed, 7 insertions(+) diff --git a/src/Tgstation.Server.Host/Models/CompileJob.cs b/src/Tgstation.Server.Host/Models/CompileJob.cs index e372065af0..3dc141372c 100644 --- a/src/Tgstation.Server.Host/Models/CompileJob.cs +++ b/src/Tgstation.Server.Host/Models/CompileJob.cs @@ -11,6 +11,11 @@ namespace Tgstation.Server.Host.Models /// public Job Job { get; set; } + /// + /// The of + /// + public long JobId { get; set; } + /// /// See /// diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs index c79f697839..51998ddc2c 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -112,6 +112,8 @@ namespace Tgstation.Server.Host.Models modelBuilder.Entity().HasIndex(x => x.DirectoryName); + modelBuilder.Entity().HasOne().WithOne(x => x.Job).OnDelete(DeleteBehavior.Restrict); + var chatChannel = modelBuilder.Entity(); chatChannel.HasIndex(x => new { x.ChatSettingsId, x.IrcChannel }).IsUnique(); chatChannel.HasIndex(x => new { x.ChatSettingsId, x.DiscordChannelId }).IsUnique(); From 4b84e1f48eabfd53c42c13eb77d40eca849aec8b Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 18 Sep 2018 15:18:05 -0400 Subject: [PATCH 146/230] Fix watchdog reattach information foreign key --- src/Tgstation.Server.Host/Models/DatabaseContext.cs | 1 + .../Models/WatchdogReattachInformation.cs | 11 ++++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs index 51998ddc2c..4fb568d700 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -130,6 +130,7 @@ namespace Tgstation.Server.Host.Models instanceModel.HasMany(x => x.RevisionInformations).WithOne(x => x.Instance).OnDelete(DeleteBehavior.Cascade); instanceModel.HasMany(x => x.InstanceUsers).WithOne(x => x.Instance).OnDelete(DeleteBehavior.Cascade); instanceModel.HasMany(x => x.Jobs).WithOne(x => x.Instance).OnDelete(DeleteBehavior.Cascade); + instanceModel.HasOne(x => x.WatchdogReattachInformation).WithOne().OnDelete(DeleteBehavior.Cascade); } /// diff --git a/src/Tgstation.Server.Host/Models/WatchdogReattachInformation.cs b/src/Tgstation.Server.Host/Models/WatchdogReattachInformation.cs index db27049745..204baed3b3 100644 --- a/src/Tgstation.Server.Host/Models/WatchdogReattachInformation.cs +++ b/src/Tgstation.Server.Host/Models/WatchdogReattachInformation.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Tgstation.Server.Host.Models +namespace Tgstation.Server.Host.Models { /// /// Database representation of @@ -14,6 +10,11 @@ namespace Tgstation.Server.Host.Models /// public long Id { get; set; } + /// + /// The of the the belongs to + /// + public long InstanceId { get; set; } + /// /// The for the Alpha server /// From ff1a4d5f26fff220b7a4d2a54ccabc67d73c0c9b Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 18 Sep 2018 15:28:46 -0400 Subject: [PATCH 147/230] Mark TestMerge.PrimaryRevisionInformation as required --- src/Tgstation.Server.Host/Models/TestMerge.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Tgstation.Server.Host/Models/TestMerge.cs b/src/Tgstation.Server.Host/Models/TestMerge.cs index bbab03bea0..754951c355 100644 --- a/src/Tgstation.Server.Host/Models/TestMerge.cs +++ b/src/Tgstation.Server.Host/Models/TestMerge.cs @@ -15,6 +15,7 @@ namespace Tgstation.Server.Host.Models /// /// The initial the was merged with /// + [Required] public RevisionInformation PrimaryRevisionInformation { get; set; } /// From 9a6e414dd60164fbe6775367330b90b552e978ab Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 18 Sep 2018 16:42:14 -0400 Subject: [PATCH 148/230] Restrict instead of SetNull. We have no use for SetNull --- src/Tgstation.Server.Host/Models/DatabaseContext.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs index 4fb568d700..7578c21c8a 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -107,7 +107,7 @@ namespace Tgstation.Server.Host.Models var revInfo = modelBuilder.Entity(); revInfo.HasMany(x => x.CompileJobs).WithOne(x => x.RevisionInformation).OnDelete(DeleteBehavior.Cascade); revInfo.HasMany(x => x.ActiveTestMerges).WithOne(x => x.RevisionInformation).OnDelete(DeleteBehavior.Cascade); - revInfo.HasOne(x => x.PrimaryTestMerge).WithOne(x => x.PrimaryRevisionInformation).OnDelete(DeleteBehavior.SetNull); + revInfo.HasOne(x => x.PrimaryTestMerge).WithOne(x => x.PrimaryRevisionInformation).OnDelete(DeleteBehavior.Restrict); revInfo.HasIndex(x => x.CommitSha).IsUnique(); modelBuilder.Entity().HasIndex(x => x.DirectoryName); From abdb8553bf65f0d61da7de76fb23db3ec503d8a2 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 18 Sep 2018 16:54:41 -0400 Subject: [PATCH 149/230] NullableAndForeignKeyCleanup Migration --- ...MYNullableAndForeignKeyCleanup.Designer.cs | 652 +++++++++++++++++ ...18204520_MYNullableAndForeignKeyCleanup.cs | 219 ++++++ ...MSNullableAndForeignKeyCleanup.Designer.cs | 679 ++++++++++++++++++ ...18205224_MSNullableAndForeignKeyCleanup.cs | 216 ++++++ .../MySqlDatabaseContextModelSnapshot.cs | 47 +- .../SqlServerDatabaseContextModelSnapshot.cs | 49 +- 6 files changed, 1820 insertions(+), 42 deletions(-) create mode 100644 src/Tgstation.Server.Host/Models/Migrations/20180918204520_MYNullableAndForeignKeyCleanup.Designer.cs create mode 100644 src/Tgstation.Server.Host/Models/Migrations/20180918204520_MYNullableAndForeignKeyCleanup.cs create mode 100644 src/Tgstation.Server.Host/Models/Migrations/20180918205224_MSNullableAndForeignKeyCleanup.Designer.cs create mode 100644 src/Tgstation.Server.Host/Models/Migrations/20180918205224_MSNullableAndForeignKeyCleanup.cs diff --git a/src/Tgstation.Server.Host/Models/Migrations/20180918204520_MYNullableAndForeignKeyCleanup.Designer.cs b/src/Tgstation.Server.Host/Models/Migrations/20180918204520_MYNullableAndForeignKeyCleanup.Designer.cs new file mode 100644 index 0000000000..c267dc1782 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/Migrations/20180918204520_MYNullableAndForeignKeyCleanup.Designer.cs @@ -0,0 +1,652 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Tgstation.Server.Host.Models.Migrations +{ + [DbContext(typeof(MySqlDatabaseContext))] + [Migration("20180918204520_MYNullableAndForeignKeyCleanup")] + partial class MYNullableAndForeignKeyCleanup + { + /// + /// Builds the target model + /// + /// The to use + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.3-rtm-32065") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConnectionString") + .IsRequired(); + + b.Property("Enabled"); + + b.Property("InstanceId"); + + b.Property("Name") + .IsRequired(); + + b.Property("Provider"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("ChatBots"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChatSettingsId"); + + b.Property("DiscordChannelId"); + + b.Property("IrcChannel"); + + b.Property("IsAdminChannel") + .IsRequired(); + + b.Property("IsUpdatesChannel") + .IsRequired(); + + b.Property("IsWatchdogChannel") + .IsRequired(); + + b.Property("Tag"); + + b.HasKey("Id"); + + b.HasIndex("ChatSettingsId", "DiscordChannelId") + .IsUnique(); + + b.HasIndex("ChatSettingsId", "IrcChannel") + .IsUnique(); + + b.ToTable("ChatChannels"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ByondVersion") + .IsRequired(); + + b.Property("DirectoryName") + .IsRequired(); + + b.Property("DmeName") + .IsRequired(); + + b.Property("JobId"); + + b.Property("MinimumSecurityLevel"); + + b.Property("Output") + .IsRequired(); + + b.Property("RevisionInformationId"); + + b.HasKey("Id"); + + b.HasIndex("DirectoryName"); + + b.HasIndex("JobId") + .IsUnique(); + + b.HasIndex("RevisionInformationId"); + + b.ToTable("CompileJobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessToken"); + + b.Property("AllowWebClient") + .IsRequired(); + + b.Property("AutoStart") + .IsRequired(); + + b.Property("InstanceId"); + + b.Property("PrimaryPort") + .IsRequired(); + + b.Property("ProcessId"); + + b.Property("SecondaryPort") + .IsRequired(); + + b.Property("SecurityLevel"); + + b.Property("SoftRestart") + .IsRequired(); + + b.Property("SoftShutdown") + .IsRequired(); + + b.Property("StartupTimeout") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamDaemonSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ApiValidationPort") + .IsRequired(); + + b.Property("ApiValidationSecurityLevel"); + + b.Property("InstanceId"); + + b.Property("ProjectName"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamMakerSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoUpdateInterval") + .IsRequired(); + + b.Property("ConfigurationType"); + + b.Property("Name") + .IsRequired(); + + b.Property("Online") + .IsRequired(); + + b.Property("Path") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("Path") + .IsUnique(); + + b.ToTable("Instances"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ByondRights"); + + b.Property("ChatBotRights"); + + b.Property("ConfigurationRights"); + + b.Property("DreamDaemonRights"); + + b.Property("DreamMakerRights"); + + b.Property("InstanceId"); + + b.Property("InstanceUserRights"); + + b.Property("RepositoryRights"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("UserId", "InstanceId") + .IsUnique(); + + b.ToTable("InstanceUsers"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CancelRight"); + + b.Property("CancelRightsType"); + + b.Property("Cancelled") + .IsRequired(); + + b.Property("CancelledById"); + + b.Property("Description") + .IsRequired(); + + b.Property("ExceptionDetails"); + + b.Property("InstanceId"); + + b.Property("StartedAt") + .IsRequired(); + + b.Property("StartedById"); + + b.Property("StoppedAt"); + + b.HasKey("Id"); + + b.HasIndex("CancelledById"); + + b.HasIndex("InstanceId"); + + b.HasIndex("StartedById"); + + b.ToTable("Jobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessIdentifier") + .IsRequired(); + + b.Property("ChatChannelsJson") + .IsRequired(); + + b.Property("ChatCommandsJson") + .IsRequired(); + + b.Property("CompileJobId"); + + b.Property("IsPrimary"); + + b.Property("Port"); + + b.Property("ProcessId"); + + b.Property("RebootState"); + + b.Property("ServerCommandsJson") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("CompileJobId"); + + b.ToTable("ReattachInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessToken"); + + b.Property("AccessUser"); + + b.Property("AutoUpdatesKeepTestMerges") + .IsRequired(); + + b.Property("AutoUpdatesSynchronize") + .IsRequired(); + + b.Property("CommitterEmail") + .IsRequired(); + + b.Property("CommitterName") + .IsRequired(); + + b.Property("InstanceId"); + + b.Property("PushTestMergeCommits") + .IsRequired(); + + b.Property("ShowTestMergeCommitters") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("RepositorySettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("RevisionInformationId"); + + b.Property("TestMergeId"); + + b.HasKey("Id"); + + b.HasIndex("RevisionInformationId"); + + b.HasIndex("TestMergeId"); + + b.ToTable("RevInfoTestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CommitSha") + .IsRequired() + .HasMaxLength(40); + + b.Property("InstanceId"); + + b.Property("OriginCommitSha") + .IsRequired() + .HasMaxLength(40); + + b.HasKey("Id"); + + b.HasIndex("CommitSha") + .IsUnique(); + + b.HasIndex("InstanceId"); + + b.ToTable("RevisionInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Author") + .IsRequired(); + + b.Property("BodyAtMerge") + .IsRequired(); + + b.Property("Comment"); + + b.Property("MergedAt"); + + b.Property("MergedById"); + + b.Property("Number") + .IsRequired(); + + b.Property("PrimaryRevisionInformationId") + .IsRequired(); + + b.Property("PullRequestRevision") + .IsRequired(); + + b.Property("TitleAtMerge") + .IsRequired(); + + b.Property("Url") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("MergedById"); + + b.HasIndex("PrimaryRevisionInformationId") + .IsUnique(); + + b.ToTable("TestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AdministrationRights"); + + b.Property("CanonicalName") + .IsRequired(); + + b.Property("CreatedAt") + .IsRequired(); + + b.Property("CreatedById"); + + b.Property("Enabled") + .IsRequired(); + + b.Property("InstanceManagerRights"); + + b.Property("LastPasswordUpdate"); + + b.Property("Name") + .IsRequired(); + + b.Property("PasswordHash"); + + b.Property("SystemIdentifier"); + + b.HasKey("Id"); + + b.HasIndex("CanonicalName") + .IsUnique(); + + b.HasIndex("CreatedById"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.WatchdogReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AlphaId"); + + b.Property("AlphaIsActive"); + + b.Property("BravoId"); + + b.Property("InstanceId"); + + b.HasKey("Id"); + + b.HasIndex("AlphaId"); + + b.HasIndex("BravoId"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("WatchdogReattachInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("ChatSettings") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings") + .WithMany("Channels") + .HasForeignKey("ChatSettingsId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.HasOne("Tgstation.Server.Host.Models.Job", "Job") + .WithOne() + .HasForeignKey("Tgstation.Server.Host.Models.CompileJob", "JobId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("CompileJobs") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamDaemonSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamMakerSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("InstanceUsers") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User") + .WithMany("InstanceUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CancelledBy") + .WithMany() + .HasForeignKey("CancelledById"); + + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("Jobs") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy") + .WithMany() + .HasForeignKey("StartedById") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") + .WithMany() + .HasForeignKey("CompileJobId"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("RepositorySettings") + .HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("ActiveTestMerges") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge") + .WithMany("RevisonInformations") + .HasForeignKey("TestMergeId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("RevisionInformations") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy") + .WithMany("TestMerges") + .HasForeignKey("MergedById") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation") + .WithOne("PrimaryTestMerge") + .HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy") + .WithMany("CreatedUsers") + .HasForeignKey("CreatedById"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.WatchdogReattachInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.ReattachInformation", "Alpha") + .WithMany() + .HasForeignKey("AlphaId"); + + b.HasOne("Tgstation.Server.Host.Models.ReattachInformation", "Bravo") + .WithMany() + .HasForeignKey("BravoId"); + + b.HasOne("Tgstation.Server.Host.Models.Instance") + .WithOne("WatchdogReattachInformation") + .HasForeignKey("Tgstation.Server.Host.Models.WatchdogReattachInformation", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Tgstation.Server.Host/Models/Migrations/20180918204520_MYNullableAndForeignKeyCleanup.cs b/src/Tgstation.Server.Host/Models/Migrations/20180918204520_MYNullableAndForeignKeyCleanup.cs new file mode 100644 index 0000000000..c5bf5fa166 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/Migrations/20180918204520_MYNullableAndForeignKeyCleanup.cs @@ -0,0 +1,219 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Tgstation.Server.Host.Models.Migrations +{ + /// + /// Cleans up of nullable columns and foreign keys MySQL/MariaDB + /// + public partial class MYNullableAndForeignKeyCleanup : Migration + { + /// + /// Applies the migration + /// + /// The to use + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Instances_WatchdogReattachInformations_WatchdogReattachInfor~", + table: "Instances"); + + migrationBuilder.DropForeignKey( + name: "FK_TestMerges_RevisionInformations_PrimaryRevisionInformationId", + table: "TestMerges"); + + migrationBuilder.DropForeignKey( + name: "FK_CompileJobs_Jobs_JobId", + table: "CompileJobs"); + + migrationBuilder.DropIndex( + name: "IX_Instances_WatchdogReattachInformationId", + table: "Instances"); + + migrationBuilder.DropIndex( + name: "IX_CompileJobs_JobId", + table: "CompileJobs"); + + migrationBuilder.DropColumn( + name: "WatchdogReattachInformationId", + table: "Instances"); + + migrationBuilder.AddColumn( + name: "InstanceId", + table: "WatchdogReattachInformations", + nullable: false, + defaultValue: 0L); + + migrationBuilder.AlterColumn( + name: "PrimaryRevisionInformationId", + table: "TestMerges", + nullable: false, + oldClrType: typeof(long), + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "Output", + table: "CompileJobs", + nullable: false, + oldClrType: typeof(string), + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "JobId", + table: "CompileJobs", + nullable: false, + oldClrType: typeof(long), + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "DmeName", + table: "CompileJobs", + nullable: false, + oldClrType: typeof(string), + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "DirectoryName", + table: "CompileJobs", + nullable: false, + oldClrType: typeof(Guid), + oldNullable: true); + + migrationBuilder.CreateIndex( + name: "IX_WatchdogReattachInformations_InstanceId", + table: "WatchdogReattachInformations", + column: "InstanceId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_CompileJobs_JobId", + table: "CompileJobs", + column: "JobId", + unique: true); + + migrationBuilder.AddForeignKey( + name: "FK_CompileJobs_Jobs_JobId", + table: "CompileJobs", + column: "JobId", + principalTable: "Jobs", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + name: "FK_TestMerges_RevisionInformations_PrimaryRevisionInformationId", + table: "TestMerges", + column: "PrimaryRevisionInformationId", + principalTable: "RevisionInformations", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + name: "FK_WatchdogReattachInformations_Instances_InstanceId", + table: "WatchdogReattachInformations", + column: "InstanceId", + principalTable: "Instances", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + /// Unapplies the migration + /// + /// The to use + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_TestMerges_RevisionInformations_PrimaryRevisionInformationId", + table: "TestMerges"); + + migrationBuilder.DropForeignKey( + name: "FK_WatchdogReattachInformations_Instances_InstanceId", + table: "WatchdogReattachInformations"); + + migrationBuilder.DropForeignKey( + name: "FK_CompileJobs_Jobs_JobId", + table: "CompileJobs"); + + migrationBuilder.DropIndex( + name: "IX_WatchdogReattachInformations_InstanceId", + table: "WatchdogReattachInformations"); + + migrationBuilder.DropIndex( + name: "IX_CompileJobs_JobId", + table: "CompileJobs"); + + migrationBuilder.DropColumn( + name: "InstanceId", + table: "WatchdogReattachInformations"); + + migrationBuilder.AlterColumn( + name: "PrimaryRevisionInformationId", + table: "TestMerges", + nullable: true, + oldClrType: typeof(long)); + + migrationBuilder.AddColumn( + name: "WatchdogReattachInformationId", + table: "Instances", + nullable: true); + + migrationBuilder.AlterColumn( + name: "Output", + table: "CompileJobs", + nullable: true, + oldClrType: typeof(string)); + + migrationBuilder.AlterColumn( + name: "JobId", + table: "CompileJobs", + nullable: true, + oldClrType: typeof(long)); + + migrationBuilder.AlterColumn( + name: "DmeName", + table: "CompileJobs", + nullable: true, + oldClrType: typeof(string)); + + migrationBuilder.AlterColumn( + name: "DirectoryName", + table: "CompileJobs", + nullable: true, + oldClrType: typeof(Guid)); + + migrationBuilder.CreateIndex( + name: "IX_Instances_WatchdogReattachInformationId", + table: "Instances", + column: "WatchdogReattachInformationId"); + + migrationBuilder.CreateIndex( + name: "IX_CompileJobs_JobId", + table: "CompileJobs", + column: "JobId"); + + migrationBuilder.AddForeignKey( + name: "FK_CompileJobs_Jobs_JobId", + table: "CompileJobs", + column: "JobId", + principalTable: "Jobs", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + name: "FK_Instances_WatchdogReattachInformations_WatchdogReattachInfor~", + table: "Instances", + column: "WatchdogReattachInformationId", + principalTable: "WatchdogReattachInformations", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + name: "FK_TestMerges_RevisionInformations_PrimaryRevisionInformationId", + table: "TestMerges", + column: "PrimaryRevisionInformationId", + principalTable: "RevisionInformations", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + } + } +} diff --git a/src/Tgstation.Server.Host/Models/Migrations/20180918205224_MSNullableAndForeignKeyCleanup.Designer.cs b/src/Tgstation.Server.Host/Models/Migrations/20180918205224_MSNullableAndForeignKeyCleanup.Designer.cs new file mode 100644 index 0000000000..d8bf97f528 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/Migrations/20180918205224_MSNullableAndForeignKeyCleanup.Designer.cs @@ -0,0 +1,679 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Tgstation.Server.Host.Models.Migrations +{ + [DbContext(typeof(SqlServerDatabaseContext))] + [Migration("20180918205224_MSNullableAndForeignKeyCleanup")] + partial class MSNullableAndForeignKeyCleanup + { + /// + /// Builds the target model + /// + /// The to use + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.3-rtm-32065") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ConnectionString") + .IsRequired(); + + b.Property("Enabled"); + + b.Property("InstanceId"); + + b.Property("Name") + .IsRequired(); + + b.Property("Provider"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("ChatBots"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ChatSettingsId"); + + b.Property("DiscordChannelId") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("IrcChannel"); + + b.Property("IsAdminChannel") + .IsRequired(); + + b.Property("IsUpdatesChannel") + .IsRequired(); + + b.Property("IsWatchdogChannel") + .IsRequired(); + + b.Property("Tag"); + + b.HasKey("Id"); + + b.HasIndex("ChatSettingsId", "DiscordChannelId") + .IsUnique() + .HasFilter("[DiscordChannelId] IS NOT NULL"); + + b.HasIndex("ChatSettingsId", "IrcChannel") + .IsUnique() + .HasFilter("[IrcChannel] IS NOT NULL"); + + b.ToTable("ChatChannels"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ByondVersion") + .IsRequired(); + + b.Property("DirectoryName") + .IsRequired(); + + b.Property("DmeName") + .IsRequired(); + + b.Property("JobId"); + + b.Property("MinimumSecurityLevel"); + + b.Property("Output") + .IsRequired(); + + b.Property("RevisionInformationId"); + + b.HasKey("Id"); + + b.HasIndex("DirectoryName"); + + b.HasIndex("JobId") + .IsUnique(); + + b.HasIndex("RevisionInformationId"); + + b.ToTable("CompileJobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AccessToken"); + + b.Property("AllowWebClient") + .IsRequired(); + + b.Property("AutoStart") + .IsRequired(); + + b.Property("InstanceId"); + + b.Property("PrimaryPort"); + + b.Property("ProcessId"); + + b.Property("SecondaryPort"); + + b.Property("SecurityLevel"); + + b.Property("SoftRestart") + .IsRequired(); + + b.Property("SoftShutdown") + .IsRequired(); + + b.Property("StartupTimeout"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamDaemonSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ApiValidationPort"); + + b.Property("ApiValidationSecurityLevel"); + + b.Property("InstanceId"); + + b.Property("ProjectName"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamMakerSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AutoUpdateInterval"); + + b.Property("ConfigurationType"); + + b.Property("Name") + .IsRequired(); + + b.Property("Online") + .IsRequired(); + + b.Property("Path") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("Path") + .IsUnique(); + + b.ToTable("Instances"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ByondRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("ChatBotRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("ConfigurationRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("DreamDaemonRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("DreamMakerRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("InstanceId"); + + b.Property("InstanceUserRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("RepositoryRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("UserId", "InstanceId") + .IsUnique(); + + b.ToTable("InstanceUsers"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CancelRight") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("CancelRightsType") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("Cancelled") + .IsRequired(); + + b.Property("CancelledById"); + + b.Property("Description") + .IsRequired(); + + b.Property("ExceptionDetails"); + + b.Property("InstanceId"); + + b.Property("StartedAt") + .IsRequired(); + + b.Property("StartedById"); + + b.Property("StoppedAt"); + + b.HasKey("Id"); + + b.HasIndex("CancelledById"); + + b.HasIndex("InstanceId"); + + b.HasIndex("StartedById"); + + b.ToTable("Jobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AccessIdentifier") + .IsRequired(); + + b.Property("ChatChannelsJson") + .IsRequired(); + + b.Property("ChatCommandsJson") + .IsRequired(); + + b.Property("CompileJobId"); + + b.Property("IsPrimary"); + + b.Property("Port"); + + b.Property("ProcessId"); + + b.Property("RebootState"); + + b.Property("ServerCommandsJson") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("CompileJobId"); + + b.ToTable("ReattachInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AccessToken"); + + b.Property("AccessUser"); + + b.Property("AutoUpdatesKeepTestMerges") + .IsRequired(); + + b.Property("AutoUpdatesSynchronize") + .IsRequired(); + + b.Property("CommitterEmail") + .IsRequired(); + + b.Property("CommitterName") + .IsRequired(); + + b.Property("InstanceId"); + + b.Property("PushTestMergeCommits") + .IsRequired(); + + b.Property("ShowTestMergeCommitters") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("RepositorySettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("RevisionInformationId"); + + b.Property("TestMergeId"); + + b.HasKey("Id"); + + b.HasIndex("RevisionInformationId"); + + b.HasIndex("TestMergeId"); + + b.ToTable("RevInfoTestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CommitSha") + .IsRequired() + .HasMaxLength(40); + + b.Property("InstanceId"); + + b.Property("OriginCommitSha") + .IsRequired() + .HasMaxLength(40); + + b.HasKey("Id"); + + b.HasIndex("CommitSha") + .IsUnique(); + + b.HasIndex("InstanceId"); + + b.ToTable("RevisionInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Author") + .IsRequired(); + + b.Property("BodyAtMerge") + .IsRequired(); + + b.Property("Comment"); + + b.Property("MergedAt"); + + b.Property("MergedById"); + + b.Property("Number") + .IsRequired(); + + b.Property("PrimaryRevisionInformationId") + .IsRequired(); + + b.Property("PullRequestRevision") + .IsRequired(); + + b.Property("TitleAtMerge") + .IsRequired(); + + b.Property("Url") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("MergedById"); + + b.HasIndex("PrimaryRevisionInformationId") + .IsUnique(); + + b.ToTable("TestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AdministrationRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("CanonicalName") + .IsRequired(); + + b.Property("CreatedAt") + .IsRequired(); + + b.Property("CreatedById"); + + b.Property("Enabled") + .IsRequired(); + + b.Property("InstanceManagerRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("LastPasswordUpdate"); + + b.Property("Name") + .IsRequired(); + + b.Property("PasswordHash"); + + b.Property("SystemIdentifier"); + + b.HasKey("Id"); + + b.HasIndex("CanonicalName") + .IsUnique(); + + b.HasIndex("CreatedById"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.WatchdogReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AlphaId"); + + b.Property("AlphaIsActive"); + + b.Property("BravoId"); + + b.Property("InstanceId"); + + b.HasKey("Id"); + + b.HasIndex("AlphaId"); + + b.HasIndex("BravoId"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("WatchdogReattachInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("ChatSettings") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings") + .WithMany("Channels") + .HasForeignKey("ChatSettingsId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.HasOne("Tgstation.Server.Host.Models.Job", "Job") + .WithOne() + .HasForeignKey("Tgstation.Server.Host.Models.CompileJob", "JobId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("CompileJobs") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamDaemonSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamMakerSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("InstanceUsers") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User") + .WithMany("InstanceUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CancelledBy") + .WithMany() + .HasForeignKey("CancelledById"); + + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("Jobs") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy") + .WithMany() + .HasForeignKey("StartedById") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") + .WithMany() + .HasForeignKey("CompileJobId"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("RepositorySettings") + .HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("ActiveTestMerges") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge") + .WithMany("RevisonInformations") + .HasForeignKey("TestMergeId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("RevisionInformations") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy") + .WithMany("TestMerges") + .HasForeignKey("MergedById") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation") + .WithOne("PrimaryTestMerge") + .HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy") + .WithMany("CreatedUsers") + .HasForeignKey("CreatedById"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.WatchdogReattachInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.ReattachInformation", "Alpha") + .WithMany() + .HasForeignKey("AlphaId"); + + b.HasOne("Tgstation.Server.Host.Models.ReattachInformation", "Bravo") + .WithMany() + .HasForeignKey("BravoId"); + + b.HasOne("Tgstation.Server.Host.Models.Instance") + .WithOne("WatchdogReattachInformation") + .HasForeignKey("Tgstation.Server.Host.Models.WatchdogReattachInformation", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Tgstation.Server.Host/Models/Migrations/20180918205224_MSNullableAndForeignKeyCleanup.cs b/src/Tgstation.Server.Host/Models/Migrations/20180918205224_MSNullableAndForeignKeyCleanup.cs new file mode 100644 index 0000000000..3cc60483d6 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/Migrations/20180918205224_MSNullableAndForeignKeyCleanup.cs @@ -0,0 +1,216 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Tgstation.Server.Host.Models.Migrations +{ + /// + /// Cleans up of nullable columns and foreign keys MSSQL + /// + public partial class MSNullableAndForeignKeyCleanup : Migration + { + /// + /// Applies the migration + /// + /// The to use + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Instances_WatchdogReattachInformations_WatchdogReattachInformationId", + table: "Instances"); + + migrationBuilder.DropForeignKey( + name: "FK_TestMerges_RevisionInformations_PrimaryRevisionInformationId", + table: "TestMerges"); + + migrationBuilder.DropIndex( + name: "IX_TestMerges_PrimaryRevisionInformationId", + table: "TestMerges"); + + migrationBuilder.DropIndex( + name: "IX_Instances_WatchdogReattachInformationId", + table: "Instances"); + + migrationBuilder.DropIndex( + name: "IX_CompileJobs_JobId", + table: "CompileJobs"); + + migrationBuilder.DropColumn( + name: "WatchdogReattachInformationId", + table: "Instances"); + + migrationBuilder.AddColumn( + name: "InstanceId", + table: "WatchdogReattachInformations", + nullable: false, + defaultValue: 0L); + + migrationBuilder.AlterColumn( + name: "PrimaryRevisionInformationId", + table: "TestMerges", + nullable: false, + oldClrType: typeof(long), + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "Output", + table: "CompileJobs", + nullable: false, + oldClrType: typeof(string), + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "JobId", + table: "CompileJobs", + nullable: false, + oldClrType: typeof(long), + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "DmeName", + table: "CompileJobs", + nullable: false, + oldClrType: typeof(string), + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "DirectoryName", + table: "CompileJobs", + nullable: false, + oldClrType: typeof(Guid), + oldNullable: true); + + migrationBuilder.CreateIndex( + name: "IX_WatchdogReattachInformations_InstanceId", + table: "WatchdogReattachInformations", + column: "InstanceId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_TestMerges_PrimaryRevisionInformationId", + table: "TestMerges", + column: "PrimaryRevisionInformationId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_CompileJobs_JobId", + table: "CompileJobs", + column: "JobId", + unique: true); + + migrationBuilder.AddForeignKey( + name: "FK_TestMerges_RevisionInformations_PrimaryRevisionInformationId", + table: "TestMerges", + column: "PrimaryRevisionInformationId", + principalTable: "RevisionInformations", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + name: "FK_WatchdogReattachInformations_Instances_InstanceId", + table: "WatchdogReattachInformations", + column: "InstanceId", + principalTable: "Instances", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + /// Unapplies the migration + /// + /// The to use + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_TestMerges_RevisionInformations_PrimaryRevisionInformationId", + table: "TestMerges"); + + migrationBuilder.DropForeignKey( + name: "FK_WatchdogReattachInformations_Instances_InstanceId", + table: "WatchdogReattachInformations"); + + migrationBuilder.DropIndex( + name: "IX_WatchdogReattachInformations_InstanceId", + table: "WatchdogReattachInformations"); + + migrationBuilder.DropIndex( + name: "IX_TestMerges_PrimaryRevisionInformationId", + table: "TestMerges"); + + migrationBuilder.DropIndex( + name: "IX_CompileJobs_JobId", + table: "CompileJobs"); + + migrationBuilder.DropColumn( + name: "InstanceId", + table: "WatchdogReattachInformations"); + + migrationBuilder.AlterColumn( + name: "PrimaryRevisionInformationId", + table: "TestMerges", + nullable: true, + oldClrType: typeof(long)); + + migrationBuilder.AddColumn( + name: "WatchdogReattachInformationId", + table: "Instances", + nullable: true); + + migrationBuilder.AlterColumn( + name: "Output", + table: "CompileJobs", + nullable: true, + oldClrType: typeof(string)); + + migrationBuilder.AlterColumn( + name: "JobId", + table: "CompileJobs", + nullable: true, + oldClrType: typeof(long)); + + migrationBuilder.AlterColumn( + name: "DmeName", + table: "CompileJobs", + nullable: true, + oldClrType: typeof(string)); + + migrationBuilder.AlterColumn( + name: "DirectoryName", + table: "CompileJobs", + nullable: true, + oldClrType: typeof(Guid)); + + migrationBuilder.CreateIndex( + name: "IX_TestMerges_PrimaryRevisionInformationId", + table: "TestMerges", + column: "PrimaryRevisionInformationId", + unique: true, + filter: "[PrimaryRevisionInformationId] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_Instances_WatchdogReattachInformationId", + table: "Instances", + column: "WatchdogReattachInformationId"); + + migrationBuilder.CreateIndex( + name: "IX_CompileJobs_JobId", + table: "CompileJobs", + column: "JobId"); + + migrationBuilder.AddForeignKey( + name: "FK_Instances_WatchdogReattachInformations_WatchdogReattachInformationId", + table: "Instances", + column: "WatchdogReattachInformationId", + principalTable: "WatchdogReattachInformations", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + name: "FK_TestMerges_RevisionInformations_PrimaryRevisionInformationId", + table: "TestMerges", + column: "PrimaryRevisionInformationId", + principalTable: "RevisionInformations", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + } + } +} diff --git a/src/Tgstation.Server.Host/Models/Migrations/MySqlDatabaseContextModelSnapshot.cs b/src/Tgstation.Server.Host/Models/Migrations/MySqlDatabaseContextModelSnapshot.cs index 7658150957..e273129437 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/MySqlDatabaseContextModelSnapshot.cs +++ b/src/Tgstation.Server.Host/Models/Migrations/MySqlDatabaseContextModelSnapshot.cs @@ -2,6 +2,8 @@ using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Models.Migrations { @@ -83,15 +85,18 @@ namespace Tgstation.Server.Host.Models.Migrations b.Property("ByondVersion") .IsRequired(); - b.Property("DirectoryName"); + b.Property("DirectoryName") + .IsRequired(); - b.Property("DmeName"); + b.Property("DmeName") + .IsRequired(); - b.Property("JobId"); + b.Property("JobId"); b.Property("MinimumSecurityLevel"); - b.Property("Output"); + b.Property("Output") + .IsRequired(); b.Property("RevisionInformationId"); @@ -99,7 +104,8 @@ namespace Tgstation.Server.Host.Models.Migrations b.HasIndex("DirectoryName"); - b.HasIndex("JobId"); + b.HasIndex("JobId") + .IsUnique(); b.HasIndex("RevisionInformationId"); @@ -189,15 +195,11 @@ namespace Tgstation.Server.Host.Models.Migrations b.Property("Path") .IsRequired(); - b.Property("WatchdogReattachInformationId"); - b.HasKey("Id"); b.HasIndex("Path") .IsUnique(); - b.HasIndex("WatchdogReattachInformationId"); - b.ToTable("Instances"); }); @@ -408,7 +410,8 @@ namespace Tgstation.Server.Host.Models.Migrations b.Property("Number") .IsRequired(); - b.Property("PrimaryRevisionInformationId"); + b.Property("PrimaryRevisionInformationId") + .IsRequired(); b.Property("PullRequestRevision") .IsRequired(); @@ -479,12 +482,17 @@ namespace Tgstation.Server.Host.Models.Migrations b.Property("BravoId"); + b.Property("InstanceId"); + b.HasKey("Id"); b.HasIndex("AlphaId"); b.HasIndex("BravoId"); + b.HasIndex("InstanceId") + .IsUnique(); + b.ToTable("WatchdogReattachInformations"); }); @@ -507,8 +515,9 @@ namespace Tgstation.Server.Host.Models.Migrations modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => { b.HasOne("Tgstation.Server.Host.Models.Job", "Job") - .WithMany() - .HasForeignKey("JobId"); + .WithOne() + .HasForeignKey("Tgstation.Server.Host.Models.CompileJob", "JobId") + .OnDelete(DeleteBehavior.Restrict); b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") .WithMany("CompileJobs") @@ -532,13 +541,6 @@ namespace Tgstation.Server.Host.Models.Migrations .OnDelete(DeleteBehavior.Cascade); }); - modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => - { - b.HasOne("Tgstation.Server.Host.Models.WatchdogReattachInformation", "WatchdogReattachInformation") - .WithMany() - .HasForeignKey("WatchdogReattachInformationId"); - }); - modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => { b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") @@ -615,7 +617,7 @@ namespace Tgstation.Server.Host.Models.Migrations b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation") .WithOne("PrimaryTestMerge") .HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId") - .OnDelete(DeleteBehavior.SetNull); + .OnDelete(DeleteBehavior.Restrict); }); modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => @@ -634,6 +636,11 @@ namespace Tgstation.Server.Host.Models.Migrations b.HasOne("Tgstation.Server.Host.Models.ReattachInformation", "Bravo") .WithMany() .HasForeignKey("BravoId"); + + b.HasOne("Tgstation.Server.Host.Models.Instance") + .WithOne("WatchdogReattachInformation") + .HasForeignKey("Tgstation.Server.Host.Models.WatchdogReattachInformation", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); }); #pragma warning restore 612, 618 } diff --git a/src/Tgstation.Server.Host/Models/Migrations/SqlServerDatabaseContextModelSnapshot.cs b/src/Tgstation.Server.Host/Models/Migrations/SqlServerDatabaseContextModelSnapshot.cs index d8ea3e4706..0174e55a09 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/SqlServerDatabaseContextModelSnapshot.cs +++ b/src/Tgstation.Server.Host/Models/Migrations/SqlServerDatabaseContextModelSnapshot.cs @@ -4,6 +4,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Models.Migrations { @@ -92,15 +93,18 @@ namespace Tgstation.Server.Host.Models.Migrations b.Property("ByondVersion") .IsRequired(); - b.Property("DirectoryName"); + b.Property("DirectoryName") + .IsRequired(); - b.Property("DmeName"); + b.Property("DmeName") + .IsRequired(); - b.Property("JobId"); + b.Property("JobId"); b.Property("MinimumSecurityLevel"); - b.Property("Output"); + b.Property("Output") + .IsRequired(); b.Property("RevisionInformationId"); @@ -108,7 +112,8 @@ namespace Tgstation.Server.Host.Models.Migrations b.HasIndex("DirectoryName"); - b.HasIndex("JobId"); + b.HasIndex("JobId") + .IsUnique(); b.HasIndex("RevisionInformationId"); @@ -196,15 +201,11 @@ namespace Tgstation.Server.Host.Models.Migrations b.Property("Path") .IsRequired(); - b.Property("WatchdogReattachInformationId"); - b.HasKey("Id"); b.HasIndex("Path") .IsUnique(); - b.HasIndex("WatchdogReattachInformationId"); - b.ToTable("Instances"); }); @@ -431,7 +432,8 @@ namespace Tgstation.Server.Host.Models.Migrations b.Property("Number") .IsRequired(); - b.Property("PrimaryRevisionInformationId"); + b.Property("PrimaryRevisionInformationId") + .IsRequired(); b.Property("PullRequestRevision") .IsRequired(); @@ -447,8 +449,7 @@ namespace Tgstation.Server.Host.Models.Migrations b.HasIndex("MergedById"); b.HasIndex("PrimaryRevisionInformationId") - .IsUnique() - .HasFilter("[PrimaryRevisionInformationId] IS NOT NULL"); + .IsUnique(); b.ToTable("TestMerges"); }); @@ -507,12 +508,17 @@ namespace Tgstation.Server.Host.Models.Migrations b.Property("BravoId"); + b.Property("InstanceId"); + b.HasKey("Id"); b.HasIndex("AlphaId"); b.HasIndex("BravoId"); + b.HasIndex("InstanceId") + .IsUnique(); + b.ToTable("WatchdogReattachInformations"); }); @@ -535,8 +541,9 @@ namespace Tgstation.Server.Host.Models.Migrations modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => { b.HasOne("Tgstation.Server.Host.Models.Job", "Job") - .WithMany() - .HasForeignKey("JobId"); + .WithOne() + .HasForeignKey("Tgstation.Server.Host.Models.CompileJob", "JobId") + .OnDelete(DeleteBehavior.Restrict); b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") .WithMany("CompileJobs") @@ -560,13 +567,6 @@ namespace Tgstation.Server.Host.Models.Migrations .OnDelete(DeleteBehavior.Cascade); }); - modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => - { - b.HasOne("Tgstation.Server.Host.Models.WatchdogReattachInformation", "WatchdogReattachInformation") - .WithMany() - .HasForeignKey("WatchdogReattachInformationId"); - }); - modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => { b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") @@ -643,7 +643,7 @@ namespace Tgstation.Server.Host.Models.Migrations b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation") .WithOne("PrimaryTestMerge") .HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId") - .OnDelete(DeleteBehavior.SetNull); + .OnDelete(DeleteBehavior.Restrict); }); modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => @@ -662,6 +662,11 @@ namespace Tgstation.Server.Host.Models.Migrations b.HasOne("Tgstation.Server.Host.Models.ReattachInformation", "Bravo") .WithMany() .HasForeignKey("BravoId"); + + b.HasOne("Tgstation.Server.Host.Models.Instance") + .WithOne("WatchdogReattachInformation") + .HasForeignKey("Tgstation.Server.Host.Models.WatchdogReattachInformation", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); }); #pragma warning restore 612, 618 } From d8fafff1cdede1ee9ec9c33f9c7aa0cd511b94c8 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 18 Sep 2018 16:59:38 -0400 Subject: [PATCH 150/230] Thank god EF caught this for us --- src/Tgstation.Server.Host/Models/CompileJob.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Tgstation.Server.Host/Models/CompileJob.cs b/src/Tgstation.Server.Host/Models/CompileJob.cs index 3dc141372c..3fadc5b182 100644 --- a/src/Tgstation.Server.Host/Models/CompileJob.cs +++ b/src/Tgstation.Server.Host/Models/CompileJob.cs @@ -9,6 +9,7 @@ namespace Tgstation.Server.Host.Models /// /// See /// + [Required] public Job Job { get; set; } /// From 6d0006f3606d152078883f6110d63867c0869c73 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 18 Sep 2018 17:25:34 -0400 Subject: [PATCH 151/230] Configure committer name and email in local repository settings before synchronization script --- .../Components/Repository/IRepository.cs | 4 +++- .../Components/Repository/Repository.cs | 12 +++++++++++- .../Controllers/RepositoryController.cs | 6 +++--- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Repository/IRepository.cs b/src/Tgstation.Server.Host/Components/Repository/IRepository.cs index 244ef15f91..5cdd2e08ea 100644 --- a/src/Tgstation.Server.Host/Components/Repository/IRepository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/IRepository.cs @@ -113,10 +113,12 @@ namespace Tgstation.Server.Host.Components.Repository /// /// The username to fetch from the origin repository /// The password to fetch from the origin repository + /// The name of the potential committer + /// The e-mail of the potential committer /// If the synchronizations should be made to the tracked reference as opposed to a temporary branch /// The for the operation /// A representing the running operation - Task Sychronize(string username, string password, bool synchronizeTrackedBranch, CancellationToken cancellationToken); + Task Sychronize(string username, string password, string committerName, string committerEmail, bool synchronizeTrackedBranch, CancellationToken cancellationToken); /// /// Copies the current working directory to a given diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs index afa1e36c5c..889f899ecb 100644 --- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs @@ -375,7 +375,7 @@ namespace Tgstation.Server.Host.Components.Repository } /// - public async Task Sychronize(string username, string password, bool synchronizeTrackedBranch, CancellationToken cancellationToken) + public async Task Sychronize(string username, string password, string committerName, string committerEmail, bool synchronizeTrackedBranch, CancellationToken cancellationToken) { if (username == null && password == null) return; @@ -384,9 +384,19 @@ namespace Tgstation.Server.Host.Components.Repository throw new ArgumentNullException(nameof(username)); if (password == null) throw new ArgumentNullException(nameof(password)); + if (committerName == null) + throw new ArgumentNullException(nameof(committerName)); + if (committerEmail == null) + throw new ArgumentNullException(nameof(committerEmail)); var startHead = Head; + await Task.Factory.StartNew(() => + { + repository.Config.Set("name", committerName); + repository.Config.Set("email", committerEmail); + }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); + if (!await eventConsumer.HandleEvent(EventType.RepoPreSynchronize, new List { ioMananger.ResolvePath(".") }, cancellationToken).ConfigureAwait(false)) return; diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index 240227aaaf..330d242422 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -476,7 +476,7 @@ namespace Tgstation.Server.Host.Controllers if (fastForward.Value) { lastRevisionInfo.OriginCommitSha = repo.Head; - await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, true, ct).ConfigureAwait(false); + await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, currentModel.CommitterName, currentModel.CommitterEmail, true, ct).ConfigureAwait(false); } } } @@ -502,7 +502,7 @@ namespace Tgstation.Server.Host.Controllers if (!repo.Tracking) throw new JobException("Checked out reference does not track a remote object!"); await repo.ResetToOrigin(ct).ConfigureAwait(false); - await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, true, ct).ConfigureAwait(false); + await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, currentModel.CommitterName, currentModel.CommitterEmail, true, ct).ConfigureAwait(false); await LoadRevisionInformation(repo, databaseContext, attachedInstance, null, x => lastRevisionInfo = x, ct).ConfigureAwait(false); //repo head is on origin so force this //will update the db if necessary @@ -695,7 +695,7 @@ namespace Tgstation.Server.Host.Controllers if (startSha != repo.Head) { - await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, false, ct).ConfigureAwait(false); + await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, currentModel.CommitterName, currentModel.CommitterEmail, false, ct).ConfigureAwait(false); await UpdateRevInfo().ConfigureAwait(false); } await databaseContext.Save(ct).ConfigureAwait(false); From beb6661bda0a9604a7de4a931227fe81af044f4d Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 18 Sep 2018 17:26:40 -0400 Subject: [PATCH 152/230] Use correct configuration keys --- src/Tgstation.Server.Host/Components/Repository/Repository.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs index 889f899ecb..30cb9c1d94 100644 --- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs @@ -393,8 +393,8 @@ namespace Tgstation.Server.Host.Components.Repository await Task.Factory.StartNew(() => { - repository.Config.Set("name", committerName); - repository.Config.Set("email", committerEmail); + repository.Config.Set("user.name", committerName); + repository.Config.Set("user.email", committerEmail); }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); if (!await eventConsumer.HandleEvent(EventType.RepoPreSynchronize, new List { ioMananger.ResolvePath(".") }, cancellationToken).ConfigureAwait(false)) From 573316547bf04e3be64baa50b2a7b1cb5749e0fd Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Tue, 18 Sep 2018 19:49:57 -0400 Subject: [PATCH 153/230] Fix build --- src/Tgstation.Server.Host/Components/Instance.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index 708725c349..1c36c2e917 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -251,7 +251,7 @@ namespace Tgstation.Server.Host.Components //synch if necessary if (repositorySettings.AutoUpdatesSynchronize.Value && startSha != repo.Head) - await repo.Sychronize(repositorySettings.AccessUser, repositorySettings.AccessToken, shouldSyncTracked, jobCancellationToken).ConfigureAwait(false); + await repo.Sychronize(repositorySettings.AccessUser, repositorySettings.AccessToken, repositorySettings.CommitterName, repositorySettings.CommitterEmail, shouldSyncTracked, jobCancellationToken).ConfigureAwait(false); progressReporter(5 * ProgressStep); } From 86677e0395798b9381b02e857312ac6c6401343d Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Tue, 18 Sep 2018 19:56:34 -0400 Subject: [PATCH 154/230] Fix crashing while processing unknown chat commands --- src/Tgstation.Server.Host/Components/Chat/Chat.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Chat/Chat.cs b/src/Tgstation.Server.Host/Components/Chat/Chat.cs index e5653dc5e0..64c763c22e 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Chat.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Chat.cs @@ -269,18 +269,18 @@ namespace Tgstation.Server.Host.Components.Chat var commandHandler = await GetCommand(command).ConfigureAwait(false); - if (commandHandler.AdminOnly && !message.User.Channel.IsAdmin) - { - await SendMessage("Use this command in an admin channel!", new List { message.User.Channel.RealId }, cancellationToken).ConfigureAwait(false); - return; - } - if (commandHandler == default) { await SendMessage(UnknownCommandMessage, new List { message.User.Channel.RealId }, cancellationToken).ConfigureAwait(false); return; } + if (commandHandler.AdminOnly && !message.User.Channel.IsAdmin) + { + await SendMessage("Use this command in an admin channel!", new List { message.User.Channel.RealId }, cancellationToken).ConfigureAwait(false); + return; + } + var result = await commandHandler.Invoke(arguments, message.User, cancellationToken).ConfigureAwait(false); if (result != null) await SendMessage(result, new List { message.User.Channel.RealId }, cancellationToken).ConfigureAwait(false); From df68ff9644a1020170cfe4b301b314ba21f99ecb Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Tue, 18 Sep 2018 20:05:30 -0400 Subject: [PATCH 155/230] Remove lie from event documentation --- src/Tgstation.Server.Host/Components/EventType.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/EventType.cs b/src/Tgstation.Server.Host/Components/EventType.cs index add5b3c21a..a46b4ddb4e 100644 --- a/src/Tgstation.Server.Host/Components/EventType.cs +++ b/src/Tgstation.Server.Host/Components/EventType.cs @@ -22,7 +22,7 @@ /// RepoMergePullRequest = 3, /// - /// Parameters: Absolute path to repository root, committer name, committer email + /// Parameters: Absolute path to repository root /// RepoPreSynchronize = 4, From 28456b843b88df4aff263f4375c7fe7d2c515491 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Tue, 18 Sep 2018 20:46:13 -0400 Subject: [PATCH 156/230] Fix ActiveCompileJob only being set if watchdog is running --- src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs index c13ec7e820..c9f2520e6d 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs @@ -124,7 +124,7 @@ namespace Tgstation.Server.Host.Controllers if (revision) { - result.ActiveCompileJob = dd.ActiveCompileJob?.ToApi(); + result.ActiveCompileJob = (dd.ActiveCompileJob ?? instance.LatestCompileJob())?.ToApi(); var compileJob = instance.LatestCompileJob(); result.StagedCompileJob = compileJob?.ToApi(); } From 7474bd7d0aed9413483ca886897f2b82825287e5 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Tue, 18 Sep 2018 20:47:37 -0400 Subject: [PATCH 157/230] Fix StagedCompileJob being the same as the ActiveCompileJob --- .../Controllers/DreamDaemonController.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs index c9f2520e6d..af3bb8cc15 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs @@ -124,9 +124,10 @@ namespace Tgstation.Server.Host.Controllers if (revision) { - result.ActiveCompileJob = (dd.ActiveCompileJob ?? instance.LatestCompileJob())?.ToApi(); - var compileJob = instance.LatestCompileJob(); - result.StagedCompileJob = compileJob?.ToApi(); + var latestCompileJob = instance.LatestCompileJob(); + result.ActiveCompileJob = (dd.ActiveCompileJob ?? latestCompileJob)?.ToApi(); + if (latestCompileJob?.Id != result.ActiveCompileJob?.Id) + result.StagedCompileJob = latestCompileJob?.ToApi(); } return Json(result); From eaafbac7ed3d243cbb8417a8604db70c7115333b Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Tue, 18 Sep 2018 20:58:27 -0400 Subject: [PATCH 158/230] Fix typo --- src/Tgstation.Server.Host/Controllers/RepositoryController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index 330d242422..d04b10b932 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -388,7 +388,7 @@ namespace Tgstation.Server.Host.Controllers string description = null; if (model.UpdateFromOrigin == true) if (model.Reference != null) - description = String.Format(CultureInfo.InvariantCulture, "Fetch and hard reset repsitory to origin/{0}", model.Reference); + description = String.Format(CultureInfo.InvariantCulture, "Fetch and hard reset repository to origin/{0}", model.Reference); else if (model.CheckoutSha != null) description = String.Format(CultureInfo.InvariantCulture, "Fetch and checkout {0} in repository", model.CheckoutSha); else From 72f270e9127e97a8ef2e02b02e372df6cba58939 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Wed, 19 Sep 2018 00:10:07 -0400 Subject: [PATCH 159/230] Add ApiValidationSecurityLevel to /DreamMaker response --- .../Controllers/DreamMakerController.cs | 6 +----- .../Models/DreamMakerSettings.cs | 11 +++++++++++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs index 48843b419d..8551b58622 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs @@ -51,11 +51,7 @@ namespace Tgstation.Server.Host.Controllers { var instance = instanceManager.GetInstance(Instance); var dreamMakerSettings = await DatabaseContext.DreamMakerSettings.Where(x => x.InstanceId == Instance.Id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); - return Json(new DreamMaker - { - ProjectName = dreamMakerSettings.ProjectName, - ApiValidationPort = dreamMakerSettings.ApiValidationPort - }); + return Json(dreamMakerSettings.ToApi()); } /// diff --git a/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs b/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs index 6520d92f52..2306587463 100644 --- a/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs +++ b/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs @@ -20,5 +20,16 @@ namespace Tgstation.Server.Host.Models /// [Required] public Instance Instance { get; set; } + + /// + /// Convert the to it's API form + /// + /// A new + public Api.Models.DreamMaker ToApi() => new Api.Models.DreamMaker + { + ProjectName = ProjectName, + ApiValidationPort = ApiValidationPort, + ApiValidationSecurityLevel = ApiValidationSecurityLevel + }; } } From bf96da16b95192490f2e6572561df505f897f7a6 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Wed, 19 Sep 2018 09:02:25 -0400 Subject: [PATCH 160/230] Fix doc comment --- src/Tgstation.Server.Host/Models/DreamMakerSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs b/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs index 2306587463..0137be1678 100644 --- a/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs +++ b/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs @@ -22,7 +22,7 @@ namespace Tgstation.Server.Host.Models public Instance Instance { get; set; } /// - /// Convert the to it's API form + /// Convert the to it's API form /// /// A new public Api.Models.DreamMaker ToApi() => new Api.Models.DreamMaker From f5429c1c21681b5c1de08b7a3fdabac21f789bbe Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 19 Sep 2018 09:42:50 -0400 Subject: [PATCH 161/230] Remove untracked files after PreSynchronize script --- .../Components/Repository/Repository.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs index 30cb9c1d94..36a69e8484 100644 --- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs @@ -397,8 +397,15 @@ namespace Tgstation.Server.Host.Components.Repository repository.Config.Set("user.email", committerEmail); }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); - if (!await eventConsumer.HandleEvent(EventType.RepoPreSynchronize, new List { ioMananger.ResolvePath(".") }, cancellationToken).ConfigureAwait(false)) - return; + try + { + if (!await eventConsumer.HandleEvent(EventType.RepoPreSynchronize, new List { ioMananger.ResolvePath(".") }, cancellationToken).ConfigureAwait(false)) + return; + } + finally + { + await Task.Factory.StartNew(repository.RemoveUntrackedFiles, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); + } if (!synchronizeTrackedBranch) { From 9f935c292865982e9c86b0e2aeca9f819262050d Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 19 Sep 2018 09:47:06 -0400 Subject: [PATCH 162/230] Inject loggers into RepositoryManager and Repository --- .../Components/InstanceFactory.cs | 2 +- .../Components/Repository/Repository.cs | 10 +++++++++- .../Components/Repository/RepositoryManager.cs | 17 +++++++++++++++-- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index 35c0fd23df..a703c61c02 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -148,7 +148,7 @@ namespace Tgstation.Server.Host.Components var dmbFactory = new DmbFactory(databaseContextFactory, gameIoManager, loggerFactory.CreateLogger(), metadata.CloneMetadata()); try { - var repoManager = new RepositoryManager(metadata.RepositorySettings, repoIoManager, eventConsumer); + var repoManager = new RepositoryManager(metadata.RepositorySettings, repoIoManager, eventConsumer, loggerFactory.CreateLogger(), loggerFactory.CreateLogger()); try { var byond = new ByondManager(byondIOManager, byondInstaller, loggerFactory.CreateLogger()); diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs index 36a69e8484..0259b42935 100644 --- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs @@ -1,4 +1,5 @@ using LibGit2Sharp; +using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Globalization; @@ -66,6 +67,11 @@ namespace Tgstation.Server.Host.Components.Repository /// readonly IEventConsumer eventConsumer; + /// + /// The for the + /// + readonly ILogger logger; + /// /// to be taken when is called /// @@ -90,12 +96,14 @@ namespace Tgstation.Server.Host.Components.Repository /// The value of /// The value of /// The value of + /// The value of /// The value if - public Repository(LibGit2Sharp.IRepository repository, IIOManager ioMananger, IEventConsumer eventConsumer, Action onDispose) + public Repository(LibGit2Sharp.IRepository repository, IIOManager ioMananger, IEventConsumer eventConsumer, ILogger logger, Action onDispose) { this.repository = repository ?? throw new ArgumentNullException(nameof(repository)); this.ioMananger = ioMananger ?? throw new ArgumentNullException(nameof(ioMananger)); this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer)); + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); this.onDispose = onDispose ?? throw new ArgumentNullException(nameof(onDispose)); IsGitHubRepository = Origin.Contains(GitHubUrl, StringComparison.InvariantCultureIgnoreCase); if (IsGitHubRepository) diff --git a/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs b/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs index 9b7ef5e95b..a34c461d70 100644 --- a/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs +++ b/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs @@ -1,4 +1,5 @@ using LibGit2Sharp; +using Microsoft.Extensions.Logging; using System; using System.Threading; using System.Threading.Tasks; @@ -27,6 +28,16 @@ namespace Tgstation.Server.Host.Components.Repository /// readonly IEventConsumer eventConsumer; + /// + /// The created s + /// + readonly ILogger repositoryLogger; + + /// + /// The for the + /// + readonly ILogger logger; + /// /// The for the /// @@ -43,7 +54,9 @@ namespace Tgstation.Server.Host.Components.Repository /// The value of /// The value of /// The value of - public RepositoryManager(RepositorySettings repositorySettings, IIOManager ioManager, IEventConsumer eventConsumer) + /// The value of + /// The value of + public RepositoryManager(RepositorySettings repositorySettings, IIOManager ioManager, IEventConsumer eventConsumer, ILogger repositoryLogger, ILogger logger) { this.repositorySettings = repositorySettings ?? throw new ArgumentNullException(nameof(repositorySettings)); this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); @@ -139,7 +152,7 @@ namespace Tgstation.Server.Host.Components.Repository return null; } var localSemaphore = semaphore; - return new Repository(repo, ioManager, eventConsumer, () => + return new Repository(repo, ioManager, eventConsumer, repositoryLogger, () => { localSemaphore?.Release(); localSemaphore = null; From 305d076b29b673fba9ea3f1666d3106f79ae9c6d Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 19 Sep 2018 10:04:29 -0400 Subject: [PATCH 163/230] Add logging to RepositoryManager --- .../Repository/RepositoryManager.cs | 77 +++++++++++++++---- 1 file changed, 64 insertions(+), 13 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs b/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs index a34c461d70..cc50ff085e 100644 --- a/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs +++ b/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs @@ -65,11 +65,21 @@ namespace Tgstation.Server.Host.Components.Repository } /// - public void Dispose() => semaphore.Dispose(); + public void Dispose() + { + logger.LogTrace("Disposed"); + semaphore.Dispose(); + } /// public async Task CloneRepository(Uri url, string initialBranch, string username, string password, Action progressReporter, CancellationToken cancellationToken) { + if (url == null) + throw new ArgumentNullException(nameof(url)); + if (progressReporter == null) + throw new ArgumentNullException(nameof(progressReporter)); + + logger.LogInformation("Begin clone {0} (Branch: {1})", url, initialBranch); lock (this) { if (CloneInProgress) @@ -79,6 +89,8 @@ namespace Tgstation.Server.Host.Components.Repository try { using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) + { + logger.LogTrace("Semaphore acquired"); if (!await ioManager.DirectoryExists(".", cancellationToken).ConfigureAwait(false)) try { @@ -100,11 +112,31 @@ namespace Tgstation.Server.Host.Components.Repository OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested, RepositoryOperationStarting = (a) => !cancellationToken.IsCancellationRequested, BranchName = initialBranch, - CredentialsProvider = (a, b, c) => username != null ? (Credentials)new UsernamePasswordCredentials + CredentialsProvider = (a, b, supportedCredentialTypes) => { - Username = username, - Password = password - } : new DefaultCredentials() + 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!"); + } }); } catch (UserCancelledException) { } @@ -115,13 +147,22 @@ namespace Tgstation.Server.Host.Components.Repository { try { + logger.LogTrace("Deleting partially cloned repository..."); await ioManager.DeleteDirectory(".", default).ConfigureAwait(false); } - catch { } + catch (Exception e) + { + logger.LogDebug("Error deleting partially cloned repository! Exception: {0}", e); + } throw; } else + { + logger.LogDebug("Repository exists, clone aborted!"); return null; + } + } + logger.LogInformation("Clone complete!"); } finally { @@ -133,6 +174,7 @@ namespace Tgstation.Server.Host.Components.Repository /// public async Task LoadRepository(CancellationToken cancellationToken) { + logger.LogTrace("Begin LoadRepository..."); lock (this) if (CloneInProgress) throw new InvalidOperationException("The repository is being cloned!"); @@ -142,28 +184,37 @@ namespace Tgstation.Server.Host.Components.Repository { try { + logger.LogTrace("Creating LibGit2Sharp.Repository..."); repo = new LibGit2Sharp.Repository(ioManager.ResolvePath(".")); } - catch (RepositoryNotFoundException) { } + catch (RepositoryNotFoundException e) + { + logger.LogDebug("Repository not found!"); + logger.LogTrace("Exception: {0}", e); + } + catch + { + semaphore.Release(); + throw; + } }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); if (repo == null) { semaphore.Release(); return null; } - var localSemaphore = semaphore; - return new Repository(repo, ioManager, eventConsumer, repositoryLogger, () => - { - localSemaphore?.Release(); - localSemaphore = null; - }); + return new Repository(repo, ioManager, eventConsumer, repositoryLogger, () => semaphore.Release()); } /// public async Task DeleteRepository(CancellationToken cancellationToken) { + logger.LogInformation("Deleting repository..."); using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) + { + logger.LogTrace("Semaphore acquired, deleting Repository directory..."); await ioManager.DeleteDirectory(".", cancellationToken).ConfigureAwait(false); + } } } } From 3f821d22f84d6b97bda63c375d3fe7774ebc1e3d Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 19 Sep 2018 11:46:13 -0400 Subject: [PATCH 164/230] Massive Repository Cleanup: - Added (I)CredentialsProvider for easier formatting - Refactored out several portions of duplicated Repository code - Many more repository functions now take progress reporters via OnCheckoutProgress and PushOptions progress handlers - Added much needed logging to Repository functions - Converted most Repository InvalidOperationExceptions to JobExceptions - Added needed calls to RemoveUntrackedFiles() in several places - Progress reporters are now required arguments --- .../Components/Instance.cs | 31 ++- .../Components/InstanceFactory.cs | 11 +- .../Repository/CredentialsProvider.cs | 52 ++++ .../Repository/ICredentialsProvider.cs | 18 ++ .../Components/Repository/IRepository.cs | 19 +- .../Components/Repository/Repository.cs | 260 +++++++++++++----- .../Repository/RepositoryManager.cs | 45 ++- .../Controllers/RepositoryController.cs | 53 ++-- src/Tgstation.Server.Host/Core/Application.cs | 2 + 9 files changed, 346 insertions(+), 145 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/Repository/CredentialsProvider.cs create mode 100644 src/Tgstation.Server.Host/Components/Repository/ICredentialsProvider.cs diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index 1c36c2e917..91302f8fdc 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -207,9 +207,19 @@ namespace Tgstation.Server.Host.Components var repositorySettingsTask = databaseContext.RepositorySettings.Where(x => x.InstanceId == metadata.Id).FirstAsync(jobCancellationToken); //assume 5 steps with synchronize - const int ProgressSections = 5; + const int ProgressSections = 7; const int ProgressStep = 100 / ProgressSections; - progressReporter(0 * ProgressStep); + + + const int NumSteps = 3; + var doneSteps = 0; + + Action NextProgressReporter() + { + var tmpDoneSteps = doneSteps; + ++doneSteps; + return progress => progressReporter((progress + 100 * tmpDoneSteps) / NumSteps); + }; using (var repo = await RepositoryManager.LoadRepository(jobCancellationToken).ConfigureAwait(false)) { @@ -219,17 +229,11 @@ namespace Tgstation.Server.Host.Components noRepo = true; return; } - progressReporter(1 * ProgressStep); var repositorySettings = await repositorySettingsTask.ConfigureAwait(false); - - const int SecondStepProgress = 2 * ProgressStep; - progressReporter(SecondStepProgress); - + //the main point of auto update is to pull the remote - await repo.FetchOrigin(repositorySettings.AccessUser, repositorySettings.AccessToken, x => progressReporter(SecondStepProgress + (x / ProgressSections)), jobCancellationToken).ConfigureAwait(false); - - progressReporter(3 * ProgressStep); + await repo.FetchOrigin(repositorySettings.AccessUser, repositorySettings.AccessToken, NextProgressReporter(), jobCancellationToken).ConfigureAwait(false); var startSha = repo.Head; @@ -237,21 +241,20 @@ namespace Tgstation.Server.Host.Components bool shouldSyncTracked; if (repositorySettings.AutoUpdatesKeepTestMerges.Value) { - var result = await repo.MergeOrigin(repositorySettings.CommitterName, repositorySettings.CommitterEmail, jobCancellationToken).ConfigureAwait(false); + var result = await repo.MergeOrigin(repositorySettings.CommitterName, repositorySettings.CommitterEmail, NextProgressReporter(), jobCancellationToken).ConfigureAwait(false); if (!result.HasValue) return; shouldSyncTracked = result.Value; } else { - await repo.ResetToOrigin(jobCancellationToken).ConfigureAwait(false); + await repo.ResetToOrigin(NextProgressReporter(), jobCancellationToken).ConfigureAwait(false); shouldSyncTracked = true; } - progressReporter(4 * ProgressStep); //synch if necessary if (repositorySettings.AutoUpdatesSynchronize.Value && startSha != repo.Head) - await repo.Sychronize(repositorySettings.AccessUser, repositorySettings.AccessToken, repositorySettings.CommitterName, repositorySettings.CommitterEmail, shouldSyncTracked, jobCancellationToken).ConfigureAwait(false); + await repo.Sychronize(repositorySettings.AccessUser, repositorySettings.AccessToken, repositorySettings.CommitterName, repositorySettings.CommitterEmail, NextProgressReporter(), shouldSyncTracked, jobCancellationToken).ConfigureAwait(false); progressReporter(5 * ProgressStep); } diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index a703c61c02..c9494cbd0c 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -93,6 +93,11 @@ namespace Tgstation.Server.Host.Components /// readonly IJobManager jobManager; + /// + /// The for the + /// + readonly ICredentialsProvider credentialsProvider; + /// /// Construct an /// @@ -111,7 +116,8 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// The value of - public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IByondTopicSender byondTopicSender, IServerControl serverUpdater, ICryptographySuite cryptographySuite, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IByondInstaller byondInstaller, IProviderFactory providerFactory, IProcessExecutor processExecutor, IPostWriteHandler postWriteHandler, IWatchdogFactory watchdogFactory, IJobManager jobManager) + /// The value of + public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IByondTopicSender byondTopicSender, IServerControl serverUpdater, ICryptographySuite cryptographySuite, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IByondInstaller byondInstaller, IProviderFactory providerFactory, IProcessExecutor processExecutor, IPostWriteHandler postWriteHandler, IWatchdogFactory watchdogFactory, IJobManager jobManager, ICredentialsProvider credentialsProvider) { this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); @@ -128,6 +134,7 @@ 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)); } /// @@ -148,7 +155,7 @@ namespace Tgstation.Server.Host.Components var dmbFactory = new DmbFactory(databaseContextFactory, gameIoManager, loggerFactory.CreateLogger(), metadata.CloneMetadata()); try { - var repoManager = new RepositoryManager(metadata.RepositorySettings, repoIoManager, eventConsumer, loggerFactory.CreateLogger(), loggerFactory.CreateLogger()); + var repoManager = new RepositoryManager(metadata.RepositorySettings, repoIoManager, eventConsumer, credentialsProvider, loggerFactory.CreateLogger(), loggerFactory.CreateLogger()); try { var byond = new ByondManager(byondIOManager, byondInstaller, loggerFactory.CreateLogger()); diff --git a/src/Tgstation.Server.Host/Components/Repository/CredentialsProvider.cs b/src/Tgstation.Server.Host/Components/Repository/CredentialsProvider.cs new file mode 100644 index 0000000000..38dd416d11 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Repository/CredentialsProvider.cs @@ -0,0 +1,52 @@ +using LibGit2Sharp; +using LibGit2Sharp.Handlers; +using Microsoft.Extensions.Logging; +using System; + +namespace Tgstation.Server.Host.Components.Repository +{ + /// + sealed class CredentialsProvider : ICredentialsProvider + { + /// + /// The for the + /// + readonly ILogger logger; + + /// + /// Construct a + /// + /// The value of + public CredentialsProvider(ILogger logger) + { + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + /// + 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!"); + }; + } +} diff --git a/src/Tgstation.Server.Host/Components/Repository/ICredentialsProvider.cs b/src/Tgstation.Server.Host/Components/Repository/ICredentialsProvider.cs new file mode 100644 index 0000000000..c130b02a78 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Repository/ICredentialsProvider.cs @@ -0,0 +1,18 @@ +using LibGit2Sharp.Handlers; + +namespace Tgstation.Server.Host.Components.Repository +{ + /// + /// For generating s + /// + interface ICredentialsProvider + { + /// + /// Generate a from a given and + /// + /// The optional username to use in the + /// The optional password to use in the + /// A new + CredentialsHandler GenerateHandler(string username, string password); + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/Repository/IRepository.cs b/src/Tgstation.Server.Host/Components/Repository/IRepository.cs index 5cdd2e08ea..594469d138 100644 --- a/src/Tgstation.Server.Host/Components/Repository/IRepository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/IRepository.cs @@ -57,9 +57,10 @@ namespace Tgstation.Server.Host.Components.Repository /// Checks out a given /// /// The sha or reference to checkout + /// to report 0-100 progress of the operation /// The for the operation /// A representing the running operation - Task CheckoutObject(string committish, CancellationToken cancellationToken); + Task CheckoutObject(string committish, Action progressReporter, CancellationToken cancellationToken); /// /// Attempt to merge a GitHub pull request into HEAD @@ -70,7 +71,7 @@ namespace Tgstation.Server.Host.Components.Repository /// The username to fetch from the origin repository /// The password to fetch from the origin repository /// The for the operation - /// Optional function to report 0-100 progress of the clone + /// to report 0-100 progress of the operation /// A resulting in a representing the merge result that is after a fast forward or up to date, on a merge, on a conflict Task AddTestMerge(TestMergeParameters testMergeParameters, string committerName, string committerEmail, string username, string password, Action progressReporter, CancellationToken cancellationToken); @@ -79,7 +80,7 @@ namespace Tgstation.Server.Host.Components.Repository /// /// The username to fetch from the origin repository /// The password to fetch from the origin repository - /// Optional function to report 0-100 progress of the clone + /// to report 0-100 progress of the operation /// The for the operation /// A representing the running operation Task FetchOrigin(string username, string password, Action progressReporter, CancellationToken cancellationToken); @@ -87,26 +88,29 @@ namespace Tgstation.Server.Host.Components.Repository /// /// Requires the current HEAD to be a tracked reference. Hard resets the reference to what it tracks on the origin repository /// + /// to report 0-100 progress of the operation /// The for the operation /// A resulting in the SHA of the new HEAD - Task ResetToOrigin(CancellationToken cancellationToken); + Task ResetToOrigin(Action progressReporter, CancellationToken cancellationToken); /// /// Requires the current HEAD to be a reference. Hard resets the reference to the given sha /// /// The sha hash to reset to + /// to report 0-100 progress of the operation /// The for the operation /// A resulting in the SHA of the new HEAD - Task ResetToSha(string sha, CancellationToken cancellationToken); + Task ResetToSha(string sha, Action progressReporter, CancellationToken cancellationToken); /// /// Requires the current HEAD to be a tracked reference. Merges the reference to what it tracks on the origin repository /// /// The name of the merge committer /// The e-mail of the merge committer + /// to report 0-100 progress of the operation /// The for the operation /// A resulting in a representing the merge result that is after a fast forward, on a merge or up to date, on a conflict - Task MergeOrigin(string committerName, string committerEmail, CancellationToken cancellationToken); + Task MergeOrigin(string committerName, string committerEmail, Action progressReporter, CancellationToken cancellationToken); /// /// Runs the synchronize event script and attempts to push any changes made to the if on a tracked branch @@ -116,9 +120,10 @@ namespace Tgstation.Server.Host.Components.Repository /// The name of the potential committer /// The e-mail of the potential committer /// If the synchronizations should be made to the tracked reference as opposed to a temporary branch + /// to report 0-100 progress of the operation /// The for the operation /// A representing the running operation - Task Sychronize(string username, string password, string committerName, string committerEmail, bool synchronizeTrackedBranch, CancellationToken cancellationToken); + Task Sychronize(string username, string password, string committerName, string committerEmail, Action progressReporter, bool synchronizeTrackedBranch, CancellationToken cancellationToken); /// /// Copies the current working directory to a given diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs index 0259b42935..7eb8890da0 100644 --- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs @@ -1,4 +1,5 @@ using LibGit2Sharp; +using LibGit2Sharp.Handlers; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; @@ -67,6 +68,11 @@ namespace Tgstation.Server.Host.Components.Repository /// readonly IEventConsumer eventConsumer; + /// + /// The for the + /// + readonly ICredentialsProvider credentialsProvider; + /// /// The for the /// @@ -77,7 +83,7 @@ namespace Tgstation.Server.Host.Components.Repository /// readonly Action onDispose; - static void GetRepositoryOwnerName(string remote, out string owner, out string name) + void GetRepositoryOwnerName(string remote, out string owner, out string name) { //Assume standard gh format: [(git)|(https)]://github.com/owner/repo(.git)[0-1] //Yes use .git twice in case it was weird @@ -88,21 +94,32 @@ namespace Tgstation.Server.Host.Components.Repository var splits = remote.Split('/'); name = splits[splits.Length - 1]; owner = splits[splits.Length - 2].Split('.')[0]; + + logger.LogTrace("GetRepositoryOwnerName({0}) => {1} / {2}", remote, owner, name); } + /// + /// Converts a given to a + /// + /// to report 0-100 progress of the operation + /// A based on + static CheckoutProgressHandler CheckoutProgressHandler(Action progressReporter) => (a, completedSteps, totalSteps) => progressReporter((int)((((float)completedSteps) / totalSteps) * 100)); + /// /// Construct a /// /// The value of /// The value of /// The value of + /// The value of /// The value of /// The value if - public Repository(LibGit2Sharp.IRepository repository, IIOManager ioMananger, IEventConsumer eventConsumer, ILogger logger, Action onDispose) + public Repository(LibGit2Sharp.IRepository repository, IIOManager ioMananger, IEventConsumer eventConsumer, ICredentialsProvider credentialsProvider, ILogger logger, Action onDispose) { this.repository = repository ?? throw new ArgumentNullException(nameof(repository)); this.ioMananger = ioMananger ?? throw new ArgumentNullException(nameof(ioMananger)); this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer)); + this.credentialsProvider = credentialsProvider ?? throw new ArgumentNullException(nameof(credentialsProvider)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); this.onDispose = onDispose ?? throw new ArgumentNullException(nameof(onDispose)); IsGitHubRepository = Origin.Contains(GitHubUrl, StringComparison.InvariantCultureIgnoreCase); @@ -117,20 +134,57 @@ namespace Tgstation.Server.Host.Components.Repository /// public void Dispose() { + logger.LogTrace("Disposing..."); repository.Dispose(); onDispose.Invoke(); } + /// + /// Generate a standard set of + /// + /// to report 0-100 progress of the operation + /// The username for the + /// The password for the + /// The for the operation + /// A new set of + PushOptions GeneratePushOptions(Action progressReporter, string username, string password, CancellationToken cancellationToken) => new PushOptions + { + OnPackBuilderProgress = (stage, current, total) => + { + var baseProgress = stage == PackBuilderStage.Counting ? 0 : 25; + progressReporter(baseProgress + ((int)(25 * ((float)current) / total))); + return !cancellationToken.IsCancellationRequested; + }, + OnNegotiationCompletedBeforePush = (a) => !cancellationToken.IsCancellationRequested, + OnPushTransferProgress = (a, sentBytes, totalBytes) => + { + progressReporter(50 + ((int)(50 * ((float)sentBytes) / totalBytes))); + return !cancellationToken.IsCancellationRequested; + }, + CredentialsProvider = credentialsProvider.GenerateHandler(username, password) + }; + /// /// Runs a blocking force checkout to /// /// The committish to checkout - void RawCheckout(string committish) + /// Progress reporter + /// The for the operation + void RawCheckout(string committish, Action progressReporter, CancellationToken cancellationToken) { + logger.LogTrace("Checkout: {0}", committish); + + progressReporter(0); + cancellationToken.ThrowIfCancellationRequested(); + Commands.Checkout(repository, committish, new CheckoutOptions { - CheckoutModifiers = CheckoutModifiers.Force + CheckoutModifiers = CheckoutModifiers.Force, + OnCheckoutProgress = CheckoutProgressHandler(progressReporter) }); + + cancellationToken.ThrowIfCancellationRequested(); + repository.RemoveUntrackedFiles(); } @@ -139,22 +193,25 @@ namespace Tgstation.Server.Host.Components.Repository { if (testMergeParameters == null) throw new ArgumentNullException(nameof(testMergeParameters)); - if (committerName == null) throw new ArgumentNullException(nameof(committerName)); if (committerEmail == null) throw new ArgumentNullException(nameof(committerEmail)); + if (progressReporter == null) + throw new ArgumentNullException(nameof(progressReporter)); + + logger.LogDebug("Begin AddTestMerge: #{0} at {1} ({4}) by <{2} ({3})>", testMergeParameters.Number, testMergeParameters.PullRequestRevision?.Substring(0, 7), committerName, committerEmail, testMergeParameters.Comment); if (!IsGitHubRepository) - throw new InvalidOperationException("Test merging is only available on GitHub hosted origin repositories!"); + throw new JobException("Test merging is only available on GitHub hosted origin repositories!"); var commitMessage = String.Format(CultureInfo.InvariantCulture, "Test merge of pull request #{0}{1}{2}", testMergeParameters.Number.Value, testMergeParameters.Comment != null ? Environment.NewLine : String.Empty, testMergeParameters.Comment ?? String.Empty); - var prBranchName = String.Format(CultureInfo.InvariantCulture, "pr-{0}", testMergeParameters.Number); var localBranchName = String.Format(CultureInfo.InvariantCulture, "pull/{0}/headrefs/heads/{1}", testMergeParameters.Number, prBranchName); - var Refspec = new List { String.Format(CultureInfo.InvariantCulture, "pull/{0}/head:{1}", testMergeParameters.Number, prBranchName) }; + var refSpec = String.Format(CultureInfo.InvariantCulture, "pull/{0}/head:{1}", testMergeParameters.Number, prBranchName); + var refSpecList = new List { refSpec }; var logMessage = String.Format(CultureInfo.InvariantCulture, "Merge remote pull request #{0}", testMergeParameters.Number); var originalCommit = repository.Head; @@ -168,39 +225,45 @@ namespace Tgstation.Server.Host.Components.Repository { try { + logger.LogTrace("Fetching refspec {0}...", refSpec); + var remote = repository.Network.Remotes.First(); - Commands.Fetch((LibGit2Sharp.Repository)repository, remote.Name, Refspec, new FetchOptions + progressReporter(0); + Commands.Fetch((LibGit2Sharp.Repository)repository, remote.Name, refSpecList, new FetchOptions { Prune = true, OnProgress = (a) => !cancellationToken.IsCancellationRequested, OnTransferProgress = (a) => { - var percentage = 100 * (((float)a.IndexedObjects + a.ReceivedObjects) / (a.TotalObjects * 2)); - progressReporter?.Invoke((int)percentage); + var percentage = 50 * (((float)a.IndexedObjects + a.ReceivedObjects) / (a.TotalObjects * 2)); + progressReporter((int)percentage); return !cancellationToken.IsCancellationRequested; }, OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested, - CredentialsProvider = (a, b, c) => username != null ? (Credentials)new UsernamePasswordCredentials - { - Username = username, - Password = password - } : new DefaultCredentials() + CredentialsProvider = credentialsProvider.GenerateHandler(username, password) }, logMessage); } catch (UserCancelledException) { } cancellationToken.ThrowIfCancellationRequested(); + repository.RemoveUntrackedFiles(); + + cancellationToken.ThrowIfCancellationRequested(); + testMergeParameters.PullRequestRevision = repository.Lookup(testMergeParameters.PullRequestRevision ?? localBranchName).Sha; cancellationToken.ThrowIfCancellationRequested(); + logger.LogTrace("Merging {0} into {1}...", testMergeParameters.PullRequestRevision.Substring(0, 7), Reference); + result = repository.Merge(testMergeParameters.PullRequestRevision, sig, new MergeOptions { CommitOnSuccess = commitMessage == null, FailOnConflict = true, FastForwardStrategy = FastForwardStrategy.NoFastForward, - SkipReuc = true + SkipReuc = true, + OnCheckoutProgress = (a, completedSteps, totalSteps) => progressReporter(50 + ((int)((((float)completedSteps) / totalSteps) * 50))) }); } finally @@ -212,7 +275,9 @@ namespace Tgstation.Server.Host.Components.Repository if (result.Status == MergeStatus.Conflicts) { - RawCheckout(originalCommit.CanonicalName ?? originalCommit.Tip.Sha); + var revertTo = originalCommit.CanonicalName ?? originalCommit.Tip.Sha; + logger.LogDebug("Merge conflict, aborting and reverting to {0}", revertTo); + RawCheckout(revertTo, progressReporter, cancellationToken); cancellationToken.ThrowIfCancellationRequested(); } @@ -226,27 +291,41 @@ namespace Tgstation.Server.Host.Components.Repository } if (commitMessage != null && result.Status != MergeStatus.UpToDate) - repository.Commit(commitMessage, sig, sig, new CommitOptions + { + logger.LogTrace("Committing merge: \"{0}\"...", commitMessage); + await Task.Factory.StartNew(() => repository.Commit(commitMessage, sig, sig, new CommitOptions { PrettifyMessage = true - }); + }), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); + } return true; } /// - public async Task CheckoutObject(string committish, CancellationToken cancellationToken) + public async Task CheckoutObject(string committish, Action progressReporter, CancellationToken cancellationToken) { if (committish == null) throw new ArgumentNullException(nameof(committish)); + if (progressReporter == null) + throw new ArgumentNullException(nameof(progressReporter)); + logger.LogDebug("Checkout object: {0}...", committish); await eventConsumer.HandleEvent(EventType.RepoCheckout, new List { committish }, cancellationToken).ConfigureAwait(false); - await Task.Factory.StartNew(() => RawCheckout(committish), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); + await Task.Factory.StartNew(() => + { + repository.RemoveUntrackedFiles(); + RawCheckout(committish, progressReporter, cancellationToken); + }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); } /// - public Task FetchOrigin(string username, string password, Action progressReporter, CancellationToken cancellationToken) => Task.WhenAll( - eventConsumer.HandleEvent(EventType.RepoFetch, Array.Empty(), cancellationToken), - Task.Factory.StartNew(() => + public async Task FetchOrigin(string username, string password, Action progressReporter, CancellationToken cancellationToken) + { + if (progressReporter == null) + throw new ArgumentNullException(nameof(progressReporter)); + logger.LogDebug("Fetch origin..."); + await eventConsumer.HandleEvent(EventType.RepoFetch, Array.Empty(), cancellationToken).ConfigureAwait(false); + await Task.Factory.StartNew(() => { var remote = repository.Network.Remotes.First(); try @@ -258,32 +337,31 @@ namespace Tgstation.Server.Host.Components.Repository OnTransferProgress = (a) => { var percentage = 100 * (((float)a.IndexedObjects + a.ReceivedObjects) / (a.TotalObjects * 2)); - progressReporter?.Invoke((int)percentage); + progressReporter((int)percentage); return !cancellationToken.IsCancellationRequested; }, OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested, - CredentialsProvider = (a, b, c) => username != null ? (Credentials)new UsernamePasswordCredentials - { - Username = username, - Password = password - } : new DefaultCredentials() + CredentialsProvider = credentialsProvider.GenerateHandler(username, password) }, "Fetch origin commits"); } catch (UserCancelledException) { cancellationToken.ThrowIfCancellationRequested(); } - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current)); + }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); + } /// - /// Force push the current repository HEAD to ; + /// Force push the current repository HEAD to ; /// /// The username to fetch from the origin repository /// The password to fetch from the origin repository + /// to report 0-100 progress of the operation /// The for the operation /// A representing the running operation - Task PushHeadToTemporaryBranch(string username, string password, CancellationToken cancellationToken) => Task.Factory.StartNew(() => + Task PushHeadToTemporaryBranch(string username, string password, Action progressReporter, CancellationToken cancellationToken) => Task.Factory.StartNew(() => { + logger.LogInformation("Pushing changes to temporary remote branch..."); var branch = repository.CreateBranch(RemoteTemporaryBranchName); try { @@ -291,17 +369,7 @@ namespace Tgstation.Server.Host.Components.Repository var remote = repository.Network.Remotes.First(); try { - repository.Network.Push(remote, String.Format(CultureInfo.InvariantCulture, "+{0}:{0}", branch.CanonicalName), new PushOptions - { - OnPackBuilderProgress = (a, b, c) => !cancellationToken.IsCancellationRequested, - OnNegotiationCompletedBeforePush = (a) => !cancellationToken.IsCancellationRequested, - OnPushTransferProgress = (a, b, c) => !cancellationToken.IsCancellationRequested, - CredentialsProvider = (a, b, c) => username != null ? (Credentials)new UsernamePasswordCredentials - { - Username = username, - Password = password - } : new DefaultCredentials() - }); + repository.Network.Push(remote, String.Format(CultureInfo.InvariantCulture, "+{0}:{0}", branch.CanonicalName), GeneratePushOptions(progressReporter, username, password, cancellationToken)); } catch (UserCancelledException) { @@ -315,21 +383,41 @@ namespace Tgstation.Server.Host.Components.Repository }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); /// - public async Task ResetToOrigin(CancellationToken cancellationToken) + public async Task ResetToOrigin(Action progressReporter, CancellationToken cancellationToken) { - if (!repository.Head.IsTracking) - throw new InvalidOperationException("Cannot reset to origin while not on a tracked reference!"); + if (progressReporter == null) + throw new ArgumentNullException(nameof(progressReporter)); + if (!Tracking) + throw new JobException("Cannot reset to origin while not on a tracked reference!"); + logger.LogTrace("Reset to origin..."); var trackedBranch = repository.Head.TrackedBranch; await eventConsumer.HandleEvent(EventType.RepoResetOrigin, new List { trackedBranch.FriendlyName, trackedBranch.Tip.Sha }, cancellationToken).ConfigureAwait(false); - await ResetToSha(trackedBranch.Tip.Sha, cancellationToken).ConfigureAwait(false); + await ResetToSha(trackedBranch.Tip.Sha, progressReporter, cancellationToken).ConfigureAwait(false); } /// - public Task ResetToSha(string sha, CancellationToken cancellationToken) => Task.Factory.StartNew(() => + public Task ResetToSha(string sha, Action progressReporter, CancellationToken cancellationToken) => Task.Factory.StartNew(() => { - repository.Reset(ResetMode.Hard, sha); - cancellationToken.ThrowIfCancellationRequested(); + if (sha == null) + throw new ArgumentNullException(nameof(sha)); + if (progressReporter == null) + throw new ArgumentNullException(nameof(progressReporter)); + + logger.LogDebug("Reset to sha: {0}", sha.Substring(0, 7)); + repository.RemoveUntrackedFiles(); + cancellationToken.ThrowIfCancellationRequested(); + + var gitObject = repository.Lookup(sha, ObjectType.Commit); + cancellationToken.ThrowIfCancellationRequested(); + + if (gitObject == null) + throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Cannot reset to non-existent SHA: {0}", sha)); + + repository.Reset(ResetMode.Hard, gitObject.Peel(), new CheckoutOptions + { + OnCheckoutProgress = CheckoutProgressHandler(progressReporter) + }); }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); /// @@ -337,12 +425,16 @@ namespace Tgstation.Server.Host.Components.Repository { if (path == null) throw new ArgumentNullException(nameof(path)); + logger.LogTrace("Copying to {0}...", path); await ioMananger.CopyDirectory(".", path, new List { ".git" }, cancellationToken).ConfigureAwait(false); } /// - public async Task MergeOrigin(string committerName, string committerEmail, CancellationToken cancellationToken) + public async Task MergeOrigin(string committerName, string committerEmail, Action progressReporter, CancellationToken cancellationToken) { + if (progressReporter == null) + throw new ArgumentNullException(nameof(progressReporter)); + MergeResult result = null; Branch trackedBranch = null; @@ -350,23 +442,30 @@ namespace Tgstation.Server.Host.Components.Repository await Task.Factory.StartNew(() => { - if (!repository.Head.IsTracking) - throw new InvalidOperationException("Cannot reset to origin while not on a tracked reference!"); - trackedBranch = repository.Head.TrackedBranch; + if (!Tracking) + throw new JobException("Cannot reset to origin while not on a tracked reference!"); + repository.RemoveUntrackedFiles(); + + cancellationToken.ThrowIfCancellationRequested(); + + trackedBranch = repository.Head.TrackedBranch; + logger.LogDebug("Merge origin/{2}: <{0} ({1})>", committerName, committerEmail, trackedBranch.FriendlyName); result = repository.Merge(trackedBranch, new Signature(new Identity(committerName, committerEmail), DateTimeOffset.Now), new MergeOptions { CommitOnSuccess = true, FailOnConflict = true, FastForwardStrategy = FastForwardStrategy.Default, SkipReuc = true, + OnCheckoutProgress = CheckoutProgressHandler(progressReporter) }); cancellationToken.ThrowIfCancellationRequested(); if (result.Status == MergeStatus.Conflicts) { - RawCheckout(oldHead.CanonicalName); + logger.LogDebug("Merge conflict, aborting and reverting to {0}", oldHead.FriendlyName); + RawCheckout(oldHead.CanonicalName, progressReporter, cancellationToken); cancellationToken.ThrowIfCancellationRequested(); } @@ -383,64 +482,73 @@ namespace Tgstation.Server.Host.Components.Repository } /// - public async Task Sychronize(string username, string password, string committerName, string committerEmail, bool synchronizeTrackedBranch, CancellationToken cancellationToken) + public async Task Sychronize(string username, string password, string committerName, string committerEmail, Action progressReporter, bool synchronizeTrackedBranch, CancellationToken cancellationToken) { + if (committerName == null) + throw new ArgumentNullException(nameof(committerName)); + if (committerEmail == null) + throw new ArgumentNullException(nameof(committerEmail)); + if (progressReporter == null) + throw new ArgumentNullException(nameof(progressReporter)); + if (username == null && password == null) + { + logger.LogTrace("Not synchronizing due to lack of credentials!"); return; + } + logger.LogTrace("Begin Synchronize..."); if (username == null) throw new ArgumentNullException(nameof(username)); if (password == null) throw new ArgumentNullException(nameof(password)); - if (committerName == null) - throw new ArgumentNullException(nameof(committerName)); - if (committerEmail == null) - throw new ArgumentNullException(nameof(committerEmail)); var startHead = Head; + logger.LogTrace("Configuring <{0} ({1})> as author/committer", committerName, committerEmail); await Task.Factory.StartNew(() => { repository.Config.Set("user.name", committerName); + cancellationToken.ThrowIfCancellationRequested(); repository.Config.Set("user.email", committerEmail); }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); try { if (!await eventConsumer.HandleEvent(EventType.RepoPreSynchronize, new List { ioMananger.ResolvePath(".") }, cancellationToken).ConfigureAwait(false)) + { + logger.LogDebug("Aborted synchronize due to event handler response!"); return; + } } finally { + logger.LogTrace("Cleaning untracked files..."); await Task.Factory.StartNew(repository.RemoveUntrackedFiles, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); } if (!synchronizeTrackedBranch) { - await PushHeadToTemporaryBranch(username, password, cancellationToken).ConfigureAwait(false); + await PushHeadToTemporaryBranch(username, password, progressReporter, cancellationToken).ConfigureAwait(false); return; } - if (Head == startHead || !repository.Head.IsTracking) + var sameHead = Head == startHead; + if (sameHead || !Tracking) + { + logger.LogTrace("Aborted synchronize due to {0}!", sameHead ? "lack of changes" : "not being on tracked reference"); return; + } + + logger.LogInformation("Synchronizing with origin..."); await Task.Factory.StartNew(() => { - cancellationToken.ThrowIfCancellationRequested(); var remote = repository.Network.Remotes.First(); try { - repository.Network.Push(repository.Head, new PushOptions - { - OnPackBuilderProgress = (a, b, c) => !cancellationToken.IsCancellationRequested, - OnNegotiationCompletedBeforePush = (a) => !cancellationToken.IsCancellationRequested, - OnPushTransferProgress = (a, b, c) => !cancellationToken.IsCancellationRequested, - CredentialsProvider = (a, b, c) => username != null ? (Credentials)new UsernamePasswordCredentials - { - Username = username, - Password = password - } : new DefaultCredentials() - }); + repository.Network.Push(repository.Head, GeneratePushOptions(progressReporter, username, password, cancellationToken)); } catch (UserCancelledException) { diff --git a/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs b/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs index cc50ff085e..2ec1c57468 100644 --- a/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs +++ b/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs @@ -28,6 +28,11 @@ namespace Tgstation.Server.Host.Components.Repository /// readonly IEventConsumer eventConsumer; + /// + /// The for the + /// + readonly ICredentialsProvider credentialsProvider; + /// /// The created s /// @@ -54,20 +59,24 @@ namespace Tgstation.Server.Host.Components.Repository /// The value of /// The value of /// The value of + /// The value of /// The value of /// The value of - public RepositoryManager(RepositorySettings repositorySettings, IIOManager ioManager, IEventConsumer eventConsumer, ILogger repositoryLogger, ILogger logger) + public RepositoryManager(RepositorySettings repositorySettings, IIOManager ioManager, IEventConsumer eventConsumer, ICredentialsProvider credentialsProvider, ILogger repositoryLogger, ILogger logger) { 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); } /// public void Dispose() { - logger.LogTrace("Disposed"); + logger.LogTrace("Disposing..."); semaphore.Dispose(); } @@ -112,31 +121,7 @@ namespace Tgstation.Server.Host.Components.Repository OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested, RepositoryOperationStarting = (a) => !cancellationToken.IsCancellationRequested, BranchName = initialBranch, - CredentialsProvider = (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!"); - } + CredentialsProvider = credentialsProvider.GenerateHandler(username, password) }); } catch (UserCancelledException) { } @@ -203,7 +188,11 @@ namespace Tgstation.Server.Host.Components.Repository semaphore.Release(); return null; } - return new Repository(repo, ioManager, eventConsumer, repositoryLogger, () => semaphore.Release()); + return new Repository(repo, ioManager, eventConsumer, credentialsProvider, repositoryLogger, () => + { + logger.LogTrace("Releasing semaphore due to Repository disposal..."); + semaphore.Release(); + }); } /// diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index d04b10b932..7c18d5ec06 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -434,10 +434,19 @@ namespace Tgstation.Server.Host.Controllers var committerName = currentModel.ShowTestMergeCommitters.Value ? AuthenticationContext.User.Name : currentModel.CommitterName; - var numFetches = (model.NewTestMerges?.Count ?? 0) + (model.UpdateFromOrigin == true ? 1 : 0); - var doneFetches = 0; - if (numFetches > 0) - progressReporter(0); + var hardResettingToOriginReference = model.UpdateFromOrigin == true && model.Reference != null; + + var numSteps = (model.NewTestMerges?.Count ?? 0) + (model.UpdateFromOrigin == true ? 1 : 0) + (!modelHasShaOrReference ? 2 : (hardResettingToOriginReference ? 3 : 1)); + var doneSteps = 0; + + Action NextProgressReporter() + { + var tmpDoneSteps = doneSteps; + ++doneSteps; + return progress => progressReporter((progress + 100 * tmpDoneSteps) / numSteps); + }; + + progressReporter(0); //get a base line for where we are Models.RevisionInformation lastRevisionInfo = null; @@ -465,19 +474,21 @@ namespace Tgstation.Server.Host.Controllers { if (!repo.Tracking) throw new JobException("Not on an updatable reference!"); - await repo.FetchOrigin(currentModel.AccessUser, currentModel.AccessToken, x => progressReporter(x / numFetches), ct).ConfigureAwait(false); - doneFetches = 1; + await repo.FetchOrigin(currentModel.AccessUser, currentModel.AccessToken, NextProgressReporter(), ct).ConfigureAwait(false); + doneSteps = 1; if (!modelHasShaOrReference) { - var fastForward = await repo.MergeOrigin(committerName, currentModel.CommitterEmail, ct).ConfigureAwait(false); + var fastForward = await repo.MergeOrigin(committerName, currentModel.CommitterEmail, NextProgressReporter(), ct).ConfigureAwait(false); if (!fastForward.HasValue) throw new JobException("Merge conflict occurred during origin update!"); await UpdateRevInfo().ConfigureAwait(false); if (fastForward.Value) { lastRevisionInfo.OriginCommitSha = repo.Head; - await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, currentModel.CommitterName, currentModel.CommitterEmail, true, ct).ConfigureAwait(false); + await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, currentModel.CommitterName, currentModel.CommitterEmail, NextProgressReporter(), true, ct).ConfigureAwait(false); } + else + NextProgressReporter()(100); } } @@ -493,16 +504,18 @@ namespace Tgstation.Server.Host.Controllers if ((isSha && model.Reference != null) || (!isSha && model.CheckoutSha != null)) throw new JobException("Attempted to checkout a SHA or reference that was actually the opposite!"); - await repo.CheckoutObject(committish, ct).ConfigureAwait(false); + await repo.CheckoutObject(committish, NextProgressReporter(), ct).ConfigureAwait(false); await LoadRevisionInformation(repo, databaseContext, attachedInstance, null, x => lastRevisionInfo = x, ct).ConfigureAwait(false); //we've either seen origin before or what we're checking out is on origin } + else + NextProgressReporter()(100); - if (model.UpdateFromOrigin == true && model.Reference != null) + if (hardResettingToOriginReference) { if (!repo.Tracking) throw new JobException("Checked out reference does not track a remote object!"); - await repo.ResetToOrigin(ct).ConfigureAwait(false); - await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, currentModel.CommitterName, currentModel.CommitterEmail, true, ct).ConfigureAwait(false); + await repo.ResetToOrigin(NextProgressReporter(), ct).ConfigureAwait(false); + await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, currentModel.CommitterName, currentModel.CommitterEmail, NextProgressReporter(), true, ct).ConfigureAwait(false); await LoadRevisionInformation(repo, databaseContext, attachedInstance, null, x => lastRevisionInfo = x, ct).ConfigureAwait(false); //repo head is on origin so force this //will update the db if necessary @@ -619,7 +632,7 @@ namespace Tgstation.Server.Host.Controllers if (revInfoWereLookingFor != null) { //goteem - await repo.ResetToSha(revInfoWereLookingFor.CommitSha, cancellationToken).ConfigureAwait(false); + await repo.ResetToSha(revInfoWereLookingFor.CommitSha, NextProgressReporter(), cancellationToken).ConfigureAwait(false); lastRevisionInfo = revInfoWereLookingFor; } @@ -660,12 +673,12 @@ namespace Tgstation.Server.Host.Controllers if (I.PullRequestRevision == null && pr != null) I.PullRequestRevision = pr.Head.Sha; - var mergeResult = await repo.AddTestMerge(I, committerName, currentModel.CommitterEmail, currentModel.AccessUser, currentModel.AccessToken, x => progressReporter((x + 100 * doneFetches) / numFetches), ct).ConfigureAwait(false); + var mergeResult = await repo.AddTestMerge(I, committerName, currentModel.CommitterEmail, currentModel.AccessUser, currentModel.AccessToken, NextProgressReporter(), ct).ConfigureAwait(false); if (!mergeResult.HasValue) //conflict, we don't care, dd already knows continue; - ++doneFetches; + ++doneSteps; var revInfoUpdateTask = UpdateRevInfo(); @@ -695,17 +708,21 @@ namespace Tgstation.Server.Host.Controllers if (startSha != repo.Head) { - await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, currentModel.CommitterName, currentModel.CommitterEmail, false, ct).ConfigureAwait(false); + await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, currentModel.CommitterName, currentModel.CommitterEmail, NextProgressReporter(), false, ct).ConfigureAwait(false); await UpdateRevInfo().ConfigureAwait(false); } await databaseContext.Save(ct).ConfigureAwait(false); } catch { + doneSteps = 0; + numSteps = 2; //the stuff didn't make it into the db, forget what we've done and abort - await repo.CheckoutObject(startReference ?? startSha, default).ConfigureAwait(false); + await repo.CheckoutObject(startReference ?? startSha, NextProgressReporter(), default).ConfigureAwait(false); if (startReference != null && repo.Head != startSha) - await repo.ResetToSha(startSha, default).ConfigureAwait(false); + await repo.ResetToSha(startSha, NextProgressReporter(), default).ConfigureAwait(false); + else + progressReporter(100); throw; } } diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 6233129ca1..b024cde939 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -22,6 +22,7 @@ using System.Threading.Tasks; using Tgstation.Server.Host.Components; using Tgstation.Server.Host.Components.Byond; using Tgstation.Server.Host.Components.Chat; +using Tgstation.Server.Host.Components.Repository; using Tgstation.Server.Host.Components.Watchdog; using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Controllers; @@ -183,6 +184,7 @@ namespace Tgstation.Server.Host.Core services.AddSingleton, PasswordHasher>(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); From 7b19a41fa1614bad597b78bf9bb9fde0afddae6c Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 19 Sep 2018 14:04:54 -0400 Subject: [PATCH 165/230] Keep throwing cancelled requests --- src/Tgstation.Server.Host/Controllers/ApiController.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Tgstation.Server.Host/Controllers/ApiController.cs b/src/Tgstation.Server.Host/Controllers/ApiController.cs index 0fc660be6c..7b62fa020d 100644 --- a/src/Tgstation.Server.Host/Controllers/ApiController.cs +++ b/src/Tgstation.Server.Host/Controllers/ApiController.cs @@ -146,6 +146,7 @@ namespace Tgstation.Server.Host.Controllers catch (OperationCanceledException e) { Logger.LogDebug("Request cancelled! Exception: {0}", e); + throw; } } } From 9ad6d40a27323f5af74f1bfcf8da74cc860c3d00 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 19 Sep 2018 14:09:55 -0400 Subject: [PATCH 166/230] Clean up NonFastForwardException exceptions --- src/Tgstation.Server.Host/Components/Repository/Repository.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs index 7eb8890da0..10f7b12e84 100644 --- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs @@ -550,6 +550,10 @@ namespace Tgstation.Server.Host.Components.Repository { repository.Network.Push(repository.Head, GeneratePushOptions(progressReporter, username, password, cancellationToken)); } + catch (NonFastForwardException) + { + logger.LogInformation("Synchronize aborted, non-fast forward!"); + } catch (UserCancelledException) { cancellationToken.ThrowIfCancellationRequested(); From bdd7f5f5dcb71edd38637b3f4fe640329ea97ce9 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 19 Sep 2018 14:12:16 -0400 Subject: [PATCH 167/230] Fix test merge commits sometimes not pushing --- .../Controllers/RepositoryController.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index 7c18d5ec06..0c5ba6841b 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -428,6 +428,7 @@ namespace Tgstation.Server.Host.Controllers var startReference = repo.Reference; var startSha = repo.Head; + string postUpdateSha = null; if (newTestMerges && !repo.IsGitHubRepository) throw new JobException("Cannot test merge on a non GitHub based repository!"); @@ -486,6 +487,7 @@ namespace Tgstation.Server.Host.Controllers { lastRevisionInfo.OriginCommitSha = repo.Head; await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, currentModel.CommitterName, currentModel.CommitterEmail, NextProgressReporter(), true, ct).ConfigureAwait(false); + postUpdateSha = repo.Head; } else NextProgressReporter()(100); @@ -706,7 +708,8 @@ namespace Tgstation.Server.Host.Controllers } } - if (startSha != repo.Head) + var currentHead = repo.Head; + if (startSha != currentHead || (postUpdateSha != null && postUpdateSha != currentHead)) { await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, currentModel.CommitterName, currentModel.CommitterEmail, NextProgressReporter(), false, ct).ConfigureAwait(false); await UpdateRevInfo().ConfigureAwait(false); From efd347bfbd7172025ee5f5de43a32ec35639be86 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 19 Sep 2018 14:16:31 -0400 Subject: [PATCH 168/230] Prefer instance GitHub credentials to global ones --- .../Controllers/RepositoryController.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index 0c5ba6841b..3ca9664847 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -533,7 +533,11 @@ namespace Tgstation.Server.Host.Controllers foreach (var I in model.NewTestMerges.Where(x => String.IsNullOrWhiteSpace(x.PullRequestRevision))) I.PullRequestRevision = null; - var gitHubClient = String.IsNullOrEmpty(generalConfiguration.GitHubAccessToken) ? (currentModel.AccessToken != null ? gitHubClientFactory.CreateClient(currentModel.AccessToken) : gitHubClientFactory.CreateClient()) : gitHubClientFactory.CreateClient(generalConfiguration.GitHubAccessToken); + var gitHubClient = currentModel.AccessToken != null + ? gitHubClientFactory.CreateClient(currentModel.AccessToken) + : (String.IsNullOrEmpty(generalConfiguration.GitHubAccessToken) + ? gitHubClientFactory.CreateClient() + : gitHubClientFactory.CreateClient(generalConfiguration.GitHubAccessToken)); var repoOwner = repo.GitHubOwner; var repoName = repo.GitHubRepoName; @@ -665,6 +669,10 @@ namespace Tgstation.Server.Host.Controllers //you look at your anonymous access and sigh errorMessage = "P.R.E. RATE LIMITED"; } + catch (Octokit.AuthorizationException) + { + errorMessage = "P.R.E. BAD CREDENTIALS"; + } catch (Octokit.NotFoundException) { //you look at your shithub and sigh From 1db1fc35b58b7adc8c54585e4033b61e14361380 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 19 Sep 2018 14:19:02 -0400 Subject: [PATCH 169/230] At this level, AddTestMerge should throw an InvalidOperationException --- src/Tgstation.Server.Host/Components/Repository/Repository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs index 10f7b12e84..d6cc835f61 100644 --- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs @@ -203,7 +203,7 @@ namespace Tgstation.Server.Host.Components.Repository logger.LogDebug("Begin AddTestMerge: #{0} at {1} ({4}) by <{2} ({3})>", testMergeParameters.Number, testMergeParameters.PullRequestRevision?.Substring(0, 7), committerName, committerEmail, testMergeParameters.Comment); if (!IsGitHubRepository) - throw new JobException("Test merging is only available on GitHub hosted origin repositories!"); + throw new InvalidOperationException("Test merging is only available on GitHub hosted origin repositories!"); var commitMessage = String.Format(CultureInfo.InvariantCulture, "Test merge of pull request #{0}{1}{2}", testMergeParameters.Number.Value, testMergeParameters.Comment != null ? Environment.NewLine : String.Empty, testMergeParameters.Comment ?? String.Empty); From 3b9065061cddaefd0d265e1f4c7e00bc248666b2 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 19 Sep 2018 14:50:47 -0400 Subject: [PATCH 170/230] Return failed dependency when random GitHub API exceptions occur --- docs/API.dox | 1 + .../Controllers/AdministrationController.cs | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/docs/API.dox b/docs/API.dox index c05ca4243d..79ca621469 100644 --- a/docs/API.dox +++ b/docs/API.dox @@ -62,6 +62,7 @@ TGS will only every return the response codes listed here - 409: Conflict. Documented in the requests that use them - 410: Gone. Attempted to access/modify a resource that ideally should have been ready, but isn't or no longer is - 422: Unprocessable Entity: Used specifically when an operation that requires a server restart is unable to be performed due to the @ref Tgstation.Server.Host.Watchdog not being present in the deployment. Blame MSO. Response body contains an @ref Tgstation.Server.Api.Models.ErrorMessage +- 424: Failed Dependency: When a request that depends on the GitHub API fails for a reason other than rate limiting. Check server logs, usually this indicates a bad access token. - 426: Upgrade required: Used when the client's API version is not compatible with the server's. Response body contains an @ref Tgstation.Server.Api.Models.ErrorMessage - 429: Rate limited. Used with operations that rely on GitHub.com. If a rate limit is hit for an operation this will be returned. Response will contain a Retry-After header - 500: Server error. Please report the request and response body to the code repository diff --git a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs index e3d8fb5e6a..553163da07 100644 --- a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs +++ b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs @@ -30,6 +30,8 @@ namespace Tgstation.Server.Host.Controllers { const string RestartNotSupportedException = "This deployment of tgstation-server is lacking the Tgstation.Server.Host.Watchdog component. Restarts and version changes cannot be completed!"; + const string OctokitException = "Bad GitHub API response, check configuration! Exception: {0}"; + /// /// The for the /// @@ -128,6 +130,11 @@ namespace Tgstation.Server.Host.Controllers { return RateLimit(e); } + catch (ApiException e) + { + Logger.LogWarning(OctokitException, e); + return StatusCode((int)HttpStatusCode.FailedDependency); + } } /// @@ -154,6 +161,11 @@ namespace Tgstation.Server.Host.Controllers { return RateLimit(e); } + catch (ApiException e) + { + Logger.LogWarning(OctokitException, e); + return StatusCode((int)HttpStatusCode.FailedDependency); + } Logger.LogTrace("Release query complete!"); foreach (var release in releases) From 4ce201a79fc2dec2677a8c2d9e48a62d541e3bb3 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 19 Sep 2018 16:16:18 -0400 Subject: [PATCH 171/230] Add IChatBotsClient.GetId --- .../Components/ChatBotsClient.cs | 3 +++ .../Components/IChatBotsClient.cs | 12 ++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Client/Components/ChatBotsClient.cs b/src/Tgstation.Server.Client/Components/ChatBotsClient.cs index 73cdd02680..17dd92da59 100644 --- a/src/Tgstation.Server.Client/Components/ChatBotsClient.cs +++ b/src/Tgstation.Server.Client/Components/ChatBotsClient.cs @@ -41,5 +41,8 @@ namespace Tgstation.Server.Client.Components /// public Task Update(ChatBot settings, CancellationToken cancellationToken) => apiClient.Update(Routes.Chat, settings ?? throw new ArgumentNullException(nameof(settings)), instance.Id, cancellationToken); + + /// + public Task GetId(ChatBot settings, CancellationToken cancellationToken) => apiClient.Read(Routes.SetID(Routes.Chat, (settings ?? throw new ArgumentNullException(nameof(settings))).Id), instance.Id, cancellationToken); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Client/Components/IChatBotsClient.cs b/src/Tgstation.Server.Client/Components/IChatBotsClient.cs index 538fdf72bc..c3f223938e 100644 --- a/src/Tgstation.Server.Client/Components/IChatBotsClient.cs +++ b/src/Tgstation.Server.Client/Components/IChatBotsClient.cs @@ -11,7 +11,7 @@ namespace Tgstation.Server.Client.Components public interface IChatBotsClient { /// - /// List the + /// List the s /// /// The for the operation /// A resulting in a of the of the server @@ -26,13 +26,21 @@ namespace Tgstation.Server.Client.Components Task Create(ChatBot settings, CancellationToken cancellationToken); /// - /// Updates a setttings + /// Updates a 's setttings /// /// The to update /// The for the operation /// A resulting in the updated Task Update(ChatBot settings, CancellationToken cancellationToken); + /// + /// Get a 's setttings + /// + /// The to get + /// The for the operation + /// A resulting in the + Task GetId(ChatBot settings, CancellationToken cancellationToken); + /// /// Delete a /// From c8d908038cf113791249aef41f518726bbfe6979 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 19 Sep 2018 16:16:42 -0400 Subject: [PATCH 172/230] Bump client preview version --- src/Tgstation.Server.Client/Tgstation.Server.Client.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj index 97763f5f38..cba09ccc5d 100644 --- a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj +++ b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj @@ -3,7 +3,7 @@ netstandard2.0 Full - 4.0.0.0-preview9110 + 4.0.0.0-preview9111 true Cyberboss /tg/station 13 From 6b749b7dbc856bff97563b9e63019d17d974ea1c Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 19 Sep 2018 16:36:50 -0400 Subject: [PATCH 173/230] Fix chat bots list performing the wrong HTTP action --- src/Tgstation.Server.Client/Components/ChatBotsClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Client/Components/ChatBotsClient.cs b/src/Tgstation.Server.Client/Components/ChatBotsClient.cs index 17dd92da59..5c303f5f77 100644 --- a/src/Tgstation.Server.Client/Components/ChatBotsClient.cs +++ b/src/Tgstation.Server.Client/Components/ChatBotsClient.cs @@ -37,7 +37,7 @@ namespace Tgstation.Server.Client.Components public Task Delete(ChatBot settings, CancellationToken cancellationToken) => apiClient.Delete(Routes.SetID(Routes.Chat, settings?.Id ?? throw new ArgumentNullException(nameof(settings))), instance.Id, cancellationToken); /// - public Task> List(CancellationToken cancellationToken) => apiClient.Create>(Routes.List(Routes.Chat), instance.Id, cancellationToken); + public Task> List(CancellationToken cancellationToken) => apiClient.Read>(Routes.List(Routes.Chat), instance.Id, cancellationToken); /// public Task Update(ChatBot settings, CancellationToken cancellationToken) => apiClient.Update(Routes.Chat, settings ?? throw new ArgumentNullException(nameof(settings)), instance.Id, cancellationToken); From c95990d14acaf270d94dc6f68ee66d14f6c5189d Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 19 Sep 2018 16:41:28 -0400 Subject: [PATCH 174/230] Reset hard after PreSynchronize script --- .../Components/Repository/Repository.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs index d6cc835f61..9f8ec89daa 100644 --- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs @@ -524,13 +524,23 @@ namespace Tgstation.Server.Host.Components.Repository } finally { - logger.LogTrace("Cleaning untracked files..."); - await Task.Factory.StartNew(repository.RemoveUntrackedFiles, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); + logger.LogTrace("Resetting and cleaning untracked files..."); + await Task.Factory.StartNew(() => + { + repository.RemoveUntrackedFiles(); + cancellationToken.ThrowIfCancellationRequested(); + repository.Reset(ResetMode.Hard, repository.Head.Tip, new CheckoutOptions + { + OnCheckoutProgress = CheckoutProgressHandler(progress => progressReporter(progress / 10)) + }); + }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); } + void FinalReporter(int progress) => progressReporter((int)(((float)progress) / 100 * 90)); + if (!synchronizeTrackedBranch) { - await PushHeadToTemporaryBranch(username, password, progressReporter, cancellationToken).ConfigureAwait(false); + await PushHeadToTemporaryBranch(username, password, FinalReporter, cancellationToken).ConfigureAwait(false); return; } @@ -548,7 +558,7 @@ namespace Tgstation.Server.Host.Components.Repository var remote = repository.Network.Remotes.First(); try { - repository.Network.Push(repository.Head, GeneratePushOptions(progressReporter, username, password, cancellationToken)); + repository.Network.Push(repository.Head, GeneratePushOptions(FinalReporter, username, password, cancellationToken)); } catch (NonFastForwardException) { From b37ddfe1bc88204c535b9e5b8de0d16f987cbd81 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 20 Sep 2018 10:45:53 -0400 Subject: [PATCH 175/230] Fix restarts triggering when the same launch parameters are reloaded --- .../Internal/DreamDaemonLaunchParameters.cs | 15 ++++++++++++++- .../Components/Watchdog/Watchdog.cs | 3 +++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs index b1f3496329..5e79d35cff 100644 --- a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs +++ b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs @@ -1,4 +1,5 @@ -using System.ComponentModel.DataAnnotations; +using System; +using System.ComponentModel.DataAnnotations; namespace Tgstation.Server.Api.Models.Internal { @@ -36,5 +37,17 @@ namespace Tgstation.Server.Api.Models.Internal /// [Required] public uint? StartupTimeout { get; set; } + + /// + /// Check if we match a given set of + /// + /// The to compare against + /// if they match, otherwise + public bool Match(DreamDaemonLaunchParameters otherParameters) => + AllowWebClient == otherParameters.AllowWebClient + && SecurityLevel == otherParameters.SecurityLevel + && PrimaryPort == otherParameters.PrimaryPort + && SecondaryPort == otherParameters.SecondaryPort + && StartupTimeout == otherParameters.StartupTimeout; } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index 412612e49c..6bee502b02 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -617,6 +617,8 @@ namespace Tgstation.Server.Host.Components.Watchdog { using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) { + if (launchParameters.Match(ActiveLaunchParameters)) + return; ActiveLaunchParameters = launchParameters; if (Running) //queue an update @@ -780,6 +782,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// public async Task Restart(bool graceful, CancellationToken cancellationToken) { + logger.LogTrace("Begin Restart. Graceful: {0}", graceful); using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) { if (!graceful || !Running) From a268b4925fb84503ecb9d955c14446d39fc00c76 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 20 Sep 2018 10:52:13 -0400 Subject: [PATCH 176/230] Fix inactive server rebooting twice when launch parameters are changed --- src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index 6bee502b02..2975471eeb 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -441,11 +441,10 @@ namespace Tgstation.Server.Host.Components.Watchdog monitorState.NextAction = MonitorAction.Continue; break; case MonitorActivationReason.NewDmbAvailable: - monitorState.InactiveServerHasStagedDmb = true; - await UpdateAndRestartInactiveServer(true).ConfigureAwait(false); //next case does same thing - break; + monitorState.InactiveServerHasStagedDmb = true; + goto case MonitorActivationReason.ActiveLaunchParametersUpdated; case MonitorActivationReason.ActiveLaunchParametersUpdated: - await UpdateAndRestartInactiveServer(false).ConfigureAwait(false); + await UpdateAndRestartInactiveServer(true).ConfigureAwait(false); break; } } From 06e70bee3ffe8744b389ddeb97ab560f1038073d Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 20 Sep 2018 11:25:29 -0400 Subject: [PATCH 177/230] Immediately try to delete the temporary branch after pushing --- .../Components/Repository/Repository.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs index 9f8ec89daa..66845fc093 100644 --- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs @@ -369,7 +369,10 @@ namespace Tgstation.Server.Host.Components.Repository var remote = repository.Network.Remotes.First(); try { - repository.Network.Push(remote, String.Format(CultureInfo.InvariantCulture, "+{0}:{0}", branch.CanonicalName), GeneratePushOptions(progressReporter, username, password, cancellationToken)); + var forcePushString = String.Format(CultureInfo.InvariantCulture, "+{0}:{0}", branch.CanonicalName); + repository.Network.Push(remote,forcePushString, GeneratePushOptions(progress => progressReporter((int)(0.9f * progress)), username, password, cancellationToken)); + var removalString = String.Format(CultureInfo.InvariantCulture, ":{0}", branch.CanonicalName); + repository.Network.Push(remote, removalString, GeneratePushOptions(progress => progressReporter(90 + (int)(0.1f * progress)), username, password, cancellationToken)); } catch (UserCancelledException) { From cb6fd27ed0b5d0505e4c3926b7facddad6d79efd Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 20 Sep 2018 11:39:35 -0400 Subject: [PATCH 178/230] lock() startupTcs in Application while using it --- src/Tgstation.Server.Host/Core/Application.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index b024cde939..0abd7cadaf 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -25,7 +25,6 @@ using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Components.Repository; using Tgstation.Server.Host.Components.Watchdog; using Tgstation.Server.Host.Configuration; -using Tgstation.Server.Host.Controllers; using Tgstation.Server.Host.IO; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; @@ -47,7 +46,7 @@ namespace Tgstation.Server.Host.Core /// /// The for the /// - readonly Microsoft.Extensions.Configuration.IConfiguration configuration; + readonly IConfiguration configuration; /// /// The for the @@ -61,7 +60,7 @@ namespace Tgstation.Server.Host.Core /// /// The value of /// The value of - public Application(Microsoft.Extensions.Configuration.IConfiguration configuration, Microsoft.AspNetCore.Hosting.IHostingEnvironment hostingEnvironment) + public Application(IConfiguration configuration, Microsoft.AspNetCore.Hosting.IHostingEnvironment hostingEnvironment) { this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); this.hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); @@ -76,9 +75,7 @@ namespace Tgstation.Server.Host.Core /// Configure dependency injected services /// /// The to configure -#pragma warning disable CA1822 // Mark members as static public void ConfigureServices(IServiceCollection services) -#pragma warning restore CA1822 // Mark members as static { if (services == null) throw new ArgumentNullException(nameof(services)); @@ -266,12 +263,15 @@ namespace Tgstation.Server.Host.Core /// public void Ready(Exception initializationError) { - if (startupTcs.Task.IsCompleted) - throw new InvalidOperationException("Ready has already been called!"); - if (initializationError == null) - startupTcs.SetResult(null); - else - startupTcs.SetException(initializationError); + lock (startupTcs) + { + if (startupTcs.Task.IsCompleted) + throw new InvalidOperationException("Ready has already been called!"); + if (initializationError == null) + startupTcs.SetResult(null); + else + startupTcs.SetException(initializationError); + } } } } \ No newline at end of file From 171d46c1359f8704e4d1b42112053f852707abb0 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 20 Sep 2018 11:54:13 -0400 Subject: [PATCH 179/230] Add example DMAPI integration --- README.md | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/README.md b/README.md index 10e2440f64..9d7ba1a5c8 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,52 @@ A breaking change from V3: tgstation-server 4 now REQUIRES the DMAPI to be integ The DMAPI is fully backwards compatible and should function with any tgstation-server version to date. Updates can be performed in the same manner. Using the `TGS_EXTERNAL_CONFIGURATION` is recommended in order to make the process as easy as replacing `tgs.dm` and the `tgs` folder with a new version +### Example + +Here is a bare minimum example project that implements the essential code changes for integrating the DMAPI + +Before `tgs.dm`: +``` +//Remember, every codebase is different, you probably have better methods for these defines than the ones given here +#define TGS_EXTERNAL_CONFIGURATION +#define TGS_DEFINE_AND_SET_GLOBAL(Name, Value) var/global/##Name = ##Value +#define TGS_READ_GLOBAL(Name) global.##Name +#define TGS_WRITE_GLOBAL(Name, Value) global.##Name = ##Value +#define TGS_WORLD_ANNOUNCE(message) world << ##message +#define TGS_INFO_LOG(message) world.log << "TGS Info: [##message]" +#define TGS_ERROR_LOG(message) world.log << "TGS Error: [##message]" +#define TGS_NOTIFY_ADMINS(event) world.log << "TGS Admin Message: [##event]" +#define TGS_CLIENT_COUNT global.client_cout +#define TGS_PROTECT_DATUM(Path) // Leave blank if your codebase doesn't give administrators code reflection capabilities +``` + +Anywhere else: +```dm +var/global/client_count = 0 + +/world/New() + ..() + TgsNew() + TgsInitializationsComplete() + +/world/Reboot() + TgsReboot() + ..() + +/world/Topic() + TGS_TOPIC + ..() + +/client/New() + ..() + ++global.client_count + +/client/Del() + ..() + --global.client_count + +``` + ## Remote Access tgstation-server is an [ASP.Net Core](https://docs.microsoft.com/en-us/aspnet/core/) based on the Kestrel web server. This section is meant to serve as a general use case overview, but the entire Kestrel configuration can be modified to your liking with the configuration JSON. See [the official documentation](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel) for details. From bab28f4b3da81da1bc6c0cb17d0fa8ae56c812a2 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 20 Sep 2018 12:29:27 -0400 Subject: [PATCH 180/230] Fix file log levels among other things --- README.md | 4 ++-- build/Dockerfile | 5 ++--- build/tgs.docker.sh | 1 - .../Configuration/GeneralConfiguration.cs | 5 +++++ src/Tgstation.Server.Host/Core/Application.cs | 9 ++++++++- .../appsettings.Docker.json | 19 ------------------- src/Tgstation.Server.Host/appsettings.json | 5 +---- 7 files changed, 18 insertions(+), 30 deletions(-) delete mode 100644 src/Tgstation.Server.Host/appsettings.Docker.json diff --git a/README.md b/README.md index 9d7ba1a5c8..cc56545b7d 100644 --- a/README.md +++ b/README.md @@ -65,9 +65,9 @@ Create an `appsettings.Production.json` file next to `appsettings.json`. This wi - `General:GitHubAccessToken`: Specify a GitHub personal access token with no scopes here to highly mitigate the possiblity of 429 response codes from GitHub requests -- `Logging:LogLevel:Default`: Can be one of `Trace`, `Debug`, `Information`, `Warning`, `Error`, or `Critical`. Restricts what is put into the log files. Currently `Debug` is reccommended for help with error reporting. +- `General:LogFileLevel`: Can be one of `Trace`, `Debug`, `Information`, `Warning`, `Error`, or `Critical`. Restricts what is put into the log files. Currently `Debug` is reccommended for help with error reporting. -- `Kestrel:Endpoints:Http:Url`: The URL (i.e. interface and ports) your application should listen on. General use case should be `http://localhost:` for restricted local connections. See the Remote Access section for configuring public access to the World Wide Web. +- `Kestrel:Endpoints:Http:Url`: The URL (i.e. interface and ports) your application should listen on. General use case should be `http://localhost:` for restricted local connections. See the Remote Access section for configuring public access to the World Wide Web. This doesn't need to be changed using the docker setup and should be mapped with the `-p` option instead - `Database:DatabaseType`: Can be one of `SqlServer`, `MariaDB`, or `MySql` diff --git a/build/Dockerfile b/build/Dockerfile index 270755a249..240aeb77f9 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -20,7 +20,7 @@ WORKDIR /src/src/Tgstation.Server.Host RUN dotnet publish -c Release -o /app/lib/Default && mv /app/lib/Default/appsettings* /app FROM microsoft/dotnet:2.1-aspnetcore-runtime -EXPOSE 5000 +EXPOSE 80 #needed for byond RUN apt-get update \ @@ -32,9 +32,8 @@ WORKDIR /app COPY --from=build /app . COPY --from=build /src/build/tgs.docker.sh tgs.sh -COPY --from=build /src/src/Tgstation.Server.Host/appsettings.Docker.json . -RUN mkdir /config_data && mv appsettings.Docker.json /config_data/appsettings.Production.json +RUN mkdir /config_data VOLUME ["/config_data", "/tgs_logs", "/app/lib"] ENTRYPOINT ["./tgs.sh"] diff --git a/build/tgs.docker.sh b/build/tgs.docker.sh index f397e9b9b5..da2ae831f3 100755 --- a/build/tgs.docker.sh +++ b/build/tgs.docker.sh @@ -1,6 +1,5 @@ #!/bin/sh -mkdir /config_data cp -r /config_data/* ./ exec dotnet Tgstation.Server.Host.Console.dll "$@" diff --git a/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs b/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs index 0451038e48..fc5cf03f09 100644 --- a/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs +++ b/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs @@ -15,6 +15,11 @@ /// public string LogFileDirectory { get; set; } + /// + /// The stringified for file logging + /// + public string LogFileLevel { get; set; } + /// /// If file logging is disabled /// diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 0abd7cadaf..9ee1c8eca9 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -54,6 +54,12 @@ namespace Tgstation.Server.Host.Core readonly Microsoft.AspNetCore.Hosting.IHostingEnvironment hostingEnvironment; readonly TaskCompletionSource startupTcs; + static LogLevel GetMinimumLogLevel(string stringLevel) + { + if (String.IsNullOrWhiteSpace(stringLevel) || !Enum.TryParse(stringLevel, out var minimumLevel)) + minimumLevel = LogLevel.Information; + return minimumLevel; + } /// /// Construct an @@ -94,7 +100,8 @@ namespace Tgstation.Server.Host.Core if (generalConfiguration?.DisableFileLogging != true) { var logPath = !String.IsNullOrEmpty(generalConfiguration?.LogFileDirectory) ? generalConfiguration.LogFileDirectory : ioManager.ConcatPath(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), VersionPrefix, "Logs"); - services.AddLogging(builder => builder.AddFile(ioManager.ConcatPath(logPath, "tgs-{Date}.log"))); + + services.AddLogging(builder => builder.AddFile(ioManager.ConcatPath(logPath, "tgs-{Date}.log"), GetMinimumLogLevel(generalConfiguration?.LogFileLevel))); } services.AddOptions(); diff --git a/src/Tgstation.Server.Host/appsettings.Docker.json b/src/Tgstation.Server.Host/appsettings.Docker.json deleted file mode 100644 index 142c7e376b..0000000000 --- a/src/Tgstation.Server.Host/appsettings.Docker.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "General": { - "LogFileDirectory": "/tgs_logs", - "MinimumPasswordLength": 15, - "GitHubAccessToken": null - }, - "Kestrel": { - "Endpoints": { - "Http": { - "Url": "http://localhost:5000" - } - } - }, - "Database": { - "DatabaseType": "SqlServer or MySQL or MariaDB", - "ConnectionString": "", - "MySqlServerVersion": "" - } -} diff --git a/src/Tgstation.Server.Host/appsettings.json b/src/Tgstation.Server.Host/appsettings.json index 79fa36f60b..0504931d30 100644 --- a/src/Tgstation.Server.Host/appsettings.json +++ b/src/Tgstation.Server.Host/appsettings.json @@ -2,6 +2,7 @@ "General": { "LogFileDirectory": null, //use the default path "DisableFileLogging": false, + "LogFileLevel": "Debug", "MinimumPasswordLength": 15, "GitHubAccessToken": null }, @@ -25,10 +26,6 @@ "Default": "Trace", "Microsoft": "Warning" } - }, - "LogLevel": { - "Default": "Information", - "Microsoft": "Warning" } }, "Updates": { From a142ca1c6cba2b5a5ab92e387400c8ce23c50806 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 20 Sep 2018 12:33:40 -0400 Subject: [PATCH 181/230] Gah --- build/tgs.docker.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/tgs.docker.sh b/build/tgs.docker.sh index da2ae831f3..690f9e1c03 100755 --- a/build/tgs.docker.sh +++ b/build/tgs.docker.sh @@ -1,5 +1,5 @@ #!/bin/sh -cp -r /config_data/* ./ +ln -s /config_data/appsettings.Production.json /app/appsettings.Production.json exec dotnet Tgstation.Server.Host.Console.dll "$@" From ae022a6348e39fa5dc9e027753ff59e9bcb4542d Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 20 Sep 2018 14:58:08 -0400 Subject: [PATCH 182/230] Changing chat providers implicitly deletes all associated channels --- .../Controllers/ChatController.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Tgstation.Server.Host/Controllers/ChatController.cs b/src/Tgstation.Server.Host/Controllers/ChatController.cs index baca707185..6828a5fe5e 100644 --- a/src/Tgstation.Server.Host/Controllers/ChatController.cs +++ b/src/Tgstation.Server.Host/Controllers/ChatController.cs @@ -210,6 +210,8 @@ namespace Tgstation.Server.Host.Controllers return false; }; + var oldProvider = current.Provider; + if (CheckModified(x => x.ConnectionString, ChatBotRights.WriteConnectionString) || CheckModified(x => x.Enabled, ChatBotRights.WriteEnabled) || CheckModified(x => x.Name, ChatBotRights.WriteName) @@ -217,12 +219,18 @@ namespace Tgstation.Server.Host.Controllers || (model.Channels != null && !userRights.HasFlag(ChatBotRights.WriteChannels))) return Forbid(); - if (model.Channels != null) + var hasChannels = model.Channels != null; + if (hasChannels || (model.Provider.HasValue && model.Provider != oldProvider)) { DatabaseContext.ChatChannels.RemoveRange(current.Channels); - var dbChannels = model.Channels.Select(x => ConvertApiChatChannel(x)).ToList(); - DatabaseContext.ChatChannels.AddRange(dbChannels); - current.Channels = dbChannels; + if (hasChannels) + { + var dbChannels = model.Channels.Select(x => ConvertApiChatChannel(x)).ToList(); + DatabaseContext.ChatChannels.AddRange(dbChannels); + current.Channels = dbChannels; + } + else + current.Channels.Clear(); } await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); From e0c06e9599aa54883dbc5bc0f92a6917539f0089 Mon Sep 17 00:00:00 2001 From: steamport Date: Thu, 20 Sep 2018 15:50:59 -0400 Subject: [PATCH 183/230] Update README for reverse proxy instructions --- README.md | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index cc56545b7d..1280ec519c 100644 --- a/README.md +++ b/README.md @@ -176,21 +176,48 @@ Once complete, test that your configuration worked by visiting your proxy site f 8. You may get a prompt about enabling proxy functionality. Click `OK` 9. In the window that appears set the `Inbound Rules` textbox to the URL of your tgstation-server i.e. `http://localhost:5000`. Ensure `Enable SSL Offloading` is checked, then click `OK` -### Nginx (Reccommended for Linux) +### Caddy (Reccommended for Linux, or those unfamilar with configuring NGINX or Apache) -TODO +1. Setup a basic website configuration. Instructions on how to do so are out of scope. +2. In your Caddyfile, under a server entry, add the following (replace 8080 with the port TGS is hosted on): +``` +proxy /tgs localhost:8080 { + transparent +} +``` + +See https://caddyserver.com/docs/proxy + +### NGINX (Reccommended for Linux) + +1. Setup a basic website configuration. Instructions on how to do so are out of scope. +2. Acquire an HTTPS certificate, likely via Let's Encrypt, and configure NGINX to use it. +3. Setup a path under a server like the following (replace 8080 with the port TGS is hosted on): +``` +location /tgs { + proxy_pass http://127.0.0.1:8080; + break; +} +``` See https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/ ### Apache -TODO +1. Ensure the `mod_proxy` extension is installed. +2. Setup a basic website configuration. Instructions on how to do so are out of scope. +3. Acquire an HTTPS certificate, likely via Let's Encrypt, and configure Apache to use it. +4. Under a VirtualHost entry, setup the following (replace 8080 with the port TGS is hosted on): +``` +ProxyPass / http://127.0.0.1:8080 +ProxyPassReverse / http://127.0.0.1:8080 +``` See https://httpd.apache.org/docs/2.4/howto/reverse_proxy.html ## Usage -tgstation-sever v4 is controlled via a RESTful HTTP json API. Documentation on this API can be found [here](https://tgstation.github.io/tgstation-server/api.html). This section serves to document the concepts of the server. +tgstation-server v4 is controlled via a RESTful HTTP json API. Documentation on this API can be found [here](https://tgstation.github.io/tgstation-server/api.html). This section serves to document the concepts of the server. ### Users From fffe15229a7f75c2cc858101ff710f625b670615 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Thu, 20 Sep 2018 23:38:30 -0400 Subject: [PATCH 184/230] Fix dictionary key clash issue in IrcProvider --- .../Components/Chat/Providers/IrcProvider.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs index fa7555c7de..ce081b13e7 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs @@ -172,7 +172,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers return true; })) { - resultId = ++channelIdCounter; + resultId = channelIdCounter++; dicToCheck.Add(resultId.Value, channelName); if (dicToCheck == queryChannelIdMap) channelIdMap.Add(resultId.Value, null); @@ -359,7 +359,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers return (IReadOnlyList)channels.Select(x => { - var id = channelIdCounter; + ulong? id = null; if (!channelIdMap.Any(y => { if (y.Value != x.IrcChannel) @@ -368,15 +368,15 @@ namespace Tgstation.Server.Host.Components.Chat.Providers return true; })) { - channelIdMap.Add(id, x.IrcChannel); - ++channelIdCounter; + id = channelIdCounter++; + channelIdMap.Add(id.Value, x.IrcChannel); } return new Channel { - RealId = id, + RealId = id.Value, IsAdmin = x.IsAdminChannel == true, ConnectionName = address, - FriendlyName = channelIdMap[id], + FriendlyName = channelIdMap[id.Value], IsPrivate = false, Tag = x.Tag }; From bbdd3f805c8f736c07ddde96ddd4584904dff194 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 21 Sep 2018 10:39:28 -0400 Subject: [PATCH 185/230] Chat bots now default to enabled: false when created --- src/Tgstation.Server.Host/Controllers/ChatController.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Tgstation.Server.Host/Controllers/ChatController.cs b/src/Tgstation.Server.Host/Controllers/ChatController.cs index baca707185..0a2b5eee5d 100644 --- a/src/Tgstation.Server.Host/Controllers/ChatController.cs +++ b/src/Tgstation.Server.Host/Controllers/ChatController.cs @@ -81,12 +81,11 @@ namespace Tgstation.Server.Host.Controllers return BadRequest(new ErrorMessage { Message = "Invalid provider!" }); } - if (!model.Enabled.HasValue) - return BadRequest(new ErrorMessage { Message = "enabled cannot be null!" }); - if (!model.ValidateProviderChannelTypes()) return BadRequest(new ErrorMessage { Message = "One or more of channels aren't formatted correctly for the given provider!" }); + model.Enabled = model.Enabled ?? false; + //try to update das db first var dbModel = new Models.ChatBot { From db30c6cf56d1b452797e2adc39bd793eb3af2505 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 21 Sep 2018 10:40:33 -0400 Subject: [PATCH 186/230] Fix NullReferenceException in ValidateProviderChannelTypes() --- src/Tgstation.Server.Api/Models/ChatBot.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Api/Models/ChatBot.cs b/src/Tgstation.Server.Api/Models/ChatBot.cs index 716469fd6e..578e84446e 100644 --- a/src/Tgstation.Server.Api/Models/ChatBot.cs +++ b/src/Tgstation.Server.Api/Models/ChatBot.cs @@ -21,9 +21,9 @@ namespace Tgstation.Server.Api.Models switch (Provider) { case ChatProvider.Discord: - return Channels.Select(x => x.DiscordChannelId.HasValue && x.IrcChannel == null).All(x => x); + return Channels?.Select(x => x.DiscordChannelId.HasValue && x.IrcChannel == null).All(x => x) ?? true; case ChatProvider.Irc: - return Channels.Select(x => !x.DiscordChannelId.HasValue && x.IrcChannel != null).All(x => x); + return Channels?.Select(x => !x.DiscordChannelId.HasValue && x.IrcChannel != null).All(x => x) ?? true; default: throw new InvalidOperationException("Invalid provider type!"); } From fee3300fc3b4d65cdcfec156182248b2ce97f407 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 21 Sep 2018 10:41:23 -0400 Subject: [PATCH 187/230] Fix ValidateProviderChannelTypes() doc comment --- src/Tgstation.Server.Api/Models/ChatBot.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Api/Models/ChatBot.cs b/src/Tgstation.Server.Api/Models/ChatBot.cs index 578e84446e..97ce91ce3a 100644 --- a/src/Tgstation.Server.Api/Models/ChatBot.cs +++ b/src/Tgstation.Server.Api/Models/ChatBot.cs @@ -15,7 +15,7 @@ namespace Tgstation.Server.Api.Models /// /// Validates are correct for the /// - /// + /// if the are valid for the , otherwise public bool ValidateProviderChannelTypes() { switch (Provider) From 1b5ce43e7f39d9e9996e68e71f99b18cfd50ece8 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 21 Sep 2018 10:44:24 -0400 Subject: [PATCH 188/230] Move IrcPasswordType to the API --- .../Models}/IrcPasswordType.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename src/{Tgstation.Server.Host/Components/Chat/Providers => Tgstation.Server.Api/Models}/IrcPasswordType.cs (60%) diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcPasswordType.cs b/src/Tgstation.Server.Api/Models/IrcPasswordType.cs similarity index 60% rename from src/Tgstation.Server.Host/Components/Chat/Providers/IrcPasswordType.cs rename to src/Tgstation.Server.Api/Models/IrcPasswordType.cs index d31a3aba73..bb3451bfbd 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcPasswordType.cs +++ b/src/Tgstation.Server.Api/Models/IrcPasswordType.cs @@ -1,9 +1,9 @@ -namespace Tgstation.Server.Host.Components.Chat.Providers +namespace Tgstation.Server.Api.Models { /// - /// Represents the type of a password passed to the constructor of + /// Represents the type of a password for a /// - enum IrcPasswordType + public enum IrcPasswordType { /// /// Use server authentication From 962d08f9a6ed8f6d7413949298e9855d75941cf8 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 21 Sep 2018 11:21:35 -0400 Subject: [PATCH 189/230] Add ChatConnectionStringBuilder A way to more publically display how to create chat bot connection strings --- .../Models/DiscordConnectionStringBuilder.cs | 37 ++++++ .../Models/Internal/ChatBot.cs | 32 ++++- .../Internal/ChatConnectionStringBuilder.cs | 19 +++ .../Models/IrcConnectionStringBuilder.cs | 122 ++++++++++++++++++ .../Components/Chat/ProviderFactory.cs | 49 +------ 5 files changed, 216 insertions(+), 43 deletions(-) create mode 100644 src/Tgstation.Server.Api/Models/DiscordConnectionStringBuilder.cs create mode 100644 src/Tgstation.Server.Api/Models/Internal/ChatConnectionStringBuilder.cs create mode 100644 src/Tgstation.Server.Api/Models/IrcConnectionStringBuilder.cs diff --git a/src/Tgstation.Server.Api/Models/DiscordConnectionStringBuilder.cs b/src/Tgstation.Server.Api/Models/DiscordConnectionStringBuilder.cs new file mode 100644 index 0000000000..a919e18380 --- /dev/null +++ b/src/Tgstation.Server.Api/Models/DiscordConnectionStringBuilder.cs @@ -0,0 +1,37 @@ +using System; +using Tgstation.Server.Api.Models.Internal; + +namespace Tgstation.Server.Api.Models +{ + /// + /// for + /// + public sealed class DiscordConnectionStringBuilder : ChatConnectionStringBuilder + { + /// + public override bool Valid => !String.IsNullOrEmpty(BotToken); + + /// + /// The Discord bot token + /// + /// See https://discordapp.com/developers/docs/topics/oauth2#bots + public string BotToken { get; set; } + + /// + /// Construct a + /// + public DiscordConnectionStringBuilder() { } + + /// + /// Construct a from a + /// + /// The connection string + public DiscordConnectionStringBuilder(string connectionString) + { + BotToken = connectionString ?? throw new ArgumentNullException(nameof(connectionString)); + } + + /// + public override string ToString() => BotToken; + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Api/Models/Internal/ChatBot.cs b/src/Tgstation.Server.Api/Models/Internal/ChatBot.cs index 784133ffd2..577321132d 100644 --- a/src/Tgstation.Server.Api/Models/Internal/ChatBot.cs +++ b/src/Tgstation.Server.Api/Models/Internal/ChatBot.cs @@ -1,4 +1,6 @@ -using System.ComponentModel.DataAnnotations; +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; namespace Tgstation.Server.Api.Models.Internal { @@ -33,5 +35,33 @@ namespace Tgstation.Server.Api.Models.Internal /// [Required] public string ConnectionString { get; set; } + + /// + /// The which maps to the + /// + [NotMapped] + public ChatConnectionStringBuilder ConnectionStringBuilder + { + get + { + if (ConnectionString == null) + return null; + switch (Provider) + { + case ChatProvider.Discord: + return new DiscordConnectionStringBuilder(ConnectionString); + case ChatProvider.Irc: + return new IrcConnectionStringBuilder(ConnectionString); + default: + throw new InvalidOperationException("Invalid Provider!"); + } + } + set + { + if (value?.Valid == false) + throw new InvalidOperationException("Cannot set invalid ChatConnectionStringBuilder!"); + ConnectionString = value?.ToString(); + } + } } } diff --git a/src/Tgstation.Server.Api/Models/Internal/ChatConnectionStringBuilder.cs b/src/Tgstation.Server.Api/Models/Internal/ChatConnectionStringBuilder.cs new file mode 100644 index 0000000000..b5d3b5f9a3 --- /dev/null +++ b/src/Tgstation.Server.Api/Models/Internal/ChatConnectionStringBuilder.cs @@ -0,0 +1,19 @@ +namespace Tgstation.Server.Api.Models.Internal +{ + /// + /// Helper for building s + /// + public abstract class ChatConnectionStringBuilder + { + /// + /// If the evaluates to a valid + /// + public abstract bool Valid { get; } + + /// + /// Gets the associated with the + /// + /// + public abstract override string ToString(); + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Api/Models/IrcConnectionStringBuilder.cs b/src/Tgstation.Server.Api/Models/IrcConnectionStringBuilder.cs new file mode 100644 index 0000000000..ba2f460d47 --- /dev/null +++ b/src/Tgstation.Server.Api/Models/IrcConnectionStringBuilder.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Tgstation.Server.Api.Models.Internal; + +namespace Tgstation.Server.Api.Models +{ + /// + /// for + /// + public sealed class IrcConnectionStringBuilder : ChatConnectionStringBuilder + { + /// + public override bool Valid => Address != null && Port.HasValue && Port != 0 && UseSsl.HasValue && (PasswordType.HasValue ^ Password == null); + + /// + /// The IP address or URL of the IRC server + /// + public string Address { get; set; } + + /// + /// The port the server runs on + /// + public ushort? Port { get; set; } + + /// + /// The nickname for the bot to use + /// + public string Nickname { get; set; } + + /// + /// If the connection should be made using SSL + /// + public bool? UseSsl { get; set; } + + /// + /// The optional to use + /// + public IrcPasswordType? PasswordType { get; set; } + + /// + /// The optional password to use + /// + public string Password { get; set; } + + /// + /// Construct an + /// + public IrcConnectionStringBuilder() { } + + /// + /// Construct a from a + /// + /// The connection string + public IrcConnectionStringBuilder(string connectionString) + { + if (connectionString == null) + throw new ArgumentNullException(nameof(connectionString)); + var splits = connectionString.Split(';'); + + Address = splits[0]; + + if (splits.Length < 2) + return; + + if (UInt16.TryParse(splits[1], out var port)) + Port = port; + + if (splits.Length < 3) + return; + + Nickname = splits[2]; + + if (splits.Length < 4) + return; + + if (Boolean.TryParse(splits[3], out var useSsl)) + UseSsl = useSsl; + + if (splits.Length < 5) + return; + if (Enum.TryParse(splits[4], out var passwordType)) + switch (passwordType) + { + case IrcPasswordType.NickServ: + case IrcPasswordType.Sasl: + case IrcPasswordType.Server: + PasswordType = passwordType; + break; + } + + if (splits.Length < 6) + return; + + var rest = new List(splits); + rest.RemoveRange(0, 5); + Password = String.Join(";", rest); + } + + /// + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append(Address); + sb.Append(';'); + sb.Append(Port); + sb.Append(';'); + sb.Append(Nickname); + sb.Append(';'); + if(UseSsl.HasValue) + sb.Append(Convert.ToInt32(UseSsl.Value)); + if (PasswordType.HasValue) + { + sb.Append(';'); + sb.Append((int)PasswordType); + sb.Append(';'); + sb.Append(Password); + } + return sb.ToString(); + } + } +} diff --git a/src/Tgstation.Server.Host/Components/Chat/ProviderFactory.cs b/src/Tgstation.Server.Host/Components/Chat/ProviderFactory.cs index 928e64f387..aee9d43965 100644 --- a/src/Tgstation.Server.Host/Components/Chat/ProviderFactory.cs +++ b/src/Tgstation.Server.Host/Components/Chat/ProviderFactory.cs @@ -37,52 +37,17 @@ namespace Tgstation.Server.Host.Components.Chat { if (settings == null) throw new ArgumentNullException(nameof(settings)); + var builder = settings.ConnectionStringBuilder; + if (builder == null || !builder.Valid) + throw new InvalidOperationException("Invalid ChatConnectionStringBuilder!"); switch (settings.Provider) { case ChatProvider.Irc: - //Connection string semicolon delimited until the password field - if (settings.ConnectionString == null) - throw new InvalidOperationException("ConnectionString cannot be null!"); - var splits = settings.ConnectionString.Split(';'); - if (splits.Length < 4) - throw new InvalidOperationException("Invalid connection string!"); - - var address = splits[0]; - if (!UInt16.TryParse(splits[1], out var port)) - throw new InvalidOperationException("Unable to parse port!"); - var nick = splits[2]; - if (!Int32.TryParse(splits[3], out var intSsl)) - throw new InvalidOperationException("Unable to parse ssl option!"); - - IrcPasswordType? passwordType = null; - string password = null; - if (splits.Length > 4) - { - if (splits.Length < 6) - throw new InvalidOperationException("Invalid connection string!"); - if (!Int32.TryParse(splits[4], out var intPasswordType)) - throw new InvalidOperationException("Unable to parse password type!"); - - passwordType = (IrcPasswordType)intPasswordType; - switch (passwordType) - { - case IrcPasswordType.NickServ: - case IrcPasswordType.Sasl: - case IrcPasswordType.Server: - break; - default: - throw new InvalidOperationException("Invalid password type!"); - } - - var rest = new List(splits); - rest.RemoveRange(0, 5); - password = String.Join(";", rest); - } - - return new IrcProvider(loggerFactory.CreateLogger(), application, address, port, nick, password, passwordType, intSsl != 0); + var ircBuilder = (IrcConnectionStringBuilder)builder; + return new IrcProvider(loggerFactory.CreateLogger(), application, ircBuilder.Address, ircBuilder.Port.Value, ircBuilder.Nickname, ircBuilder.Password, ircBuilder.PasswordType, ircBuilder.UseSsl.Value); case ChatProvider.Discord: - //discord is just the bot token - return new DiscordProvider(loggerFactory.CreateLogger(), settings.ConnectionString); + var discordBuilder = (DiscordConnectionStringBuilder)builder; + return new DiscordProvider(loggerFactory.CreateLogger(), discordBuilder.BotToken); default: throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid ChatProvider: {0}", settings.Provider)); } From f06216f3c6acd01f91c6b0c7923a287a29427bc0 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 21 Sep 2018 11:24:05 -0400 Subject: [PATCH 190/230] Update chat bot documentation --- docs/API.dox | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/API.dox b/docs/API.dox index 79ca621469..2eb1a0f76a 100644 --- a/docs/API.dox +++ b/docs/API.dox @@ -232,26 +232,26 @@ I DELETE "/Job/{JobId}" => OK @subsection api_chat Chat Bots -Each chat bot is represented by a @ref Tgstation.Server.Api.Models.ChatSettings object +Each chat bot is represented by a @ref Tgstation.Server.Api.Models.ChatBot object Chat bots can be created/updated/deleted with the following requests respectively -I PUT "/Chat" @ref Tgstation.Server.Api.Models.ChatSettings => Tgstation.Server.Api.Models.ChatSettings -I POST "/Chat" @ref Tgstation.Server.Api.Models.ChatSettings => Tgstation.Server.Api.Models.ChatSettings -I DELETE "/Chat/{ChatSettingsId}" => OK +I PUT "/Chat" @ref Tgstation.Server.Api.Models.ChatBot => Tgstation.Server.Api.Models.ChatBot +I POST "/Chat" @ref Tgstation.Server.Api.Models.ChatBot => Tgstation.Server.Api.Models.ChatBot +I DELETE "/Chat/{ChatBotId}" => OK -The @ref Tgstation.Server.Api.Models.Internal.ChatSettings.ConnectionString must differ based on what kind of chat bot you wish to create +The @ref Tgstation.Server.Api.Models.Internal.ChatBot.ConnectionString must differ based on what kind of chat bot you wish to create. Each @ref Tgstation.Server.Api.Models.ChatProvider has a @ref Tgstation.Server.Api.Models.Internal.ChatConnectionStringBuilder that dictates how to form it -For IRC chat bots it should be in the following format: -`";;;<1 to use SSL, 0 otherwise>[;<`The @ref Tgstation.Server.Api.Models.IrcPasswordType`;]"` +For IRC chat bots see @ref Tgstation.Server.Api.Models.IrcConnectionStringBuilder +For Discord chat bots see @ref Tgstation.Server.Api.Models.DiscordConnectionStringBuilder For Discord chat bots it should be the bot's Token A specific bot's settings may be retrieved with: -I GET "/Chat/{ChatSettingsId}" => @ref Tgstation.Server.Api.Models.ChatSettings +I GET "/Chat/{ChatBotId}" => @ref Tgstation.Server.Api.Models.ChatBot -Also note that if the @ref Tgstation.Server.Api.Models.ChatSettings.Channels is present in a POST request, the list will fully replace any active channels +Also note that if the @ref Tgstation.Server.Api.Models.ChatBot.Channels is present in a POST request, the list will fully replace any active channels @subsection api_byond Byond Version Management From 17a368ae58b2194088a3722aaf2e4348a7b8cf53 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 21 Sep 2018 11:24:13 -0400 Subject: [PATCH 191/230] Bump nuget package versions --- src/Tgstation.Server.Api/Tgstation.Server.Api.csproj | 2 +- src/Tgstation.Server.Client/Tgstation.Server.Client.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj index d3a57d45bd..744e45438e 100644 --- a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj +++ b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj @@ -17,7 +17,7 @@ 4.0.0.0 json web api tgstation-server tgstation ss13 byond Prototype release - 4.0.0.0-preview6004 + 4.0.0.0-preview6005 diff --git a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj index cba09ccc5d..3251c49c5d 100644 --- a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj +++ b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj @@ -3,7 +3,7 @@ netstandard2.0 Full - 4.0.0.0-preview9111 + 4.0.0.0-preview9112 true Cyberboss /tg/station 13 From 76628f749569371626c36fcdd57f14c310beb354 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 21 Sep 2018 12:25:28 -0400 Subject: [PATCH 192/230] Clean up restart handling Events now fire before the main CancellationToken Added IRestartHandler and IRestartRegistration Moved Chat shutdown broadcast to the chat level Moved chat factory construction to the composition root Cleanup up update exception handling --- .../Components/Chat/Chat.cs | 19 ++++- .../Components/Chat/ChatFactory.cs | 26 +++--- .../Components/Chat/IChat.cs | 8 -- .../Components/Chat/IChatFactory.cs | 6 +- .../Components/InstanceFactory.cs | 18 ++-- .../Components/InstanceManager.cs | 31 +------ .../Components/Watchdog/Watchdog.cs | 28 +++++-- .../Components/Watchdog/WatchdogFactory.cs | 10 +-- .../Controllers/AdministrationController.cs | 12 +-- src/Tgstation.Server.Host/Core/Application.cs | 1 + .../Core/IRestartHandler.cs | 20 +++++ .../Core/IRestartRegistration.cs | 11 +++ .../Core/IServerControl.cs | 14 ++-- .../Core/RestartRegistration.cs | 26 ++++++ src/Tgstation.Server.Host/Server.cs | 83 +++++++++++++++---- 15 files changed, 203 insertions(+), 110 deletions(-) create mode 100644 src/Tgstation.Server.Host/Core/IRestartHandler.cs create mode 100644 src/Tgstation.Server.Host/Core/IRestartRegistration.cs create mode 100644 src/Tgstation.Server.Host/Core/RestartRegistration.cs diff --git a/src/Tgstation.Server.Host/Components/Chat/Chat.cs b/src/Tgstation.Server.Host/Components/Chat/Chat.cs index 64c763c22e..456b882168 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Chat.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Chat.cs @@ -9,12 +9,13 @@ using System.Threading.Tasks; using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Components.Chat.Commands; using Tgstation.Server.Host.Components.Chat.Providers; +using Tgstation.Server.Host.Core; using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host.Components.Chat { /// - sealed class Chat : IChat + sealed class Chat : IChat, IRestartHandler { const string CommonMention = "!tgs"; @@ -33,6 +34,11 @@ namespace Tgstation.Server.Host.Components.Chat /// readonly ICommandFactory commandFactory; + /// + /// The for the + /// + readonly IRestartRegistration restartRegistration; + /// /// The for the /// @@ -100,15 +106,20 @@ namespace Tgstation.Server.Host.Components.Chat /// The value of /// The value of /// The value of + /// The to populate with /// The used to populate - public Chat(IProviderFactory providerFactory, IIOManager ioManager, ICommandFactory commandFactory, ILogger logger, IEnumerable initialChatBots) + public Chat(IProviderFactory providerFactory, IIOManager ioManager, ICommandFactory commandFactory, IServerControl serverControl, ILogger logger, IEnumerable initialChatBots) { this.providerFactory = providerFactory ?? throw new ArgumentNullException(nameof(providerFactory)); this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.commandFactory = commandFactory ?? throw new ArgumentNullException(nameof(commandFactory)); + if (serverControl == null) + throw new ArgumentNullException(nameof(serverControl)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); this.initialChatBots = initialChatBots?.ToList() ?? throw new ArgumentNullException(nameof(initialChatBots)); + restartRegistration = serverControl.RegisterForRestart(this); + builtinCommands = new Dictionary(); providers = new Dictionary(); mappedChannels = new Dictionary(); @@ -121,6 +132,7 @@ namespace Tgstation.Server.Host.Components.Chat /// public void Dispose() { + restartRegistration.Dispose(); handlerCts.Dispose(); foreach (var I in providers) I.Value.Dispose(); @@ -563,8 +575,9 @@ namespace Tgstation.Server.Host.Components.Chat } /// - public Task SendBroadcast(string message, CancellationToken cancellationToken) + public Task HandleRestart(Version updateVersion, CancellationToken cancellationToken) { + var message = updateVersion == null ? "TGS: Restart requested..." : String.Format(CultureInfo.InvariantCulture, "TGS: Updating to version {0}...", updateVersion); List wdChannels; lock (mappedChannels) //so it doesn't change while we're using it wdChannels = mappedChannels.Select(x => x.Key).ToList(); diff --git a/src/Tgstation.Server.Host/Components/Chat/ChatFactory.cs b/src/Tgstation.Server.Host/Components/Chat/ChatFactory.cs index 4fc1329766..8b3db001ce 100644 --- a/src/Tgstation.Server.Host/Components/Chat/ChatFactory.cs +++ b/src/Tgstation.Server.Host/Components/Chat/ChatFactory.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using Tgstation.Server.Host.Components.Chat.Commands; +using Tgstation.Server.Host.Core; using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host.Components.Chat @@ -9,42 +10,35 @@ namespace Tgstation.Server.Host.Components.Chat /// sealed class ChatFactory : IChatFactory { - /// - /// The for the - /// - readonly IIOManager ioManager; - /// /// The for the /// readonly ILoggerFactory loggerFactory; - /// - /// The for the - /// - readonly ICommandFactory commandFactory; - /// /// The for the /// readonly IProviderFactory providerFactory; + /// + /// The for the + /// + readonly IServerControl serverControl; + /// /// Construct a /// - /// The value of /// The value of - /// The value of /// The value of - public ChatFactory(IIOManager ioManager, ILoggerFactory loggerFactory, ICommandFactory commandFactory, IProviderFactory providerFactory) + /// The value of + public ChatFactory(ILoggerFactory loggerFactory, IProviderFactory providerFactory, IServerControl serverControl) { - this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); - this.commandFactory = commandFactory ?? throw new ArgumentNullException(nameof(commandFactory)); this.providerFactory = providerFactory ?? throw new ArgumentNullException(nameof(providerFactory)); + this.serverControl = serverControl ?? throw new ArgumentNullException(nameof(serverControl)); } /// - public IChat CreateChat(IEnumerable initialChatBots) => new Chat(providerFactory, ioManager, commandFactory, loggerFactory.CreateLogger(), initialChatBots); + public IChat CreateChat(IIOManager ioManager, ICommandFactory commandFactory, IEnumerable initialChatBots) => new Chat(providerFactory, ioManager, commandFactory, serverControl, loggerFactory.CreateLogger(), initialChatBots); } } diff --git a/src/Tgstation.Server.Host/Components/Chat/IChat.cs b/src/Tgstation.Server.Host/Components/Chat/IChat.cs index fb8a1166df..59ecc05587 100644 --- a/src/Tgstation.Server.Host/Components/Chat/IChat.cs +++ b/src/Tgstation.Server.Host/Components/Chat/IChat.cs @@ -75,14 +75,6 @@ namespace Tgstation.Server.Host.Components.Chat /// A representing the running operation Task SendUpdateMessage(string message, CancellationToken cancellationToken); - /// - /// Send a chat to all channels - /// - /// The message being sent - /// The for the operation - /// A representing the running operation - Task SendBroadcast(string message, CancellationToken cancellationToken); - /// /// Start tracking json files for commands and channels /// diff --git a/src/Tgstation.Server.Host/Components/Chat/IChatFactory.cs b/src/Tgstation.Server.Host/Components/Chat/IChatFactory.cs index 6950552eb7..a47c2c13d2 100644 --- a/src/Tgstation.Server.Host/Components/Chat/IChatFactory.cs +++ b/src/Tgstation.Server.Host/Components/Chat/IChatFactory.cs @@ -1,4 +1,6 @@ using System.Collections.Generic; +using Tgstation.Server.Host.Components.Chat.Commands; +using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host.Components.Chat { @@ -10,8 +12,10 @@ namespace Tgstation.Server.Host.Components.Chat /// /// Create a /// + /// The for the + /// The for the /// The initial for the /// A new - IChat CreateChat(IEnumerable initialChatBots); + IChat CreateChat(IIOManager ioManager, ICommandFactory commandFactory, IEnumerable initialChatBots); } } diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index c9494cbd0c..a66f643e36 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -43,11 +43,6 @@ namespace Tgstation.Server.Host.Components /// readonly IByondTopicSender byondTopicSender; - /// - /// The for the - /// - readonly IServerControl serverUpdater; - /// /// The for the /// @@ -71,7 +66,7 @@ namespace Tgstation.Server.Host.Components /// /// The for the /// - readonly IProviderFactory providerFactory; + readonly IChatFactory chatFactory; /// /// The for the @@ -106,30 +101,28 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// The value of - /// The value of /// The value of /// The value of /// The value of /// The value of - /// The value of + /// The value of /// The value of /// The value of /// The value of /// The value of /// The value of - public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IByondTopicSender byondTopicSender, IServerControl serverUpdater, ICryptographySuite cryptographySuite, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IByondInstaller byondInstaller, IProviderFactory providerFactory, IProcessExecutor processExecutor, IPostWriteHandler postWriteHandler, IWatchdogFactory watchdogFactory, IJobManager jobManager, ICredentialsProvider credentialsProvider) + public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IByondTopicSender byondTopicSender, ICryptographySuite cryptographySuite, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IByondInstaller byondInstaller, IChatFactory chatFactory, IProcessExecutor processExecutor, IPostWriteHandler postWriteHandler, IWatchdogFactory watchdogFactory, IJobManager jobManager, ICredentialsProvider credentialsProvider) { this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); this.application = application ?? throw new ArgumentNullException(nameof(application)); this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); this.byondTopicSender = byondTopicSender ?? throw new ArgumentNullException(nameof(byondTopicSender)); - this.serverUpdater = serverUpdater ?? throw new ArgumentNullException(nameof(serverUpdater)); this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite)); this.synchronousIOManager = synchronousIOManager ?? throw new ArgumentNullException(nameof(synchronousIOManager)); this.symlinkFactory = symlinkFactory ?? throw new ArgumentNullException(nameof(symlinkFactory)); this.byondInstaller = byondInstaller ?? throw new ArgumentNullException(nameof(byondInstaller)); - this.providerFactory = providerFactory ?? throw new ArgumentNullException(nameof(providerFactory)); + this.chatFactory = chatFactory ?? throw new ArgumentNullException(nameof(chatFactory)); this.processExecutor = processExecutor ?? throw new ArgumentNullException(nameof(processExecutor)); this.postWriteHandler = postWriteHandler ?? throw new ArgumentNullException(nameof(postWriteHandler)); this.watchdogFactory = watchdogFactory ?? throw new ArgumentNullException(nameof(watchdogFactory)); @@ -161,9 +154,8 @@ namespace Tgstation.Server.Host.Components var byond = new ByondManager(byondIOManager, byondInstaller, loggerFactory.CreateLogger()); var commandFactory = new CommandFactory(application, byond, repoManager, databaseContextFactory, metadata); - var chatFactory = new ChatFactory(instanceIoManager, loggerFactory, commandFactory, providerFactory); - var chat = chatFactory.CreateChat(metadata.ChatSettings); + var chat = chatFactory.CreateChat(instanceIoManager, commandFactory, metadata.ChatSettings); try { var sessionControllerFactory = new SessionControllerFactory(processExecutor, byond, byondTopicSender, cryptographySuite, application, gameIoManager, chat, loggerFactory, metadata.CloneMetadata()); diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs index 1165c0c390..00b4e42e3c 100644 --- a/src/Tgstation.Server.Host/Components/InstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs @@ -49,16 +49,6 @@ namespace Tgstation.Server.Host.Components /// readonly Dictionary instances; - /// - /// of s to finish in - /// - readonly List shutdownTasks; - - /// - /// Used as a temporary for - /// - readonly CancellationTokenSource shutdownCancellationTokenSource; - /// /// Construct an /// @@ -67,31 +57,17 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// The value of - /// The for the /// The value of - public InstanceManager(IInstanceFactory instanceFactory, IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, IJobManager jobManager, IServerControl serverControl, ILogger logger) + public InstanceManager(IInstanceFactory instanceFactory, IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, IJobManager jobManager, ILogger logger) { this.instanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory)); this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); this.application = application ?? throw new ArgumentNullException(nameof(application)); this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); - - if (serverControl == null) - throw new ArgumentNullException(nameof(serverControl)); - - shutdownCancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = shutdownCancellationTokenSource.Token; - serverControl.RegisterForRestart(() => - { - lock (this) - shutdownTasks.AddRange(instances.Select(x => x.Value.Chat.SendBroadcast("TGS: Restart requested...", cancellationToken))); - }); - this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); instances = new Dictionary(); - shutdownTasks = new List(); } /// @@ -99,7 +75,6 @@ namespace Tgstation.Server.Host.Components { foreach (var I in instances) I.Value.Dispose(); - shutdownCancellationTokenSource.Dispose(); } /// @@ -237,11 +212,7 @@ namespace Tgstation.Server.Host.Components public async Task StopAsync(CancellationToken cancellationToken) { await jobManager.StopAsync(cancellationToken).ConfigureAwait(false); - - using (cancellationToken.Register(() => shutdownCancellationTokenSource.Cancel())) - await Task.WhenAll(shutdownTasks).ConfigureAwait(false); await Task.WhenAll(instances.Select(x => x.Value.StopAsync(cancellationToken))).ConfigureAwait(false); - await instanceFactory.StopAsync(cancellationToken).ConfigureAwait(false); } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index 2975471eeb..1baed19018 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -20,7 +20,7 @@ using Tgstation.Server.Host.Core; namespace Tgstation.Server.Host.Components.Watchdog { /// - sealed class Watchdog : IWatchdog, ICustomCommandHandler + sealed class Watchdog : IWatchdog, ICustomCommandHandler, IRestartHandler { /// /// The time in seconds to wait from starting to start . Does not take responsiveness into account @@ -93,6 +93,11 @@ namespace Tgstation.Server.Host.Components.Watchdog /// readonly IJobManager jobManager; + /// + /// The for the + /// + readonly IRestartRegistration restartRegistration; + /// /// The for the /// @@ -143,17 +148,17 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The value of /// The value of /// The value of - /// The for the /// The value of /// The value of /// The value of /// The value of /// The value of /// The value of + /// The to populate with /// The initial value of . May be modified /// The value of /// The value of - public Watchdog(IChat chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, IServerControl serverUpdater, ILogger logger, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IEventConsumer eventConsumer, IJobManager jobManager, DreamDaemonLaunchParameters initialLaunchParameters, Api.Models.Instance instance, bool autoStart) + public Watchdog(IChat chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, ILogger logger, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IEventConsumer eventConsumer, IJobManager jobManager, IServerControl serverControl, DreamDaemonLaunchParameters initialLaunchParameters, Api.Models.Instance instance, bool autoStart) { this.chat = chat ?? throw new ArgumentNullException(nameof(chat)); this.sessionControllerFactory = sessionControllerFactory ?? throw new ArgumentNullException(nameof(sessionControllerFactory)); @@ -167,10 +172,10 @@ namespace Tgstation.Server.Host.Components.Watchdog this.instance = instance ?? throw new ArgumentNullException(nameof(instance)); this.autoStart = autoStart; - if (serverUpdater == null) - throw new ArgumentNullException(nameof(serverUpdater)); + if (serverControl == null) + throw new ArgumentNullException(nameof(serverControl)); - serverUpdater.RegisterForRestart(() => releaseServers = true); + restartRegistration = serverControl.RegisterForRestart(this); chat.RegisterCommandHandler(this); @@ -186,6 +191,7 @@ namespace Tgstation.Server.Host.Components.Watchdog { DisposeAndNullControllers(); semaphore.Dispose(); + restartRegistration.Dispose(); } /// @@ -641,7 +647,7 @@ namespace Tgstation.Server.Host.Components.Watchdog if (startMonitor && await StopMonitor().ConfigureAwait(false)) chatTask = chat.SendWatchdogMessage("Automatic retry sequence cancelled by manual launch. Restarting...", cancellationToken); else if (announce) - chatTask = chat.SendWatchdogMessage("Starting...", cancellationToken); + chatTask = chat.SendWatchdogMessage(doReattach ? "Reattaching..." : "Starting...", cancellationToken); else chatTask = Task.CompletedTask; //start both servers @@ -925,5 +931,13 @@ namespace Tgstation.Server.Host.Components.Watchdog return await activeServer.SendCommand(command, cancellationToken).ConfigureAwait(false) ?? "ERROR: Bad topic exchange!"; } } + + /// + public async Task HandleRestart(Version updateVersion, CancellationToken cancellationToken) + { + releaseServers = true; + if (Running) + await chat.SendWatchdogMessage("Detaching...", cancellationToken).ConfigureAwait(false); + } } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs index b200d8f93b..b2d33d838d 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs @@ -14,7 +14,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// /// The for the /// - readonly IServerControl serverUpdater; + readonly IServerControl serverControl; /// /// The for the @@ -39,14 +39,14 @@ namespace Tgstation.Server.Host.Components.Watchdog /// /// Construct a /// - /// The value of + /// The value of /// The value of /// The value of /// The value of /// The value of - public WatchdogFactory(IServerControl serverUpdater, ILoggerFactory loggerFactory, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IJobManager jobManager) + public WatchdogFactory(IServerControl serverControl, ILoggerFactory loggerFactory, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IJobManager jobManager) { - this.serverUpdater = serverUpdater ?? throw new ArgumentNullException(nameof(serverUpdater)); + this.serverControl = serverControl ?? throw new ArgumentNullException(nameof(serverControl)); this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); this.byondTopicSender = byondTopicSender ?? throw new ArgumentNullException(nameof(byondTopicSender)); @@ -54,6 +54,6 @@ namespace Tgstation.Server.Host.Components.Watchdog } /// - public IWatchdog CreateWatchdog(IChat chat, IDmbFactory dmbFactory, IReattachInfoHandler reattachInfoHandler, IEventConsumer eventConsumer, ISessionControllerFactory sessionControllerFactory, Api.Models.Instance instance, DreamDaemonSettings settings) => new Watchdog(chat, sessionControllerFactory, dmbFactory, serverUpdater, loggerFactory.CreateLogger(), reattachInfoHandler, databaseContextFactory, byondTopicSender, eventConsumer, jobManager, settings, instance, settings.AutoStart.Value); + public IWatchdog CreateWatchdog(IChat chat, IDmbFactory dmbFactory, IReattachInfoHandler reattachInfoHandler, IEventConsumer eventConsumer, ISessionControllerFactory sessionControllerFactory, Api.Models.Instance instance, DreamDaemonSettings settings) => new Watchdog(chat, sessionControllerFactory, dmbFactory, loggerFactory.CreateLogger(), reattachInfoHandler, databaseContextFactory, byondTopicSender, eventConsumer, jobManager, serverControl, settings, instance, settings.AutoStart.Value); } } diff --git a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs index 553163da07..fdb40c2462 100644 --- a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs +++ b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs @@ -180,7 +180,7 @@ namespace Tgstation.Server.Host.Controllers try { Logger.LogDebug("Extracting server update..."); - if (!await serverUpdater.ApplyUpdate(assetBytes, ioManager, cancellationToken).ConfigureAwait(false)) + if (!await serverUpdater.ApplyUpdate(version, assetBytes, ioManager, cancellationToken).ConfigureAwait(false)) return UnprocessableEntity(new ErrorMessage { Message = RestartNotSupportedException @@ -201,23 +201,23 @@ namespace Tgstation.Server.Host.Controllers /// [HttpDelete] [TgsAuthorize(AdministrationRights.RestartHost)] - public Task Delete() + public async Task Delete() { try { - var result = serverUpdater.Restart(); + var result = await serverUpdater.Restart().ConfigureAwait(false); if (result) Logger.LogInformation("Restarting host by request..."); else Logger.LogDebug("Restart request failed due to lack of host watchdog!"); - return Task.FromResult(result ? (IActionResult)Ok() : UnprocessableEntity(new ErrorMessage + return result ? (IActionResult)Ok() : UnprocessableEntity(new ErrorMessage { Message = RestartNotSupportedException - })); + }); } catch (InvalidOperationException) { - return Task.FromResult(StatusCode((int)HttpStatusCode.ServiceUnavailable)); + return StatusCode((int)HttpStatusCode.ServiceUnavailable); } } } diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 9ee1c8eca9..e82a7d9147 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -215,6 +215,7 @@ namespace Tgstation.Server.Host.Core SendTimeout = 5000 }); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Tgstation.Server.Host/Core/IRestartHandler.cs b/src/Tgstation.Server.Host/Core/IRestartHandler.cs new file mode 100644 index 0000000000..f3b0c3afca --- /dev/null +++ b/src/Tgstation.Server.Host/Core/IRestartHandler.cs @@ -0,0 +1,20 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Core +{ + /// + /// Handler for server restarts + /// + public interface IRestartHandler + { + /// + /// Handle a restart of the server + /// + /// The being updated to, if not being changed + /// The for the operation + /// A representing the running operation + Task HandleRestart(Version updateVersion, CancellationToken cancellationToken); + } +} diff --git a/src/Tgstation.Server.Host/Core/IRestartRegistration.cs b/src/Tgstation.Server.Host/Core/IRestartRegistration.cs new file mode 100644 index 0000000000..78aa00ffc5 --- /dev/null +++ b/src/Tgstation.Server.Host/Core/IRestartRegistration.cs @@ -0,0 +1,11 @@ +using System; + +namespace Tgstation.Server.Host.Core +{ + /// + /// Represents the lifetime of a registration + /// + public interface IRestartRegistration : IDisposable + { + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Core/IServerControl.cs b/src/Tgstation.Server.Host/Core/IServerControl.cs index dc97c4bb56..4c20880dd7 100644 --- a/src/Tgstation.Server.Host/Core/IServerControl.cs +++ b/src/Tgstation.Server.Host/Core/IServerControl.cs @@ -13,22 +13,24 @@ namespace Tgstation.Server.Host.Core /// /// Run a new assembly and stop the current one. This will likely trigger all active s /// + /// The the is updating to /// The s of the .zip file that contains the new assembly /// The for the operation /// The for the operation /// A resulting in if live updates are supported, otherwise - Task ApplyUpdate(byte[] updateZipData, IIOManager ioManager, CancellationToken cancellationToken); + Task ApplyUpdate(Version version, byte[] updateZipData, IIOManager ioManager, CancellationToken cancellationToken); /// - /// Register a given to run before stopping the server for a restart + /// Register a given to run before stopping the server for a restart /// - /// The to run - void RegisterForRestart(Action action); + /// The to register + /// A new representing the scope of the registration + IRestartRegistration RegisterForRestart(IRestartHandler handler); /// /// Restarts the /// - /// if live restarts are supported, otherwise - bool Restart(); + /// A resulting in if live restarts are supported, otherwise + Task Restart(); } } diff --git a/src/Tgstation.Server.Host/Core/RestartRegistration.cs b/src/Tgstation.Server.Host/Core/RestartRegistration.cs new file mode 100644 index 0000000000..8df2cf3ad2 --- /dev/null +++ b/src/Tgstation.Server.Host/Core/RestartRegistration.cs @@ -0,0 +1,26 @@ +using System; +using Tgstation.Server.Host.Core; + +namespace Tgstation.Server.Host.Core +{ + /// + sealed class RestartRegistration : IRestartRegistration + { + /// + /// The + /// + readonly Action onDispose; + + /// + /// Construct a + /// + /// The value of + public RestartRegistration(Action onDispose) + { + this.onDispose = onDispose ?? throw new ArgumentNullException(nameof(onDispose)); + } + + /// + public void Dispose() => onDispose(); + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Server.cs b/src/Tgstation.Server.Host/Server.cs index 7bc3192638..18a5749478 100644 --- a/src/Tgstation.Server.Host/Server.cs +++ b/src/Tgstation.Server.Host/Server.cs @@ -1,8 +1,10 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using System; +using System.Collections.Generic; using System.IO; using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Host.Core; @@ -30,6 +32,11 @@ namespace Tgstation.Server.Host /// The absolute path to install updates to /// readonly string updatePath; + + /// + /// The s to run when the restarts + /// + readonly List restartHandlers; /// /// If a server update has been applied @@ -51,6 +58,7 @@ namespace Tgstation.Server.Host this.webHostBuilder = webHostBuilder ?? throw new ArgumentNullException(nameof(webHostBuilder)); this.updatePath = updatePath; + restartHandlers = new List(); semaphore = new SemaphoreSlim(1); updated = false; RestartRequested = false; @@ -85,8 +93,15 @@ namespace Tgstation.Server.Host } /// - public async Task ApplyUpdate(byte[] updateZipData, IIOManager ioManager, CancellationToken cancellationToken) + public async Task ApplyUpdate(Version version, byte[] updateZipData, IIOManager ioManager, CancellationToken cancellationToken) { + if (version == null) + throw new ArgumentNullException(nameof(version)); + if (updateZipData == null) + throw new ArgumentNullException(nameof(updateZipData)); + if (ioManager == null) + throw new ArgumentNullException(nameof(ioManager)); + if (updatePath == null) return false; using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) @@ -98,44 +113,82 @@ namespace Tgstation.Server.Host { await ioManager.ZipToDirectory(updatePath, updateZipData, cancellationToken).ConfigureAwait(false); } - catch + catch (Exception e) { + updated = false; try { //important to not leave this directory around if possible await ioManager.DeleteDirectory(updatePath, default).ConfigureAwait(false); } - catch { } - updated = false; + catch (Exception e2) + { + throw new AggregateException(e, e2); + } throw; } - Restart(); + await Restart(version).ConfigureAwait(false); return true; } } /// - public void RegisterForRestart(Action action) + public IRestartRegistration RegisterForRestart(IRestartHandler handler) { - if (action == null) - throw new ArgumentNullException(nameof(action)); + if (handler == null) + throw new ArgumentNullException(nameof(handler)); if (cancellationTokenSource == null) throw new InvalidOperationException("Tried to register an update action on a non-running Server!"); - cancellationTokenSource.Token.Register(() => - { - if (RestartRequested) - action(); - }); + lock (this) + if (!RestartRequested) + { + restartHandlers.Add(handler); + return new RestartRegistration(() => + { + lock (this) + if(!RestartRequested) + restartHandlers.Remove(handler); + }); + } + return new RestartRegistration(() => { }); } /// - public bool Restart() + public Task Restart() => Restart(null); + + /// + /// Implements + /// + /// The of any potential updates being applied + /// + async Task Restart(Version newVersion) { if (updatePath == null) return false; if (cancellationTokenSource == null) throw new InvalidOperationException("Tried to restart a non-running Server!"); - RestartRequested = true; + lock (this) + { + if (RestartRequested) + return true; + RestartRequested = true; + } + + using (var cts = new CancellationTokenSource()) + { + var cancellationToken = cts.Token; + var eventsTask = Task.WhenAll(restartHandlers.Select(x => x.HandleRestart(newVersion, cancellationToken))); + //YA GOT 10 SECONDS + var expiryTask = Task.Delay(TimeSpan.FromSeconds(10)); + await Task.WhenAny(eventsTask, expiryTask).ConfigureAwait(false); + cts.Cancel(); + try + { + await eventsTask.ConfigureAwait(false); + } + catch (OperationCanceledException) { } + } + cancellationTokenSource.Cancel(); return true; } From b7cf3f6532956478387c95d76239d9e3273afa92 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 21 Sep 2018 14:15:17 -0400 Subject: [PATCH 193/230] Test and fix IrcConnectionStringBuilder --- .../Models/IrcConnectionStringBuilder.cs | 4 +-- .../Models/TestIrcConnectionStringBuilder.cs | 28 +++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 tests/Tgstation.Server.Api.Tests/Models/TestIrcConnectionStringBuilder.cs diff --git a/src/Tgstation.Server.Api/Models/IrcConnectionStringBuilder.cs b/src/Tgstation.Server.Api/Models/IrcConnectionStringBuilder.cs index ba2f460d47..55df172e51 100644 --- a/src/Tgstation.Server.Api/Models/IrcConnectionStringBuilder.cs +++ b/src/Tgstation.Server.Api/Models/IrcConnectionStringBuilder.cs @@ -74,8 +74,8 @@ namespace Tgstation.Server.Api.Models if (splits.Length < 4) return; - if (Boolean.TryParse(splits[3], out var useSsl)) - UseSsl = useSsl; + if (Int32.TryParse(splits[3], out var intSsl)) + UseSsl = Convert.ToBoolean(intSsl); if (splits.Length < 5) return; diff --git a/tests/Tgstation.Server.Api.Tests/Models/TestIrcConnectionStringBuilder.cs b/tests/Tgstation.Server.Api.Tests/Models/TestIrcConnectionStringBuilder.cs new file mode 100644 index 0000000000..5c64cf6776 --- /dev/null +++ b/tests/Tgstation.Server.Api.Tests/Models/TestIrcConnectionStringBuilder.cs @@ -0,0 +1,28 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Tgstation.Server.Api.Models.Tests +{ + [TestClass] + public sealed class TestIrcConnectionStringBuilder + { + [TestMethod] + public void TestBasicParseAndBuild() + { + const string exampleString = "server;1234;nick;1;2;asdf"; + var builder = new IrcConnectionStringBuilder(exampleString); + + Assert.IsTrue(builder.Valid); + Assert.AreEqual("server", builder.Address); + Assert.AreEqual((ushort)1234, builder.Port); + Assert.AreEqual("nick", builder.Nickname); + Assert.IsTrue(builder.UseSsl.Value); + Assert.AreEqual(IrcPasswordType.NickServ, builder.PasswordType); + Assert.AreEqual("asdf", builder.Password); + + Assert.AreEqual(exampleString, builder.ToString()); + } + } +} From 388b989a271b8fc7ea3c803368a0e79a73f0669f Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 21 Sep 2018 14:36:50 -0400 Subject: [PATCH 194/230] Version bump nuget packages --- src/Tgstation.Server.Api/Tgstation.Server.Api.csproj | 2 +- src/Tgstation.Server.Client/Tgstation.Server.Client.csproj | 2 +- .../Models/TestIrcConnectionStringBuilder.cs | 3 --- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj index 744e45438e..9030def747 100644 --- a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj +++ b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj @@ -17,7 +17,7 @@ 4.0.0.0 json web api tgstation-server tgstation ss13 byond Prototype release - 4.0.0.0-preview6005 + 4.0.0.0-preview6006 diff --git a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj index 3251c49c5d..9b8921d040 100644 --- a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj +++ b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj @@ -3,7 +3,7 @@ netstandard2.0 Full - 4.0.0.0-preview9112 + 4.0.0.0-preview9113 true Cyberboss /tg/station 13 diff --git a/tests/Tgstation.Server.Api.Tests/Models/TestIrcConnectionStringBuilder.cs b/tests/Tgstation.Server.Api.Tests/Models/TestIrcConnectionStringBuilder.cs index 5c64cf6776..4eca51f170 100644 --- a/tests/Tgstation.Server.Api.Tests/Models/TestIrcConnectionStringBuilder.cs +++ b/tests/Tgstation.Server.Api.Tests/Models/TestIrcConnectionStringBuilder.cs @@ -1,7 +1,4 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; -using System.Collections.Generic; -using System.Text; namespace Tgstation.Server.Api.Models.Tests { From c462a77937aaa5c6b16ce352e3d6b500e6fe67ce Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 21 Sep 2018 16:26:26 -0400 Subject: [PATCH 195/230] Use configurationRelativePath when listing directory contents --- .../Components/StaticFiles/Configuration.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs index 69846570a3..d5b972483d 100644 --- a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs +++ b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs @@ -169,7 +169,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles result.AddRange(enumerator.Select(x => new ConfigurationFile { IsDirectory = true, - Path = ioManager.ConcatPath(path, x), + Path = ioManager.ConcatPath(configurationRelativePath, x), })); } catch (IOException e) @@ -182,7 +182,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles result.AddRange(enumerator.Select(x => new ConfigurationFile { IsDirectory = false, - Path = ioManager.ConcatPath(path, x), + Path = ioManager.ConcatPath(configurationRelativePath, x), })); } From 8dc3de847ffbc0a88c042796d7cc9a8b82b44671 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 21 Sep 2018 17:17:26 -0400 Subject: [PATCH 196/230] Adds CreateDirectory to client --- .../Components/ConfigurationClient.cs | 3 +++ .../Components/IConfigurationClient.cs | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/src/Tgstation.Server.Client/Components/ConfigurationClient.cs b/src/Tgstation.Server.Client/Components/ConfigurationClient.cs index d3f1a5bccf..03eb20df13 100644 --- a/src/Tgstation.Server.Client/Components/ConfigurationClient.cs +++ b/src/Tgstation.Server.Client/Components/ConfigurationClient.cs @@ -47,6 +47,9 @@ namespace Tgstation.Server.Client.Components /// public Task DeleteEmptyDirectory(ConfigurationFile directory, CancellationToken cancellationToken) => apiClient.Delete(Routes.Configuration, directory, instance.Id, cancellationToken); + /// + public Task CreateDirectory(ConfigurationFile directory, CancellationToken cancellationToken) => apiClient.Create(Routes.Configuration, directory, instance.Id, cancellationToken); + /// public Task> List(string directory, CancellationToken cancellationToken) => apiClient.Read>(Routes.List(Routes.Configuration) + SanitizeGetPath(directory), instance.Id, cancellationToken); diff --git a/src/Tgstation.Server.Client/Components/IConfigurationClient.cs b/src/Tgstation.Server.Client/Components/IConfigurationClient.cs index 2d2337ff50..f70a9c46a2 100644 --- a/src/Tgstation.Server.Client/Components/IConfigurationClient.cs +++ b/src/Tgstation.Server.Client/Components/IConfigurationClient.cs @@ -41,5 +41,13 @@ namespace Tgstation.Server.Client.Components /// The for the operation /// A representing the running operation Task DeleteEmptyDirectory(ConfigurationFile directory, CancellationToken cancellationToken); + + /// + /// Creates an empty + /// + /// The representing the directory to create + /// The for the operation + /// A representing the running operation + Task CreateDirectory(ConfigurationFile directory, CancellationToken cancellationToken); } } From 465e79276f90b3d2af940ecd82e521d74bf36a57 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 21 Sep 2018 17:17:48 -0400 Subject: [PATCH 197/230] Bump client preview version [NugetDeploy] --- src/Tgstation.Server.Client/Tgstation.Server.Client.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj index 9b8921d040..b150117ac3 100644 --- a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj +++ b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj @@ -3,7 +3,7 @@ netstandard2.0 Full - 4.0.0.0-preview9113 + 4.0.0.0-preview9114 true Cyberboss /tg/station 13 From ae3d1ab39ac840168e3b4adb902ce7edd1c6a3b7 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Fri, 21 Sep 2018 18:27:44 -0400 Subject: [PATCH 198/230] Committing to master intensifies [NugetDeploy] --- src/Tgstation.Server.Client/Components/ConfigurationClient.cs | 2 +- .../Components/IConfigurationClient.cs | 4 ++-- src/Tgstation.Server.Client/Tgstation.Server.Client.csproj | 2 +- .../Components/StaticFiles/Configuration.cs | 3 +++ 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Tgstation.Server.Client/Components/ConfigurationClient.cs b/src/Tgstation.Server.Client/Components/ConfigurationClient.cs index 03eb20df13..2105906336 100644 --- a/src/Tgstation.Server.Client/Components/ConfigurationClient.cs +++ b/src/Tgstation.Server.Client/Components/ConfigurationClient.cs @@ -48,7 +48,7 @@ namespace Tgstation.Server.Client.Components public Task DeleteEmptyDirectory(ConfigurationFile directory, CancellationToken cancellationToken) => apiClient.Delete(Routes.Configuration, directory, instance.Id, cancellationToken); /// - public Task CreateDirectory(ConfigurationFile directory, CancellationToken cancellationToken) => apiClient.Create(Routes.Configuration, directory, instance.Id, cancellationToken); + public Task CreateDirectory(ConfigurationFile directory, CancellationToken cancellationToken) => apiClient.Create(Routes.Configuration, directory, instance.Id, cancellationToken); /// public Task> List(string directory, CancellationToken cancellationToken) => apiClient.Read>(Routes.List(Routes.Configuration) + SanitizeGetPath(directory), instance.Id, cancellationToken); diff --git a/src/Tgstation.Server.Client/Components/IConfigurationClient.cs b/src/Tgstation.Server.Client/Components/IConfigurationClient.cs index f70a9c46a2..d61e426af3 100644 --- a/src/Tgstation.Server.Client/Components/IConfigurationClient.cs +++ b/src/Tgstation.Server.Client/Components/IConfigurationClient.cs @@ -47,7 +47,7 @@ namespace Tgstation.Server.Client.Components /// /// The representing the directory to create /// The for the operation - /// A representing the running operation - Task CreateDirectory(ConfigurationFile directory, CancellationToken cancellationToken); + /// A resulting in the new + Task CreateDirectory(ConfigurationFile directory, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj index b150117ac3..6dad8322e9 100644 --- a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj +++ b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj @@ -3,7 +3,7 @@ netstandard2.0 Full - 4.0.0.0-preview9114 + 4.0.0.0-preview9115 true Cyberboss /tg/station 13 diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs index d5b972483d..1bb59369e4 100644 --- a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs +++ b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs @@ -159,6 +159,9 @@ namespace Tgstation.Server.Host.Components.StaticFiles await EnsureDirectories(cancellationToken).ConfigureAwait(false); var path = ValidateConfigRelativePath(configurationRelativePath); + if (configurationRelativePath == null) + configurationRelativePath = "/"; + List result = new List(); void ListImpl() From 36b34a4f02b1279532ea575967c0c8e63264d21e Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Fri, 21 Sep 2018 19:34:32 -0400 Subject: [PATCH 199/230] Fix directory enumeration again --- src/Tgstation.Server.Host/IO/SynchronousIOManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs b/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs index d31e652e5d..c3937d5a9c 100644 --- a/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs +++ b/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs @@ -42,7 +42,7 @@ namespace Tgstation.Server.Host.IO { foreach (var I in Directory.EnumerateDirectories(path)) { - yield return I; + yield return Path.GetFileName(I); cancellationToken.ThrowIfCancellationRequested(); } } @@ -52,7 +52,7 @@ namespace Tgstation.Server.Host.IO { foreach (var I in Directory.EnumerateFiles(path)) { - yield return I; + yield return Path.GetFileName(I); cancellationToken.ThrowIfCancellationRequested(); } } From d9f75b7d858b487650f068943b4674ddb49f22f9 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Fri, 21 Sep 2018 19:53:04 -0400 Subject: [PATCH 200/230] Fix Repository.AddTestMerge return value --- .../Components/Repository/IRepository.cs | 2 +- src/Tgstation.Server.Host/Components/Repository/Repository.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Repository/IRepository.cs b/src/Tgstation.Server.Host/Components/Repository/IRepository.cs index 594469d138..323ae7a5ad 100644 --- a/src/Tgstation.Server.Host/Components/Repository/IRepository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/IRepository.cs @@ -72,7 +72,7 @@ namespace Tgstation.Server.Host.Components.Repository /// The password to fetch from the origin repository /// The for the operation /// to report 0-100 progress of the operation - /// A resulting in a representing the merge result that is after a fast forward or up to date, on a merge, on a conflict + /// A resulting in a representing the merge result that is after a fast forward or up to date, on a non-fast-forward, on a conflict Task AddTestMerge(TestMergeParameters testMergeParameters, string committerName, string committerEmail, string username, string password, Action progressReporter, CancellationToken cancellationToken); /// diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs index 66845fc093..4113a66945 100644 --- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs @@ -287,7 +287,7 @@ namespace Tgstation.Server.Host.Components.Repository if (result.Status == MergeStatus.Conflicts) { await eventConsumer.HandleEvent(EventType.RepoMergeConflict, new List { originalCommit.Tip.Sha, testMergeParameters.PullRequestRevision, originalCommit.FriendlyName ?? UnknownReference, prBranchName }, cancellationToken).ConfigureAwait(false); - return false; + return null; } if (commitMessage != null && result.Status != MergeStatus.UpToDate) @@ -299,7 +299,7 @@ namespace Tgstation.Server.Host.Components.Repository }), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); } - return true; + return result.Status != MergeStatus.NonFastForward; } /// From b20eb6d155cd696cf69029addd17de03130a1ba0 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Fri, 21 Sep 2018 19:55:23 -0400 Subject: [PATCH 201/230] Throw a JobException on PR merge conflicts It's just saner this way --- src/Tgstation.Server.Host/Controllers/RepositoryController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index 3ca9664847..f8efbfb785 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -685,8 +685,8 @@ namespace Tgstation.Server.Host.Controllers var mergeResult = await repo.AddTestMerge(I, committerName, currentModel.CommitterEmail, currentModel.AccessUser, currentModel.AccessToken, NextProgressReporter(), ct).ConfigureAwait(false); - if (!mergeResult.HasValue) //conflict, we don't care, dd already knows - continue; + if (!mergeResult.HasValue) + throw new JobException(String.Format(CultureInfo.InvariantCulture, "Merge of PR #{0} at {1} conflicted!", I.Number, I.PullRequestRevision.Substring(0, 7))); ++doneSteps; From 5e4716809004390a6b6a842f9279274f56964fa4 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Fri, 21 Sep 2018 21:29:13 -0400 Subject: [PATCH 202/230] Fix configuration file routing [NugetDeploy] --- src/Tgstation.Server.Api/Routes.cs | 10 ++++++++++ src/Tgstation.Server.Api/Tgstation.Server.Api.csproj | 2 +- .../Components/ConfigurationClient.cs | 2 +- .../Tgstation.Server.Client.csproj | 2 +- .../Controllers/ConfigurationController.cs | 2 +- 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Tgstation.Server.Api/Routes.cs b/src/Tgstation.Server.Api/Routes.cs index da1bfc9bd9..2ba4b4b19b 100644 --- a/src/Tgstation.Server.Api/Routes.cs +++ b/src/Tgstation.Server.Api/Routes.cs @@ -48,6 +48,16 @@ namespace Tgstation.Server.Api /// public const string Configuration = Root + "Config"; + /// + /// To be paired with for accessing s + /// + public const string File = "File"; + + /// + /// Full combination of and + /// + public const string ConfigurationFile = Configuration + "/" + File; + /// /// The controller /// diff --git a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj index 9030def747..6f2d970b73 100644 --- a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj +++ b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj @@ -17,7 +17,7 @@ 4.0.0.0 json web api tgstation-server tgstation ss13 byond Prototype release - 4.0.0.0-preview6006 + 4.0.0.0-preview6007 diff --git a/src/Tgstation.Server.Client/Components/ConfigurationClient.cs b/src/Tgstation.Server.Client/Components/ConfigurationClient.cs index 2105906336..1f1af74469 100644 --- a/src/Tgstation.Server.Client/Components/ConfigurationClient.cs +++ b/src/Tgstation.Server.Client/Components/ConfigurationClient.cs @@ -58,7 +58,7 @@ namespace Tgstation.Server.Client.Components { if (file == null) throw new ArgumentNullException(nameof(file)); - return apiClient.Read(Routes.Configuration + SanitizeGetPath(file.Path), instance.Id, cancellationToken); + return apiClient.Read(Routes.ConfigurationFile + SanitizeGetPath(file.Path), instance.Id, cancellationToken); } /// diff --git a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj index 6dad8322e9..3f96200434 100644 --- a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj +++ b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj @@ -3,7 +3,7 @@ netstandard2.0 Full - 4.0.0.0-preview9115 + 4.0.0.0-preview9116 true Cyberboss /tg/station 13 diff --git a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs index d60ae85a35..acaf5206dd 100644 --- a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs +++ b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs @@ -95,7 +95,7 @@ namespace Tgstation.Server.Host.Controllers /// The path of the file to get /// The for the operation /// A resulting in the for the operation - [HttpGet("File/{*filePath}")] + [HttpGet(Routes.File + "/{*filePath}")] [TgsAuthorize(ConfigurationRights.Read)] public async Task File(string filePath, CancellationToken cancellationToken) { From d1dcfe2ed57815e0f920cd656f7ee96635f1a5ba Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Fri, 21 Sep 2018 22:14:40 -0400 Subject: [PATCH 203/230] Throw in some sanity --- src/Tgstation.Server.Host/Controllers/ConfigurationController.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs index acaf5206dd..40906558fb 100644 --- a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs +++ b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs @@ -171,6 +171,7 @@ namespace Tgstation.Server.Host.Controllers try { + model.IsDirectory = true; return await instanceManager.GetInstance(Instance).Configuration.CreateDirectory(model.Path, AuthenticationContext.SystemIdentity, cancellationToken).ConfigureAwait(false) ? (IActionResult)Json(model) : StatusCode((int)HttpStatusCode.Created, model); } catch (NotImplementedException) From e490a7f7b146af1277b2a0408c9cf52b441d7d51 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Fri, 21 Sep 2018 22:45:43 -0400 Subject: [PATCH 204/230] Fix some config shit --- .../Components/StaticFiles/IConfiguration.cs | 2 +- .../Controllers/ConfigurationController.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs b/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs index 29b8b66da0..7c6ec4298b 100644 --- a/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs +++ b/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs @@ -74,7 +74,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles /// The data to write. If , the file is deleted /// The hash any existing file must match in order for the write to succeed /// The for the operation. Usage may result in partial writes - /// A resulting in the updated + /// A resulting in the updated or if the write failed due to conflicts Task Write(string configurationRelativePath, ISystemIdentity systemIdentity, byte[] data, string previousHash, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs index 40906558fb..327f289072 100644 --- a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs +++ b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs @@ -68,7 +68,7 @@ namespace Tgstation.Server.Host.Controllers if (newFile == null) return Conflict(new ErrorMessage { - Message = "" + Message = "This file has been updated since you last viewed it!" }); newFile.Content = null; From efdad3b6108966721e007a319a6c31eecd502a9a Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Fri, 21 Sep 2018 23:14:02 -0400 Subject: [PATCH 205/230] Add missing call to SetAutoUpdateInterval in InstanceController --- src/Tgstation.Server.Host/Controllers/InstanceController.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs index 0c6801cbf2..28cc4d34e7 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs @@ -295,6 +295,7 @@ namespace Tgstation.Server.Host.Controllers } } + var oldAutoUpdateInterval = originalModel.AutoUpdateInterval.Value; var originalOnline = originalModel.Online.Value; var renamed = model.Name != null && originalModel.Name != model.Name; @@ -356,6 +357,9 @@ namespace Tgstation.Server.Host.Controllers api.MoveJob = job.ToApi(); } + if (originalModel.Online.Value && model.AutoUpdateInterval.HasValue && oldAutoUpdateInterval != model.AutoUpdateInterval) + await instanceManager.GetInstance(originalModel).SetAutoUpdateInterval(model.AutoUpdateInterval.Value).ConfigureAwait(false); + return Json(api); } From 91543d73ff6948921ba84df52004567768537e4d Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Fri, 21 Sep 2018 23:19:27 -0400 Subject: [PATCH 206/230] Cleanup TimeSpan usage --- src/Tgstation.Server.Host/Components/Instance.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index 91302f8fdc..e4b4d6079e 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -184,7 +184,7 @@ namespace Tgstation.Server.Host.Components while (true) try { - await Task.Delay(new TimeSpan(0, minutes > Int32.MaxValue ? Int32.MaxValue : (int)minutes, 0), cancellationToken).ConfigureAwait(false); + await Task.Delay(TimeSpan.FromMinutes(minutes > Int32.MaxValue ? Int32.MaxValue : (int)minutes), cancellationToken).ConfigureAwait(false); try { From 3a049117e36e8df416f1c8090143d9ba351fc5dc Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Fri, 21 Sep 2018 23:20:16 -0400 Subject: [PATCH 207/230] Add missing user to auto update repository job --- src/Tgstation.Server.Host/Components/Instance.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index e4b4d6079e..2db114c54f 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -198,7 +198,8 @@ namespace Tgstation.Server.Host.Components }, Description = "Scheduled repository update", CancelRightsType = RightsType.Repository, - CancelRight = (ulong)RepositoryRights.CancelPendingChanges + CancelRight = (ulong)RepositoryRights.CancelPendingChanges, + StartedBy = user }; var noRepo = false; From 7b5cf1821ea6d4fbe5c29cacd7d3ddc671aff9bc Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Fri, 21 Sep 2018 23:37:40 -0400 Subject: [PATCH 208/230] Adds a bit of auto update logging and exception cases --- .../Components/Instance.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index 2db114c54f..b676c9f94f 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -185,7 +185,7 @@ namespace Tgstation.Server.Host.Components try { await Task.Delay(TimeSpan.FromMinutes(minutes > Int32.MaxValue ? Int32.MaxValue : (int)minutes), cancellationToken).ConfigureAwait(false); - + logger.LogDebug("Beginning auto update..."); try { Models.User user = null; @@ -202,7 +202,7 @@ namespace Tgstation.Server.Host.Components StartedBy = user }; - var noRepo = false; + var repositoryUpdateSuccess = false; await jobManager.RegisterOperation(repositoryUpdateJob, async (paramJob, databaseContext, progressReporter, jobCancellationToken) => { var repositorySettingsTask = databaseContext.RepositorySettings.Where(x => x.InstanceId == metadata.Id).FirstAsync(jobCancellationToken); @@ -226,8 +226,7 @@ namespace Tgstation.Server.Host.Components { if (repo == null) { - //no repo, no auto updates - noRepo = true; + logger.LogTrace("Aborting repo update, no repository!"); return; } @@ -242,13 +241,15 @@ namespace Tgstation.Server.Host.Components bool shouldSyncTracked; if (repositorySettings.AutoUpdatesKeepTestMerges.Value) { + logger.LogTrace("Preserving test merges..."); var result = await repo.MergeOrigin(repositorySettings.CommitterName, repositorySettings.CommitterEmail, NextProgressReporter(), jobCancellationToken).ConfigureAwait(false); if (!result.HasValue) - return; + throw new JobException("Merge conflict while preserving test merges!"); shouldSyncTracked = result.Value; } else { + logger.LogTrace("Not preserving test merges..."); await repo.ResetToOrigin(NextProgressReporter(), jobCancellationToken).ConfigureAwait(false); shouldSyncTracked = true; } @@ -259,12 +260,16 @@ namespace Tgstation.Server.Host.Components progressReporter(5 * ProgressStep); } + repositoryUpdateSuccess = true; }, cancellationToken).ConfigureAwait(false); await jobManager.WaitForJobCompletion(repositoryUpdateJob, user, cancellationToken, default).ConfigureAwait(false); - if (noRepo) + if (!repositoryUpdateSuccess) + { + logger.LogTrace("Aborting auto update, repository error!"); continue; + } //finally set up the job var compileProcessJob = new Job From 3ed59d28a6a5bdd926ccb220785aaee428143560 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Fri, 21 Sep 2018 23:40:15 -0400 Subject: [PATCH 209/230] Fix MergeOrigin aborting --- .../Components/Repository/Repository.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs index 4113a66945..0c7f7687ee 100644 --- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs @@ -442,6 +442,7 @@ namespace Tgstation.Server.Host.Components.Repository Branch trackedBranch = null; var oldHead = repository.Head; + var oldTip = oldHead.Tip; await Task.Factory.StartNew(() => { @@ -468,7 +469,10 @@ namespace Tgstation.Server.Host.Components.Repository if (result.Status == MergeStatus.Conflicts) { logger.LogDebug("Merge conflict, aborting and reverting to {0}", oldHead.FriendlyName); - RawCheckout(oldHead.CanonicalName, progressReporter, cancellationToken); + repository.Reset(ResetMode.Hard, oldTip, new CheckoutOptions + { + OnCheckoutProgress = CheckoutProgressHandler(progressReporter) + }); cancellationToken.ThrowIfCancellationRequested(); } @@ -477,7 +481,7 @@ namespace Tgstation.Server.Host.Components.Repository if (result.Status == MergeStatus.Conflicts) { - await eventConsumer.HandleEvent(EventType.RepoMergeConflict, new List { oldHead.Tip.Sha, trackedBranch.Tip.Sha, oldHead.FriendlyName ?? UnknownReference, trackedBranch.FriendlyName }, cancellationToken).ConfigureAwait(false); + await eventConsumer.HandleEvent(EventType.RepoMergeConflict, new List { oldTip.Sha, trackedBranch.Tip.Sha, oldHead.FriendlyName ?? UnknownReference, trackedBranch.FriendlyName }, cancellationToken).ConfigureAwait(false); return null; } From ac84657b06fa2a911f8d2ec98cf2049a129ec983 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 22 Sep 2018 00:43:46 -0400 Subject: [PATCH 210/230] Fix watchdog not working in paths with spaces --- .../Watchdog.cs | 138 +++++++++--------- 1 file changed, 69 insertions(+), 69 deletions(-) diff --git a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs index d69ff7ee72..0227e83be7 100644 --- a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs @@ -35,77 +35,77 @@ namespace Tgstation.Server.Host.Watchdog { logger.LogInformation("Host watchdog starting..."); - var enviromentPath = Environment.GetEnvironmentVariable("PATH"); - var paths = enviromentPath.Split(';'); - var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - - var exeName = "dotnet"; - IEnumerable enumerator; - if (isWindows) - { - exeName += ".exe"; - enumerator = paths; - } - else - enumerator = paths.Select(x => x.Split(':')).SelectMany(x => x); - - enumerator = enumerator.Select(x => Path.Combine(x, exeName)); - - var dotnetPath = enumerator - .Where(x => - { - logger.LogTrace("Checking for dotnet at {0}", x); - return File.Exists(x); - }) - .FirstOrDefault(); - - if (dotnetPath == default) - { - logger.LogCritical("Unable to locate dotnet executable in PATH! Please ensure the .NET Core runtime is installed and is in your PATH!"); - return; - } - logger.LogInformation("Detected dotnet executable at {0}", dotnetPath); - - var rootLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - - var assemblyStoragePath = Path.Combine(rootLocation, "lib"); //always always next to watchdog - var defaultAssemblyPath = Path.GetFullPath(Path.Combine(assemblyStoragePath, "Default")); -#if DEBUG - //just copy the shit where it belongs - Directory.Delete(assemblyStoragePath, true); - Directory.CreateDirectory(defaultAssemblyPath); - - var sourcePath = "../../../../Tgstation.Server.Host/bin/Debug/netcoreapp2.1"; - foreach (string dirPath in Directory.GetDirectories(sourcePath, "*", SearchOption.AllDirectories)) - Directory.CreateDirectory(dirPath.Replace(sourcePath, defaultAssemblyPath)); - - foreach (string newPath in Directory.GetFiles(sourcePath, "*.*", SearchOption.AllDirectories)) - File.Copy(newPath, newPath.Replace(sourcePath, defaultAssemblyPath), true); - - const string AppSettingsJson = "appsettings.json"; - var rootJson = Path.Combine(rootLocation, AppSettingsJson); - File.Delete(rootJson); - File.Move(Path.Combine(defaultAssemblyPath, AppSettingsJson), rootJson); -#endif - - var assemblyName = String.Join(".", nameof(Tgstation), nameof(Server), nameof(Host), "dll"); - var assemblyPath = Path.Combine(defaultAssemblyPath, assemblyName); - - if (assemblyPath.Contains("\"")) - { - logger.LogCritical("Running from paths with \"'s in the name is not supported!"); - return; - } - - if (!File.Exists(assemblyPath)) - { - logger.LogCritical("Unable to locate host assembly!"); - return; - } - string updateDirectory = null; try { + var enviromentPath = Environment.GetEnvironmentVariable("PATH"); + var paths = enviromentPath.Split(';'); + var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + + var exeName = "dotnet"; + IEnumerable enumerator; + if (isWindows) + { + exeName += ".exe"; + enumerator = paths; + } + else + enumerator = paths.Select(x => x.Split(':')).SelectMany(x => x); + + enumerator = enumerator.Select(x => Path.Combine(x, exeName)); + + var dotnetPath = enumerator + .Where(x => + { + logger.LogTrace("Checking for dotnet at {0}", x); + return File.Exists(x); + }) + .FirstOrDefault(); + + if (dotnetPath == default) + { + logger.LogCritical("Unable to locate dotnet executable in PATH! Please ensure the .NET Core runtime is installed and is in your PATH!"); + return; + } + logger.LogInformation("Detected dotnet executable at {0}", dotnetPath); + + var rootLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + + var assemblyStoragePath = Path.Combine(rootLocation, "lib"); //always always next to watchdog + var defaultAssemblyPath = Path.GetFullPath(Path.Combine(assemblyStoragePath, "Default")); +#if DEBUG + //just copy the shit where it belongs + Directory.Delete(assemblyStoragePath, true); + Directory.CreateDirectory(defaultAssemblyPath); + + var sourcePath = "../../../../Tgstation.Server.Host/bin/Debug/netcoreapp2.1"; + foreach (string dirPath in Directory.GetDirectories(sourcePath, "*", SearchOption.AllDirectories)) + Directory.CreateDirectory(dirPath.Replace(sourcePath, defaultAssemblyPath)); + + foreach (string newPath in Directory.GetFiles(sourcePath, "*.*", SearchOption.AllDirectories)) + File.Copy(newPath, newPath.Replace(sourcePath, defaultAssemblyPath), true); + + const string AppSettingsJson = "appsettings.json"; + var rootJson = Path.Combine(rootLocation, AppSettingsJson); + File.Delete(rootJson); + File.Move(Path.Combine(defaultAssemblyPath, AppSettingsJson), rootJson); +#endif + + var assemblyName = String.Join(".", nameof(Tgstation), nameof(Server), nameof(Host), "dll"); + var assemblyPath = Path.Combine(defaultAssemblyPath, assemblyName); + + if (assemblyPath.Contains("\"")) + { + logger.LogCritical("Running from paths with \"'s in the name is not supported!"); + return; + } + + if (!File.Exists(assemblyPath)) + { + logger.LogCritical("Unable to locate host assembly!"); + return; + } + while (!cancellationToken.IsCancellationRequested) using (logger.BeginScope("Host invocation")) { @@ -119,7 +119,7 @@ namespace Tgstation.Server.Host.Watchdog var arguments = new List { '"' + assemblyPath + '"', - updateDirectory + '"' + updateDirectory + '"' }; if (Debugger.IsAttached) arguments.Add("--attach-debugger"); From e5d38a2db945077f8a6ecb6c780b533cfbb22239 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 22 Sep 2018 01:01:12 -0400 Subject: [PATCH 211/230] Fix chat channel tags not getting set in and out of the API --- src/Tgstation.Server.Host/Controllers/ChatController.cs | 3 ++- src/Tgstation.Server.Host/Models/ChatChannel.cs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Controllers/ChatController.cs b/src/Tgstation.Server.Host/Controllers/ChatController.cs index 325f639731..2e6d3a9ebc 100644 --- a/src/Tgstation.Server.Host/Controllers/ChatController.cs +++ b/src/Tgstation.Server.Host/Controllers/ChatController.cs @@ -53,7 +53,8 @@ namespace Tgstation.Server.Host.Controllers IrcChannel = api.IrcChannel, IsAdminChannel = api.IsAdminChannel ?? false, IsWatchdogChannel = api.IsWatchdogChannel ?? false, - IsUpdatesChannel = api.IsUpdatesChannel ?? false + IsUpdatesChannel = api.IsUpdatesChannel ?? false, + Tag = api.Tag }; /// diff --git a/src/Tgstation.Server.Host/Models/ChatChannel.cs b/src/Tgstation.Server.Host/Models/ChatChannel.cs index 17e1d0c367..99bdedf7a6 100644 --- a/src/Tgstation.Server.Host/Models/ChatChannel.cs +++ b/src/Tgstation.Server.Host/Models/ChatChannel.cs @@ -30,7 +30,8 @@ namespace Tgstation.Server.Host.Models IsAdminChannel = IsAdminChannel, IsWatchdogChannel = IsWatchdogChannel, IsUpdatesChannel = IsUpdatesChannel, - IrcChannel = IrcChannel + IrcChannel = IrcChannel, + Tag = Tag }; } } From 14c708b5da4d27b7c31a0e7f01a0eea201ed53a1 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 22 Sep 2018 11:41:56 -0400 Subject: [PATCH 212/230] Do not auto update if revision is same as current revision --- src/Tgstation.Server.Host/Components/Instance.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index b676c9f94f..4a7324dcd7 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -202,7 +202,7 @@ namespace Tgstation.Server.Host.Components StartedBy = user }; - var repositoryUpdateSuccess = false; + string deploySha = null; await jobManager.RegisterOperation(repositoryUpdateJob, async (paramJob, databaseContext, progressReporter, jobCancellationToken) => { var repositorySettingsTask = databaseContext.RepositorySettings.Where(x => x.InstanceId == metadata.Id).FirstAsync(jobCancellationToken); @@ -259,18 +259,24 @@ namespace Tgstation.Server.Host.Components await repo.Sychronize(repositorySettings.AccessUser, repositorySettings.AccessToken, repositorySettings.CommitterName, repositorySettings.CommitterEmail, NextProgressReporter(), shouldSyncTracked, jobCancellationToken).ConfigureAwait(false); progressReporter(5 * ProgressStep); + deploySha = repo.Head; } - repositoryUpdateSuccess = true; }, cancellationToken).ConfigureAwait(false); await jobManager.WaitForJobCompletion(repositoryUpdateJob, user, cancellationToken, default).ConfigureAwait(false); - if (!repositoryUpdateSuccess) + if (deploySha == null) { logger.LogTrace("Aborting auto update, repository error!"); continue; } + if(deploySha == LatestCompileJob()?.RevisionInformation.CommitSha) + { + logger.LogTrace("Aborting auto update, same revision as latest CompileJob"); + continue; + } + //finally set up the job var compileProcessJob = new Job { From 54044d76af947a9432c46771ce2647cdf3aa6166 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 22 Sep 2018 11:44:50 -0400 Subject: [PATCH 213/230] Add missing Instance ID clauses to some RevisionInformation wheres --- src/Tgstation.Server.Host/Components/Instance.cs | 2 +- src/Tgstation.Server.Host/Controllers/RepositoryController.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index 4a7324dcd7..0580758b21 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -146,7 +146,7 @@ namespace Tgstation.Server.Host.Components throw new JobException("Missing Repository!"); var repoSha = repo.Head; - revInfo = await databaseContext.RevisionInformations.Where(x => x.CommitSha == repoSha).Include(x => x.ActiveTestMerges).ThenInclude(x => x.TestMerge).FirstOrDefaultAsync().ConfigureAwait(false); + revInfo = await databaseContext.RevisionInformations.Where(x => x.CommitSha == repoSha && x.Instance.Id == metadata.Id).Include(x => x.ActiveTestMerges).ThenInclude(x => x.TestMerge).FirstOrDefaultAsync().ConfigureAwait(false); if (revInfo == default) { diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index f8efbfb785..40f8837f7d 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -78,7 +78,7 @@ namespace Tgstation.Server.Host.Controllers .FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); //search every rev info because LOL SHA COLLISIONS if (revisionInfo == default) - revisionInfo = databaseContext.RevisionInformations.Local.Where(x => x.CommitSha == repoSha).FirstOrDefault(); + revisionInfo = databaseContext.RevisionInformations.Local.Where(x => x.CommitSha == repoSha && x.Instance.Id == instance.Id).FirstOrDefault(); var needsDbUpdate = revisionInfo == default; if (needsDbUpdate) From 3863f87c6ac6ab5842e5e46022ef7701d63f5bc3 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 22 Sep 2018 12:35:11 -0400 Subject: [PATCH 214/230] Remove cwd usage from Watchdog Fix a bad variable name --- src/Tgstation.Server.Host.Watchdog/Watchdog.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs index 0227e83be7..3e3ad9aad0 100644 --- a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs @@ -70,7 +70,7 @@ namespace Tgstation.Server.Host.Watchdog logger.LogInformation("Detected dotnet executable at {0}", dotnetPath); var rootLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - + var assemblyStoragePath = Path.Combine(rootLocation, "lib"); //always always next to watchdog var defaultAssemblyPath = Path.GetFullPath(Path.Combine(assemblyStoragePath, "Default")); #if DEBUG @@ -114,7 +114,7 @@ namespace Tgstation.Server.Host.Watchdog using (var process = new Process()) { process.StartInfo.FileName = dotnetPath; - process.StartInfo.WorkingDirectory = Environment.CurrentDirectory; //for appsettings + process.StartInfo.WorkingDirectory = rootLocation; //for appsettings var arguments = new List { @@ -138,7 +138,7 @@ namespace Tgstation.Server.Host.Watchdog logger.LogInformation("Launching host..."); - var iShotTheSheriff = false; + var killedHostProcess = false; try { process.Start(); @@ -172,7 +172,7 @@ namespace Tgstation.Server.Host.Watchdog { if (!process.HasExited) { - iShotTheSheriff = true; + killedHostProcess = true; process.Kill(); process.WaitForExit(); } @@ -206,7 +206,7 @@ namespace Tgstation.Server.Host.Watchdog } throw new Exception(String.Format(CultureInfo.InvariantCulture, "Host propagated exception: {0}", data)); default: - if (iShotTheSheriff) + if (killedHostProcess) { logger.LogWarning("Watchdog forced to kill host process!"); cancellationToken.ThrowIfCancellationRequested(); From c441f096a3e3ab8dd235f550b2db5ccd35f028ca Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 22 Sep 2018 12:42:33 -0400 Subject: [PATCH 215/230] Add a return value to IRepository.Synchronize --- .../Components/Repository/IRepository.cs | 4 ++-- .../Components/Repository/Repository.cs | 17 ++++++++++------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Repository/IRepository.cs b/src/Tgstation.Server.Host/Components/Repository/IRepository.cs index 323ae7a5ad..e993508172 100644 --- a/src/Tgstation.Server.Host/Components/Repository/IRepository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/IRepository.cs @@ -122,8 +122,8 @@ namespace Tgstation.Server.Host.Components.Repository /// If the synchronizations should be made to the tracked reference as opposed to a temporary branch /// to report 0-100 progress of the operation /// The for the operation - /// A representing the running operation - Task Sychronize(string username, string password, string committerName, string committerEmail, Action progressReporter, bool synchronizeTrackedBranch, CancellationToken cancellationToken); + /// A resulting in if commits were pushed to the tracked origin reference, otherwise + Task Sychronize(string username, string password, string committerName, string committerEmail, Action progressReporter, bool synchronizeTrackedBranch, CancellationToken cancellationToken); /// /// Copies the current working directory to a given diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs index 0c7f7687ee..c7652fddfc 100644 --- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs @@ -489,7 +489,7 @@ namespace Tgstation.Server.Host.Components.Repository } /// - public async Task Sychronize(string username, string password, string committerName, string committerEmail, Action progressReporter, bool synchronizeTrackedBranch, CancellationToken cancellationToken) + public async Task Sychronize(string username, string password, string committerName, string committerEmail, Action progressReporter, bool synchronizeTrackedBranch, CancellationToken cancellationToken) { if (committerName == null) throw new ArgumentNullException(nameof(committerName)); @@ -501,7 +501,7 @@ namespace Tgstation.Server.Host.Components.Repository if (username == null && password == null) { logger.LogTrace("Not synchronizing due to lack of credentials!"); - return; + return false; } logger.LogTrace("Begin Synchronize..."); @@ -526,7 +526,7 @@ namespace Tgstation.Server.Host.Components.Repository if (!await eventConsumer.HandleEvent(EventType.RepoPreSynchronize, new List { ioMananger.ResolvePath(".") }, cancellationToken).ConfigureAwait(false)) { logger.LogDebug("Aborted synchronize due to event handler response!"); - return; + return false; } } finally @@ -548,32 +548,35 @@ namespace Tgstation.Server.Host.Components.Repository if (!synchronizeTrackedBranch) { await PushHeadToTemporaryBranch(username, password, FinalReporter, cancellationToken).ConfigureAwait(false); - return; + return false; } var sameHead = Head == startHead; if (sameHead || !Tracking) { logger.LogTrace("Aborted synchronize due to {0}!", sameHead ? "lack of changes" : "not being on tracked reference"); - return; + return false; } logger.LogInformation("Synchronizing with origin..."); - await Task.Factory.StartNew(() => + return await Task.Factory.StartNew(() => { var remote = repository.Network.Remotes.First(); try { repository.Network.Push(repository.Head, GeneratePushOptions(FinalReporter, username, password, cancellationToken)); + return true; } catch (NonFastForwardException) { logger.LogInformation("Synchronize aborted, non-fast forward!"); + return false; } - catch (UserCancelledException) + catch (UserCancelledException e) { cancellationToken.ThrowIfCancellationRequested(); + throw new InvalidOperationException("Caught UserCancelledException without cancellationToken triggering", e); } }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); } From e7d47baa0adcd22b79cff65abb7cdc48d87196e7 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 22 Sep 2018 13:13:29 -0400 Subject: [PATCH 216/230] Adds database handling to auto update --- .../Components/Instance.cs | 96 ++++++++++++++++++- 1 file changed, 92 insertions(+), 4 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index 0580758b21..50077a86db 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -1,6 +1,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using System; +using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -230,33 +231,120 @@ namespace Tgstation.Server.Host.Components return; } + var startSha = repo.Head; + if (!repo.Tracking) + { + logger.LogTrace("Aborting repo update, not tracking origin!"); + deploySha = startSha; + return; + } + var repositorySettings = await repositorySettingsTask.ConfigureAwait(false); - + //the main point of auto update is to pull the remote await repo.FetchOrigin(repositorySettings.AccessUser, repositorySettings.AccessToken, NextProgressReporter(), jobCancellationToken).ConfigureAwait(false); - var startSha = repo.Head; + RevisionInformation currentRevInfo = null; + bool hasDbChanges = false; + + Task LoadRevInfo() => databaseContext.RevisionInformations + .Where(x => x.CommitSha == startSha && x.Instance.Id == metadata.Id) + .Include(x => x.ActiveTestMerges).ThenInclude(x => x.TestMerge) + .FirstOrDefaultAsync(cancellationToken); + + async Task UpdateRevInfo(string currentHead, bool onOrigin) + { + if(currentRevInfo == null) + currentRevInfo = await LoadRevInfo().ConfigureAwait(false); + + if (currentRevInfo == default) + { + logger.LogWarning(Repository.Repository.OriginTrackingErrorTemplate, currentHead); + onOrigin = true; + } + + var attachedInstance = new Models.Instance + { + Id = metadata.Id + }; + var oldRevInfo = currentRevInfo; + currentRevInfo = new RevisionInformation + { + CommitSha = currentHead, + OriginCommitSha = onOrigin ? currentHead : oldRevInfo.OriginCommitSha, + Instance = attachedInstance + }; + if (!onOrigin) + currentRevInfo.ActiveTestMerges = new List(oldRevInfo.ActiveTestMerges); + + databaseContext.Instances.Attach(attachedInstance); + databaseContext.RevisionInformations.Add(currentRevInfo); + hasDbChanges = true; + } //take appropriate auto update actions bool shouldSyncTracked; if (repositorySettings.AutoUpdatesKeepTestMerges.Value) { logger.LogTrace("Preserving test merges..."); + + var currentRevInfoTask = LoadRevInfo(); + var result = await repo.MergeOrigin(repositorySettings.CommitterName, repositorySettings.CommitterEmail, NextProgressReporter(), jobCancellationToken).ConfigureAwait(false); + if (!result.HasValue) throw new JobException("Merge conflict while preserving test merges!"); - shouldSyncTracked = result.Value; + + currentRevInfo = await currentRevInfoTask.ConfigureAwait(false); + + var lastRevInfoWasOriginCommit = currentRevInfo == default || currentRevInfo.CommitSha == currentRevInfo.OriginCommitSha; + var stillOnOrigin = result.Value && lastRevInfoWasOriginCommit; + + var currentHead = repo.Head; + if (currentHead != startSha) + { + await UpdateRevInfo(currentHead, stillOnOrigin).ConfigureAwait(false); + shouldSyncTracked = stillOnOrigin; + } + else + shouldSyncTracked = false; } else { logger.LogTrace("Not preserving test merges..."); await repo.ResetToOrigin(NextProgressReporter(), jobCancellationToken).ConfigureAwait(false); + + var currentHead = repo.Head; + + currentRevInfo = await databaseContext.RevisionInformations + .Where(x => x.CommitSha == currentHead && x.Instance.Id == metadata.Id) + .FirstOrDefaultAsync(jobCancellationToken).ConfigureAwait(false); + + if (currentHead != startSha && currentRevInfo != default) + await UpdateRevInfo(currentHead, true).ConfigureAwait(false); + shouldSyncTracked = true; } //synch if necessary if (repositorySettings.AutoUpdatesSynchronize.Value && startSha != repo.Head) - await repo.Sychronize(repositorySettings.AccessUser, repositorySettings.AccessToken, repositorySettings.CommitterName, repositorySettings.CommitterEmail, NextProgressReporter(), shouldSyncTracked, jobCancellationToken).ConfigureAwait(false); + { + var pushedOrigin = await repo.Sychronize(repositorySettings.AccessUser, repositorySettings.AccessToken, repositorySettings.CommitterName, repositorySettings.CommitterEmail, NextProgressReporter(), shouldSyncTracked, jobCancellationToken).ConfigureAwait(false); + var currentHead = repo.Head; + if (currentHead != currentRevInfo.CommitSha) + await UpdateRevInfo(currentHead, pushedOrigin).ConfigureAwait(false); + } + + if(hasDbChanges) + try + { + await databaseContext.Save(cancellationToken).ConfigureAwait(false); + } + catch + { + await repo.ResetToSha(startSha, progressReporter, default).ConfigureAwait(false); + throw; + } progressReporter(5 * ProgressStep); deploySha = repo.Head; From 78dc40b95e161982d73ba7f3d292aab832ec478c Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 22 Sep 2018 13:23:09 -0400 Subject: [PATCH 217/230] Fix DiscordProvider thinking all channels are private channels --- .../Components/Chat/Providers/DiscordProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs index 3ee2e682ad..b94d3078b2 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs @@ -92,7 +92,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers Channel = new Channel { RealId = e.Channel.Id, - IsPrivate = true, + IsPrivate = pm, ConnectionName = pm ? e.Author.Username : (e.Channel as ITextChannel)?.Guild.Name ?? "UNKNOWN", FriendlyName = e.Channel.Name //isAdmin and Tag populated by manager From d60616916c84efa248f9f4776fc54a079d153848 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 22 Sep 2018 14:34:49 -0400 Subject: [PATCH 218/230] Fix chat mapping IDs --- src/Tgstation.Server.Host/Components/Chat/Chat.cs | 1 + .../Components/Chat/Providers/DiscordProvider.cs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Chat/Chat.cs b/src/Tgstation.Server.Host/Components/Chat/Chat.cs index 456b882168..af7da0a334 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Chat.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Chat.cs @@ -207,6 +207,7 @@ namespace Tgstation.Server.Host.Components.Chat { //need to add tag and isAdminChannel var mapping = enumerable.First().Value; + message.User.Channel.Id = mapping.Channel.Id; message.User.Channel.Tag = mapping.Channel.Tag; message.User.Channel.IsAdmin = mapping.Channel.IsAdmin; } diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs index b94d3078b2..f11834a7cc 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs @@ -182,7 +182,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers }; }; - var enumerator = channels.Select(x => GetChannelForChatChannel(x)).Where(x => x != null); + var enumerator = channels.Select(x => GetChannelForChatChannel(x)).Where(x => x != null).ToList(); lock (this) { @@ -190,7 +190,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers mappedChannels.AddRange(enumerator.Select(x => x.RealId)); } - return Task.FromResult>(enumerator.ToList()); + return Task.FromResult>(enumerator); } /// From 0ae0cfdfc396248fae377a634ff33c8fe62ee0c8 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 22 Sep 2018 17:02:22 -0400 Subject: [PATCH 219/230] Fix an InvalidOperationException --- .../Components/Chat/Providers/DiscordProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs index f11834a7cc..d1a33aac23 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs @@ -122,7 +122,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers var channelsAvailable = new TaskCompletionSource(); client.Ready += () => { - channelsAvailable.SetResult(null); + channelsAvailable.TrySetResult(null); return Task.CompletedTask; }; using (cancellationToken.Register(() => channelsAvailable.SetCanceled())) From c828c5f637c481228653be32eaab275de71fed8c Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 22 Sep 2018 17:18:21 -0400 Subject: [PATCH 220/230] Cleanup nickname protection --- .../Components/Chat/Providers/IrcProvider.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs index ce081b13e7..12374f26fd 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs @@ -291,16 +291,17 @@ namespace Tgstation.Server.Host.Components.Chat.Providers listenTask = Task.Factory.StartNew(() => { - while (!disconnecting && client.IsConnected) + while (!disconnecting && client.IsConnected && client.Nickname != nickname) { client.ListenOnce(true); if (disconnecting || !client.IsConnected) break; client.Listen(false); //ensure we have the correct nick - if (client.Nickname != nickname && client.GetIrcUser(nickname) == null) + if (client.GetIrcUser(nickname) == null) client.RfcNick(nickname); } + client.Listen(); }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); } catch (Exception e) From fed4b92675a7e53efa0dab2e674545f0178f1ae0 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 22 Sep 2018 19:24:59 -0400 Subject: [PATCH 221/230] Fix some watchdog issues --- src/DMAPI/tgs/v4/api.dm | 5 ++--- src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs | 4 +++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/DMAPI/tgs/v4/api.dm b/src/DMAPI/tgs/v4/api.dm index b5bc692f11..9edc4970e9 100644 --- a/src/DMAPI/tgs/v4/api.dm +++ b/src/DMAPI/tgs/v4/api.dm @@ -222,14 +222,13 @@ export_lock = FALSE /datum/tgs_api/v4/OnReboot() - var/json = Export(TGS4_COMM_WORLD_REBOOT) - var/list/result = json_decode(json) + var/list/result = Export(TGS4_COMM_WORLD_REBOOT) if(!result) return //okay so the standard TGS4 proceedure is: right before rebooting change the port to whatever was sent to us in the above json's data parameter - var/port = json[TGS4_PARAMETER_DATA] + var/port = result[TGS4_PARAMETER_DATA] if(!isnum(port)) return //this is valid, server may just want use to reboot diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index 1baed19018..f2c1f12673 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -713,9 +713,11 @@ namespace Tgstation.Server.Host.Components.Watchdog await Task.WhenAny(allTask, cancelTcs.Task).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); - //both servers are now running, alpha is the active server, huzzah + //both servers are now running, alpha is the active server(unless reattach), huzzah AlphaIsActive = doReattach ? reattachInfo?.AlphaIsActive ?? true : true; LastLaunchResult = alphaLrt.Result; + (AlphaIsActive ? alphaServer : bravoServer).ClosePortOnReboot = true; + logger.LogInformation("Launched servers successfully"); Running = true; From caf9fb4b1f37e70574d7c19f97e4c35179fde41c Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 24 Sep 2018 15:31:06 -0400 Subject: [PATCH 222/230] Add several User32.dll NativeMethods --- src/Tgstation.Server.Host/NativeMethods.cs | 37 ++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/Tgstation.Server.Host/NativeMethods.cs b/src/Tgstation.Server.Host/NativeMethods.cs index 398185ab2a..afe778312a 100644 --- a/src/Tgstation.Server.Host/NativeMethods.cs +++ b/src/Tgstation.Server.Host/NativeMethods.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.InteropServices; +using System.Text; namespace Tgstation.Server.Host { @@ -8,6 +9,42 @@ namespace Tgstation.Server.Host /// static class NativeMethods { + /// + /// See https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getwindowthreadprocessid + /// + [DllImport("user32.dll")] + public static extern int GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId); + + /// + /// See https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-findwindoww + /// + [DllImport("user32.dll", CharSet = CharSet.Unicode)] + public static extern IntPtr FindWindow(string lpClassName, string lpWindowName); + + /// + /// See https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-sendmessage + /// + [DllImport("user32.dll")] + public static extern int SendMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam); + + /// + /// See https://msdn.microsoft.com/en-us/library/ms633493(v=VS.85).aspx + /// + public delegate bool EnumWindowProc(IntPtr hwnd, IntPtr lParam); + + /// + /// See https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-enumchildwindows + /// + [DllImport("user32")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool EnumChildWindows(IntPtr window, EnumWindowProc callback, IntPtr lParam); + + /// + /// See https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getwindowtextw + /// + [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); + /// /// See https://msdn.microsoft.com/en-us/library/windows/desktop/aa378184(v=vs.85).aspx /// From 11e76507d0962c2b6e7a782678b754c0cd4219d9 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 24 Sep 2018 15:31:15 -0400 Subject: [PATCH 223/230] Add NetworkPromptReaper --- .../Watchdog/INetworkPromptReaper.cs | 16 ++ .../Watchdog/NetworkPromptReaper.cs | 191 ++++++++++++++++++ 2 files changed, 207 insertions(+) create mode 100644 src/Tgstation.Server.Host/Components/Watchdog/INetworkPromptReaper.cs create mode 100644 src/Tgstation.Server.Host/Components/Watchdog/NetworkPromptReaper.cs diff --git a/src/Tgstation.Server.Host/Components/Watchdog/INetworkPromptReaper.cs b/src/Tgstation.Server.Host/Components/Watchdog/INetworkPromptReaper.cs new file mode 100644 index 0000000000..eb939218e7 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Watchdog/INetworkPromptReaper.cs @@ -0,0 +1,16 @@ +using Tgstation.Server.Host.Core; + +namespace Tgstation.Server.Host.Components.Watchdog +{ + /// + /// On Windows, DreamDaemon will show an unskippable prompt when using /world/proc/OpenPort(). This looks out for those prompts and immediately clicks "Yes" if the owning process has registered for it + /// + interface INetworkPromptReaper + { + /// + /// Register a given for network prompt reaping + /// + /// The to register + void RegisterProcess(IProcess process); + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/Watchdog/NetworkPromptReaper.cs b/src/Tgstation.Server.Host/Components/Watchdog/NetworkPromptReaper.cs new file mode 100644 index 0000000000..e2ed8ac3d7 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Watchdog/NetworkPromptReaper.cs @@ -0,0 +1,191 @@ +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Host.Core; + +namespace Tgstation.Server.Host.Components.Watchdog +{ + /// + sealed class NetworkPromptReaper : IHostedService, INetworkPromptReaper, IDisposable + { + /// + /// The for the + /// + readonly ILogger logger; + + /// + /// The for the + /// + readonly CancellationTokenSource cancellationTokenSource; + + /// + /// The list of s registered + /// + readonly List registeredProcesses; + + /// + /// The representing the lifetime of the + /// + Task runTask; + + static bool EnumWindow(IntPtr hWnd, IntPtr lParam) + { + var gcChildhandlesList = GCHandle.FromIntPtr(lParam); + + if (gcChildhandlesList == null || gcChildhandlesList.Target == null) + return false; + + var childHandles = (List )gcChildhandlesList.Target; + childHandles.Add(hWnd); + + return true; + } + + static List GetAllChildHandles(IntPtr main) + { + var childHandles = new List(); + + var gcChildhandlesList = GCHandle.Alloc(childHandles); + var pointerChildHandlesList = GCHandle.ToIntPtr(gcChildhandlesList); + + try + { + NativeMethods.EnumWindowProc childProc = new NativeMethods.EnumWindowProc(EnumWindow); + NativeMethods.EnumChildWindows(main, childProc, pointerChildHandlesList); + } + finally + { + gcChildhandlesList.Free(); + } + + return childHandles; + } + + /// + /// Construct a + /// + /// The value of + public NetworkPromptReaper(ILogger logger) + { + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + + registeredProcesses = new List(); + cancellationTokenSource = new CancellationTokenSource(); + } + + /// + public void Dispose() => cancellationTokenSource.Dispose(); + + async Task Run(CancellationToken cancellationToken) + { + logger.LogDebug("Starting network prompt reaper..."); + try + { + while(!cancellationToken.IsCancellationRequested) + { + //check every quarter second + await Task.Delay(TimeSpan.FromMilliseconds(250), cancellationToken).ConfigureAwait(false); + + IntPtr window; + int processId; + lock (registeredProcesses) + { + if (registeredProcesses.Count == 0) + continue; + + window = NativeMethods.FindWindow(null, "Network Accessibility"); + if (window == IntPtr.Zero) + continue; + + //found a bitch + var threadId = NativeMethods.GetWindowThreadProcessId(window, out processId); + if (!registeredProcesses.Any(x => x.Id == processId)) + //not our bitch + continue; + } + logger.LogTrace("Identified \"Network Accessibility\" window in owned process {0}", processId); + + var found = false; + foreach(var I in GetAllChildHandles(window)) + { + const int MaxLength = 10; + var stringBuilder = new StringBuilder(MaxLength + 1); + + if(NativeMethods.GetWindowText(I, stringBuilder, MaxLength) == 0) + { + logger.LogWarning("Error calling GetWindowText! Exception: {0}", new Win32Exception(Marshal.GetLastWin32Error())); + continue; + } + + var windowText = stringBuilder.ToString(); + if (windowText == "Yes") + { + //smash_button_meme.jpg + //yes send it five times for good measure + logger.LogTrace("Sending \"Yes\" button clicks..."); + for (var J = 0; J < 5; ++J) + { + const int BM_CLICK = 0x00F5; + var result = NativeMethods.SendMessage(I, BM_CLICK, IntPtr.Zero, IntPtr.Zero); + } + found = true; + break; + } + } + + if (!found) + logger.LogDebug("Unable to find \"Yes\" button for \"Network Accessibility\" window in owned process {0}!", processId); + } + } + finally + { + logger.LogDebug("Exiting network prompt reaper..."); + } + } + + /// + public Task StartAsync(CancellationToken cancellationToken) + { + runTask = Run(cancellationTokenSource.Token); + return Task.CompletedTask; + } + + /// + public async Task StopAsync(CancellationToken cancellationToken) + { + logger.LogTrace("Stopping network prompt reaper..."); + cancellationTokenSource.Cancel(); + await runTask.ConfigureAwait(false); + registeredProcesses.Clear(); + } + + /// + public void RegisterProcess(IProcess process) + { + if (process == null) + throw new ArgumentNullException(nameof(process)); + + lock (registeredProcesses) + { + if (registeredProcesses.Contains(process)) + throw new InvalidOperationException("This process has already been registered for network prompt reaping!"); + logger.LogTrace("Registering process {0}...", process.Id); + registeredProcesses.Add(process); + } + + process.Lifetime.ContinueWith(x => + { + logger.LogTrace("Unregistering process {0}...", process.Id); + lock (registeredProcesses) + registeredProcesses.Remove(process); + }, TaskScheduler.Current); + } + } +} From 5040acf58469f10841862f2867f324d63b5182f4 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 24 Sep 2018 15:34:04 -0400 Subject: [PATCH 224/230] Register INetworkPromptReaper in SessionControllerFactory --- .../Components/InstanceFactory.cs | 11 +++++++++-- .../Components/Watchdog/SessionControllerFactory.cs | 12 +++++++++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index a66f643e36..c702355b55 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -93,6 +93,11 @@ namespace Tgstation.Server.Host.Components /// readonly ICredentialsProvider credentialsProvider; + /// + /// The for the + /// + readonly INetworkPromptReaper networkPromptReaper; + /// /// Construct an /// @@ -111,7 +116,8 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// The value of - public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IByondTopicSender byondTopicSender, ICryptographySuite cryptographySuite, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IByondInstaller byondInstaller, IChatFactory chatFactory, IProcessExecutor processExecutor, IPostWriteHandler postWriteHandler, IWatchdogFactory watchdogFactory, IJobManager jobManager, ICredentialsProvider credentialsProvider) + /// The value of + public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IByondTopicSender byondTopicSender, ICryptographySuite cryptographySuite, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IByondInstaller byondInstaller, IChatFactory chatFactory, IProcessExecutor processExecutor, IPostWriteHandler postWriteHandler, IWatchdogFactory watchdogFactory, IJobManager jobManager, ICredentialsProvider credentialsProvider, INetworkPromptReaper networkPromptReaper) { this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); @@ -128,6 +134,7 @@ namespace Tgstation.Server.Host.Components 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)); } /// @@ -158,7 +165,7 @@ namespace Tgstation.Server.Host.Components var chat = chatFactory.CreateChat(instanceIoManager, commandFactory, metadata.ChatSettings); try { - var sessionControllerFactory = new SessionControllerFactory(processExecutor, byond, byondTopicSender, cryptographySuite, application, gameIoManager, chat, loggerFactory, metadata.CloneMetadata()); + var sessionControllerFactory = new SessionControllerFactory(processExecutor, byond, byondTopicSender, cryptographySuite, application, gameIoManager, chat, networkPromptReaper, loggerFactory, metadata.CloneMetadata()); var reattachInfoHandler = new ReattachInfoHandler(databaseContextFactory, dmbFactory, loggerFactory.CreateLogger(), metadata.CloneMetadata()); var watchdog = watchdogFactory.CreateWatchdog(chat, dmbFactory, reattachInfoHandler, configuration, sessionControllerFactory, metadata.CloneMetadata(), metadata.DreamDaemonSettings); eventConsumer.SetWatchdog(watchdog); diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs index 9e4601e314..d4af485ee8 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs @@ -57,6 +57,11 @@ namespace Tgstation.Server.Host.Components.Watchdog /// readonly IChat chat; + /// + /// The for the + /// + readonly INetworkPromptReaper networkPromptReaper; + /// /// The for the /// @@ -98,8 +103,9 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The value of /// The value of /// The value of + /// The value of /// The value of - public SessionControllerFactory(IProcessExecutor processExecutor, IByondManager byond, IByondTopicSender byondTopicSender, ICryptographySuite cryptographySuite, IApplication application, IIOManager ioManager, IChat chat, ILoggerFactory loggerFactory, Api.Models.Instance instance) + public SessionControllerFactory(IProcessExecutor processExecutor, IByondManager byond, IByondTopicSender byondTopicSender, ICryptographySuite cryptographySuite, IApplication application, IIOManager ioManager, IChat chat, INetworkPromptReaper networkPromptReaper, ILoggerFactory loggerFactory, Api.Models.Instance instance) { this.processExecutor = processExecutor ?? throw new ArgumentNullException(nameof(processExecutor)); this.byond = byond ?? throw new ArgumentNullException(nameof(byond)); @@ -109,6 +115,7 @@ namespace Tgstation.Server.Host.Components.Watchdog this.instance = instance ?? throw new ArgumentNullException(nameof(instance)); this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.chat = chat ?? throw new ArgumentNullException(nameof(chat)); + this.networkPromptReaper = networkPromptReaper ?? throw new ArgumentNullException(nameof(networkPromptReaper)); this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); } @@ -206,6 +213,8 @@ namespace Tgstation.Server.Host.Components.Watchdog var process = processExecutor.LaunchProcess(byondLock.DreamDaemonPath, basePath, arguments, noShellExecute: true); try { + networkPromptReaper.RegisterProcess(process); + //return the session controller for it var result = new SessionController(new ReattachInformation { @@ -269,6 +278,7 @@ namespace Tgstation.Server.Host.Components.Watchdog var process = processExecutor.GetProcess(reattachInformation.ProcessId); try { + networkPromptReaper.RegisterProcess(process); return new SessionController(reattachInformation, process, byondLock, byondTopicSender, chatJsonTrackingContext, context, chat, loggerFactory.CreateLogger(), null, null); } catch From 0a05586e40f522915f5334852ad94e9f05600368 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 24 Sep 2018 15:35:21 -0400 Subject: [PATCH 225/230] Add NetworkPromptReaper to composition root --- src/Tgstation.Server.Host/Core/Application.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index e82a7d9147..b83b24a073 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -198,6 +198,10 @@ namespace Tgstation.Server.Host.Core services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + + services.AddSingleton(); + services.AddSingleton(x => x.GetRequiredService()); + services.AddSingleton(x => x.GetRequiredService()); } else { From b92b93135437bcf7dbbb29adff13eb0fc44bdb67 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 24 Sep 2018 15:47:32 -0400 Subject: [PATCH 226/230] Move some magic numbers to consts --- .../Components/Watchdog/NetworkPromptReaper.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/NetworkPromptReaper.cs b/src/Tgstation.Server.Host/Components/Watchdog/NetworkPromptReaper.cs index e2ed8ac3d7..c8b89ac553 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/NetworkPromptReaper.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/NetworkPromptReaper.cs @@ -15,6 +15,16 @@ namespace Tgstation.Server.Host.Components.Watchdog /// sealed class NetworkPromptReaper : IHostedService, INetworkPromptReaper, IDisposable { + /// + /// Number of times to send the button click message. Should be at least 2 or it may fail to focus the window + /// + const int SendMessageCount = 5; + + /// + /// Check for prompts each time this amount of milliseconds pass + /// + const int RecheckDelayMs = 250; + /// /// The for the /// @@ -90,8 +100,7 @@ namespace Tgstation.Server.Host.Components.Watchdog { while(!cancellationToken.IsCancellationRequested) { - //check every quarter second - await Task.Delay(TimeSpan.FromMilliseconds(250), cancellationToken).ConfigureAwait(false); + await Task.Delay(TimeSpan.FromMilliseconds(RecheckDelayMs), cancellationToken).ConfigureAwait(false); IntPtr window; int processId; @@ -128,9 +137,8 @@ namespace Tgstation.Server.Host.Components.Watchdog if (windowText == "Yes") { //smash_button_meme.jpg - //yes send it five times for good measure logger.LogTrace("Sending \"Yes\" button clicks..."); - for (var J = 0; J < 5; ++J) + for (var J = 0; J < SendMessageCount; ++J) { const int BM_CLICK = 0x00F5; var result = NativeMethods.SendMessage(I, BM_CLICK, IntPtr.Zero, IntPtr.Zero); From 0f10aeb2d119053adab978a968e446d0a78537ae Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 24 Sep 2018 15:50:52 -0400 Subject: [PATCH 227/230] Add missing POSIX version of INetworkPromptReaper --- .../Watchdog/PosixNetworkPromptReaper.cs | 12 ++++++++++++ ...omptReaper.cs => WindowsNetworkPromptReaper.cs} | 14 +++++++------- src/Tgstation.Server.Host/Core/Application.cs | 7 ++++--- 3 files changed, 23 insertions(+), 10 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/Watchdog/PosixNetworkPromptReaper.cs rename src/Tgstation.Server.Host/Components/Watchdog/{NetworkPromptReaper.cs => WindowsNetworkPromptReaper.cs} (92%) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/PosixNetworkPromptReaper.cs b/src/Tgstation.Server.Host/Components/Watchdog/PosixNetworkPromptReaper.cs new file mode 100644 index 0000000000..f12cfbc7b9 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Watchdog/PosixNetworkPromptReaper.cs @@ -0,0 +1,12 @@ +using Tgstation.Server.Host.Core; + +namespace Tgstation.Server.Host.Components.Watchdog +{ + //POSIX BYOND doesn't prompt you when you change the port + /// + sealed class PosixNetworkPromptReaper : INetworkPromptReaper + { + /// + public void RegisterProcess(IProcess process) { } + } +} diff --git a/src/Tgstation.Server.Host/Components/Watchdog/NetworkPromptReaper.cs b/src/Tgstation.Server.Host/Components/Watchdog/WindowsNetworkPromptReaper.cs similarity index 92% rename from src/Tgstation.Server.Host/Components/Watchdog/NetworkPromptReaper.cs rename to src/Tgstation.Server.Host/Components/Watchdog/WindowsNetworkPromptReaper.cs index c8b89ac553..cd8fc90471 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/NetworkPromptReaper.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WindowsNetworkPromptReaper.cs @@ -13,7 +13,7 @@ using Tgstation.Server.Host.Core; namespace Tgstation.Server.Host.Components.Watchdog { /// - sealed class NetworkPromptReaper : IHostedService, INetworkPromptReaper, IDisposable + sealed class WindowsNetworkPromptReaper : IHostedService, INetworkPromptReaper, IDisposable { /// /// Number of times to send the button click message. Should be at least 2 or it may fail to focus the window @@ -26,12 +26,12 @@ namespace Tgstation.Server.Host.Components.Watchdog const int RecheckDelayMs = 250; /// - /// The for the + /// The for the /// - readonly ILogger logger; + readonly ILogger logger; /// - /// The for the + /// The for the /// readonly CancellationTokenSource cancellationTokenSource; @@ -41,7 +41,7 @@ namespace Tgstation.Server.Host.Components.Watchdog readonly List registeredProcesses; /// - /// The representing the lifetime of the + /// The representing the lifetime of the /// Task runTask; @@ -79,10 +79,10 @@ namespace Tgstation.Server.Host.Components.Watchdog } /// - /// Construct a + /// Construct a /// /// The value of - public NetworkPromptReaper(ILogger logger) + public WindowsNetworkPromptReaper(ILogger logger) { this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index b83b24a073..3c654d2c52 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -199,9 +199,9 @@ namespace Tgstation.Server.Host.Core services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(x => x.GetRequiredService()); - services.AddSingleton(x => x.GetRequiredService()); + services.AddSingleton(); + services.AddSingleton(x => x.GetRequiredService()); + services.AddSingleton(x => x.GetRequiredService()); } else { @@ -209,6 +209,7 @@ namespace Tgstation.Server.Host.Core services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); } services.AddSingleton(); From 53a9476a36fd509774f93142b345f3b667b6bcef Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 24 Sep 2018 18:28:39 -0400 Subject: [PATCH 228/230] Fix missing return in TGS4_TOPIC_CHANGE_PORT case --- src/DMAPI/tgs/v4/api.dm | 1 + 1 file changed, 1 insertion(+) diff --git a/src/DMAPI/tgs/v4/api.dm b/src/DMAPI/tgs/v4/api.dm index 9edc4970e9..954303ea77 100644 --- a/src/DMAPI/tgs/v4/api.dm +++ b/src/DMAPI/tgs/v4/api.dm @@ -166,6 +166,7 @@ event_handler.HandleEvent(TGS_EVENT_PORT_SWAP, new_port) if(!world.OpenPort(new_port)) return "Port change failed!" + return if(TGS4_TOPIC_CHANGE_REBOOT_MODE) var/new_reboot_mode = text2num(params[TGS4_PARAMETER_DATA]) event_handler.HandleEvent(TGS_EVENT_REBOOT_MODE_CHANGE, reboot_mode, new_reboot_mode) From 2926fd152ac9c8cf51254a4006b4fd29b270c928 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 24 Sep 2018 21:21:00 -0400 Subject: [PATCH 229/230] Fix exporting to the wrong port when DD requests a new one --- src/DMAPI/tgs/v4/api.dm | 13 ++++++++++--- .../Components/Watchdog/SessionController.cs | 13 +++++++++---- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/DMAPI/tgs/v4/api.dm b/src/DMAPI/tgs/v4/api.dm index 954303ea77..aedcf1429e 100644 --- a/src/DMAPI/tgs/v4/api.dm +++ b/src/DMAPI/tgs/v4/api.dm @@ -33,6 +33,8 @@ var/server_commands_json_path var/reboot_mode = TGS_REBOOT_MODE_NORMAL var/security_level + + var/requesting_new_port = FALSE var/list/intercepted_message_queue @@ -175,27 +177,31 @@ return "Unknown command: [command]" -/datum/tgs_api/v4/proc/Export(command, list/data) +/datum/tgs_api/v4/proc/Export(command, list/data, override_requesting_new_port = FALSE) if(!data) data = list() data[TGS4_PARAMETER_COMMAND] = command var/json = json_encode(data) + while(requesting_new_port && !override_requesting_new_port) + sleep(1) + //we need some port open at this point to facilitate return communication if(!world.port) + requesting_new_port = TRUE if(!world.OpenPort(0)) //open any port TGS_ERROR_LOG("Unable to open random port to retrieve new port![TGS4_PORT_CRITFAIL_MESSAGE]") del(world) //request a new port export_lock = FALSE - var/list/new_port_json = Export(TGS4_COMM_NEW_PORT, list(TGS4_PARAMETER_DATA = "[world.port]")) //stringify this on purpose + var/list/new_port_json = Export(TGS4_COMM_NEW_PORT, list(TGS4_PARAMETER_DATA = "[world.port]"), TRUE) //stringify this on purpose if(!new_port_json) TGS_ERROR_LOG("No new port response from server![TGS4_PORT_CRITFAIL_MESSAGE]") del(world) - var/new_port = new_port_json["port"] + var/new_port = new_port_json[TGS4_PARAMETER_DATA] if(!isnum(new_port) || new_port <= 0) TGS_ERROR_LOG("Malformed new port json ([json_encode(new_port_json)])![TGS4_PORT_CRITFAIL_MESSAGE]") del(world) @@ -203,6 +209,7 @@ if(new_port != world.port && !world.OpenPort(new_port)) TGS_ERROR_LOG("Unable to open port [new_port]![TGS4_PORT_CRITFAIL_MESSAGE]") del(world) + requesting_new_port = FALSE while(export_lock) sleep(1) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs index b71d7f0c62..eeacaa941a 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs @@ -278,6 +278,7 @@ namespace Tgstation.Server.Host.Components.Watchdog object content; Action postRespond = null; + ushort? overrideResponsePort = null; if (query.TryGetValue(Constants.DMParameterCommand, out var method)) { content = new object(); @@ -310,6 +311,7 @@ namespace Tgstation.Server.Host.Components.Watchdog //if it fails it'll kill itself content = new Dictionary { { Constants.DMParameterData, nextPort.Value } }; reattachInformation.Port = nextPort.Value; + overrideResponsePort = currentPort; nextPort = null; //we'll also get here from SetPort so complete that task @@ -367,7 +369,7 @@ namespace Tgstation.Server.Host.Components.Watchdog content = new ErrorMessage { Message = "Missing command parameter!" }; var json = JsonConvert.SerializeObject(content); - var response = await SendCommand(String.Format(CultureInfo.InvariantCulture, "{0}&{1}={2}", byondTopicSender.SanitizeString(Constants.DMTopicInteropResponse), byondTopicSender.SanitizeString(Constants.DMParameterData), byondTopicSender.SanitizeString(json)), cancellationToken).ConfigureAwait(false); + var response = await SendCommand(String.Format(CultureInfo.InvariantCulture, "{0}&{1}={2}", byondTopicSender.SanitizeString(Constants.DMTopicInteropResponse), byondTopicSender.SanitizeString(Constants.DMParameterData), byondTopicSender.SanitizeString(json)), overrideResponsePort, cancellationToken).ConfigureAwait(false); if (response != Constants.DMResponseSuccess) logger.LogWarning("Recieved error response while responding to interop: {0}", response); @@ -399,7 +401,9 @@ namespace Tgstation.Server.Host.Components.Watchdog } /// - public async Task SendCommand(string command, CancellationToken cancellationToken) + public Task SendCommand(string command, CancellationToken cancellationToken) => SendCommand(command, null, cancellationToken); + + async Task SendCommand(string command, ushort? overridePort, CancellationToken cancellationToken) { try { @@ -411,10 +415,11 @@ namespace Tgstation.Server.Host.Components.Watchdog //intentionally don't sanitize command, that's up to the caller command); - logger.LogTrace("Export to :{0}. Query: {1}", reattachInformation.Port, commandString); + var targetPort = overridePort ?? reattachInformation.Port; + logger.LogTrace("Export to :{0}. Query: {1}", targetPort, commandString); return await byondTopicSender.SendTopic( - new IPEndPoint(IPAddress.Loopback, reattachInformation.Port), + new IPEndPoint(IPAddress.Loopback, targetPort), commandString, cancellationToken).ConfigureAwait(false); } From f96a21ea5bb20327925c7e96864afc51f487ac2f Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 24 Sep 2018 21:25:11 -0400 Subject: [PATCH 230/230] Properly catch WindowsNetworkPromptReaper OperationCanceledException --- .../Components/Watchdog/WindowsNetworkPromptReaper.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WindowsNetworkPromptReaper.cs b/src/Tgstation.Server.Host/Components/Watchdog/WindowsNetworkPromptReaper.cs index cd8fc90471..2298971e6a 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WindowsNetworkPromptReaper.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WindowsNetworkPromptReaper.cs @@ -98,7 +98,7 @@ namespace Tgstation.Server.Host.Components.Watchdog logger.LogDebug("Starting network prompt reaper..."); try { - while(!cancellationToken.IsCancellationRequested) + while (!cancellationToken.IsCancellationRequested) { await Task.Delay(TimeSpan.FromMilliseconds(RecheckDelayMs), cancellationToken).ConfigureAwait(false); @@ -122,12 +122,12 @@ namespace Tgstation.Server.Host.Components.Watchdog logger.LogTrace("Identified \"Network Accessibility\" window in owned process {0}", processId); var found = false; - foreach(var I in GetAllChildHandles(window)) + foreach (var I in GetAllChildHandles(window)) { const int MaxLength = 10; var stringBuilder = new StringBuilder(MaxLength + 1); - if(NativeMethods.GetWindowText(I, stringBuilder, MaxLength) == 0) + if (NativeMethods.GetWindowText(I, stringBuilder, MaxLength) == 0) { logger.LogWarning("Error calling GetWindowText! Exception: {0}", new Win32Exception(Marshal.GetLastWin32Error())); continue; @@ -152,6 +152,7 @@ namespace Tgstation.Server.Host.Components.Watchdog logger.LogDebug("Unable to find \"Yes\" button for \"Network Accessibility\" window in owned process {0}!", processId); } } + catch (OperationCanceledException) { } finally { logger.LogDebug("Exiting network prompt reaper...");