diff --git a/src/DMAPI/tgs.dm b/src/DMAPI/tgs.dm index 4493aef72c..d0cf1618a6 100644 --- a/src/DMAPI/tgs.dm +++ b/src/DMAPI/tgs.dm @@ -95,6 +95,8 @@ #define TGS_EVENT_WATCHDOG_SHUTDOWN 15 /// Before the watchdog detaches for a TGS update/restart. No parameters. #define TGS_EVENT_WATCHDOG_DETACH 16 +// We don't actually implement this value as the DMAPI can never receive it +// #define TGS_EVENT_WATCHDOG_LAUNCH 17 // OTHER ENUMS diff --git a/src/Tgstation.Server.Api/Models/Internal/RevisionInformation.cs b/src/Tgstation.Server.Api/Models/Internal/RevisionInformation.cs index efdb763aa2..1ee31b1993 100644 --- a/src/Tgstation.Server.Api/Models/Internal/RevisionInformation.cs +++ b/src/Tgstation.Server.Api/Models/Internal/RevisionInformation.cs @@ -11,14 +11,14 @@ namespace Tgstation.Server.Api.Models.Internal /// The revision sha /// [Required] - [StringLength(40)] + [StringLength(Limits.MaximumCommitShaLength)] public string? CommitSha { get; set; } /// /// The sha of the most recent remote commit /// [Required] - [StringLength(40)] + [StringLength(Limits.MaximumCommitShaLength)] public string? OriginCommitSha { get; set; } } } diff --git a/src/Tgstation.Server.Api/Models/Limits.cs b/src/Tgstation.Server.Api/Models/Limits.cs index be61530fe7..249ecd0763 100644 --- a/src/Tgstation.Server.Api/Models/Limits.cs +++ b/src/Tgstation.Server.Api/Models/Limits.cs @@ -14,5 +14,10 @@ /// Length limit for s. /// public const int MaximumIndexableStringLength = 100; + + /// + /// Length limit for git commit SHAs. + /// + public const int MaximumCommitShaLength = 40; } } \ No newline at end of file diff --git a/src/Tgstation.Server.Api/Models/Repository.cs b/src/Tgstation.Server.Api/Models/Repository.cs index 26a3e1fa46..88b53ab95c 100644 --- a/src/Tgstation.Server.Api/Models/Repository.cs +++ b/src/Tgstation.Server.Api/Models/Repository.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; namespace Tgstation.Server.Api.Models { @@ -12,9 +13,15 @@ namespace Tgstation.Server.Api.Models /// public string? Origin { get; set; } + /// + /// If submodules should be recursively cloned. + /// + public bool? RecurseSubmodules { get; set; } + /// /// The commit HEAD should point to. Not populated in responses, use instead for retrieval /// + [StringLength(Limits.MaximumCommitShaLength)] public string? CheckoutSha { get; set; } /// @@ -45,6 +52,7 @@ namespace Tgstation.Server.Api.Models /// /// The branch or tag HEAD points to /// + [StringLength(Limits.MaximumStringLength)] public string? Reference { get; set; } /// diff --git a/src/Tgstation.Server.Client/Components/InstanceClient.cs b/src/Tgstation.Server.Client/Components/InstanceClient.cs index 2bf76eca71..f5f31907ec 100644 --- a/src/Tgstation.Server.Client/Components/InstanceClient.cs +++ b/src/Tgstation.Server.Client/Components/InstanceClient.cs @@ -33,19 +33,16 @@ namespace Tgstation.Server.Client.Components /// public IJobsClient Jobs { get; } - /// - /// The for the - /// - readonly IApiClient apiClient; - /// /// Construct a /// - /// The value of + /// The used to construct component clients. /// The value of public InstanceClient(IApiClient apiClient, Instance instance) { - this.apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient)); + if (apiClient == null) + throw new ArgumentNullException(nameof(apiClient)); + Metadata = instance ?? throw new ArgumentNullException(nameof(instance)); Byond = new ByondClient(apiClient, instance); diff --git a/src/Tgstation.Server.Host/Components/Events/EventType.cs b/src/Tgstation.Server.Host/Components/Events/EventType.cs index 419fb9ec3b..9b648382c9 100644 --- a/src/Tgstation.Server.Host/Components/Events/EventType.cs +++ b/src/Tgstation.Server.Host/Components/Events/EventType.cs @@ -106,5 +106,11 @@ /// [EventScript("WatchdogDetach")] WatchdogDetach, + + /// + /// Before the watchdog launches. No parameters. + /// + [EventScript("WatchdogLaunch")] + WatchdogLaunch } } diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index 15876d33cd..cad5c38fa7 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -250,6 +250,7 @@ namespace Tgstation.Server.Host.Components sessionControllerFactory, gameIoManager, diagnosticsIOManager, + eventConsumer, metadata.CloneMetadata(), metadata.DreamDaemonSettings); eventConsumer.SetWatchdog(watchdog); diff --git a/src/Tgstation.Server.Host/Components/Repository/IRepositoryManager.cs b/src/Tgstation.Server.Host/Components/Repository/IRepositoryManager.cs index 2f9c59df69..06bf3fb467 100644 --- a/src/Tgstation.Server.Host/Components/Repository/IRepositoryManager.cs +++ b/src/Tgstation.Server.Host/Components/Repository/IRepositoryManager.cs @@ -15,7 +15,7 @@ namespace Tgstation.Server.Host.Components.Repository bool InUse { get; } /// - /// If a operation is in progress + /// If a operation is in progress. /// bool CloneInProgress { get; } @@ -34,9 +34,17 @@ namespace Tgstation.Server.Host.Components.Repository /// The username to clone from /// The password to clone from /// A function to report 0-100 progress of the clone + /// If submodules should be recusively cloned and initialized. /// The for the operation /// The newly cloned , if one already exists - Task CloneRepository(Uri url, string initialBranch, string username, string password, Action progressReporter, CancellationToken cancellationToken); + Task CloneRepository( + Uri url, + string initialBranch, + string username, + string password, + Action progressReporter, + bool recurseSubmodules, + CancellationToken cancellationToken); /// /// Delete the current repository diff --git a/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs b/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs index 4c5fefc314..55c19fdc82 100644 --- a/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs +++ b/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs @@ -89,7 +89,14 @@ namespace Tgstation.Server.Host.Components.Repository } /// - public async Task CloneRepository(Uri url, string initialBranch, string username, string password, Action progressReporter, CancellationToken cancellationToken) + public async Task CloneRepository( + Uri url, + string initialBranch, + string username, + string password, + Action progressReporter, + bool recurseSubmodules, + CancellationToken cancellationToken) { if (url == null) throw new ArgumentNullException(nameof(url)); @@ -122,7 +129,7 @@ namespace Tgstation.Server.Host.Components.Repository progressReporter((int)percentage); return !cancellationToken.IsCancellationRequested; }, - RecurseSubmodules = true, + RecurseSubmodules = recurseSubmodules, OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested, RepositoryOperationStarting = (a) => !cancellationToken.IsCancellationRequested, BranchName = initialBranch, diff --git a/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs index 73d7a74d22..63cdb20557 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Components.Deployment; +using Tgstation.Server.Host.Components.Events; using Tgstation.Server.Host.Components.Session; using Tgstation.Server.Host.Core; using Tgstation.Server.Host.Database; @@ -51,6 +52,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The for the . /// The for the . /// The for the . + /// The for the . /// The for the . /// The for the . /// The for the . @@ -65,6 +67,7 @@ namespace Tgstation.Server.Host.Components.Watchdog IServerControl serverControl, IAsyncDelayer asyncDelayer, IIOManager diagnosticsIOManager, + IEventConsumer eventConsumer, ILogger logger, DreamDaemonLaunchParameters initialLaunchParameters, Api.Models.Instance instance, @@ -79,6 +82,7 @@ namespace Tgstation.Server.Host.Components.Watchdog serverControl, asyncDelayer, diagnosticsIOManager, + eventConsumer, logger, initialLaunchParameters, instance, diff --git a/src/Tgstation.Server.Host/Components/Watchdog/ExperimentalWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/ExperimentalWatchdog.cs index 0addffef36..95d2fc12b2 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/ExperimentalWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/ExperimentalWatchdog.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Components.Deployment; +using Tgstation.Server.Host.Components.Events; using Tgstation.Server.Host.Components.Session; using Tgstation.Server.Host.Core; using Tgstation.Server.Host.Database; @@ -62,6 +63,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The for the . /// The for the . /// The for the . + /// The for the . /// The for the . /// The for the . /// The for the . @@ -76,6 +78,7 @@ namespace Tgstation.Server.Host.Components.Watchdog IServerControl serverControl, IAsyncDelayer asyncDelayer, IIOManager diagnosticsIOManager, + IEventConsumer eventConsumer, ILogger logger, DreamDaemonLaunchParameters initialLaunchParameters, Api.Models.Instance instance, bool autoStart) @@ -89,6 +92,7 @@ namespace Tgstation.Server.Host.Components.Watchdog serverControl, asyncDelayer, diagnosticsIOManager, + eventConsumer, logger, initialLaunchParameters, instance, diff --git a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdogFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdogFactory.cs index 63f30ec362..134628b460 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdogFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdogFactory.cs @@ -1,6 +1,7 @@ using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Components.Deployment; +using Tgstation.Server.Host.Components.Events; using Tgstation.Server.Host.Components.Session; using Tgstation.Server.Host.IO; @@ -20,6 +21,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The for the /// The pointing to the Game directory for the . /// The pointing to the Diagnostics directory for the . + /// The for the . /// The for the /// The initial for the /// A new @@ -30,6 +32,7 @@ namespace Tgstation.Server.Host.Components.Watchdog ISessionControllerFactory sessionControllerFactory, IIOManager gameIOManager, IIOManager diagnosticsIOManager, + IEventConsumer eventConsumer, Api.Models.Instance instance, DreamDaemonSettings settings); } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs index d8e6139afa..f95eb8111b 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs @@ -120,6 +120,11 @@ namespace Tgstation.Server.Host.Components.Watchdog /// readonly IIOManager diagnosticsIOManager; + /// + /// The that is not the + /// + readonly IEventConsumer eventConsumer; + /// /// used for . /// @@ -177,6 +182,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The to populate with /// The value of . /// The value of . + /// The value of . /// The value of /// The initial value of . May be modified /// The value of @@ -191,6 +197,7 @@ namespace Tgstation.Server.Host.Components.Watchdog IServerControl serverControl, IAsyncDelayer asyncDelayer, IIOManager diagnosticsIOManager, + IEventConsumer eventConsumer, ILogger logger, DreamDaemonLaunchParameters initialLaunchParameters, Api.Models.Instance instance, @@ -204,6 +211,7 @@ namespace Tgstation.Server.Host.Components.Watchdog this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); AsyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer)); this.diagnosticsIOManager = diagnosticsIOManager ?? throw new ArgumentNullException(nameof(diagnosticsIOManager)); + this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer)); Logger = logger ?? throw new ArgumentNullException(nameof(logger)); ActiveLaunchParameters = initialLaunchParameters ?? throw new ArgumentNullException(nameof(initialLaunchParameters)); this.instance = instance ?? throw new ArgumentNullException(nameof(instance)); @@ -255,7 +263,7 @@ namespace Tgstation.Server.Host.Components.Watchdog return; if (!graceful) { - var eventTask = HandleEvent(releaseServers ? EventType.WatchdogDetach : EventType.WatchdogShutdown, null, cancellationToken); + var eventTask = eventConsumer.HandleEvent(releaseServers ? EventType.WatchdogDetach : EventType.WatchdogShutdown, null, cancellationToken); var chatTask = announce ? Chat.SendWatchdogMessage("Shutting down...", false, cancellationToken) : Task.CompletedTask; @@ -353,13 +361,19 @@ namespace Tgstation.Server.Host.Components.Watchdog throw new JobException(ErrorCode.WatchdogCompileJobCorrupted); // this is necessary, the monitor could be in it's sleep loop trying to restart, if so cancel THAT monitor and start our own with blackjack and hookers - Task chatTask; + Task announceTask; if (startMonitor && await StopMonitor().ConfigureAwait(false)) - chatTask = Chat.SendWatchdogMessage("Automatic retry sequence cancelled by manual launch. Restarting...", false, cancellationToken); + announceTask = Chat.SendWatchdogMessage("Automatic retry sequence cancelled by manual launch. Restarting...", false, cancellationToken); else if (announce) - chatTask = Chat.SendWatchdogMessage(reattachInfo == null ? "Launching..." : "Reattaching...", false, cancellationToken); // simple announce + { + announceTask = Chat.SendWatchdogMessage(reattachInfo == null ? "Launching..." : "Reattaching...", false, cancellationToken); // simple announce + if (reattachInfo == null) + announceTask = Task.WhenAll( + eventConsumer.HandleEvent(EventType.WatchdogLaunch, Enumerable.Empty(), cancellationToken), + announceTask); + } else - chatTask = Task.CompletedTask; // no announce + announceTask = Task.CompletedTask; // no announce // since neither server is running, this is safe to do LastLaunchParameters = ActiveLaunchParameters; @@ -369,11 +383,11 @@ namespace Tgstation.Server.Host.Components.Watchdog var recursiveCallToHappen = false; try { - await InitControllers(() => recursiveCallToHappen = true, chatTask, reattachInfo, cancellationToken).ConfigureAwait(false); + await InitControllers(() => recursiveCallToHappen = true, announceTask, reattachInfo, cancellationToken).ConfigureAwait(false); if (recursiveCallToHappen) return; - await chatTask.ConfigureAwait(false); + await announceTask.ConfigureAwait(false); Logger.LogInformation("Launched servers successfully"); Running = true; @@ -389,14 +403,14 @@ namespace Tgstation.Server.Host.Components.Watchdog // don't try to send chat tasks or warning logs if were suppressing exceptions or cancelled if (!recursiveCallToHappen && !cancellationToken.IsCancellationRequested) { - var originalChatTask = chatTask; + var originalChatTask = announceTask; async Task ChainChatTaskWithErrorMessage() { await originalChatTask.ConfigureAwait(false); await Chat.SendWatchdogMessage("Startup failed!", false, cancellationToken).ConfigureAwait(false); } - chatTask = ChainChatTaskWithErrorMessage(); + announceTask = ChainChatTaskWithErrorMessage(); Logger.LogWarning("Failed to start watchdog: {0}", e.ToString()); } @@ -407,9 +421,12 @@ namespace Tgstation.Server.Host.Components.Watchdog // finish the chat task that's in flight try { - await chatTask.ConfigureAwait(false); + await announceTask.ConfigureAwait(false); + } + catch (OperationCanceledException) + { + Logger.LogTrace("Announcement task canceled!"); } - catch (OperationCanceledException) { } } } @@ -751,8 +768,9 @@ namespace Tgstation.Server.Host.Components.Watchdog } /// - public async Task HandleEvent(EventType eventType, IEnumerable parameters, CancellationToken cancellationToken) + async Task IEventConsumer.HandleEvent(EventType eventType, IEnumerable parameters, CancellationToken cancellationToken) { + // Method explicitly implemented to prevent accidental calls when this.eventConsumer should be used. var activeServer = GetActiveController(); // Server may have ended diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs index b7fcaaa3bd..abbe180c96 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs @@ -4,6 +4,7 @@ using System; using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Components.Deployment; +using Tgstation.Server.Host.Components.Events; using Tgstation.Server.Host.Components.Session; using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Core; @@ -79,6 +80,7 @@ namespace Tgstation.Server.Host.Components.Watchdog ISessionControllerFactory sessionControllerFactory, IIOManager gameIOManager, IIOManager diagnosticsIOManager, + IEventConsumer eventConsumer, Api.Models.Instance instance, DreamDaemonSettings settings) { @@ -93,6 +95,7 @@ namespace Tgstation.Server.Host.Components.Watchdog ServerControl, AsyncDelayer, diagnosticsIOManager, + eventConsumer, LoggerFactory.CreateLogger(), settings, instance, @@ -105,6 +108,7 @@ namespace Tgstation.Server.Host.Components.Watchdog sessionControllerFactory, gameIOManager, diagnosticsIOManager, + eventConsumer, instance, settings); } @@ -118,6 +122,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The for the /// The pointing to the Game directory for the . /// The pointing to the Diagnostics directory for the . + /// The for the . /// The for the /// The initial for the /// A new @@ -128,6 +133,7 @@ namespace Tgstation.Server.Host.Components.Watchdog ISessionControllerFactory sessionControllerFactory, IIOManager gameIOManager, IIOManager diagnosticsIOManager, + IEventConsumer eventConsumer, Api.Models.Instance instance, DreamDaemonSettings settings) => new BasicWatchdog( @@ -140,6 +146,7 @@ namespace Tgstation.Server.Host.Components.Watchdog ServerControl, AsyncDelayer, diagnosticsIOManager, + eventConsumer, LoggerFactory.CreateLogger(), settings, instance, diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdog.cs index 206995886c..aa59fc3d35 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdog.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Components.Deployment; +using Tgstation.Server.Host.Components.Events; using Tgstation.Server.Host.Components.Session; using Tgstation.Server.Host.Core; using Tgstation.Server.Host.Database; @@ -55,6 +56,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The for the . /// The for the . /// The for the . + /// The for the . /// The value of . /// The value of . /// The for the . @@ -71,6 +73,7 @@ namespace Tgstation.Server.Host.Components.Watchdog IServerControl serverControl, IAsyncDelayer asyncDelayer, IIOManager diagnosticsIOManager, + IEventConsumer eventConsumer, IIOManager gameIOManager, ISymlinkFactory symlinkFactory, ILogger logger, @@ -86,6 +89,7 @@ namespace Tgstation.Server.Host.Components.Watchdog serverControl, asyncDelayer, diagnosticsIOManager, + eventConsumer, logger, initialLaunchParameters, instance, diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdogFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdogFactory.cs index d8c4314280..f1f23e22f6 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdogFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdogFactory.cs @@ -4,6 +4,7 @@ using System; using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Components.Deployment; +using Tgstation.Server.Host.Components.Events; using Tgstation.Server.Host.Components.Session; using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Core; @@ -60,6 +61,7 @@ namespace Tgstation.Server.Host.Components.Watchdog ISessionControllerFactory sessionControllerFactory, IIOManager gameIOManager, IIOManager diagnosticsIOManager, + IEventConsumer eventConsumer, Api.Models.Instance instance, DreamDaemonSettings settings) => new WindowsWatchdog( @@ -72,6 +74,7 @@ namespace Tgstation.Server.Host.Components.Watchdog ServerControl, AsyncDelayer, diagnosticsIOManager, + eventConsumer, gameIOManager, symlinkFactory, LoggerFactory.CreateLogger(), diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index d6536518e3..0094cb8764 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -204,7 +204,15 @@ namespace Tgstation.Server.Host.Controllers var api = currentModel.ToApi(); await jobManager.RegisterOperation(job, async (paramJob, databaseContextFactory, progressReporter, ct) => { - using var repos = await repoManager.CloneRepository(new Uri(origin), cloneBranch, currentModel.AccessUser, currentModel.AccessToken, progressReporter, ct).ConfigureAwait(false); + using var repos = await repoManager.CloneRepository( + new Uri(origin), + cloneBranch, + currentModel.AccessUser, + currentModel.AccessToken, + progressReporter, + model.RecurseSubmodules ?? true, + ct) + .ConfigureAwait(false); if (repos == null) throw new JobException(ErrorCode.RepoExists); var instance = new Models.Instance