From 45a44987769e92581e8734875f335e5d1da7387f Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 9 Oct 2023 13:03:00 -0400 Subject: [PATCH] WIP OpenDream Support --- src/DMAPI/tgs.dm | 11 +- src/Tgstation.Server.Api/Models/EngineType.cs | 18 ++ .../Models/Internal/ByondVersion.cs | 109 ++++++++++++ .../Request/ByondVersionDeleteRequest.cs | 9 +- .../Models/Request/ByondVersionRequest.cs | 15 +- .../Models/Response/ByondResponse.cs | 15 +- .../Models/Response/CompileJobResponse.cs | 13 +- .../Rights/ByondRights.cs | 26 ++- .../Components/Byond/ByondExecutableLock.cs | 28 --- .../Components/Byond/ByondInstallation.cs | 126 +++++++++++-- .../Components/Byond/ByondInstallerBase.cs | 19 +- .../Components/Byond/ByondManager.cs | 166 +++++++++++------- .../Components/Byond/EngineExecutableLock.cs | 46 +++++ .../Components/Byond/IByondExecutableLock.cs | 2 +- .../Components/Byond/IByondInstallation.cs | 35 ---- .../Components/Byond/IByondInstaller.cs | 31 ++-- .../Components/Byond/IByondManager.cs | 44 +++-- .../Components/Byond/IEngineInstallation.cs | 58 ++++++ .../Components/Byond/OpenDreamInstallation.cs | 60 +++++++ .../Components/Byond/PosixByondInstaller.cs | 17 +- .../Components/Byond/WindowsByondInstaller.cs | 23 +-- .../Components/Chat/ChatManager.cs | 2 +- .../Components/Chat/Commands/ByondCommand.cs | 48 +++-- .../Components/Chat/IChatManager.cs | 4 +- .../Chat/Providers/DiscordProvider.cs | 23 ++- .../Components/Chat/Providers/IProvider.cs | 7 +- .../Components/Chat/Providers/IrcProvider.cs | 7 +- .../Components/Chat/Providers/Provider.cs | 7 +- .../Components/Deployment/DmbFactory.cs | 10 +- .../Components/Deployment/DmbProvider.cs | 17 +- .../Components/Deployment/DreamMaker.cs | 12 +- .../Components/Deployment/IDmbProvider.cs | 9 +- .../Deployment/SwappableDmbProvider.cs | 7 +- .../Deployment/TemporaryDmbProvider.cs | 11 +- .../Session/SessionControllerFactory.cs | 106 +++-------- .../Controllers/ByondController.cs | 110 +++++++----- .../Models/CompileJob.cs | 12 +- .../Rights/TestRights.cs | 2 +- .../TestApiClient.cs | 22 ++- .../Byond/TestPosixByondInstaller.cs | 20 ++- .../Security/TestAuthenticationContext.cs | 4 +- .../CachingFileDownloader.cs | 17 +- .../Live/DummyChatProvider.cs | 11 +- .../Live/Instance/ByondTest.cs | 118 +++++++++---- .../Live/Instance/InstanceTest.cs | 18 +- .../Live/Instance/WatchdogTest.cs | 32 ++-- .../Live/TestLiveServer.cs | 21 ++- tests/Tgstation.Server.Tests/TestDatabase.cs | 2 +- tests/Tgstation.Server.Tests/TestVersions.cs | 44 ++++- 49 files changed, 1080 insertions(+), 494 deletions(-) create mode 100644 src/Tgstation.Server.Api/Models/EngineType.cs create mode 100644 src/Tgstation.Server.Api/Models/Internal/ByondVersion.cs delete mode 100644 src/Tgstation.Server.Host/Components/Byond/ByondExecutableLock.cs create mode 100644 src/Tgstation.Server.Host/Components/Byond/EngineExecutableLock.cs delete mode 100644 src/Tgstation.Server.Host/Components/Byond/IByondInstallation.cs create mode 100644 src/Tgstation.Server.Host/Components/Byond/IEngineInstallation.cs create mode 100644 src/Tgstation.Server.Host/Components/Byond/OpenDreamInstallation.cs diff --git a/src/DMAPI/tgs.dm b/src/DMAPI/tgs.dm index 6187a67825a..6d35cf6593e 100644 --- a/src/DMAPI/tgs.dm +++ b/src/DMAPI/tgs.dm @@ -1,6 +1,6 @@ // tgstation-server DMAPI -#define TGS_DMAPI_VERSION "6.5.3" +#define TGS_DMAPI_VERSION "6.6.0" // All functions and datums outside this document are subject to change with any version and should not be relied on. @@ -73,11 +73,11 @@ #define TGS_EVENT_REPO_MERGE_PULL_REQUEST 3 /// Before the repository makes a sychronize operation. Parameters: Absolute repostiory path. #define TGS_EVENT_REPO_PRE_SYNCHRONIZE 4 -/// Before a BYOND install operation begins. Parameters: [/datum/tgs_version] of the installing BYOND. +/// Before a BYOND install operation begins. Parameters: [/datum/tgs_version] of the installing BYOND, engine type of the installing BYOND. #define TGS_EVENT_BYOND_INSTALL_START 5 /// When a BYOND install operation fails. Parameters: Error message #define TGS_EVENT_BYOND_INSTALL_FAIL 6 -/// When the active BYOND version changes. Parameters: (Nullable) [/datum/tgs_version] of the current BYOND, [/datum/tgs_version] of the new BYOND. +/// When the active BYOND version changes. Parameters: (Nullable) [/datum/tgs_version] of the current BYOND, [/datum/tgs_version] of the new BYOND, engine type of the current BYOND, engine type of the new BYOND. #define TGS_EVENT_BYOND_ACTIVE_VERSION_CHANGE 7 /// When the compiler starts running. Parameters: Game directory path, origin commit SHA. #define TGS_EVENT_COMPILE_START 8 @@ -129,6 +129,11 @@ /// DreamDaemon Ultrasafe security level. #define TGS_SECURITY_ULTRASAFE 2 +/// The Build Your Own Net Dream engine. +#define TGS_ENGINE_TYPE_BYOND 0 +/// The OpenDream engine. +#define TGS_ENGINE_TYPE_OPENDREAM 1 + //REQUIRED HOOKS /** diff --git a/src/Tgstation.Server.Api/Models/EngineType.cs b/src/Tgstation.Server.Api/Models/EngineType.cs new file mode 100644 index 00000000000..67410c1d470 --- /dev/null +++ b/src/Tgstation.Server.Api/Models/EngineType.cs @@ -0,0 +1,18 @@ +namespace Tgstation.Server.Api.Models +{ + /// + /// The type of engine the codebase is using. + /// + public enum EngineType + { + /// + /// Build your own net dream, + /// + Byond, + + /// + /// The OpenDream BYOND reimplementation. + /// + OpenDream, + } +} diff --git a/src/Tgstation.Server.Api/Models/Internal/ByondVersion.cs b/src/Tgstation.Server.Api/Models/Internal/ByondVersion.cs new file mode 100644 index 00000000000..8526d159b62 --- /dev/null +++ b/src/Tgstation.Server.Api/Models/Internal/ByondVersion.cs @@ -0,0 +1,109 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Linq; + +namespace Tgstation.Server.Api.Models.Internal +{ + /// + /// Information about a Byond installation. + /// + public class ByondVersion : IEquatable + { + /// + /// The . + /// + [RequestOptions(FieldPresence.Required)] + public EngineType? Engine { get; set; } + + /// + /// The of the engine. + /// + [ResponseOptions] + public Version? Version { get; set; } + + /// + /// The git committish of the . On response, this will always be a commit SHA. + /// + [ResponseOptions] + [StringLength(Limits.MaximumCommitShaLength)] + public string? SourceCommittish { get; set; } + + /// + /// Parses a stringified . + /// + /// The input . + /// The output . + /// if parsing was successful, otherwise. + public static bool TryParse(string input, out ByondVersion? byondVersion) + { + if (input == null) + throw new ArgumentNullException(nameof(input)); + + var splits = input.Split(new char[] { '-' }, StringSplitOptions.RemoveEmptyEntries); + byondVersion = null; + + if (splits.Length > 2) + return false; + + EngineType engine; + if (splits.Length > 1) + { + if (!Enum.TryParse(splits[0], out engine)) + return false; + } + else + engine = EngineType.Byond; + + if (!Version.TryParse(splits.Last(), out var version)) + return false; + + byondVersion = new ByondVersion + { + Engine = engine, + Version = version, + }; + return true; + } + + /// + /// Initializes a new instance of the class. + /// + public ByondVersion() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The to copy. + public ByondVersion(ByondVersion other) + { + if (other == null) + throw new ArgumentNullException(nameof(other)); + + Version = other.Version; + Engine = other.Engine; + SourceCommittish = other.SourceCommittish; + } + + /// + public bool Equals(ByondVersion other) + { + // https://github.com/dotnet/roslyn-analyzers/issues/2875 +#pragma warning disable CA1062 // Validate arguments of public methods + return other!.Version == Version + && other.Engine == Engine; +#pragma warning restore CA1062 // Validate arguments of public methods + } + + /// + public override bool Equals(object obj) + => obj is ByondVersion other && Equals(other); + + /// + public override string ToString() => $"{(Engine != EngineType.Byond ? $"{Engine}-" : String.Empty)}{Version}"; // BYOND isn't display for backwards compatibility. SourceCommittish is not included + + /// + public override int GetHashCode() => ToString().GetHashCode(); + } +} diff --git a/src/Tgstation.Server.Api/Models/Request/ByondVersionDeleteRequest.cs b/src/Tgstation.Server.Api/Models/Request/ByondVersionDeleteRequest.cs index aaf8585c9be..ad5eac45ccd 100644 --- a/src/Tgstation.Server.Api/Models/Request/ByondVersionDeleteRequest.cs +++ b/src/Tgstation.Server.Api/Models/Request/ByondVersionDeleteRequest.cs @@ -1,16 +1,13 @@ using System; +using Tgstation.Server.Api.Models.Internal; + namespace Tgstation.Server.Api.Models.Request { /// /// A request to delete a specific . /// - public class ByondVersionDeleteRequest + public class ByondVersionDeleteRequest : ByondVersion { - /// - /// The BYOND version to install. - /// - [RequestOptions(FieldPresence.Required)] - public Version? Version { get; set; } } } diff --git a/src/Tgstation.Server.Api/Models/Request/ByondVersionRequest.cs b/src/Tgstation.Server.Api/Models/Request/ByondVersionRequest.cs index b86eea074a9..a04dfd0d7e1 100644 --- a/src/Tgstation.Server.Api/Models/Request/ByondVersionRequest.cs +++ b/src/Tgstation.Server.Api/Models/Request/ByondVersionRequest.cs @@ -1,13 +1,22 @@ -namespace Tgstation.Server.Api.Models.Request +using System; + +using Tgstation.Server.Api.Models.Internal; + +namespace Tgstation.Server.Api.Models.Request { /// - /// A request to install a BYOND . + /// A request to install a . /// - public sealed class ByondVersionRequest : ByondVersionDeleteRequest + public sealed class ByondVersionRequest : ByondVersion { /// /// If a custom BYOND version is to be uploaded. /// public bool? UploadCustomZip { get; set; } + + /// + /// The remote repository for non- s. By default, this is the original git repository of the target . + /// + public Uri? SourceRepository { get; set; } } } diff --git a/src/Tgstation.Server.Api/Models/Response/ByondResponse.cs b/src/Tgstation.Server.Api/Models/Response/ByondResponse.cs index 64b01090d53..92d524728da 100644 --- a/src/Tgstation.Server.Api/Models/Response/ByondResponse.cs +++ b/src/Tgstation.Server.Api/Models/Response/ByondResponse.cs @@ -1,16 +1,19 @@ -using System; +using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Api.Models.Response { /// - /// Represents an installed BYOND . + /// Represents an installed . /// - public sealed class ByondResponse + public sealed class ByondResponse : ByondVersion { /// - /// The installed BYOND . BYOND itself only considers the and numbers. TGS uses the number to represent installed custom versions. + /// Initializes a new instance of the class. /// - [ResponseOptions] - public Version? Version { get; set; } + /// The to copy. + public ByondResponse(ByondVersion byondVersion) + : base(byondVersion) + { + } } } diff --git a/src/Tgstation.Server.Api/Models/Response/CompileJobResponse.cs b/src/Tgstation.Server.Api/Models/Response/CompileJobResponse.cs index 60444d9c3c9..a349710f077 100644 --- a/src/Tgstation.Server.Api/Models/Response/CompileJobResponse.cs +++ b/src/Tgstation.Server.Api/Models/Response/CompileJobResponse.cs @@ -1,9 +1,11 @@ using System; +using Tgstation.Server.Api.Models.Internal; + namespace Tgstation.Server.Api.Models.Response { /// - public sealed class CompileJobResponse : Internal.CompileJob + public sealed class CompileJobResponse : CompileJob { /// /// The relating to this job. @@ -16,9 +18,14 @@ public sealed class CompileJobResponse : Internal.CompileJob public RevisionInformation? RevisionInformation { get; set; } /// - /// The the was made with. + /// The the was made with. + /// + public ByondVersion? ByondVersion { get; set; } + + /// + /// The the was made with. /// - public Version? ByondVersion { get; set; } + public EngineType? Engine { get; set; } /// /// The origin of the repository the compile job was built from. diff --git a/src/Tgstation.Server.Api/Rights/ByondRights.cs b/src/Tgstation.Server.Api/Rights/ByondRights.cs index 288d8ca3b8d..6e20a62b3b5 100644 --- a/src/Tgstation.Server.Api/Rights/ByondRights.cs +++ b/src/Tgstation.Server.Api/Rights/ByondRights.cs @@ -14,33 +14,43 @@ public enum ByondRights : ulong None = 0, /// - /// User may view the active installed BYOND version. + /// User may view the active installed engine versions. /// ReadActive = 1 << 0, /// - /// User may list all installed BYOND versions. + /// User may list all installed engine versions. /// ListInstalled = 1 << 1, /// - /// User may install official BYOND versions or change the active BYOND version. + /// User may install official versions or change the active version. /// - InstallOfficialOrChangeActiveVersion = 1 << 2, + InstallOfficialOrChangeActiveByondVersion = 1 << 2, /// - /// User may cancel BYOND installation job. + /// User may cancel an engine installation job. /// CancelInstall = 1 << 3, /// - /// User may upload and activate custom BYOND builds. + /// User may upload and activate custom builds. /// - InstallCustomVersion = 1 << 4, + InstallCustomByondVersion = 1 << 4, /// - /// User may delete non-active BYOND builds. + /// User may delete non-active engine builds. /// DeleteInstall = 1 << 5, + + /// + /// User may install official versions or change the active version. + /// + InstallOfficialOrChangeActiveOpenDreamVersion = 1 << 6, + + /// + /// User may activate custom builds via zip upload or custom git committish. + /// + InstallCustomOpenDreamVersion = 1 << 7, } } diff --git a/src/Tgstation.Server.Host/Components/Byond/ByondExecutableLock.cs b/src/Tgstation.Server.Host/Components/Byond/ByondExecutableLock.cs deleted file mode 100644 index 334b3469ea1..00000000000 --- a/src/Tgstation.Server.Host/Components/Byond/ByondExecutableLock.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; - -using Tgstation.Server.Host.Utils; - -namespace Tgstation.Server.Host.Components.Byond -{ - /// - sealed class ByondExecutableLock : ReferenceCounter, IByondExecutableLock - { - /// - public Version Version => Instance.Version; - - /// - public string DreamDaemonPath => Instance.DreamDaemonPath; - - /// - public string DreamMakerPath => Instance.DreamMakerPath; - - /// - public bool SupportsCli => Instance.SupportsCli; - - /// - public bool SupportsMapThreads => Instance.SupportsMapThreads; - - /// - public void DoNotDeleteThisSession() => DangerousDropReference(); - } -} diff --git a/src/Tgstation.Server.Host/Components/Byond/ByondInstallation.cs b/src/Tgstation.Server.Host/Components/Byond/ByondInstallation.cs index 8554190d0f6..2663f430d5e 100644 --- a/src/Tgstation.Server.Host/Components/Byond/ByondInstallation.cs +++ b/src/Tgstation.Server.Host/Components/Byond/ByondInstallation.cs @@ -1,54 +1,144 @@ using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; using System.Threading.Tasks; +using System.Web; + +using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Host.Components.Deployment; namespace Tgstation.Server.Host.Components.Byond { - /// - sealed class ByondInstallation : IByondInstallation + /// + /// Implementation of for . + /// + sealed class ByondInstallation : IEngineInstallation { /// - public Version Version { get; } + public ByondVersion Version { get; } + + /// + public string ServerExePath { get; } /// - public string DreamDaemonPath { get; } + public string CompilerExePath { get; } /// - public string DreamMakerPath { get; } + public bool PromptsForNetworkAccess { get; } /// - public bool SupportsCli { get; } + public bool HasStandardOutput { get; } /// - public bool SupportsMapThreads { get; } + public Task InstallationTask { get; } /// - /// The that completes when the BYOND version finished installing. + /// If map threads are supported by the . /// - public Task InstallationTask { get; } + readonly bool supportsMapThreads; + + /// + /// Change a given into the appropriate DreamDaemon command line word. + /// + /// The level to change. + /// A representation of the command line parameter. + static string SecurityWord(DreamDaemonSecurity securityLevel) + { + return securityLevel switch + { + DreamDaemonSecurity.Safe => "safe", + DreamDaemonSecurity.Trusted => "trusted", + DreamDaemonSecurity.Ultrasafe => "ultrasafe", + _ => throw new ArgumentOutOfRangeException(nameof(securityLevel), securityLevel, String.Format(CultureInfo.InvariantCulture, "Bad DreamDaemon security level: {0}", securityLevel)), + }; + } + + /// + /// Change a given into the appropriate DreamDaemon command line word. + /// + /// The level to change. + /// A representation of the command line parameter. + static string VisibilityWord(DreamDaemonVisibility visibility) + { + return visibility switch + { + DreamDaemonVisibility.Public => "public", + DreamDaemonVisibility.Private => "private", + DreamDaemonVisibility.Invisible => "invisible", + _ => throw new ArgumentOutOfRangeException(nameof(visibility), visibility, String.Format(CultureInfo.InvariantCulture, "Bad DreamDaemon visibility level: {0}", visibility)), + }; + } /// /// Initializes a new instance of the class. /// /// The value of . /// The value of . - /// The value of . - /// The value of . - /// The value of . - /// The value of . + /// The value of . + /// The value of . + /// If a CLI application is being used. + /// The value of . public ByondInstallation( Task installationTask, - Version version, + ByondVersion version, string dreamDaemonPath, string dreamMakerPath, bool supportsCli, bool supportsMapThreads) { InstallationTask = installationTask ?? throw new ArgumentNullException(nameof(installationTask)); + ArgumentNullException.ThrowIfNull(version); + + if (version.Engine.Value != EngineType.Byond) + throw new ArgumentException($"Invalid EngineType: {version.Engine.Value}", nameof(version)); + Version = version ?? throw new ArgumentNullException(nameof(version)); - DreamDaemonPath = dreamDaemonPath ?? throw new ArgumentNullException(nameof(dreamDaemonPath)); - DreamMakerPath = dreamMakerPath ?? throw new ArgumentNullException(nameof(dreamMakerPath)); - SupportsCli = supportsCli; - SupportsMapThreads = supportsMapThreads; + ServerExePath = dreamDaemonPath ?? throw new ArgumentNullException(nameof(dreamDaemonPath)); + CompilerExePath = dreamMakerPath ?? throw new ArgumentNullException(nameof(dreamMakerPath)); + HasStandardOutput = supportsCli; + PromptsForNetworkAccess = !supportsCli; + this.supportsMapThreads = supportsMapThreads; + } + + /// + public string FormatServerArguments( + IDmbProvider dmbProvider, + IReadOnlyDictionary parameters, + DreamDaemonLaunchParameters launchParameters, + string logFilePath) + { + ArgumentNullException.ThrowIfNull(dmbProvider); + ArgumentNullException.ThrowIfNull(parameters); + ArgumentNullException.ThrowIfNull(launchParameters); + + var parametersString = String.Join('&', parameters.Select(kvp => $"{HttpUtility.UrlEncode(kvp.Key)}={HttpUtility.UrlEncode(kvp.Value)}")); + + if (!String.IsNullOrEmpty(launchParameters.AdditionalParameters)) + parametersString = $"{parametersString}&{launchParameters.AdditionalParameters}"; + + var arguments = String.Format( + CultureInfo.InvariantCulture, + "{0} -port {1} -ports 1-65535 {2}-close -verbose -{3} -{4}{5}{6}{7} -params \"{8}\"", + dmbProvider.DmbName, + launchParameters.Port.Value, + launchParameters.AllowWebClient.Value + ? "-webclient " + : String.Empty, + SecurityWord(launchParameters.SecurityLevel.Value), + VisibilityWord(launchParameters.Visibility.Value), + logFilePath != null + ? $" -logself -log {logFilePath}" + : String.Empty, // DD doesn't output anything if -logself is set??? + launchParameters.StartProfiler.Value + ? " -profile" + : String.Empty, + supportsMapThreads && launchParameters.MapThreads.Value != 0 + ? $" -map-threads {launchParameters.MapThreads.Value}" + : String.Empty, + parametersString); + return arguments; } } } diff --git a/src/Tgstation.Server.Host/Components/Byond/ByondInstallerBase.cs b/src/Tgstation.Server.Host/Components/Byond/ByondInstallerBase.cs index 2478acdc1f7..8260eafffaa 100644 --- a/src/Tgstation.Server.Host/Components/Byond/ByondInstallerBase.cs +++ b/src/Tgstation.Server.Host/Components/Byond/ByondInstallerBase.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.Logging; +using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host.Components.Byond @@ -24,10 +25,10 @@ abstract class ByondInstallerBase : IByondInstaller public static Version MapThreadsVersion => new (515, 1609); /// - public abstract string DreamMakerName { get; } + public abstract string CompilerName { get; } /// - public abstract string PathToUserByondFolder { get; } + public abstract string PathToUserFolder { get; } /// /// Gets the URL formatter string for downloading a byond version of {0:Major} {1:Minor}. @@ -63,7 +64,7 @@ protected ByondInstallerBase(IIOManager ioManager, IFileDownloader fileDownloade } /// - public abstract string GetDreamDaemonName(Version version, out bool supportsCli, out bool supportsMapThreads); + public abstract string GetDreamDaemonName(ByondVersion version, out bool supportsCli, out bool supportsMapThreads); /// public async Task CleanCache(CancellationToken cancellationToken) @@ -73,7 +74,7 @@ public async Task CleanCache(CancellationToken cancellationToken) Logger.LogDebug("Cleaning BYOND cache..."); await IOManager.DeleteDirectory( IOManager.ConcatPath( - PathToUserByondFolder, + PathToUserFolder, CacheDirectoryName), cancellationToken); } @@ -84,18 +85,18 @@ await IOManager.DeleteDirectory( } /// - public abstract ValueTask InstallByond(Version version, string path, CancellationToken cancellationToken); + public abstract ValueTask InstallByond(ByondVersion version, string path, CancellationToken cancellationToken); /// - public abstract ValueTask UpgradeInstallation(Version version, string path, CancellationToken cancellationToken); + public abstract ValueTask UpgradeInstallation(ByondVersion version, string path, CancellationToken cancellationToken); /// - public async ValueTask DownloadVersion(Version version, CancellationToken cancellationToken) + public async ValueTask DownloadVersion(ByondVersion version, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(version); - Logger.LogTrace("Downloading BYOND version {major}.{minor}...", version.Major, version.Minor); - var url = String.Format(CultureInfo.InvariantCulture, ByondRevisionsUrlTemplate, version.Major, version.Minor); + Logger.LogTrace("Downloading BYOND version {major}.{minor}...", version.Version.Major, version.Version.Minor); + var url = String.Format(CultureInfo.InvariantCulture, ByondRevisionsUrlTemplate, version.Version.Major, version.Version.Minor); await using var download = fileDownloader.DownloadFile(new Uri(url), null); await using var buffer = new BufferedFileStreamProvider( diff --git a/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs b/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs index 73fadec9874..10b53ef340c 100644 --- a/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs +++ b/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs @@ -10,6 +10,7 @@ using Microsoft.Extensions.Logging; using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Common.Extensions; using Tgstation.Server.Host.Components.Events; using Tgstation.Server.Host.IO; @@ -47,10 +48,10 @@ sealed class ByondManager : IByondManager const string ActiveVersionFileName = "ActiveVersion.txt"; /// - public Version ActiveVersion { get; private set; } + public ByondVersion ActiveVersion { get; private set; } /// - public IReadOnlyList InstalledVersions + public IReadOnlyList InstalledVersions { get { @@ -87,7 +88,7 @@ public IReadOnlyList InstalledVersions /// /// Map of byond s to s that complete when they are installed. /// - readonly Dictionary> installedVersions; + readonly Dictionary> installedVersions; /// /// The for changing or deleting the active BYOND version. @@ -103,11 +104,14 @@ public IReadOnlyList InstalledVersions /// Validates a given parameter. /// /// The to validate. - static void CheckVersionParameter(Version version) + static void CheckVersionParameter(ByondVersion version) { ArgumentNullException.ThrowIfNull(version); - if (version.Build == 0) + if (!version.Engine.HasValue) + throw new InvalidOperationException("version.Engine cannot be null!"); + + if (version.Version.Build == 0) throw new InvalidOperationException("version.Build cannot be 0!"); } @@ -125,7 +129,7 @@ public ByondManager(IIOManager ioManager, IByondInstaller byondInstaller, IEvent this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); - installedVersions = new Dictionary>(); + installedVersions = new Dictionary>(); changeDeleteSemaphore = new SemaphoreSlim(1); activeVersionChanged = new TaskCompletionSource(); } @@ -136,7 +140,7 @@ public ByondManager(IIOManager ioManager, IByondInstaller byondInstaller, IEvent /// public async ValueTask ChangeVersion( JobProgressReporter progressReporter, - Version version, + ByondVersion version, Stream customVersionStream, bool allowInstallation, CancellationToken cancellationToken) @@ -154,7 +158,10 @@ public async ValueTask ChangeVersion( cancellationToken); // We reparse the version because it could be changed after a custom install. - version = installLock.Version; + version = new ByondVersion(version) + { + Version = installLock.Version.Version, + }; var stringVersion = version.ToString(); await ioManager.WriteAllBytes(ActiveVersionFileName, Encoding.UTF8.GetBytes(stringVersion), cancellationToken); @@ -177,7 +184,7 @@ await eventConsumer.HandleEvent( } /// - public async ValueTask UseExecutables(Version requiredVersion, string trustDmbFullPath, CancellationToken cancellationToken) + public async ValueTask UseExecutables(ByondVersion requiredVersion, string trustDmbFullPath, CancellationToken cancellationToken) { logger.LogTrace( "Acquiring lock on BYOND version {version}...", @@ -205,7 +212,7 @@ public async ValueTask UseExecutables(Version requiredVers } /// - public async ValueTask DeleteVersion(JobProgressReporter progressReporter, Version version, CancellationToken cancellationToken) + public async ValueTask DeleteVersion(JobProgressReporter progressReporter, ByondVersion version, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(progressReporter); @@ -213,15 +220,18 @@ public async ValueTask DeleteVersion(JobProgressReporter progressReporter, Versi logger.LogTrace("DeleteVersion {version}", version); - if (version == ActiveVersion) + if (version.Equals(ActiveVersion)) throw new JobException(ErrorCode.ByondCannotDeleteActiveVersion); - ReferenceCountingContainer container; + ReferenceCountingContainer container; lock (installedVersions) if (!installedVersions.TryGetValue(version, out container)) - return; // already "deleted" + { + logger.LogTrace("Version {version} already deleted.", version); + return; + } - logger.LogInformation("Deleting BYOND version {version}...", version); + logger.LogInformation("Deleting version {version}...", version); progressReporter.StageName = "Waiting for version to not be in use..."; while (true) { @@ -238,7 +248,7 @@ await Task.WhenAny( .WaitAsync(cancellationToken); if (containerTask.IsCompleted) - logger.LogTrace("All BYOND locks for {version} are gone", version); + logger.LogTrace("All locks for version {version} are gone", version); using (await SemaphoreSlimContext.Lock(changeDeleteSemaphore, cancellationToken)) { @@ -252,7 +262,7 @@ await Task.WhenAny( proceed = container.OnZeroReferences.IsCompleted; if (proceed) if (!installedVersions.TryGetValue(version, out var newerContainer)) - logger.LogWarning("Unable to remove BYOND installation {version} from list! Is there a duplicate job running?", version); + logger.LogWarning("Unable to remove engine installation {version} from list! Is there a duplicate job running?", version); else { if (container != newerContainer) @@ -291,6 +301,13 @@ await ioManager.DeleteFile( } } + /// + public async ValueTask EnsureEngineSource(Uri source, EngineType engine, CancellationToken cancellationToken) + { + await Task.Yield(); + throw new NotImplementedException(); + } + /// public async Task StartAsync(CancellationToken cancellationToken) { @@ -302,7 +319,7 @@ async ValueTask GetActiveVersion() var activeVersionBytesTask = GetActiveVersion(); - var byondDir = byondInstaller.PathToUserByondFolder; + var byondDir = byondInstaller.PathToUserFolder; if (byondDir != null) using (await SemaphoreSlimContext.Lock(UserFilesSemaphore, cancellationToken)) { @@ -328,7 +345,7 @@ await ioManager.DeleteFile( await ioManager.CreateDirectory(DefaultIOManager.CurrentDirectory, cancellationToken); var directories = await ioManager.GetDirectories(DefaultIOManager.CurrentDirectory, cancellationToken); - var installedVersionPaths = new Dictionary(); + var installedVersionPaths = new Dictionary(); async ValueTask ReadVersion(string path) { @@ -342,7 +359,7 @@ async ValueTask ReadVersion(string path) var bytes = await ioManager.ReadAllBytes(versionFile, cancellationToken); var text = Encoding.UTF8.GetString(bytes); - if (!Version.TryParse(text, out var version)) + if (!ByondVersion.TryParse(text, out var version)) { logger.LogWarning("Cleaning path with unparsable version file: {versionPath}", ioManager.ResolvePath(path)); await ioManager.DeleteDirectory(path, cancellationToken); // cleanup @@ -383,10 +400,10 @@ await ValueTaskExtensions.WhenAll( { var activeVersionString = Encoding.UTF8.GetString(activeVersionBytes); - Version activeVersion; + ByondVersion activeVersion; bool hasRequestedActiveVersion; lock (installedVersions) - hasRequestedActiveVersion = Version.TryParse(activeVersionString, out activeVersion) + hasRequestedActiveVersion = ByondVersion.TryParse(activeVersionString, out activeVersion) && installedVersions.ContainsKey(activeVersion); if (hasRequestedActiveVersion) @@ -403,46 +420,47 @@ await ValueTaskExtensions.WhenAll( public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; /// - /// Ensures a BYOND is installed if it isn't already. + /// Ensures a BYOND is installed if it isn't already. /// /// The optional for the operation. - /// The BYOND to install. + /// The to install. /// Custom zip file to use. Will cause a number to be added. /// If this BYOND version is required as part of a locking operation. - /// If an installation should be performed if the is not installed. If and an installation is required an will be thrown. + /// If an installation should be performed if the is not installed. If and an installation is required an will be thrown. /// The for the operation. - /// A resulting in the . - async ValueTask AssertAndLockVersion( + /// A resulting in the . + async ValueTask AssertAndLockVersion( JobProgressReporter progressReporter, - Version version, + ByondVersion byondVersion, Stream customVersionStream, bool neededForLock, bool allowInstallation, CancellationToken cancellationToken) { var ourTcs = new TaskCompletionSource(); - ByondInstallation installation; - ByondExecutableLock installLock; + IEngineInstallation installation; + EngineExecutableLock installLock; bool installedOrInstalling; + var byondEngine = byondVersion.Engine.Value == EngineType.Byond; lock (installedVersions) { - if (customVersionStream != null) + if (customVersionStream != null && byondEngine) { var customInstallationNumber = 1; do { - version = new Version(version.Major, version.Minor, customInstallationNumber++); + byondVersion.Version = new Version(byondVersion.Version.Major, byondVersion.Version.Minor, customInstallationNumber++); } - while (installedVersions.ContainsKey(version)); + while (installedVersions.ContainsKey(byondVersion)); } - installedOrInstalling = installedVersions.TryGetValue(version, out var installationContainer); + installedOrInstalling = installedVersions.TryGetValue(byondVersion, out var installationContainer); if (!installedOrInstalling) { if (!allowInstallation) - throw new InvalidOperationException($"BYOND version {version} not installed!"); + throw new InvalidOperationException($"BYOND version {byondVersion} not installed!"); - installationContainer = AddInstallationContainer(version, ourTcs.Task); + installationContainer = AddInstallationContainer(byondVersion, ourTcs.Task); } installation = installationContainer.Instance; @@ -457,7 +475,7 @@ async ValueTask AssertAndLockVersion( progressReporter.StageName = "Waiting for existing installation job..."; if (neededForLock && !installation.InstallationTask.IsCompleted) - logger.LogWarning("The required BYOND version ({version}) is not readily available! We will have to wait for it to install.", version); + logger.LogWarning("The required BYOND version ({version}) is not readily available! We will have to wait for it to install.", byondVersion); await installation.InstallationTask.WaitAsync(cancellationToken); return installLock; @@ -467,24 +485,24 @@ async ValueTask AssertAndLockVersion( try { if (customVersionStream != null) - logger.LogInformation("Installing custom BYOND version as {version}...", version); + logger.LogInformation("Installing custom BYOND version as {version}...", byondVersion); else if (neededForLock) { - if (version.Build > 0) + if (byondEngine && byondVersion.Version.Build > 0) throw new JobException(ErrorCode.ByondNonExistentCustomVersion); - logger.LogWarning("The required BYOND version ({version}) is not readily available! We will have to install it.", version); + logger.LogWarning("The required BYOND version ({version}) is not readily available! We will have to install it.", byondVersion); } else - logger.LogDebug("Requested BYOND version {version} not currently installed. Doing so now...", version); + logger.LogDebug("Requested BYOND version {version} not currently installed. Doing so now...", byondVersion); if (progressReporter != null) progressReporter.StageName = "Running event"; - var versionString = version.ToString(); + var versionString = byondVersion.ToString(); await eventConsumer.HandleEvent(EventType.ByondInstallStart, new List { versionString }, false, cancellationToken); - await InstallVersionFiles(progressReporter, version, customVersionStream, cancellationToken); + await InstallVersionFiles(progressReporter, byondVersion, customVersionStream, cancellationToken); ourTcs.SetResult(); } @@ -494,7 +512,7 @@ async ValueTask AssertAndLockVersion( await eventConsumer.HandleEvent(EventType.ByondInstallFail, new List { ex.Message }, false, cancellationToken); lock (installedVersions) - installedVersions.Remove(version); + installedVersions.Remove(byondVersion); ourTcs.SetException(ex); throw; @@ -513,11 +531,11 @@ async ValueTask AssertAndLockVersion( /// Installs the files for a given BYOND . /// /// The optional for the operation. - /// The BYOND being installed with the number set if appropriate. + /// The being installed with the number set if appropriate. /// Custom zip file to use. Will cause a number to be added. /// The for the operation. /// A representing the running operation. - async ValueTask InstallVersionFiles(JobProgressReporter progressReporter, Version version, Stream customVersionStream, CancellationToken cancellationToken) + async ValueTask InstallVersionFiles(JobProgressReporter progressReporter, ByondVersion version, Stream customVersionStream, CancellationToken cancellationToken) { var installFullPath = ioManager.ResolvePath(version.ToString()); async ValueTask DirectoryCleanup() @@ -585,32 +603,46 @@ await ioManager.WriteAllBytes( } /// - /// Create and add a new to . + /// Create and add a new to . /// /// The being added. /// The representing the installation process. - /// The new containing the new . - ReferenceCountingContainer AddInstallationContainer(Version version, Task installationTask) + /// The new containing the new . + ReferenceCountingContainer AddInstallationContainer(ByondVersion version, Task installationTask) { var binPathForVersion = ioManager.ConcatPath(version.ToString(), BinPath); - var installation = new ByondInstallation( - installationTask, - version, - ioManager.ResolvePath( - ioManager.ConcatPath( - binPathForVersion, - byondInstaller.GetDreamDaemonName( - version, - out var supportsCli, - out var supportsMapThreads))), - ioManager.ResolvePath( - ioManager.ConcatPath( - binPathForVersion, - byondInstaller.DreamMakerName)), - supportsCli, - supportsMapThreads); - - var installationContainer = new ReferenceCountingContainer(installation); + IEngineInstallation installation; + + switch (version.Engine.Value) + { + case EngineType.Byond: + installation = new ByondInstallation( + installationTask, + version, + ioManager.ResolvePath( + ioManager.ConcatPath( + binPathForVersion, + byondInstaller.GetDreamDaemonName( + version, + out var supportsCli, + out var supportsMapThreads))), + ioManager.ResolvePath( + ioManager.ConcatPath( + binPathForVersion, + byondInstaller.CompilerName)), + supportsCli, + supportsMapThreads); + break; + case EngineType.OpenDream: + installation = new OpenDreamInstallation( + installationTask, + version); + break; + default: + throw new InvalidOperationException($"Invalid EngineType: {version.Engine.Value}"); + } + + var installationContainer = new ReferenceCountingContainer(installation); lock (installedVersions) installedVersions.Add(version, installationContainer); @@ -626,7 +658,7 @@ ReferenceCountingContainer AddInstallati /// A representing the running operation. async ValueTask TrustDmbPath(string fullDmbPath, CancellationToken cancellationToken) { - var byondDir = byondInstaller.PathToUserByondFolder; + var byondDir = byondInstaller.PathToUserFolder; if (String.IsNullOrWhiteSpace(byondDir)) { logger.LogTrace("No relevant user BYOND directory to install a \"{fileName}\" in", TrustedDmbFileName); diff --git a/src/Tgstation.Server.Host/Components/Byond/EngineExecutableLock.cs b/src/Tgstation.Server.Host/Components/Byond/EngineExecutableLock.cs new file mode 100644 index 00000000000..245d4420c36 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Byond/EngineExecutableLock.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Host.Components.Deployment; +using Tgstation.Server.Host.Utils; + +namespace Tgstation.Server.Host.Components.Byond +{ + /// + sealed class EngineExecutableLock : ReferenceCounter, IByondExecutableLock + { + /// + public ByondVersion Version => Instance.Version; + + /// + public string ServerExePath => Instance.ServerExePath; + + /// + public string CompilerExePath => Instance.CompilerExePath; + + /// + public bool HasStandardOutput => Instance.HasStandardOutput; + + /// + public bool PromptsForNetworkAccess => Instance.PromptsForNetworkAccess; + + /// + public Task InstallationTask => Instance.InstallationTask; + + /// + public void DoNotDeleteThisSession() => DangerousDropReference(); + + /// + public string FormatServerArguments( + IDmbProvider dmbProvider, + IReadOnlyDictionary parameters, + DreamDaemonLaunchParameters launchParameters, + string logFilePath) + => Instance.FormatServerArguments( + dmbProvider, + parameters, + launchParameters, + logFilePath); + } +} diff --git a/src/Tgstation.Server.Host/Components/Byond/IByondExecutableLock.cs b/src/Tgstation.Server.Host/Components/Byond/IByondExecutableLock.cs index a320109dce0..f82562ad102 100644 --- a/src/Tgstation.Server.Host/Components/Byond/IByondExecutableLock.cs +++ b/src/Tgstation.Server.Host/Components/Byond/IByondExecutableLock.cs @@ -5,7 +5,7 @@ namespace Tgstation.Server.Host.Components.Byond /// /// Represents usage of the two primary BYOND server executables. /// - public interface IByondExecutableLock : IByondInstallation, IDisposable + public interface IByondExecutableLock : IEngineInstallation, IDisposable { /// /// Call if, during a detach, this version should not be deleted. diff --git a/src/Tgstation.Server.Host/Components/Byond/IByondInstallation.cs b/src/Tgstation.Server.Host/Components/Byond/IByondInstallation.cs deleted file mode 100644 index 52a4e694b71..00000000000 --- a/src/Tgstation.Server.Host/Components/Byond/IByondInstallation.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; - -namespace Tgstation.Server.Host.Components.Byond -{ - /// - /// Represents a BYOND installation. - /// - public interface IByondInstallation - { - /// - /// The of the . - /// - Version Version { get; } - - /// - /// The full path to the DreamDaemon executable. - /// - string DreamDaemonPath { get; } - - /// - /// The full path to the dm/DreamMaker executable. - /// - string DreamMakerPath { get; } - - /// - /// If supports being run as a command-line application. - /// - bool SupportsCli { get; } - - /// - /// If supports the -map-threads parameter. - /// - bool SupportsMapThreads { get; } - } -} diff --git a/src/Tgstation.Server.Host/Components/Byond/IByondInstaller.cs b/src/Tgstation.Server.Host/Components/Byond/IByondInstaller.cs index 9c353378303..b1a89bd48ce 100644 --- a/src/Tgstation.Server.Host/Components/Byond/IByondInstaller.cs +++ b/src/Tgstation.Server.Host/Components/Byond/IByondInstaller.cs @@ -1,8 +1,9 @@ -using System; -using System.IO; +using System.IO; using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Api.Models.Internal; + namespace Tgstation.Server.Host.Components.Byond { /// @@ -11,52 +12,52 @@ namespace Tgstation.Server.Host.Components.Byond interface IByondInstaller { /// - /// Get the file name of the DreamMaker executable. + /// Get the file name of the compiler executable. /// - string DreamMakerName { get; } + string CompilerName { get; } /// - /// The path to the BYOND folder for the user. + /// The path to the folder for the user's data. /// - string PathToUserByondFolder { get; } + string PathToUserFolder { get; } /// /// Get the file name of the DreamDaemon executable. /// - /// The of BYOND to select the executable name for. + /// The of BYOND to select the executable name for. /// Whether or not the returned path supports being run as a command-line application. /// Whether or not the returned path supports the '-map-threads' parameter. /// The file name of the DreamDaemon executable. - string GetDreamDaemonName(Version version, out bool supportsCli, out bool supportsMapThreads); + string GetDreamDaemonName(ByondVersion version, out bool supportsCli, out bool supportsMapThreads); /// /// Download a given BYOND . /// - /// The of BYOND to download. + /// The of BYOND to download. /// The for the operation. /// A resulting in a of the zipfile. - ValueTask DownloadVersion(Version version, CancellationToken cancellationToken); + ValueTask DownloadVersion(ByondVersion version, CancellationToken cancellationToken); /// /// Does actions necessary to get an extracted BYOND installation working. /// - /// The of BYOND being installed. + /// The being installed. /// The path to the BYOND installation. /// The for the operation. /// A representing the running operation. - ValueTask InstallByond(Version version, string path, CancellationToken cancellationToken); + ValueTask InstallByond(ByondVersion version, string path, CancellationToken cancellationToken); /// /// Does actions necessary to get upgrade a BYOND version installed by a previous version of TGS. /// - /// The of BYOND being installed. + /// The being installed. /// The path to the BYOND installation. /// The for the operation. /// A representing the running operation. - ValueTask UpgradeInstallation(Version version, string path, CancellationToken cancellationToken); + ValueTask UpgradeInstallation(ByondVersion version, string path, CancellationToken cancellationToken); /// - /// Attempts to cleans the BYOND cache folder for the system. + /// Attempts to cleans the engine's cache folder for the system. /// /// The for the operation. /// A representing the running operation. diff --git a/src/Tgstation.Server.Host/Components/Byond/IByondManager.cs b/src/Tgstation.Server.Host/Components/Byond/IByondManager.cs index 092034b1c0b..88aaa2005f3 100644 --- a/src/Tgstation.Server.Host/Components/Byond/IByondManager.cs +++ b/src/Tgstation.Server.Host/Components/Byond/IByondManager.cs @@ -4,6 +4,8 @@ using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Jobs; namespace Tgstation.Server.Host.Components.Byond @@ -11,48 +13,62 @@ namespace Tgstation.Server.Host.Components.Byond /// /// For managing the BYOND installation. /// - /// When passing in s, ensure they are BYOND format versions unless referring to a custom version. This means should NEVER be 0. + /// When passing in s for , ensure they are BYOND format versions unless referring to a custom version. This means should NEVER be 0. public interface IByondManager : IComponentService, IDisposable { /// - /// The currently active BYOND version. + /// The currently active . /// - Version ActiveVersion { get; } + ByondVersion ActiveVersion { get; } /// - /// The installed BYOND versions. + /// The installed s. /// - IReadOnlyList InstalledVersions { get; } + IReadOnlyList InstalledVersions { get; } /// - /// Change the active BYOND version. + /// Ensure that the given is registered for the given . + /// + /// The source of the . + /// The . + /// The for the operation. + /// A representing the running operation. + ValueTask EnsureEngineSource(Uri source, EngineType engine, CancellationToken cancellationToken); + + /// + /// Change the active . /// /// The optional for the operation. - /// The new . + /// The new . /// Optional of a custom BYOND version zip file. /// If an installation should be performed if the is not installed. If and an installation is required an will be thrown. /// The for the operation. /// A representing the running operation. - ValueTask ChangeVersion(JobProgressReporter progressReporter, Version version, Stream customVersionStream, bool allowInstallation, CancellationToken cancellationToken); + ValueTask ChangeVersion( + JobProgressReporter progressReporter, + ByondVersion version, + Stream customVersionStream, + bool allowInstallation, + CancellationToken cancellationToken); /// - /// Deletes a given BYOND version from the disk. + /// Deletes a given from the disk. /// /// The for the operation. - /// The to delete. + /// The to delete. /// The for the operation. - /// A representing the running operation. - ValueTask DeleteVersion(JobProgressReporter progressReporter, Version version, CancellationToken cancellationToken); + /// A representing the running operation. + ValueTask DeleteVersion(JobProgressReporter progressReporter, ByondVersion version, CancellationToken cancellationToken); /// /// Lock the current installation's location and return a . /// - /// The BYOND required. + /// The required. /// The optional full path to .dmb to trust while using the executables. /// The for the operation. /// A resulting in the requested . ValueTask UseExecutables( - Version requiredVersion, + ByondVersion requiredVersion, string trustDmbFullPath, CancellationToken cancellationToken); } diff --git a/src/Tgstation.Server.Host/Components/Byond/IEngineInstallation.cs b/src/Tgstation.Server.Host/Components/Byond/IEngineInstallation.cs new file mode 100644 index 00000000000..77751920db6 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Byond/IEngineInstallation.cs @@ -0,0 +1,58 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Host.Components.Deployment; + +namespace Tgstation.Server.Host.Components.Byond +{ + /// + /// Represents a BYOND installation. + /// + public interface IEngineInstallation + { + /// + /// The of the . + /// + ByondVersion Version { get; } + + /// + /// The full path to the game server executable. + /// + string ServerExePath { get; } + + /// + /// The full path to the dm/DreamMaker executable. + /// + string CompilerExePath { get; } + + /// + /// If supports being run as a command-line application and outputs log information to be captured. + /// + bool HasStandardOutput { get; } + + /// + /// If may create network prompts. + /// + bool PromptsForNetworkAccess { get; } + + /// + /// The that completes when the BYOND version finished installing. + /// + Task InstallationTask { get; } + + /// + /// Return the command line arguments for launching with given . + /// + /// The . + /// The map of parameter s as a . Should NOT include the of . + /// The . + /// The path to the log file, if any. + /// The formatted arguments . + string FormatServerArguments( + IDmbProvider dmbProvider, + IReadOnlyDictionary parameters, + DreamDaemonLaunchParameters launchParameters, + string logFilePath); + } +} diff --git a/src/Tgstation.Server.Host/Components/Byond/OpenDreamInstallation.cs b/src/Tgstation.Server.Host/Components/Byond/OpenDreamInstallation.cs new file mode 100644 index 00000000000..da55c0618b4 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Byond/OpenDreamInstallation.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Host.Components.Deployment; + +namespace Tgstation.Server.Host.Components.Byond +{ + /// + /// Implementation of for . + /// + sealed class OpenDreamInstallation : IEngineInstallation + { + /// + public ByondVersion Version { get; } + + /// + public string ServerExePath { get; } + + /// + public string CompilerExePath { get; } + + /// + public bool PromptsForNetworkAccess => false; + + /// + public bool HasStandardOutput => true; + + /// + public Task InstallationTask { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The value of . + /// The value of . + public OpenDreamInstallation( + Task installationTask, + ByondVersion version) + { + InstallationTask = installationTask ?? throw new ArgumentNullException(nameof(installationTask)); + ArgumentNullException.ThrowIfNull(version); + + if (version.Engine.Value != EngineType.OpenDream) + throw new ArgumentException($"Invalid EngineType: {version.Engine.Value}", nameof(version)); + + Version = version ?? throw new ArgumentNullException(nameof(version)); + + throw new NotImplementedException(); + } + + /// + public string FormatServerArguments(IDmbProvider dmbProvider, IReadOnlyDictionary parameters, DreamDaemonLaunchParameters launchParameters, string logFilePath) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Tgstation.Server.Host/Components/Byond/PosixByondInstaller.cs b/src/Tgstation.Server.Host/Components/Byond/PosixByondInstaller.cs index 3657e015d47..7fed2498ec4 100644 --- a/src/Tgstation.Server.Host/Components/Byond/PosixByondInstaller.cs +++ b/src/Tgstation.Server.Host/Components/Byond/PosixByondInstaller.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.Logging; +using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Common.Extensions; using Tgstation.Server.Host.IO; @@ -32,10 +33,10 @@ sealed class PosixByondInstaller : ByondInstallerBase const string ShellScriptExtension = ".sh"; /// - public override string DreamMakerName => DreamMakerExecutableName + ShellScriptExtension; + public override string CompilerName => DreamMakerExecutableName + ShellScriptExtension; /// - public override string PathToUserByondFolder { get; } + public override string PathToUserFolder { get; } /// protected override string ByondRevisionsUrlTemplate => "https://www.byond.com/download/build/{0}/{0}.{1}_byond_linux.zip"; @@ -61,7 +62,7 @@ public PosixByondInstaller( { this.postWriteHandler = postWriteHandler ?? throw new ArgumentNullException(nameof(postWriteHandler)); - PathToUserByondFolder = IOManager.ResolvePath( + PathToUserFolder = IOManager.ResolvePath( IOManager.ConcatPath( Environment.GetFolderPath( Environment.SpecialFolder.UserProfile), @@ -69,17 +70,17 @@ public PosixByondInstaller( } /// - public override string GetDreamDaemonName(Version version, out bool supportsCli, out bool supportsMapThreads) + public override string GetDreamDaemonName(ByondVersion version, out bool supportsCli, out bool supportsMapThreads) { ArgumentNullException.ThrowIfNull(version); supportsCli = true; - supportsMapThreads = version >= MapThreadsVersion; + supportsMapThreads = version.Version >= MapThreadsVersion; return DreamDaemonExecutableName + ShellScriptExtension; } /// - public override ValueTask InstallByond(Version version, string path, CancellationToken cancellationToken) + public override ValueTask InstallByond(ByondVersion version, string path, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(version); ArgumentNullException.ThrowIfNull(path); @@ -105,7 +106,7 @@ async ValueTask WriteAndMakeExecutable(string pathToScript, string script) dreamDaemonScript); var dmTask = WriteAndMakeExecutable( - IOManager.ConcatPath(basePath, DreamMakerName), + IOManager.ConcatPath(basePath, CompilerName), dreamMakerScript); var task = ValueTaskExtensions.WhenAll( @@ -119,7 +120,7 @@ async ValueTask WriteAndMakeExecutable(string pathToScript, string script) } /// - public override ValueTask UpgradeInstallation(Version version, string path, CancellationToken cancellationToken) + public override ValueTask UpgradeInstallation(ByondVersion version, string path, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(version); ArgumentNullException.ThrowIfNull(path); diff --git a/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs b/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs index 8080abc12ff..0b177584a4a 100644 --- a/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs +++ b/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.Options; using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Common.Extensions; using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.IO; @@ -53,10 +54,10 @@ sealed class WindowsByondInstaller : ByondInstallerBase, IDisposable public static Version DDExeVersion => new (515, 1598); /// - public override string DreamMakerName => "dm.exe"; + public override string CompilerName => "dm.exe"; /// - public override string PathToUserByondFolder { get; } + public override string PathToUserFolder { get; } /// protected override string ByondRevisionsUrlTemplate => "https://www.byond.com/download/build/{0}/{0}.{1}_byond.zip"; @@ -102,9 +103,9 @@ public WindowsByondInstaller( var documentsDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); if (String.IsNullOrWhiteSpace(documentsDirectory)) - PathToUserByondFolder = null; // happens with the service account + PathToUserFolder = null; // happens with the service account else - PathToUserByondFolder = IOManager.ResolvePath(IOManager.ConcatPath(documentsDirectory, "BYOND")); + PathToUserFolder = IOManager.ResolvePath(IOManager.ConcatPath(documentsDirectory, "BYOND")); semaphore = new SemaphoreSlim(1); installedDirectX = false; @@ -114,17 +115,17 @@ public WindowsByondInstaller( public void Dispose() => semaphore.Dispose(); /// - public override string GetDreamDaemonName(Version version, out bool supportsCli, out bool supportsMapThreads) + public override string GetDreamDaemonName(ByondVersion version, out bool supportsCli, out bool supportsMapThreads) { ArgumentNullException.ThrowIfNull(version); - supportsCli = version >= DDExeVersion; - supportsMapThreads = version >= MapThreadsVersion; + supportsCli = version.Version >= DDExeVersion; + supportsMapThreads = version.Version >= MapThreadsVersion; return supportsCli ? "dd.exe" : "dreamdaemon.exe"; } /// - public override ValueTask InstallByond(Version version, string path, CancellationToken cancellationToken) + public override ValueTask InstallByond(ByondVersion version, string path, CancellationToken cancellationToken) { var tasks = new List(3) { @@ -139,7 +140,7 @@ public override ValueTask InstallByond(Version version, string path, Cancellatio } /// - public override async ValueTask UpgradeInstallation(Version version, string path, CancellationToken cancellationToken) + public override async ValueTask UpgradeInstallation(ByondVersion version, string path, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(version); ArgumentNullException.ThrowIfNull(path); @@ -147,7 +148,7 @@ public override async ValueTask UpgradeInstallation(Version version, string path if (generalConfiguration.SkipAddingByondFirewallException) return; - if (version < DDExeVersion) + if (version.Version < DDExeVersion) return; if (await IOManager.FileExists(IOManager.ConcatPath(path, TgsFirewalledDDFile), cancellationToken)) @@ -227,7 +228,7 @@ async ValueTask InstallDirectX(string path, CancellationToken cancellationToken) /// The path to the BYOND installation. /// The for the operation. /// A representing the running operation. - async ValueTask AddDreamDaemonToFirewall(Version version, string path, CancellationToken cancellationToken) + async ValueTask AddDreamDaemonToFirewall(ByondVersion version, string path, CancellationToken cancellationToken) { var dreamDaemonName = GetDreamDaemonName(version, out var usesDDExe, out var _); diff --git a/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs b/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs index ecb0fa6498c..6bc1ddc015e 100644 --- a/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs +++ b/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs @@ -359,7 +359,7 @@ public void QueueWatchdogMessage(string message) /// public Func> QueueDeploymentMessage( Models.RevisionInformation revisionInformation, - Version byondVersion, + ByondVersion byondVersion, DateTimeOffset? estimatedCompletionTime, string gitHubOwner, string gitHubRepo, diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/ByondCommand.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/ByondCommand.cs index f0c87ef4df3..8f7b680293d 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Commands/ByondCommand.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/ByondCommand.cs @@ -1,5 +1,4 @@ using System; -using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -49,20 +48,45 @@ public ByondCommand(IByondManager byondManager, IWatchdog watchdog) /// public ValueTask Invoke(string arguments, ChatUser user, CancellationToken cancellationToken) { - if (arguments.Split(' ').Any(x => x.ToUpperInvariant() == "--ACTIVE")) - return ValueTask.FromResult(new MessageContent - { - Text = byondManager.ActiveVersion == null ? "None!" : String.Format(CultureInfo.InvariantCulture, "{0}.{1}", byondManager.ActiveVersion.Major, byondManager.ActiveVersion.Minor), - }); + if (arguments.Split(' ').Any(x => x.Equals("--active", StringComparison.OrdinalIgnoreCase))) + { + string text; + if (byondManager.ActiveVersion == null) + text = "None!"; + else + switch (byondManager.ActiveVersion.Engine.Value) + { + case EngineType.OpenDream: + text = $"OpenDream: {byondManager.ActiveVersion.SourceCommittish}"; + break; + case EngineType.Byond: + text = $"BYOND {byondManager.ActiveVersion.Version.Major}.{byondManager.ActiveVersion.Version.Minor}"; + if (byondManager.ActiveVersion.Version.Build != -1) + text += " (Custom Build)"; + + break; + default: + throw new InvalidOperationException($"Invalid EngineType: {byondManager.ActiveVersion.Engine.Value}"); + } + + return ValueTask.FromResult( + new MessageContent + { + Text = text, + }); + } + if (watchdog.Status == WatchdogStatus.Offline) - return ValueTask.FromResult(new MessageContent + return ValueTask.FromResult( + new MessageContent + { + Text = "Server offline!", + }); + return ValueTask.FromResult( + new MessageContent { - Text = "Server offline!", + Text = watchdog.ActiveCompileJob?.ByondVersion ?? "None!", }); - return ValueTask.FromResult(new MessageContent - { - Text = watchdog.ActiveCompileJob?.ByondVersion ?? "None!", - }); } } } diff --git a/src/Tgstation.Server.Host/Components/Chat/IChatManager.cs b/src/Tgstation.Server.Host/Components/Chat/IChatManager.cs index 769ab7b2515..3f21930cf5a 100644 --- a/src/Tgstation.Server.Host/Components/Chat/IChatManager.cs +++ b/src/Tgstation.Server.Host/Components/Chat/IChatManager.cs @@ -61,7 +61,7 @@ public interface IChatManager : IComponentService, IAsyncDisposable /// Send the message for a deployment to configured deployment channels. /// /// The of the deployment. - /// The BYOND of the deployment. + /// The of the deployment. /// The optional the deployment is expected to be completed at. /// The repository GitHub owner, if any. /// The repository GitHub name, if any. @@ -69,7 +69,7 @@ public interface IChatManager : IComponentService, IAsyncDisposable /// A to call to update the message at the deployment's conclusion. Parameters: Error message if any, DreamMaker output if any. Returns an to call to mark the deployment as active/inactive. Parameter: If the deployment is being activated or inactivated. Func> QueueDeploymentMessage( Models.RevisionInformation revisionInformation, - Version byondVersion, + ByondVersion byondVersion, DateTimeOffset? estimatedCompletionTime, string gitHubOwner, string gitHubRepo, diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs index bba9673ecf0..492eb8e332f 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs @@ -22,6 +22,7 @@ using Remora.Results; using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Common.Extensions; using Tgstation.Server.Host.Components.Interop; using Tgstation.Server.Host.Extensions; @@ -129,25 +130,35 @@ public override string BotMention /// Create a of s for a discord update embed. /// /// The of the deployment. - /// The BYOND of the deployment. + /// The of the deployment. /// The repository GitHub owner, if any. /// The repository GitHub name, if any. /// if the local deployment commit was pushed to the remote repository. /// A new of s to use. static List BuildUpdateEmbedFields( Models.RevisionInformation revisionInformation, - Version byondVersion, + ByondVersion byondVersion, string gitHubOwner, string gitHubRepo, bool localCommitPushed) { bool gitHub = gitHubOwner != null && gitHubRepo != null; - var fields = new List + var engineField = byondVersion.Engine.Value switch { - new EmbedField( + EngineType.Byond => new EmbedField( "BYOND Version", - $"{byondVersion.Major}.{byondVersion.Minor}{(byondVersion.Build > 0 ? $".{byondVersion.Build}" : String.Empty)}", + $"{byondVersion.Version.Major}.{byondVersion.Version.Minor}{(byondVersion.Version.Build > 0 ? $".{byondVersion.Version.Build}" : String.Empty)}", true), + EngineType.OpenDream => new EmbedField( + "OpenDream Version", + $"[{byondVersion.SourceCommittish[..7]}](https://github.com/OpenDreamProject/OpenDream/commit/{revisionInformation.CommitSha})", + true), + _ => throw new InvalidOperationException($"Invaild EngineType: {byondVersion.Engine.Value}"), + }; + + var fields = new List + { + engineField, new EmbedField( "Local Commit", localCommitPushed && gitHub @@ -323,7 +334,7 @@ await ValueTaskExtensions.WhenAll( /// public override async ValueTask>>> SendUpdateMessage( Models.RevisionInformation revisionInformation, - Version byondVersion, + ByondVersion byondVersion, DateTimeOffset? estimatedCompletionTime, string gitHubOwner, string gitHubRepo, diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs index 641372c3180..489d8f42880 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs @@ -3,6 +3,7 @@ using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Components.Interop; using Tgstation.Server.Host.Models; @@ -83,7 +84,7 @@ interface IProvider : IAsyncDisposable /// Send the message for a deployment. /// /// The of the deployment. - /// The BYOND of the deployment. + /// The of the deployment. /// The optional the deployment is expected to be completed at. /// The repository GitHub owner, if any. /// The repository GitHub name, if any. @@ -92,8 +93,8 @@ interface IProvider : IAsyncDisposable /// The for the operation. /// A resulting in a to call to update the message at the deployment's conclusion. Parameters: Error message if any, DreamMaker output if any. Returns another callback which should be called to mark the deployment as active. ValueTask>>> SendUpdateMessage( - RevisionInformation revisionInformation, - Version byondVersion, + Models.RevisionInformation revisionInformation, + ByondVersion byondVersion, DateTimeOffset? estimatedCompletionTime, string gitHubOwner, string gitHubRepo, diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs index 605bad64b46..7cf73595c5b 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs @@ -12,6 +12,7 @@ using Newtonsoft.Json; using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Components.Interop; using Tgstation.Server.Host.Extensions; using Tgstation.Server.Host.IO; @@ -220,7 +221,7 @@ await Task.Factory.StartNew( /// public override async ValueTask>>> SendUpdateMessage( Models.RevisionInformation revisionInformation, - Version byondVersion, + ByondVersion byondVersion, DateTimeOffset? estimatedCompletionTime, string gitHubOwner, string gitHubRepo, @@ -271,9 +272,7 @@ await SendMessage( commitInsert, testmergeInsert, remoteCommitInsert, - byondVersion.Build > 0 - ? byondVersion.ToString() - : $"{byondVersion.Major}.{byondVersion.Minor}", + byondVersion.ToString(), estimatedCompletionTime.HasValue ? $" ETA: {estimatedCompletionTime - DateTimeOffset.UtcNow}" : String.Empty), diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/Provider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/Provider.cs index 88cd5b2e2c6..4cf04a8f32f 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/Provider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/Provider.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Logging; +using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Components.Interop; using Tgstation.Server.Host.Jobs; @@ -182,8 +183,8 @@ public Task SetReconnectInterval(uint reconnectInterval, bool connectNow) /// public abstract ValueTask>>> SendUpdateMessage( - RevisionInformation revisionInformation, - Version byondVersion, + Models.RevisionInformation revisionInformation, + ByondVersion byondVersion, DateTimeOffset? estimatedCompletionTime, string gitHubOwner, string gitHubRepo, @@ -273,7 +274,7 @@ async Task ReconnectionLoop(uint reconnectInterval, bool connectNow, Cancellatio connectNow = false; if (!Connected) { - var job = new Job + var job = new Models.Job { Description = $"Reconnect chat bot: {ChatBot.Name}", CancelRight = (ulong)ChatBotRights.WriteEnabled, diff --git a/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs b/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs index b4e333a41d5..e5e361299b8 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs @@ -245,6 +245,12 @@ await databaseContextFactory.UseContext( .ThenInclude(x => x.MergedBy) .FirstAsync(cancellationToken)); // can't wait to see that query + if (!Api.Models.Internal.ByondVersion.TryParse(compileJob.ByondVersion, out var byondVersion)) + { + logger.LogWarning("Error loading compile job, bad BYOND version: {0}", compileJob.ByondVersion); + return null; // omae wa mou shinderu + } + if (!compileJob.Job.StoppedAt.HasValue) { // This happens when we're told to load the compile job that is currently finished up @@ -262,7 +268,7 @@ void CleanupAction() CleanRegisteredCompileJob(compileJob); } - var newProvider = new DmbProvider(compileJob, ioManager, CleanupAction); + var newProvider = new DmbProvider(compileJob, byondVersion, ioManager, CleanupAction); try { const string LegacyADirectoryName = "A"; @@ -299,7 +305,7 @@ void CleanupAction() // rebuild the provider because it's using the legacy style directories // Don't dispose it logger.LogDebug("Creating legacy two folder .dmb provider targeting {aDirName} directory...", LegacyADirectoryName); - newProvider = new DmbProvider(compileJob, ioManager, CleanupAction, Path.DirectorySeparatorChar + LegacyADirectoryName); + newProvider = new DmbProvider(compileJob, byondVersion, ioManager, CleanupAction, Path.DirectorySeparatorChar + LegacyADirectoryName); } lock (jobLockCounts) diff --git a/src/Tgstation.Server.Host/Components/Deployment/DmbProvider.cs b/src/Tgstation.Server.Host/Components/Deployment/DmbProvider.cs index f3711ede4f8..554cc28cdd5 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/DmbProvider.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/DmbProvider.cs @@ -1,7 +1,7 @@ using System; +using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.IO; -using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components.Deployment { @@ -14,10 +14,11 @@ sealed class DmbProvider : IDmbProvider /// public string Directory => ioManager.ResolvePath(CompileJob.DirectoryName.ToString() + directoryAppend); - /// - /// The for the . - /// - public CompileJob CompileJob { get; } + /// + public Models.CompileJob CompileJob { get; } + + /// + public ByondVersion ByondVersion { get; } /// /// The for the . @@ -25,7 +26,7 @@ sealed class DmbProvider : IDmbProvider readonly IIOManager ioManager; /// - /// Extra path to add to the end of . + /// Extra path to add to the end of . /// readonly string directoryAppend; @@ -38,12 +39,14 @@ sealed class DmbProvider : IDmbProvider /// Initializes a new instance of the class. /// /// The value of . + /// The value of . /// The value of . /// The value of . /// The optional value of . - public DmbProvider(CompileJob compileJob, IIOManager ioManager, Action onDispose, string directoryAppend = null) + public DmbProvider(Models.CompileJob compileJob, ByondVersion byondVersion, IIOManager ioManager, Action onDispose, string directoryAppend = null) { CompileJob = compileJob ?? throw new ArgumentNullException(nameof(compileJob)); + ByondVersion = byondVersion ?? throw new ArgumentNullException(nameof(byondVersion)); this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.onDispose = onDispose ?? throw new ArgumentNullException(nameof(onDispose)); this.directoryAppend = directoryAppend ?? String.Empty; diff --git a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs index 30f6a555df5..998c851be72 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs @@ -591,7 +591,7 @@ await eventConsumer.HandleEvent( { resolvedOutputDirectory, repoOrigin.ToString(), - $"{byondLock.Version.Major}.{byondLock.Version.Minor}", + byondLock.Version.ToString(), }, true, cancellationToken); @@ -630,14 +630,14 @@ await eventConsumer.HandleEvent( { resolvedOutputDirectory, repoOrigin.ToString(), - $"{byondLock.Version.Major}.{byondLock.Version.Minor}", + byondLock.Version.ToString(), }, true, cancellationToken); // run compiler progressReporter.StageName = "Running DreamMaker"; - var exitCode = await RunDreamMaker(byondLock.DreamMakerPath, job, cancellationToken); + var exitCode = await RunDreamMaker(byondLock.CompilerExePath, job, cancellationToken); // Session takes ownership of the lock and Disposes it so save this for later var byondVersion = byondLock.Version; @@ -803,7 +803,11 @@ async ValueTask VerifyApi( job.MinimumSecurityLevel = securityLevel; // needed for the TempDmbProvider ApiValidationStatus validationStatus; - using (var provider = new TemporaryDmbProvider(ioManager.ResolvePath(job.DirectoryName.ToString()), String.Concat(job.DmeName, DmbExtension), job)) + using (var provider = new TemporaryDmbProvider( + ioManager.ResolvePath(job.DirectoryName.ToString()), + String.Concat(job.DmeName, DmbExtension), + job, + byondLock.Version)) await using (var controller = await sessionControllerFactory.LaunchNew(provider, byondLock, launchParameters, true, cancellationToken)) { var launchResult = await controller.LaunchResult.WaitAsync(cancellationToken); diff --git a/src/Tgstation.Server.Host/Components/Deployment/IDmbProvider.cs b/src/Tgstation.Server.Host/Components/Deployment/IDmbProvider.cs index efc773a9193..8f81cb7fbc1 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/IDmbProvider.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/IDmbProvider.cs @@ -1,6 +1,6 @@ using System; -using Tgstation.Server.Host.Models; +using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Host.Components.Deployment { @@ -22,7 +22,12 @@ public interface IDmbProvider : IDisposable /// /// The of the .dmb. /// - CompileJob CompileJob { get; } + Models.CompileJob CompileJob { get; } + + /// + /// The used to build the .dmb. + /// + ByondVersion ByondVersion { get; } /// /// Disposing the won't cause a cleanup of the working directory. diff --git a/src/Tgstation.Server.Host/Components/Deployment/SwappableDmbProvider.cs b/src/Tgstation.Server.Host/Components/Deployment/SwappableDmbProvider.cs index b3d8505e659..cfa69ede089 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/SwappableDmbProvider.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/SwappableDmbProvider.cs @@ -2,8 +2,8 @@ using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.IO; -using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components.Deployment { @@ -24,7 +24,10 @@ sealed class SwappableDmbProvider : IDmbProvider public string Directory => ioManager.ResolvePath(LiveGameDirectory); /// - public CompileJob CompileJob => baseProvider.CompileJob; + public Models.CompileJob CompileJob => baseProvider.CompileJob; + + /// + public ByondVersion ByondVersion => baseProvider.ByondVersion; /// /// If has been run. diff --git a/src/Tgstation.Server.Host/Components/Deployment/TemporaryDmbProvider.cs b/src/Tgstation.Server.Host/Components/Deployment/TemporaryDmbProvider.cs index 0bf982cfa38..5ecc6b96709 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/TemporaryDmbProvider.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/TemporaryDmbProvider.cs @@ -1,6 +1,6 @@ using System; -using Tgstation.Server.Host.Models; +using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Host.Components.Deployment { @@ -16,7 +16,10 @@ sealed class TemporaryDmbProvider : IDmbProvider public string Directory { get; } /// - public CompileJob CompileJob { get; } + public Models.CompileJob CompileJob { get; } + + /// + public ByondVersion ByondVersion { get; } /// /// Initializes a new instance of the class. @@ -24,11 +27,13 @@ sealed class TemporaryDmbProvider : IDmbProvider /// The value of . /// The value of . /// The value of . - public TemporaryDmbProvider(string directory, string dmb, CompileJob compileJob) + /// The value of . + public TemporaryDmbProvider(string directory, string dmb, Models.CompileJob compileJob, ByondVersion byondVersion) { DmbName = dmb ?? throw new ArgumentNullException(nameof(dmb)); Directory = directory ?? throw new ArgumentNullException(nameof(directory)); CompileJob = compileJob ?? throw new ArgumentNullException(nameof(compileJob)); + ByondVersion = byondVersion ?? throw new ArgumentNullException(nameof(byondVersion)); } /// diff --git a/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs index 3bc79939c7d..212ffd76f31 100644 --- a/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs @@ -6,8 +6,6 @@ using System.Threading; using System.Threading.Tasks; -using Byond.TopicSender; - using Microsoft.Extensions.Logging; using Tgstation.Server.Api.Models; @@ -128,38 +126,6 @@ sealed class SessionControllerFactory : ISessionControllerFactory /// readonly Api.Models.Instance instance; - /// - /// Change a given into the appropriate DreamDaemon command line word. - /// - /// The level to change. - /// A representation of the command line parameter. - static string SecurityWord(DreamDaemonSecurity securityLevel) - { - return securityLevel switch - { - DreamDaemonSecurity.Safe => "safe", - DreamDaemonSecurity.Trusted => "trusted", - DreamDaemonSecurity.Ultrasafe => "ultrasafe", - _ => throw new ArgumentOutOfRangeException(nameof(securityLevel), securityLevel, String.Format(CultureInfo.InvariantCulture, "Bad DreamDaemon security level: {0}", securityLevel)), - }; - } - - /// - /// Change a given into the appropriate DreamDaemon command line word. - /// - /// The level to change. - /// A representation of the command line parameter. - static string VisibilityWord(DreamDaemonVisibility visibility) - { - return visibility switch - { - DreamDaemonVisibility.Public => "public", - DreamDaemonVisibility.Private => "private", - DreamDaemonVisibility.Invisible => "invisible", - _ => throw new ArgumentOutOfRangeException(nameof(visibility), visibility, String.Format(CultureInfo.InvariantCulture, "Bad DreamDaemon visibility level: {0}", visibility)), - }; - } - /// /// Check if a given can be bound to. /// @@ -274,7 +240,7 @@ public async ValueTask LaunchNew( // get the byond lock var byondLock = currentByondLock ?? await byond.UseExecutables( - Version.Parse(dmbProvider.CompileJob.ByondVersion), + dmbProvider.ByondVersion, gameIOManager.ConcatPath(dmbProvider.Directory, dmbProvider.DmbName), cancellationToken); try @@ -289,7 +255,7 @@ public async ValueTask LaunchNew( string outputFilePath = null; var preserveLogFile = true; - var cliSupported = byondLock.SupportsCli; + var hasStandardOutput = byondLock.HasStandardOutput; if (launchParameters.LogOutput.Value) { var now = DateTimeOffset.UtcNow; @@ -302,7 +268,7 @@ public async ValueTask LaunchNew( logger.LogInformation("Logging DreamDaemon output to {path}...", outputFilePath); } - else if (!cliSupported) + else if (!hasStandardOutput) { outputFilePath = gameIOManager.ConcatPath(dmbProvider.Directory, $"{Guid.NewGuid()}.dd.log"); preserveLogFile = false; @@ -310,17 +276,12 @@ public async ValueTask LaunchNew( var accessIdentifier = cryptographySuite.GetSecureString(); - var byondTopicSender = topicClientFactory.CreateTopicClient( - TimeSpan.FromMilliseconds( - launchParameters.TopicRequestTimeout.Value)); - if (!apiValidate && dmbProvider.CompileJob.DMApiVersion == null) logger.LogDebug("Session will have no DMAPI support!"); // launch dd - var process = await CreateDreamDaemonProcess( + var process = await CreateGameServerProcess( dmbProvider, - byondTopicSender, byondLock, launchParameters, accessIdentifier, @@ -347,6 +308,10 @@ public async ValueTask LaunchNew( accessIdentifier, launchParameters.Port.Value); + var byondTopicSender = topicClientFactory.CreateTopicClient( + TimeSpan.FromMilliseconds( + launchParameters.TopicRequestTimeout.Value)); + var sessionController = new SessionController( reattachInformation, instance, @@ -362,7 +327,7 @@ public async ValueTask LaunchNew( () => LogDDOutput( process, outputFilePath, - cliSupported, + hasStandardOutput, preserveLogFile, CancellationToken.None), // DCT: None available launchParameters.StartupTimeout, @@ -406,7 +371,7 @@ public async ValueTask Reattach( logger.LogTrace("Begin session reattach..."); var byondTopicSender = topicClientFactory.CreateTopicClient(reattachInformation.TopicRequestTimeout); var byondLock = await byond.UseExecutables( - Version.Parse(reattachInformation.Dmb.CompileJob.ByondVersion), + reattachInformation.Dmb.ByondVersion, null, // Doesn't matter if it's trusted or not on reattach cancellationToken); @@ -423,7 +388,7 @@ public async ValueTask Reattach( try { - if (!byondLock.SupportsCli) + if (byondLock.PromptsForNetworkAccess) networkPromptReaper.RegisterProcess(process); var chatTrackingContext = chat.CreateTrackingContext(); @@ -479,10 +444,9 @@ public async ValueTask Reattach( } /// - /// Creates the DreamDaemon . + /// Creates the game server . /// /// The . - /// The to use for sanitization. /// The . /// The . /// The secure string to use for the session. @@ -490,9 +454,8 @@ public async ValueTask Reattach( /// If we are only validating the DMAPI then exiting. /// The for the operation. /// A resulting in the DreamDaemon . - async ValueTask CreateDreamDaemonProcess( + async ValueTask CreateGameServerProcess( IDmbProvider dmbProvider, - ITopicClient byondTopicSender, IByondExecutableLock byondLock, DreamDaemonLaunchParameters launchParameters, string accessIdentifier, @@ -500,39 +463,26 @@ async ValueTask CreateDreamDaemonProcess( bool apiValidate, CancellationToken cancellationToken) { - // set command line options - // more sanitization here cause it uses the same scheme - var parameters = $"{DMApiConstants.ParamApiVersion}={byondTopicSender.SanitizeString(DMApiConstants.InteropVersion.Semver().ToString())}&{byondTopicSender.SanitizeString(DMApiConstants.ParamServerPort)}={serverPortProvider.HttpApiPort}&{byondTopicSender.SanitizeString(DMApiConstants.ParamAccessIdentifier)}={byondTopicSender.SanitizeString(accessIdentifier)}"; - - if (!String.IsNullOrEmpty(launchParameters.AdditionalParameters)) - parameters = $"{parameters}&{launchParameters.AdditionalParameters}"; - // important to run on all ports to allow port changing - var arguments = String.Format( - CultureInfo.InvariantCulture, - "{0} -port {1} -ports 1-65535 {2}-close -verbose -{3} -{4}{5}{6}{7} -params \"{8}\"", - dmbProvider.DmbName, - launchParameters.Port.Value, - launchParameters.AllowWebClient.Value ? "-webclient " : String.Empty, - SecurityWord(launchParameters.SecurityLevel.Value), - VisibilityWord(launchParameters.Visibility.Value), - !byondLock.SupportsCli - ? $" -logself -log {logFilePath}" - : String.Empty, // DD doesn't output anything if -logself is set??? - launchParameters.StartProfiler.Value - ? " -profile" - : String.Empty, - byondLock.SupportsMapThreads && launchParameters.MapThreads.Value != 0 - ? $" -map-threads {launchParameters.MapThreads.Value}" - : String.Empty, - parameters); + var arguments = byondLock.FormatServerArguments( + dmbProvider, + new Dictionary + { + { DMApiConstants.ParamApiVersion, DMApiConstants.InteropVersion.Semver().ToString() }, + { DMApiConstants.ParamServerPort, serverPortProvider.HttpApiPort.ToString(CultureInfo.InvariantCulture) }, + { DMApiConstants.ParamAccessIdentifier, accessIdentifier }, + }, + launchParameters, + !byondLock.HasStandardOutput + ? logFilePath + : null); var process = processExecutor.LaunchProcess( - byondLock.DreamDaemonPath, + byondLock.ServerExePath, dmbProvider.Directory, arguments, logFilePath, - byondLock.SupportsCli, + byondLock.HasStandardOutput, true); try @@ -545,7 +495,7 @@ async ValueTask CreateDreamDaemonProcess( else if (sessionConfiguration.LowPriorityDeploymentProcesses) process.AdjustPriority(false); - if (!byondLock.SupportsCli) + if (!byondLock.HasStandardOutput) networkPromptReaper.RegisterProcess(process); // If this isnt a staging DD (From a Deployment), fire off an event diff --git a/src/Tgstation.Server.Host/Controllers/ByondController.cs b/src/Tgstation.Server.Host/Controllers/ByondController.cs index 156ff784e93..7e66e40e681 100644 --- a/src/Tgstation.Server.Host/Controllers/ByondController.cs +++ b/src/Tgstation.Server.Host/Controllers/ByondController.cs @@ -43,7 +43,7 @@ public sealed class ByondController : InstanceRequiredController /// /// The to normalize. /// The normalized . May be a reference to . - static Version NormalizeVersion(Version version) => version.Build == 0 ? new Version(version.Major, version.Minor) : version; + static Version NormalizeByondVersion(Version version) => version.Build == 0 ? new Version(version.Major, version.Minor) : version; /// /// Initializes a new instance of the class. @@ -72,7 +72,7 @@ public ByondController( } /// - /// Gets the active . + /// Gets the active . /// /// A resulting in the for the operation. /// Retrieved version information successfully. @@ -82,13 +82,12 @@ public ByondController( public ValueTask Read() => WithComponentInstance(instance => ValueTask.FromResult( - Json(new ByondResponse - { - Version = instance.ByondManager.ActiveVersion, - }))); + Json( + new ByondResponse( + instance.ByondManager.ActiveVersion)))); /// - /// Lists installed s. + /// Lists installed s. /// /// The current page. /// The page size. @@ -106,10 +105,7 @@ public ValueTask List([FromQuery] int? page, [FromQuery] int? pag instance .ByondManager .InstalledVersions - .Select(x => new ByondResponse - { - Version = x, - }) + .Select(x => new ByondResponse(x)) .AsQueryable() .OrderBy(x => x.Version))), null, @@ -126,27 +122,42 @@ public ValueTask List([FromQuery] int? page, [FromQuery] int? pag /// Switched active version successfully. /// Created to install and switch active version successfully. [HttpPost] - [TgsAuthorize(ByondRights.InstallOfficialOrChangeActiveVersion | ByondRights.InstallCustomVersion)] + [TgsAuthorize( + ByondRights.InstallOfficialOrChangeActiveByondVersion + | ByondRights.InstallCustomByondVersion + | ByondRights.InstallOfficialOrChangeActiveOpenDreamVersion + | ByondRights.InstallCustomOpenDreamVersion)] [ProducesResponseType(typeof(ByondInstallResponse), 200)] [ProducesResponseType(typeof(ByondInstallResponse), 202)] -#pragma warning disable CA1506 // TODO: Decomplexify +#pragma warning disable CA1502 // TODO: Decomplexify +#pragma warning disable CA1506 public async ValueTask Update([FromBody] ByondVersionRequest model, CancellationToken cancellationToken) #pragma warning restore CA1506 +#pragma warning restore CA1502 { ArgumentNullException.ThrowIfNull(model); var uploadingZip = model.UploadCustomZip == true; + var isByondEngine = model.Engine.Value == EngineType.Byond; - if (model.Version == null - || model.Version.Revision != -1 - || (uploadingZip && model.Version.Build > 0)) + if ((isByondEngine && (model.Version.Revision != -1 || (uploadingZip && model.Version.Build > 0) || model.SourceCommittish != null || model.SourceRepository != null)) + || (!isByondEngine && (model.Version != null || String.IsNullOrWhiteSpace(model.SourceCommittish)))) return BadRequest(new ErrorMessageResponse(ErrorCode.ModelValidationFailure)); - var version = NormalizeVersion(model.Version); + Uri sourceRepo; + if (isByondEngine) + { + model.Version = NormalizeByondVersion(model.Version); + sourceRepo = null; + } + else + { + sourceRepo = model.SourceRepository ?? new Uri("https://github.com/OpenDreamProject/OpenDream"); + } var userByondRights = AuthenticationContext.InstancePermissionSet.ByondRights.Value; - if ((!userByondRights.HasFlag(ByondRights.InstallOfficialOrChangeActiveVersion) && !uploadingZip) - || (!userByondRights.HasFlag(ByondRights.InstallCustomVersion) && uploadingZip)) + if ((!userByondRights.HasFlag(ByondRights.InstallOfficialOrChangeActiveByondVersion) && !uploadingZip) + || (!userByondRights.HasFlag(ByondRights.InstallCustomByondVersion) && uploadingZip)) return Forbid(); // remove cruff fields @@ -155,44 +166,50 @@ public async ValueTask Update([FromBody] ByondVersionRequest mode async instance => { var byondManager = instance.ByondManager; - var versionAlreadyInstalled = !uploadingZip && byondManager.InstalledVersions.Any(x => x == version); + var versionAlreadyInstalled = !uploadingZip && byondManager.InstalledVersions.Any(x => x.Equals(model)); if (versionAlreadyInstalled) { Logger.LogInformation( - "User ID {userId} changing instance ID {instanceId} BYOND version to {newByondVersion}", + "User ID {userId} changing instance ID {instanceId} {engineType} version to {newByondVersion}", AuthenticationContext.User.Id, Instance.Id, - version); + model.Engine, + model.Version); try { - await byondManager.ChangeVersion(null, version, null, false, cancellationToken); + await byondManager.ChangeVersion(null, model, null, false, cancellationToken); } catch (InvalidOperationException ex) { Logger.LogDebug( ex, - "Race condition: BYOND version {version} uninstalled before we could switch to it. Creating install job instead...", - version); + "Race condition: {engineType} version {version} uninstalled before we could switch to it. Creating install job instead...", + model.Engine.Value, + model.Version); versionAlreadyInstalled = false; } } if (!versionAlreadyInstalled) { - if (version.Build > 0) + if (model.Version.Build > 0) return BadRequest(new ErrorMessageResponse(ErrorCode.ByondNonExistentCustomVersion)); Logger.LogInformation( - "User ID {userId} installing BYOND version to {newByondVersion} on instance ID {instanceId}", + "User ID {userId} installing {engineType} version {newByondVersion}{sourceCommittish} on instance ID {instanceId}", AuthenticationContext.User.Id, - version, + model.Engine.Value, + model.Version, + model.SourceCommittish != null + ? $" ({model.SourceCommittish})" + : String.Empty, Instance.Id); // run the install through the job manager var job = new Job { - Description = $"Install {(!uploadingZip ? String.Empty : "custom ")}BYOND version {version}", + Description = $"Install {(!uploadingZip ? String.Empty : "custom ")}{model.Engine.Value} version {model.Version}", StartedBy = AuthenticationContext.User, CancelRightsType = RightsType.Byond, CancelRight = (ulong)ByondRights.CancelInstall, @@ -209,7 +226,13 @@ await jobManager.RegisterOperation( job, async (core, databaseContextFactory, paramJob, progressHandler, jobCancellationToken) => { - Stream zipFileStream = null; + if (sourceRepo != null) + await core.ByondManager.EnsureEngineSource( + sourceRepo, + model.Engine.Value, + jobCancellationToken); + + MemoryStream zipFileStream = null; if (fileUploadTicket != null) await using (fileUploadTicket) { @@ -229,7 +252,7 @@ await jobManager.RegisterOperation( await using (zipFileStream) await core.ByondManager.ChangeVersion( progressHandler, - version, + model, zipFileStream, true, jobCancellationToken); @@ -260,7 +283,7 @@ await core.ByondManager.ChangeVersion( /// A resulting in the for the operation. /// Created to delete target version successfully. /// Attempted to delete the active BYOND . - /// The specified was not installed. + /// The specified was not installed. [HttpDelete] [TgsAuthorize(ByondRights.DeleteInstall)] [ProducesResponseType(typeof(JobResponse), 202)] @@ -270,22 +293,22 @@ public async ValueTask Delete([FromBody] ByondVersionDeleteReques { ArgumentNullException.ThrowIfNull(model); - if (model.Version == null - || model.Version.Revision != -1) + if (model.Version.Revision != -1) return BadRequest(new ErrorMessageResponse(ErrorCode.ModelValidationFailure)); - var version = NormalizeVersion(model.Version); + if (model.Engine == EngineType.Byond) + model.Version = NormalizeByondVersion(model.Version); var notInstalledResponse = await WithComponentInstance( instance => { var byondManager = instance.ByondManager; - if (version == byondManager.ActiveVersion) + if (model.Equals(byondManager.ActiveVersion)) return ValueTask.FromResult( Conflict(new ErrorMessageResponse(ErrorCode.ByondCannotDeleteActiveVersion))); - var versionNotInstalled = !byondManager.InstalledVersions.Any(x => x == version); + var versionNotInstalled = !byondManager.InstalledVersions.Any(x => x.Equals(model)); return ValueTask.FromResult( versionNotInstalled @@ -296,22 +319,27 @@ public async ValueTask Delete([FromBody] ByondVersionDeleteReques if (notInstalledResponse != null) return notInstalledResponse; - var isCustomVersion = version.Build != -1; + var isByondVersion = model.Engine.Value == EngineType.Byond; // run the install through the job manager var job = new Job { - Description = $"Delete installed BYOND version {version}", + Description = $"Delete installed {model.Engine.Value} version {model.Version}", StartedBy = AuthenticationContext.User, CancelRightsType = RightsType.Byond, - CancelRight = (ulong)(isCustomVersion ? ByondRights.InstallOfficialOrChangeActiveVersion : ByondRights.InstallCustomVersion), + CancelRight = (ulong)( + isByondVersion + ? model.Version.Build != -1 + ? ByondRights.InstallOfficialOrChangeActiveByondVersion + : ByondRights.InstallCustomByondVersion + : ByondRights.InstallCustomOpenDreamVersion | ByondRights.InstallOfficialOrChangeActiveOpenDreamVersion), Instance = Instance, }; await jobManager.RegisterOperation( job, (instanceCore, databaseContextFactory, job, progressReporter, jobCancellationToken) - => instanceCore.ByondManager.DeleteVersion(progressReporter, version, jobCancellationToken), + => instanceCore.ByondManager.DeleteVersion(progressReporter, model, jobCancellationToken), cancellationToken); var apiResponse = job.ToApi(); diff --git a/src/Tgstation.Server.Host/Models/CompileJob.cs b/src/Tgstation.Server.Host/Models/CompileJob.cs index 9ddde1344c1..ca5c0877d94 100644 --- a/src/Tgstation.Server.Host/Models/CompileJob.cs +++ b/src/Tgstation.Server.Host/Models/CompileJob.cs @@ -32,6 +32,12 @@ public sealed class CompileJob : Api.Models.Internal.CompileJob, IApiTransformab [Required] public string ByondVersion { get; set; } + /// + /// The of . + /// + [Required] + public EngineType Engine { get; set; } + /// /// Backing field for of . /// @@ -82,7 +88,7 @@ public override Version DMApiVersion } /// - public CompileJobResponse ToApi() => new CompileJobResponse + public CompileJobResponse ToApi() => new () { DirectoryName = DirectoryName, DmeName = DmeName, @@ -90,7 +96,9 @@ public override Version DMApiVersion Job = Job.ToApi(), Output = Output, RevisionInformation = RevisionInformation.ToApi(), - ByondVersion = Version.Parse(ByondVersion), + ByondVersion = Api.Models.Internal.ByondVersion.TryParse(ByondVersion, out var version) + ? version + : throw new InvalidOperationException($"Failed to parse BYOND version: {ByondVersion}"), MinimumSecurityLevel = MinimumSecurityLevel, DMApiVersion = DMApiVersion, RepositoryOrigin = RepositoryOrigin != null ? new Uri(RepositoryOrigin) : null, diff --git a/tests/Tgstation.Server.Api.Tests/Rights/TestRights.cs b/tests/Tgstation.Server.Api.Tests/Rights/TestRights.cs index 62e41b7d982..e773f4da941 100644 --- a/tests/Tgstation.Server.Api.Tests/Rights/TestRights.cs +++ b/tests/Tgstation.Server.Api.Tests/Rights/TestRights.cs @@ -40,7 +40,7 @@ public void TestAllPowerOfTwo() [TestMethod] public void TestAllRightsWorks() { - var allByondRights = ByondRights.CancelInstall | ByondRights.InstallOfficialOrChangeActiveVersion | ByondRights.ListInstalled | ByondRights.ReadActive | ByondRights.InstallCustomVersion | ByondRights.DeleteInstall; + var allByondRights = ByondRights.CancelInstall | ByondRights.InstallOfficialOrChangeActiveByondVersion | ByondRights.ListInstalled | ByondRights.ReadActive | ByondRights.InstallCustomByondVersion | ByondRights.DeleteInstall; var automaticByondRights = RightsHelper.AllRights(); Assert.AreEqual(allByondRights, automaticByondRights); diff --git a/tests/Tgstation.Server.Client.Tests/TestApiClient.cs b/tests/Tgstation.Server.Client.Tests/TestApiClient.cs index d4c81db6c3b..3027f9ac45a 100644 --- a/tests/Tgstation.Server.Client.Tests/TestApiClient.cs +++ b/tests/Tgstation.Server.Client.Tests/TestApiClient.cs @@ -11,6 +11,8 @@ using System.Threading.Tasks; using Tgstation.Server.Api; +using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Api.Models.Response; using Tgstation.Server.Common.Http; @@ -22,10 +24,12 @@ public sealed class TestApiClient [TestMethod] public async Task TestDeserializingByondModelsWork() { - var sample = new ByondResponse - { - Version = new Version(511, 1385, 0) - }; + var sample = new ByondResponse( + new ByondVersion + { + Engine = EngineType.Byond, + Version = new Version(511, 1385, 0) + }); var sampleJson = JsonConvert.SerializeObject(sample, new JsonSerializerSettings { @@ -51,10 +55,12 @@ public async Task TestDeserializingByondModelsWork() [TestMethod] public async Task TestUnrecognizedResponse() { - var sample = new ByondResponse - { - Version = new Version(511, 1385) - }; + var sample = new ByondResponse( + new ByondVersion + { + Engine = EngineType.Byond, + Version = new Version(511, 1385) + }); var fakeJson = "asdfasd <>F#(*)U*#JLI"; diff --git a/tests/Tgstation.Server.Host.Tests/Components/Byond/TestPosixByondInstaller.cs b/tests/Tgstation.Server.Host.Tests/Components/Byond/TestPosixByondInstaller.cs index dccf90d3e07..c57e72afa16 100644 --- a/tests/Tgstation.Server.Host.Tests/Components/Byond/TestPosixByondInstaller.cs +++ b/tests/Tgstation.Server.Host.Tests/Components/Byond/TestPosixByondInstaller.cs @@ -6,6 +6,8 @@ using System.Linq; using System.Threading.Tasks; +using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host.Components.Byond.Tests @@ -62,7 +64,11 @@ public async Task TestDownload() new MemoryStream(ourArray))) .Verifiable(); - var result = await installer.DownloadVersion(new Version(511, 1385), default); + var result = await installer.DownloadVersion(new ByondVersion + { + Engine = EngineType.Byond, + Version = new Version(123, 252345), + }, default); Assert.IsTrue(ourArray.SequenceEqual(result.ToArray())); mockIOManager.Verify(); @@ -79,9 +85,17 @@ public async Task TestInstallByond() const string FakePath = "fake"; await Assert.ThrowsExceptionAsync(() => installer.InstallByond(null, null, default).AsTask()); - await Assert.ThrowsExceptionAsync(() => installer.InstallByond(new Version(123,252345), null, default).AsTask()); - await installer.InstallByond(new Version(511, 1385), FakePath, default); + var byondVersion = new ByondVersion + { + Engine = EngineType.Byond, + Version = new Version(123, 252345), + }; + + await Assert.ThrowsExceptionAsync(() => installer.InstallByond(byondVersion, null, default).AsTask()); + + byondVersion.Version = new Version(511, 1385); + await installer.InstallByond(byondVersion, FakePath, default); mockPostWriteHandler.Verify(x => x.HandleWrite(It.IsAny()), Times.Exactly(4)); } diff --git a/tests/Tgstation.Server.Host.Tests/Security/TestAuthenticationContext.cs b/tests/Tgstation.Server.Host.Tests/Security/TestAuthenticationContext.cs index 090f10f1ec3..fb0bea813d0 100644 --- a/tests/Tgstation.Server.Host.Tests/Security/TestAuthenticationContext.cs +++ b/tests/Tgstation.Server.Host.Tests/Security/TestAuthenticationContext.cs @@ -1,4 +1,4 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using System; @@ -50,7 +50,7 @@ public void TestGetRightsGeneric() var authContext = new AuthenticationContext(null, user, instanceUser); user.PermissionSet.AdministrationRights = AdministrationRights.WriteUsers; - instanceUser.ByondRights = ByondRights.InstallOfficialOrChangeActiveVersion | ByondRights.ReadActive; + instanceUser.ByondRights = ByondRights.InstallOfficialOrChangeActiveByondVersion | ByondRights.ReadActive; Assert.AreEqual((ulong)user.PermissionSet.AdministrationRights, authContext.GetRight(RightsType.Administration)); Assert.AreEqual((ulong)instanceUser.ByondRights, authContext.GetRight(RightsType.Byond)); } diff --git a/tests/Tgstation.Server.Tests/CachingFileDownloader.cs b/tests/Tgstation.Server.Tests/CachingFileDownloader.cs index 17dcbbfaf59..e00206bfe0f 100644 --- a/tests/Tgstation.Server.Tests/CachingFileDownloader.cs +++ b/tests/Tgstation.Server.Tests/CachingFileDownloader.cs @@ -9,6 +9,7 @@ using Moq; +using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Common.Http; using Tgstation.Server.Host.Extensions; using Tgstation.Server.Host.IO; @@ -42,9 +43,9 @@ public static async Task InitializeAndInjectForLiveTests(CancellationToken cance var logger = loggerFactory.CreateLogger("CachingFileDownloader"); var cfd = new CachingFileDownloader(loggerFactory.CreateLogger()); - var edgeVersion = await ByondTest.GetEdgeVersion(cfd, cancellationToken); + var edgeVersion = await ByondTest.GetEdgeVersion(Api.Models.EngineType.Byond, cfd, cancellationToken); - await InitializeByondVersion(logger, edgeVersion, new PlatformIdentifier().IsWindows, cancellationToken); + await InitializeByondVersion(logger, edgeVersion.Version, new PlatformIdentifier().IsWindows, cancellationToken); // predownload the target github release update asset var gitHubToken = Environment.GetEnvironmentVariable("TGS_TEST_GITHUB_TOKEN"); @@ -76,10 +77,16 @@ public static async Task InitializeAndInjectForLiveTests(CancellationToken cance ServiceCollectionExtensions.UseFileDownloader(); } - public static async ValueTask InitializeByondVersion(ILogger logger, Version version, bool windows, CancellationToken cancellationToken) + public static async ValueTask InitializeByondVersion(ILogger logger, Version byondVersion, bool windows, CancellationToken cancellationToken) { + var version = new ByondVersion + { + Engine = Api.Models.EngineType.Byond, + Version = byondVersion, + }; + var url = new Uri( - $"https://www.byond.com/download/build/{version.Major}/{version.Major}.{version.Minor}_byond{(!windows ? "_linux" : string.Empty)}.zip"); + $"https://www.byond.com/download/build/{version.Version.Major}/{version.Version.Major}.{version.Version.Minor}_byond{(!windows ? "_linux" : string.Empty)}.zip"); string path = null; if (TestingUtils.RunningInGitHubActions) { @@ -91,7 +98,7 @@ public static async ValueTask InitializeByondVersion(ILogger logger, Version ver windows ? "windows" : "linux"); path = Path.Combine( dir, - $"{version.Major}.{version.Minor}.zip"); + $"{version.Version.Major}.{version.Version.Minor}.zip"); } await (await CacheFile(logger, url, null, path, cancellationToken)).DisposeAsync(); diff --git a/tests/Tgstation.Server.Tests/Live/DummyChatProvider.cs b/tests/Tgstation.Server.Tests/Live/DummyChatProvider.cs index 4f1ddf73873..255e162c8d9 100644 --- a/tests/Tgstation.Server.Tests/Live/DummyChatProvider.cs +++ b/tests/Tgstation.Server.Tests/Live/DummyChatProvider.cs @@ -12,6 +12,7 @@ using Newtonsoft.Json; +using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Components.Chat.Commands; using Tgstation.Server.Host.Components.Chat.Providers; @@ -100,7 +101,15 @@ public override ValueTask SendMessage(Message replyTo, MessageContent message, u return ValueTask.CompletedTask; } - public override ValueTask>>> SendUpdateMessage(RevisionInformation revisionInformation, Version byondVersion, DateTimeOffset? estimatedCompletionTime, string gitHubOwner, string gitHubRepo, ulong channelId, bool localCommitPushed, CancellationToken cancellationToken) + public override ValueTask>>> SendUpdateMessage( + Host.Models.RevisionInformation revisionInformation, + ByondVersion byondVersion, + DateTimeOffset? estimatedCompletionTime, + string gitHubOwner, + string gitHubRepo, + ulong channelId, + bool localCommitPushed, + CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(revisionInformation); ArgumentNullException.ThrowIfNull(byondVersion); diff --git a/tests/Tgstation.Server.Tests/Live/Instance/ByondTest.cs b/tests/Tgstation.Server.Tests/Live/Instance/ByondTest.cs index eedac0cd0e5..3e9f5ca84c9 100644 --- a/tests/Tgstation.Server.Tests/Live/Instance/ByondTest.cs +++ b/tests/Tgstation.Server.Tests/Live/Instance/ByondTest.cs @@ -6,13 +6,17 @@ using System.Threading; using System.Threading.Tasks; +using Elasticsearch.Net.Specification.IndexLifecycleManagementApi; + using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Api.Models.Request; using Tgstation.Server.Api.Models.Response; using Tgstation.Server.Client; @@ -32,16 +36,22 @@ sealed class ByondTest : JobsRequiredTest readonly Api.Models.Instance metadata; - static Version edgeVersion; + static Dictionary edgeVersions = new Dictionary + { + { EngineType.Byond, null }, + { EngineType.OpenDream, null } + }; - Version testVersion; + ByondVersion testVersion; + EngineType testEngine; - public ByondTest(IByondClient byondClient, IJobsClient jobsClient, IFileDownloader fileDownloader, Api.Models.Instance metadata) + public ByondTest(IByondClient byondClient, IJobsClient jobsClient, IFileDownloader fileDownloader, Api.Models.Instance metadata, EngineType engineType) : base(jobsClient) { this.byondClient = byondClient ?? throw new ArgumentNullException(nameof(byondClient)); this.fileDownloader = fileDownloader ?? throw new ArgumentNullException(nameof(fileDownloader)); this.metadata = metadata ?? throw new ArgumentNullException(nameof(metadata)); + this.testEngine = engineType; } public Task Run(CancellationToken cancellationToken, out Task firstInstall) @@ -50,42 +60,67 @@ public Task Run(CancellationToken cancellationToken, out Task firstInstall) return RunContinued(firstInstall, cancellationToken); } - public static async Task GetEdgeVersion(IFileDownloader fileDownloader, CancellationToken cancellationToken) + public static async ValueTask GetEdgeVersion(EngineType engineType, IFileDownloader fileDownloader, CancellationToken cancellationToken) { + var edgeVersion = edgeVersions[engineType]; + if (edgeVersion != null) return edgeVersion; - await using var provider = fileDownloader.DownloadFile(new Uri("https://www.byond.com/download/version.txt"), null); - var stream = await provider.GetResult(cancellationToken); - using var reader = new StreamReader(stream, Encoding.UTF8, false, -1, true); - var text = await reader.ReadToEndAsync(); - var splits = text.Split('\n', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); - - var targetVersion = splits.Last(); - - var missingVersionMap = new PlatformIdentifier().IsWindows - ? new Dictionary() - { - } - // linux map also needs updating in CI - : new Dictionary() - { + ByondVersion byondVersion; + if (engineType == EngineType.Byond) + { + await using var provider = fileDownloader.DownloadFile(new Uri("https://www.byond.com/download/version.txt"), null); + var stream = await provider.GetResult(cancellationToken); + using var reader = new StreamReader(stream, Encoding.UTF8, false, -1, true); + var text = await reader.ReadToEndAsync(cancellationToken); + var splits = text.Split('\n', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); + + var targetVersion = splits.Last(); + + var missingVersionMap = new PlatformIdentifier().IsWindows + ? new Dictionary() + { + } + // linux map also needs updating in CI + : new Dictionary() + { { "515.1612", "515.1611" } - }; + }; - if (missingVersionMap.TryGetValue(targetVersion, out var remappedVersion)) - targetVersion = remappedVersion; + if (missingVersionMap.TryGetValue(targetVersion, out var remappedVersion)) + targetVersion = remappedVersion; - return edgeVersion = Version.Parse(targetVersion); + Assert.IsTrue(ByondVersion.TryParse(targetVersion, out byondVersion), $"Bad version: {targetVersion}"); + } + else + { + Assert.Fail($"Edge version retrieval for {engineType} not implemented!"); + return null; + } + + return edgeVersions[engineType] = byondVersion; } async Task RunPartOne(CancellationToken cancellationToken) { - testVersion = await GetEdgeVersion(fileDownloader, cancellationToken); + testVersion = await GetEdgeVersion(EngineType.Byond, fileDownloader, cancellationToken); await TestNoVersion(cancellationToken); + await TestInstallNullVersion(cancellationToken); await TestInstallStable(cancellationToken); } + ValueTask TestInstallNullVersion(CancellationToken cancellationToken) + => ApiAssert.ThrowsException( + () => byondClient.SetActiveVersion( + new ByondVersionRequest + { + Engine = testEngine, + }, + null, + cancellationToken), + ErrorCode.ModelValidationFailure); + async Task RunContinued(Task firstInstall, CancellationToken cancellationToken) { await firstInstall; @@ -98,7 +133,8 @@ async Task TestDeletes(CancellationToken cancellationToken) { var deleteThisOneBecauseItWasntPartOfTheOriginalTest = await byondClient.DeleteVersion(new ByondVersionDeleteRequest { - Version = new(testVersion.Major, testVersion.Minor, 2) + Engine = testEngine, + Version = new(testVersion.Version.Major, testVersion.Version.Minor, 2) }, cancellationToken); await WaitForJob(deleteThisOneBecauseItWasntPartOfTheOriginalTest, 30, false, null, cancellationToken); @@ -112,14 +148,18 @@ async Task TestDeletes(CancellationToken cancellationToken) var uninstallResponseTask = byondClient.DeleteVersion( new ByondVersionDeleteRequest { - Version = testVersion + Version = testVersion.Version, + Engine = testVersion.Engine, + SourceCommittish = testVersion.SourceCommittish, }, cancellationToken); var badBecauseActiveResponseTask = ApiAssert.ThrowsException(() => byondClient.DeleteVersion( new ByondVersionDeleteRequest { - Version = new(testVersion.Major, testVersion.Minor, 1) + Version = new(testVersion.Version.Major, testVersion.Version.Minor, 1), + Engine = testVersion.Engine, + SourceCommittish = testVersion.SourceCommittish, }, cancellationToken), ErrorCode.ByondCannotDeleteActiveVersion); @@ -140,7 +180,7 @@ async Task TestDeletes(CancellationToken cancellationToken) var newVersions = await byondClient.InstalledVersions(null, cancellationToken); Assert.IsNotNull(newVersions); Assert.AreEqual(1, newVersions.Count); - Assert.AreEqual(new Version(testVersion.Major, testVersion.Minor, 1), newVersions[0].Version); + Assert.AreEqual(new Version(testVersion.Version.Major, testVersion.Version.Minor, 1), newVersions[0].Version); } async Task TestInstallFakeVersion(CancellationToken cancellationToken) @@ -158,7 +198,9 @@ async Task TestInstallStable(CancellationToken cancellationToken) { var newModel = new ByondVersionRequest { - Version = testVersion + Version = testVersion.Version, + Engine = testVersion.Engine, + SourceCommittish = testVersion.SourceCommittish, }; var test = await byondClient.SetActiveVersion(newModel, null, cancellationToken); Assert.IsNotNull(test.InstallJob); @@ -220,7 +262,9 @@ async Task TestCustomInstalls(CancellationToken cancellationToken) var test = await byondClient.SetActiveVersion( new ByondVersionRequest { - Version = testVersion, + Engine = testVersion.Engine, + Version = testVersion.Version, + SourceCommittish = testVersion.SourceCommittish, UploadCustomZip = true }, stableBytesMs, @@ -234,7 +278,9 @@ async Task TestCustomInstalls(CancellationToken cancellationToken) var test2 = await byondClient.SetActiveVersion( new ByondVersionRequest { - Version = testVersion, + Version = testVersion.Version, + SourceCommittish = testVersion.SourceCommittish, + Engine = testVersion.Engine, UploadCustomZip = true }, stableBytesMs, @@ -244,22 +290,24 @@ async Task TestCustomInstalls(CancellationToken cancellationToken) await WaitForJob(test2.InstallJob, 30, false, null, cancellationToken); var newSettings = await byondClient.ActiveVersion(cancellationToken); - Assert.AreEqual(new Version(testVersion.Major, testVersion.Minor, 2), newSettings.Version); + Assert.AreEqual(new Version(testVersion.Version.Major, testVersion.Version.Minor, 2), newSettings.Version); // test a few switches var installResponse = await byondClient.SetActiveVersion(new ByondVersionRequest { - Version = testVersion + Version = testVersion.Version, + SourceCommittish = testVersion.SourceCommittish, + Engine = testVersion.Engine, }, null, cancellationToken); Assert.IsNull(installResponse.InstallJob); await ApiAssert.ThrowsException(() => byondClient.SetActiveVersion(new ByondVersionRequest { - Version = new Version(testVersion.Major, testVersion.Minor, 3) + Version = new Version(testVersion.Version.Major, testVersion.Version.Minor, 3) }, null, cancellationToken), ErrorCode.ByondNonExistentCustomVersion); installResponse = await byondClient.SetActiveVersion(new ByondVersionRequest { - Version = new Version(testVersion.Major, testVersion.Minor, 1) + Version = new Version(testVersion.Version.Major, testVersion.Version.Minor, 1) }, null, cancellationToken); Assert.IsNull(installResponse.InstallJob); } diff --git a/tests/Tgstation.Server.Tests/Live/Instance/InstanceTest.cs b/tests/Tgstation.Server.Tests/Live/Instance/InstanceTest.cs index ed0edee01e3..ea17e68033d 100644 --- a/tests/Tgstation.Server.Tests/Live/Instance/InstanceTest.cs +++ b/tests/Tgstation.Server.Tests/Live/Instance/InstanceTest.cs @@ -10,6 +10,7 @@ using Moq; using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Api.Models.Request; using Tgstation.Server.Api.Models.Response; using Tgstation.Server.Client; @@ -43,9 +44,10 @@ public async Task RunTests( ushort ddPort, bool highPrioDD, bool lowPrioDeployment, + EngineType engineType, CancellationToken cancellationToken) { - var byondTest = new ByondTest(instanceClient.Byond, instanceClient.Jobs, fileDownloader, instanceClient.Metadata); + var byondTest = new ByondTest(instanceClient.Byond, instanceClient.Jobs, fileDownloader, instanceClient.Metadata, engineType); var chatTest = new ChatTest(instanceClient.ChatBots, instanceManagerClient, instanceClient.Jobs, instanceClient.Metadata); var configTest = new ConfigurationTest(instanceClient.Configuration, instanceClient.Metadata); var repoTest = new RepositoryTest(instanceClient.Repository, instanceClient.Jobs); @@ -66,17 +68,23 @@ public async Task RunTests( await byondTask; await new WatchdogTest( - await ByondTest.GetEdgeVersion(fileDownloader, cancellationToken), instanceClient, instanceManager, serverPort, highPrioDD, ddPort).Run(cancellationToken); + await ByondTest.GetEdgeVersion(engineType, fileDownloader, cancellationToken), instanceClient, instanceManager, serverPort, highPrioDD, ddPort).Run(cancellationToken); } public async Task RunCompatTests( - Version compatVersion, + Version compatByondVersion, IInstanceClient instanceClient, ushort dmPort, ushort ddPort, bool highPrioDD, CancellationToken cancellationToken) { + var compatVersion = new ByondVersion + { + Engine = EngineType.Byond, + Version = compatByondVersion, + }; + System.Console.WriteLine($"COMPAT TEST START: {compatVersion}"); const string Origin = "https://github.com/Cyberboss/common_core"; var cloneRequest = instanceClient.Repository.Clone(new RepositoryCreateRequest @@ -155,7 +163,9 @@ public async Task RunCompatTests( installJob2 = await instanceClient.Byond.SetActiveVersion(new ByondVersionRequest { UploadCustomZip = true, - Version = compatVersion, + Version = compatVersion.Version, + Engine = compatVersion.Engine, + SourceCommittish = compatVersion.SourceCommittish }, stableBytesMs, cancellationToken); } diff --git a/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs b/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs index 3c4b9cf460d..2726b4a5761 100644 --- a/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs +++ b/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs @@ -18,6 +18,7 @@ using System.Threading.Tasks; using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Api.Models.Request; using Tgstation.Server.Api.Models.Response; using Tgstation.Server.Client; @@ -57,11 +58,11 @@ sealed class WatchdogTest : JobsRequiredTest readonly ushort ddPort; readonly bool highPrioDD; readonly TopicClient topicClient; - readonly Version testVersion; + readonly ByondVersion testVersion; bool ranTimeoutTest = false; - public WatchdogTest(Version testVersion, IInstanceClient instanceClient, InstanceManager instanceManager, ushort serverPort, bool highPrioDD, ushort ddPort) + public WatchdogTest(ByondVersion testVersion, IInstanceClient instanceClient, InstanceManager instanceManager, ushort serverPort, bool highPrioDD, ushort ddPort) : base(instanceClient.Jobs) { this.instanceClient = instanceClient ?? throw new ArgumentNullException(nameof(instanceClient)); @@ -94,8 +95,9 @@ async Task CheckByondVersions() var byondVersion = list[0]; Assert.AreEqual(1, byondVersion.Version.Build); - Assert.AreEqual(testVersion.Major, byondVersion.Version.Major); - Assert.AreEqual(testVersion.Minor, byondVersion.Version.Minor); + Assert.AreEqual(testVersion.Version.Major, byondVersion.Version.Major); + Assert.AreEqual(testVersion.Version.Minor, byondVersion.Version.Minor); + Assert.AreEqual(testVersion.Engine, byondVersion.Engine); } await Task.WhenAll( @@ -230,10 +232,10 @@ async ValueTask RegressionTest1550(CancellationToken cancellationToken) async Task TestDeleteByondInstallErrorCasesAndQueing(CancellationToken cancellationToken) { - var testCustomVersion = new Version(testVersion.Major, testVersion.Minor, 1); + var testCustomVersion = new Version(testVersion.Version.Major, testVersion.Version.Minor, 1); var currentByond = await instanceClient.Byond.ActiveVersion(cancellationToken); Assert.IsNotNull(currentByond); - Assert.AreEqual(testVersion.Semver(), currentByond.Version); + Assert.AreEqual(testVersion.Version.Semver(), currentByond.Version); // Change the active version and check we get delayed while deleting the old one because the watchdog is using it var setActiveResponse = await instanceClient.Byond.SetActiveVersion( @@ -250,7 +252,8 @@ async Task TestDeleteByondInstallErrorCasesAndQueing(CancellationTo var deleteJob = await instanceClient.Byond.DeleteVersion( new ByondVersionDeleteRequest { - Version = testVersion, + Version = testVersion.Version, + SourceCommittish = testVersion.SourceCommittish, }, cancellationToken); @@ -265,7 +268,9 @@ async Task TestDeleteByondInstallErrorCasesAndQueing(CancellationTo setActiveResponse = await instanceClient.Byond.SetActiveVersion( new ByondVersionRequest { - Version = testVersion, + Version = testVersion.Version, + Engine = testVersion.Engine, + SourceCommittish = testVersion.SourceCommittish }, null, cancellationToken); @@ -291,7 +296,9 @@ async Task TestDeleteByondInstallErrorCasesAndQueing(CancellationTo deleteJob = await instanceClient.Byond.DeleteVersion( new ByondVersionDeleteRequest { - Version = testVersion, + Version = testVersion.Version, + Engine = testVersion.Engine, + SourceCommittish = testVersion.SourceCommittish, }, cancellationToken); @@ -954,9 +961,8 @@ async Task RunLongRunningTestThenUpdateWithByondVersionSwitch(CancellationToken System.Console.WriteLine("TEST: WATCHDOG BYOND VERSION UPDATE TEST"); var versionToInstall = testVersion; - versionToInstall = versionToInstall.Semver(); var currentByondVersion = await instanceClient.Byond.ActiveVersion(cancellationToken); - Assert.AreNotEqual(versionToInstall, currentByondVersion.Version); + Assert.AreNotEqual(versionToInstall, currentByondVersion); var initialStatus = await instanceClient.DreamDaemon.Read(cancellationToken); @@ -969,7 +975,9 @@ async Task RunLongRunningTestThenUpdateWithByondVersionSwitch(CancellationToken var byondInstallJobTask = instanceClient.Byond.SetActiveVersion( new ByondVersionRequest { - Version = versionToInstall + Version = versionToInstall.Version, + Engine = versionToInstall.Engine, + SourceCommittish = versionToInstall.SourceCommittish, }, null, cancellationToken); diff --git a/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs b/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs index fbfd2d92e6c..0b98971e3eb 100644 --- a/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs +++ b/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs @@ -28,6 +28,7 @@ using Tgstation.Server.Api; using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Api.Models.Request; using Tgstation.Server.Api.Models.Response; using Tgstation.Server.Api.Rights; @@ -955,7 +956,12 @@ await Task.WhenAny( } [TestMethod] - public async Task TestStandardTgsOperation() + public Task TestStandardTgsOperation() => TestStandardTgsOperation(EngineType.Byond); + + [TestMethod] + public Task TestOpenDreamTgsOperation() => TestStandardTgsOperation(EngineType.OpenDream); + + async Task TestStandardTgsOperation(EngineType engineType) { using(var currentProcess = System.Diagnostics.Process.GetCurrentProcess()) { @@ -980,7 +986,7 @@ public async Task TestStandardTgsOperation() ServiceCollectionExtensions.UseAdditionalLoggerProvider(); var failureTask = HardFailLoggerProvider.FailureSource; - var internalTask = TestTgsInternal(hardCancellationToken); + var internalTask = TestTgsInternal(engineType, hardCancellationToken); await Task.WhenAny( internalTask, failureTask); @@ -1012,7 +1018,7 @@ await Task.WhenAny( await internalTask; } - async Task TestTgsInternal(CancellationToken hardCancellationToken) + async Task TestTgsInternal(EngineType engineType, CancellationToken hardCancellationToken) { var discordConnectionString = Environment.GetEnvironmentVariable("TGS_TEST_DISCORD_TOKEN"); var ircConnectionString = Environment.GetEnvironmentVariable("TGS_TEST_IRC_CONNECTION_STRING"); @@ -1118,7 +1124,7 @@ async Task FailFast(Task task) async Task RunInstanceTests() { // Some earlier linux BYOND versions have a critical bug where replacing the directory in non-basic watchdogs causes the DreamDaemon cwd to change - var canRunCompatTests = new PlatformIdentifier().IsWindows; + var canRunCompatTests = engineType == EngineType.Byond && new PlatformIdentifier().IsWindows; var compatTests = canRunCompatTests ? FailFast( instanceTest @@ -1142,6 +1148,7 @@ await FailFast( mainDDPort, server.HighPriorityDreamDaemon, server.LowPriorityDeployments, + engineType, cancellationToken)); await compatTests; @@ -1308,7 +1315,7 @@ async Task WaitForInitialJobs(IInstanceClient instanceClient) preStartupTime = DateTimeOffset.UtcNow; serverTask = server.Run(cancellationToken).AsTask(); long expectedCompileJobId, expectedStaged; - var edgeByond = await ByondTest.GetEdgeVersion(fileDownloader, cancellationToken); + var edgeVersion = await ByondTest.GetEdgeVersion(engineType, fileDownloader, cancellationToken); using (var adminClient = await CreateAdminClient(server.Url, cancellationToken)) { var instanceClient = adminClient.Instances.CreateClient(instance); @@ -1319,7 +1326,7 @@ async Task WaitForInitialJobs(IInstanceClient instanceClient) Assert.AreEqual(WatchdogStatus.Online, dd.Status.Value); var compileJob = await instanceClient.DreamMaker.Compile(cancellationToken); - var wdt = new WatchdogTest(edgeByond, instanceClient, GetInstanceManager(), (ushort)server.Url.Port, server.HighPriorityDreamDaemon, mainDDPort); + var wdt = new WatchdogTest(edgeVersion, instanceClient, GetInstanceManager(), (ushort)server.Url.Port, server.HighPriorityDreamDaemon, mainDDPort); await wdt.WaitForJob(compileJob, 30, false, null, cancellationToken); dd = await instanceClient.DreamDaemon.Read(cancellationToken); @@ -1366,7 +1373,7 @@ await instanceClient.DreamDaemon.Update(new DreamDaemonRequest Assert.AreEqual(WatchdogStatus.Online, currentDD.Status); Assert.AreEqual(expectedStaged, currentDD.StagedCompileJob.Job.Id.Value); - var wdt = new WatchdogTest(edgeByond, instanceClient, GetInstanceManager(), (ushort)server.Url.Port, server.HighPriorityDreamDaemon, mainDDPort); + var wdt = new WatchdogTest(edgeVersion, instanceClient, GetInstanceManager(), (ushort)server.Url.Port, server.HighPriorityDreamDaemon, mainDDPort); currentDD = await wdt.TellWorldToReboot(cancellationToken); Assert.AreEqual(expectedStaged, currentDD.ActiveCompileJob.Job.Id.Value); Assert.IsNull(currentDD.StagedCompileJob); diff --git a/tests/Tgstation.Server.Tests/TestDatabase.cs b/tests/Tgstation.Server.Tests/TestDatabase.cs index df8ffd5e4cf..8d629bcfb67 100644 --- a/tests/Tgstation.Server.Tests/TestDatabase.cs +++ b/tests/Tgstation.Server.Tests/TestDatabase.cs @@ -134,7 +134,7 @@ DatabaseContext CreateContext() { new Host.Models.InstancePermissionSet { - ByondRights = ByondRights.InstallCustomVersion, + ByondRights = ByondRights.InstallCustomByondVersion, ChatBotRights = ChatBotRights.None, ConfigurationRights = ConfigurationRights.Read, DreamDaemonRights = DreamDaemonRights.ReadRevision, diff --git a/tests/Tgstation.Server.Tests/TestVersions.cs b/tests/Tgstation.Server.Tests/TestVersions.cs index 41c48a1196d..387438b6875 100644 --- a/tests/Tgstation.Server.Tests/TestVersions.cs +++ b/tests/Tgstation.Server.Tests/TestVersions.cs @@ -28,6 +28,8 @@ using Tgstation.Server.Host.IO; using Tgstation.Server.Host.System; using System.Net; +using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Api.Models; namespace Tgstation.Server.Tests { @@ -125,12 +127,18 @@ await CachingFileDownloader.InitializeByondVersion( const string ArchiveEntryPath = "byond/bin/dd.exe"; var hasEntry = ArchiveHasFileEntry( - await byondInstaller.DownloadVersion(WindowsByondInstaller.DDExeVersion, default), + await byondInstaller.DownloadVersion( + new ByondVersion + { + Engine = EngineType.Byond, + Version = WindowsByondInstaller.DDExeVersion + }, + default), ArchiveEntryPath); Assert.IsTrue(hasEntry); - var (byondBytes, version) = await GetByondVersionPriorTo(byondInstaller, WindowsByondInstaller.DDExeVersion); + var (byondBytes, _) = await GetByondVersionPriorTo(byondInstaller, WindowsByondInstaller.DDExeVersion); hasEntry = ArchiveHasFileEntry( byondBytes, ArchiveEntryPath); @@ -203,8 +211,18 @@ await CachingFileDownloader.InitializeByondVersion( try { await TestMapThreadsVersion( - ByondInstallerBase.MapThreadsVersion, - await byondInstaller.DownloadVersion(ByondInstallerBase.MapThreadsVersion, default), + new ByondVersion + { + Engine = EngineType.Byond, + Version = ByondInstallerBase.MapThreadsVersion, + }, + await byondInstaller.DownloadVersion( + new ByondVersion + { + Engine = EngineType.Byond, + Version = ByondInstallerBase.MapThreadsVersion + }, + default), byondInstaller, ioManager, processExecutor, @@ -380,22 +398,32 @@ static string GetMigrationTimestampString(Type type) => type Assert.AreEqual(latestMigrationSL, DatabaseContext.SLLatestMigration); } - static async Task> GetByondVersionPriorTo(IByondInstaller byondInstaller, Version version) + static async Task> GetByondVersionPriorTo(IByondInstaller byondInstaller, Version version) { var minusOneMinor = new Version(version.Major, version.Minor - 1); + var byondVersion = new ByondVersion + { + Engine = EngineType.Byond, + Version = minusOneMinor + }; try { - return Tuple.Create(await byondInstaller.DownloadVersion(minusOneMinor, default), minusOneMinor); + return Tuple.Create(await byondInstaller.DownloadVersion( + byondVersion, + CancellationToken.None), byondVersion); } catch (HttpRequestException) { var minusOneMajor = new Version(minusOneMinor.Major - 1, minusOneMinor.Minor); - return Tuple.Create(await byondInstaller.DownloadVersion(minusOneMajor, default), minusOneMajor); + byondVersion.Version = minusOneMajor; + return Tuple.Create(await byondInstaller.DownloadVersion( + byondVersion, + CancellationToken.None), byondVersion); } } static async Task TestMapThreadsVersion( - Version byondVersion, + ByondVersion byondVersion, Stream byondBytes, IByondInstaller byondInstaller, IIOManager ioManager,