mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-07-14 09:33:22 +01:00
Allow BYOND downloads to be configurable
This commit is contained in:
@@ -6,8 +6,10 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
|
||||
@@ -41,6 +43,11 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
/// <inheritdoc />
|
||||
protected override EngineType TargetEngineType => EngineType.Byond;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="GeneralConfiguration"/> <see cref="IOptionsMonitor{TOptions}"/> for the <see cref="ByondInstallerBase"/>.
|
||||
/// </summary>
|
||||
protected IOptionsMonitor<GeneralConfiguration> GeneralConfigurationOptions { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Path to the system user's local BYOND folder.
|
||||
/// </summary>
|
||||
@@ -52,25 +59,85 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
protected abstract string DreamMakerName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the URL formatter string for downloading a byond version of {0:Major} {1:Minor}.
|
||||
/// Template to do ${Marker:xxx} replacements in <see cref="GeneralConfiguration.ByondZipDownloadTemplate"/>.
|
||||
/// </summary>
|
||||
protected abstract string ByondRevisionsUrlTemplate { get; }
|
||||
protected abstract string OSMarkerTemplate { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IFileDownloader"/> for the <see cref="ByondInstallerBase"/>.
|
||||
/// </summary>
|
||||
readonly IFileDownloader fileDownloader;
|
||||
|
||||
/// <summary>
|
||||
/// Format a given <paramref name="byondZipDownloadTemplate"/>.
|
||||
/// </summary>
|
||||
/// <param name="semver">The BYOND version to download.</param>
|
||||
/// <param name="byondZipDownloadTemplate">The template.</param>
|
||||
/// <param name="osMarkerTemplate">The <see cref="OSMarkerTemplate"/>.</param>
|
||||
/// <returns>The formatted byond download <see cref="Uri"/>.</returns>
|
||||
/// <remarks>Exposed only for testability.</remarks>
|
||||
internal static Uri GetDownloadZipUrl(Version semver, string byondZipDownloadTemplate, string osMarkerTemplate)
|
||||
{
|
||||
// god forbid
|
||||
var guardGuid = Guid.NewGuid();
|
||||
|
||||
var url = byondZipDownloadTemplate
|
||||
.Replace("$$", guardGuid.ToString(), StringComparison.Ordinal)
|
||||
.Replace("${Major}", semver.Major.ToString(CultureInfo.InvariantCulture), StringComparison.Ordinal)
|
||||
.Replace("${Minor}", semver.Minor.ToString(CultureInfo.InvariantCulture), StringComparison.Ordinal);
|
||||
|
||||
var osMarkerPrefix = $"${{{osMarkerTemplate}:";
|
||||
var osMarkerIndex = url.IndexOf(osMarkerPrefix);
|
||||
while (osMarkerIndex != -1)
|
||||
{
|
||||
var start = osMarkerIndex + osMarkerPrefix.Length;
|
||||
var end = url.IndexOf('}', start);
|
||||
if (end == -1)
|
||||
break;
|
||||
|
||||
var substitution = url.Substring(start, end - start);
|
||||
url = url.Replace($"{osMarkerPrefix}{substitution}}}", substitution, StringComparison.Ordinal);
|
||||
|
||||
osMarkerIndex = url.IndexOf(osMarkerPrefix);
|
||||
}
|
||||
|
||||
// at this point, any other substitution attempts should be removed
|
||||
var otherMarkerPrefix = "${";
|
||||
var otherMarkerIndex = url.IndexOf(otherMarkerPrefix);
|
||||
while (otherMarkerIndex != -1)
|
||||
{
|
||||
var start = otherMarkerIndex + otherMarkerPrefix.Length;
|
||||
var end = url.IndexOf('}', start);
|
||||
if (end == -1)
|
||||
break;
|
||||
|
||||
var substitution = url.Substring(start, end - start);
|
||||
url = url.Replace($"{otherMarkerPrefix}{substitution}}}", String.Empty, StringComparison.Ordinal);
|
||||
|
||||
otherMarkerIndex = url.IndexOf(otherMarkerPrefix);
|
||||
}
|
||||
|
||||
url = url.Replace(guardGuid.ToString(), "$", StringComparison.Ordinal);
|
||||
|
||||
return new Uri(url);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ByondInstallerBase"/> class.
|
||||
/// </summary>
|
||||
/// <param name="ioManager">The <see cref="IIOManager"/> for the <see cref="EngineInstallerBase"/>.</param>
|
||||
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="EngineInstallerBase"/>.</param>
|
||||
/// <param name="fileDownloader">The value of <see cref="fileDownloader"/>.</param>
|
||||
protected ByondInstallerBase(IIOManager ioManager, ILogger<ByondInstallerBase> logger, IFileDownloader fileDownloader)
|
||||
/// <param name="generalConfigurationOptions">The value of <see cref="GeneralConfigurationOptions"/>.</param>
|
||||
protected ByondInstallerBase(
|
||||
IIOManager ioManager,
|
||||
ILogger<ByondInstallerBase> logger,
|
||||
IFileDownloader fileDownloader,
|
||||
IOptionsMonitor<GeneralConfiguration> generalConfigurationOptions)
|
||||
: base(ioManager, logger)
|
||||
{
|
||||
this.fileDownloader = fileDownloader ?? throw new ArgumentNullException(nameof(fileDownloader));
|
||||
GeneralConfigurationOptions = generalConfigurationOptions ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -195,8 +262,12 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
Uri GetDownloadZipUrl(EngineVersion version)
|
||||
{
|
||||
CheckVersionValidity(version);
|
||||
var url = String.Format(CultureInfo.InvariantCulture, ByondRevisionsUrlTemplate, version.Version!.Major, version.Version.Minor);
|
||||
return new Uri(url);
|
||||
|
||||
var guardGuid = Guid.NewGuid();
|
||||
|
||||
var semver = version.Version!;
|
||||
var template = GeneralConfigurationOptions.CurrentValue.ByondZipDownloadTemplate;
|
||||
return GetDownloadZipUrl(semver, template, OSMarkerTemplate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,11 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Common.Extensions;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.IO;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Engine
|
||||
@@ -39,7 +41,7 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
protected override string DreamMakerName => DreamMakerExecutableName + ShellScriptExtension;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override string ByondRevisionsUrlTemplate => "https://www.byond.com/download/build/{0}/{0}.{1}_byond_linux.zip";
|
||||
protected override string OSMarkerTemplate => "Linux";
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IPostWriteHandler"/> for the <see cref="PosixByondInstaller"/>.
|
||||
@@ -52,13 +54,15 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
/// <param name="postWriteHandler">The value of <see cref="postWriteHandler"/>.</param>
|
||||
/// <param name="ioManager">The <see cref="IIOManager"/> for the <see cref="ByondInstallerBase"/>.</param>
|
||||
/// <param name="fileDownloader">The <see cref="IFileDownloader"/> for the <see cref="ByondInstallerBase"/>.</param>
|
||||
/// <param name="generalConfigurationOptions">The <see cref="GeneralConfiguration"/> <see cref="IOptionsMonitor{TOptions}"/>.</param>
|
||||
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="ByondInstallerBase"/>.</param>
|
||||
public PosixByondInstaller(
|
||||
IPostWriteHandler postWriteHandler,
|
||||
IIOManager ioManager,
|
||||
IFileDownloader fileDownloader,
|
||||
IOptionsMonitor<GeneralConfiguration> generalConfigurationOptions,
|
||||
ILogger<PosixByondInstaller> logger)
|
||||
: base(ioManager, logger, fileDownloader)
|
||||
: base(ioManager, logger, fileDownloader, generalConfigurationOptions)
|
||||
{
|
||||
this.postWriteHandler = postWriteHandler ?? throw new ArgumentNullException(nameof(postWriteHandler));
|
||||
|
||||
|
||||
@@ -69,18 +69,13 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
protected override string PathToUserFolder { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override string ByondRevisionsUrlTemplate => "https://www.byond.com/download/build/{0}/{0}.{1}_byond.zip";
|
||||
protected override string OSMarkerTemplate => "Windows";
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IProcessExecutor"/> for the <see cref="WindowsByondInstaller"/>.
|
||||
/// </summary>
|
||||
readonly IProcessExecutor processExecutor;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="GeneralConfiguration"/> for the <see cref="WindowsByondInstaller"/>.
|
||||
/// </summary>
|
||||
readonly GeneralConfiguration generalConfiguration;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="SessionConfiguration"/> for the <see cref="WindowsByondInstaller"/>.
|
||||
/// </summary>
|
||||
@@ -100,7 +95,7 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
/// Initializes a new instance of the <see cref="WindowsByondInstaller"/> class.
|
||||
/// </summary>
|
||||
/// <param name="processExecutor">The value of <see cref="processExecutor"/>.</param>
|
||||
/// <param name="generalConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="generalConfiguration"/>.</param>
|
||||
/// <param name="generalConfigurationOptions">The <see cref="IOptionsMonitor{TOptions}"/> containing the <see cref="GeneralConfiguration"/>.</param>
|
||||
/// <param name="sessionConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="sessionConfiguration"/>.</param>
|
||||
/// <param name="ioManager">The <see cref="IIOManager"/> for the <see cref="ByondInstallerBase"/>.</param>
|
||||
/// <param name="fileDownloader">The <see cref="IFileDownloader"/> for the <see cref="ByondInstallerBase"/>.</param>
|
||||
@@ -109,13 +104,12 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
IProcessExecutor processExecutor,
|
||||
IIOManager ioManager,
|
||||
IFileDownloader fileDownloader,
|
||||
IOptions<GeneralConfiguration> generalConfigurationOptions,
|
||||
IOptionsMonitor<GeneralConfiguration> generalConfigurationOptions,
|
||||
IOptions<SessionConfiguration> sessionConfigurationOptions,
|
||||
ILogger<WindowsByondInstaller> logger)
|
||||
: base(ioManager, logger, fileDownloader)
|
||||
: base(ioManager, logger, fileDownloader, generalConfigurationOptions)
|
||||
{
|
||||
this.processExecutor = processExecutor ?? throw new ArgumentNullException(nameof(processExecutor));
|
||||
generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
|
||||
sessionConfiguration = sessionConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(sessionConfigurationOptions));
|
||||
|
||||
var useServiceSpecialTactics = Environment.Is64BitProcess && Environment.UserName == $"{Environment.MachineName}$";
|
||||
@@ -150,7 +144,7 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
installDirectXTask,
|
||||
};
|
||||
|
||||
if (!generalConfiguration.SkipAddingByondFirewallException)
|
||||
if (!GeneralConfigurationOptions.CurrentValue.SkipAddingByondFirewallException)
|
||||
{
|
||||
var firewallTask = AddDreamDaemonToFirewall(version, path, deploymentPipelineProcesses, cancellationToken);
|
||||
tasks.Add(firewallTask);
|
||||
@@ -165,7 +159,7 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
CheckVersionValidity(version);
|
||||
ArgumentNullException.ThrowIfNull(path);
|
||||
|
||||
if (generalConfiguration.SkipAddingByondFirewallException)
|
||||
if (GeneralConfigurationOptions.CurrentValue.SkipAddingByondFirewallException)
|
||||
return;
|
||||
|
||||
if (version.Version < DDExeVersion)
|
||||
|
||||
@@ -148,6 +148,17 @@ namespace Tgstation.Server.Host.Configuration
|
||||
[YamlMember(SerializeAs = typeof(string))]
|
||||
public Uri OpenDreamGitUrl { get; set; } = new Uri(DefaultOpenDreamGitUrl);
|
||||
|
||||
/// <summary>
|
||||
/// The formatter used to download official byond zip files for a given version
|
||||
/// - ${Major} is substituted with the major version number
|
||||
/// - ${Minor} is substituted with the minor version number
|
||||
/// - ${Linux:xxx}, where xxx is any string, will be substituted with xxx if running under Linux.
|
||||
/// - ${Windows:xxx}, where xxx is any string, will be substituted with xxx if running under Windows.
|
||||
/// - $$ will evaluate to a literal $ and not be used for substitutions.
|
||||
/// - Any inapplicable ${xxx} string will be removed.
|
||||
/// </summary>
|
||||
public string ByondZipDownloadTemplate { get; set; } = "https://www.byond.com/download/build/${Major}/${Major}.{Minor}_byond${Linux:_linux}.zip";
|
||||
|
||||
/// <summary>
|
||||
/// The prefix to the OpenDream semver as tags appear in the git repository.
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Engine.Tests
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class TestByondInstallerBase
|
||||
{
|
||||
[TestMethod]
|
||||
public void TestUrlTemplateFormatting()
|
||||
{
|
||||
const string OSMarker = "TempleOS";
|
||||
|
||||
Assert.AreEqual(
|
||||
new Uri("https://example.com/$515.1111_Hello Worl$d.zip"),
|
||||
ByondInstallerBase.GetDownloadZipUrl(
|
||||
new Version(515, 1111),
|
||||
"https://example.com/$$${Major}.${Minor}_${TempleOS:Hello Worl$$d}.zip${Linux:Not this}${Or This}",
|
||||
OSMarker));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using System;
|
||||
@@ -9,6 +10,7 @@ using System.Threading.Tasks;
|
||||
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.IO;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Engine.Tests
|
||||
@@ -19,16 +21,18 @@ namespace Tgstation.Server.Host.Components.Engine.Tests
|
||||
[TestMethod]
|
||||
public void TestConstruction()
|
||||
{
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new PosixByondInstaller(null, null, null, null));
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new PosixByondInstaller(null, null, null, null, null));
|
||||
var mockPostWriteHandler = new Mock<IPostWriteHandler>();
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new PosixByondInstaller(mockPostWriteHandler.Object, null, null, null));
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new PosixByondInstaller(mockPostWriteHandler.Object, null, null, null, null));
|
||||
var mockIOManager = new Mock<IIOManager>();
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, null, null));
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, null, null, null));
|
||||
var mockFileDownloader = Mock.Of<IFileDownloader>();
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockFileDownloader, null));
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockFileDownloader, null, null));
|
||||
var mockOptions = Mock.Of<IOptionsMonitor<GeneralConfiguration>>();
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockFileDownloader, mockOptions, null));
|
||||
|
||||
var mockLogger = new Mock<ILogger<PosixByondInstaller>>();
|
||||
_ = new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockFileDownloader, mockLogger.Object);
|
||||
_ = new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockFileDownloader, mockOptions, mockLogger.Object);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -38,7 +42,8 @@ namespace Tgstation.Server.Host.Components.Engine.Tests
|
||||
var mockIOManager = new Mock<IIOManager>();
|
||||
var mockLogger = new Mock<ILogger<PosixByondInstaller>>();
|
||||
var mockFileDownloader = Mock.Of<IFileDownloader>();
|
||||
var installer = new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockFileDownloader, mockLogger.Object);
|
||||
var mockOptions = Mock.Of<IOptionsMonitor<GeneralConfiguration>>();
|
||||
var installer = new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockFileDownloader, mockOptions, mockLogger.Object);
|
||||
await installer.CleanCache(default);
|
||||
}
|
||||
|
||||
@@ -50,7 +55,8 @@ namespace Tgstation.Server.Host.Components.Engine.Tests
|
||||
var mockPostWriteHandler = new Mock<IPostWriteHandler>();
|
||||
var mockLogger = new Mock<ILogger<PosixByondInstaller>>();
|
||||
var mockFileDownloader = new Mock<IFileDownloader>();
|
||||
var installer = new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockFileDownloader.Object, mockLogger.Object);
|
||||
var mockOptions = Mock.Of<IOptionsMonitor<GeneralConfiguration>>();
|
||||
var installer = new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockFileDownloader.Object, mockOptions, mockLogger.Object);
|
||||
|
||||
await Assert.ThrowsExceptionAsync<ArgumentNullException>(() => installer.DownloadVersion(null, null, default).AsTask());
|
||||
|
||||
@@ -87,7 +93,8 @@ namespace Tgstation.Server.Host.Components.Engine.Tests
|
||||
var mockPostWriteHandler = new Mock<IPostWriteHandler>();
|
||||
var mockLogger = new Mock<ILogger<PosixByondInstaller>>();
|
||||
var mockFileDownloader = Mock.Of<IFileDownloader>();
|
||||
var installer = new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockFileDownloader, mockLogger.Object);
|
||||
var mockOptions = Mock.Of<IOptionsMonitor<GeneralConfiguration>>();
|
||||
var installer = new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockFileDownloader, mockOptions, mockLogger.Object);
|
||||
|
||||
const string FakePath = "fake";
|
||||
await Assert.ThrowsExceptionAsync<ArgumentNullException>(() => installer.Install(null, null, false, default).AsTask());
|
||||
|
||||
Reference in New Issue
Block a user