Add some general configuration options to ServerInformation

Ensure we use Semver everywhere
This commit is contained in:
Jordan Brown
2020-04-24 20:52:41 -04:00
parent 306858baa2
commit ff552186a7
18 changed files with 125 additions and 63 deletions
+3 -3
View File
@@ -17,7 +17,7 @@ namespace Tgstation.Server.Api
public sealed class ApiHeaders
{
/// <summary>
/// TODO: Remove this when https://github.com/dotnet/corefx/pull/26701 makes it into the sdk
/// TODO: Remove this when we upgrade to .NET Standard 2.1
/// </summary>
public const string ApplicationJson = "application/json";
@@ -59,7 +59,7 @@ namespace Tgstation.Server.Api
/// <summary>
/// Get the version of the <see cref="Api"/> the caller is using
/// </summary>
public static Version Version => AssemblyName.Version;
public static readonly Version Version = AssemblyName.Version.Semver();
/// <summary>
/// The <see cref="Models.Instance.Id"/> being accessed
@@ -164,7 +164,7 @@ namespace Tgstation.Server.Api
if (!Version.TryParse(apiUserAgent.Product.Version, out var apiVersion))
throw new InvalidOperationException("Malformed API version!");
ApiVersion = apiVersion;
ApiVersion = apiVersion.Semver();
if (!requestHeaders.Headers.TryGetValue(HeaderNames.Authorization, out StringValues authorization))
throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Missing {0} header!", HeaderNames.Authorization));
@@ -18,7 +18,7 @@ namespace Tgstation.Server.Api.Models
public Uri TrackedRepositoryUrl { get; set; }
/// <summary>
/// The latest available version of the Tgstation.Server.Host assembly from the upstream repository. If <see cref="Version.Minor"/> is higher than <see cref="NewVersion"/>'s the update cannot be applied due to API changes
/// The latest available version of the Tgstation.Server.Host assembly from the upstream repository. If <see cref="Version.Major"/> is higher than <see cref="NewVersion"/>'s the update cannot be applied due to API changes
/// </summary>
public Version LatestVersion { get; set; }
+1 -3
View File
@@ -1,5 +1,4 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace Tgstation.Server.Api.Models
{
@@ -9,9 +8,8 @@ namespace Tgstation.Server.Api.Models
public sealed class Byond
{
/// <summary>
/// The <see cref="System.Version"/> of the <see cref="Byond"/> installation used for new compiles. Will be <see langword="null"/> if the user does not have permission to view it or there is no BYOND version installed. Only considers the <see cref="Version.Major"/> and <see cref="Version.Minor"/> numbers
/// The <see cref="System.Version"/> of the <see cref="Byond"/> installation used for new compiles. Will be <see langword="null"/> if the user does not have permission to view it or there is no BYOND version installed. Only considers the <see cref="Version.Major"/> and <see cref="Version.Minor"/> numbers.
/// </summary>
[Required]
public Version Version { get; set; }
/// <summary>
@@ -0,0 +1,23 @@
namespace Tgstation.Server.Api.Models.Internal
{
/// <summary>
/// Base class for <see cref="Models.ServerInformation"/>.
/// </summary>
public abstract class ServerInformation
{
/// <summary>
/// Minimum length of database user passwords.
/// </summary>
public uint MinimumPasswordLength { get; set; }
/// <summary>
/// The maximum number of <see cref="Instance"/>s allowed.
/// </summary>
public uint InstanceLimit { get; set; }
/// <summary>
/// The maximum number of <see cref="Models.User"/>s allowed.
/// </summary>
public uint UserLimit { get; set; }
}
}
@@ -3,9 +3,9 @@
namespace Tgstation.Server.Api.Models
{
/// <summary>
/// Represents basic server information
/// Represents basic server information.
/// </summary>
public sealed class ServerInformation
public sealed class ServerInformation : Internal.ServerInformation
{
/// <summary>
/// The version of the host
@@ -0,0 +1,26 @@
using System;
namespace Tgstation.Server.Api
{
/// <summary>
/// Extensions for the <see cref="Version"/> <see langword="class"/>.
/// </summary>
public static class VersionExtensions
{
/// <summary>
/// Converts a given <paramref name="version"/> into one with only <see cref="Version.Major"/>, <see cref="Version.Minor"/>, and <see cref="Version.Build"/>.
/// </summary>
/// <param name="version">The <see cref="Version"/> to convert.</param>
/// <returns>A semver <see cref="Version"/> base on <paramref name="version"/>.</returns>
public static Version Semver(this Version version)
{
if (version == null)
throw new ArgumentNullException(nameof(version));
return new Version(
version.Major,
version.Minor,
version.Build == -1 ? 0 : version.Build);
}
}
}
@@ -7,6 +7,7 @@ using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Api;
using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.Jobs;
@@ -50,7 +51,7 @@ namespace Tgstation.Server.Host.Components.Byond
get
{
lock (installedVersions)
return installedVersions.Select(x => Version.Parse(x.Key)).ToList();
return installedVersions.Select(x => Version.Parse(x.Key).Semver()).ToList();
}
}
@@ -297,7 +298,7 @@ namespace Tgstation.Server.Host.Components.Byond
{
var activeVersionString = Encoding.UTF8.GetString(activeVersionBytes);
if (Version.TryParse(activeVersionString, out var activeVersion))
ActiveVersion = activeVersion;
ActiveVersion = activeVersion.Semver();
else
await ioManager.DeleteFile(ActiveVersionFileName, cancellationToken).ConfigureAwait(false);
}
@@ -1,6 +1,6 @@
using Newtonsoft.Json;
using System;
using Tgstation.Server.Host.Extensions;
using Tgstation.Server.Api;
namespace Tgstation.Server.Host.Components.Interop.Converters
{
@@ -18,7 +18,7 @@ namespace Tgstation.Server.Host.Components.Interop.Converters
}
else if (value is Version version)
{
writer.WriteValue(version.Semver());
writer.WriteValue(version.Semver().ToString());
}
else
{
@@ -37,7 +37,7 @@ namespace Tgstation.Server.Host.Components.Interop.Converters
try
{
Version v = new Version((string)reader.Value);
return v;
return v.Semver();
}
catch (Exception ex)
{
@@ -5,6 +5,7 @@ using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Api;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Internal;
using Tgstation.Server.Host.Components.Byond;
@@ -13,7 +14,6 @@ using Tgstation.Server.Host.Components.Deployment;
using Tgstation.Server.Host.Components.Interop;
using Tgstation.Server.Host.Components.Interop.Bridge;
using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.Extensions;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.Security;
using Tgstation.Server.Host.System;
@@ -196,7 +196,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
// set command line options
// more sanitization here cause it uses the same scheme
var parameters = $"{DMApiConstants.ParamApiVersion}={byondTopicSender.SanitizeString(DMApiConstants.Version.Semver())}&{byondTopicSender.SanitizeString(DMApiConstants.ParamServerPort)}={serverPortProvider.HttpApiPort}&{byondTopicSender.SanitizeString(DMApiConstants.ParamAccessIdentifier)}={byondTopicSender.SanitizeString(accessIdentifier)}";
var parameters = $"{DMApiConstants.ParamApiVersion}={byondTopicSender.SanitizeString(DMApiConstants.Version.Semver().ToString())}&{byondTopicSender.SanitizeString(DMApiConstants.ParamServerPort)}={serverPortProvider.HttpApiPort}&{byondTopicSender.SanitizeString(DMApiConstants.ParamAccessIdentifier)}={byondTopicSender.SanitizeString(accessIdentifier)}";
var visibility = apiValidate ? "invisible" : "public";
@@ -1,12 +1,13 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Tgstation.Server.Api.Models.Internal;
namespace Tgstation.Server.Host.Configuration
{
/// <summary>
/// General configuration options
/// </summary>
public sealed class GeneralConfiguration
public sealed class GeneralConfiguration : ServerInformation
{
/// <summary>
/// The key for the <see cref="Microsoft.Extensions.Configuration.IConfigurationSection"/> the <see cref="GeneralConfiguration"/> resides in
@@ -14,10 +15,20 @@ namespace Tgstation.Server.Host.Configuration
public const string Section = "General";
/// <summary>
/// The default value for <see cref="MinimumPasswordLength"/>
/// The default value for <see cref="ServerInformation.MinimumPasswordLength"/>.
/// </summary>
const uint DefaultMinimumPasswordLength = 15;
/// <summary>
/// The default value for <see cref="ServerInformation.InstanceLimit"/>.
/// </summary>
const uint DefaultInstanceLimit = 10;
/// <summary>
/// The default value for <see cref="ServerInformation.UserLimit"/>.
/// </summary>
const uint DefaultUserLimit = 100;
/// <summary>
/// The default value for <see cref="ByondTopicTimeout"/>
/// </summary>
@@ -28,11 +39,6 @@ namespace Tgstation.Server.Host.Configuration
/// </summary>
const int DefaultRestartTimeout = 10000;
/// <summary>
/// Minimum length of database user passwords
/// </summary>
public uint MinimumPasswordLength { get; set; } = DefaultMinimumPasswordLength;
/// <summary>
/// A GitHub personal access token to use for bypassing rate limits on requests. Requires no scopes
/// </summary>
@@ -65,13 +71,13 @@ namespace Tgstation.Server.Host.Configuration
public bool UseBasicWatchdogOnWindows { get; set; }
/// <summary>
/// The maximum number of <see cref="Models.Instance"/>s allowed.
/// Initializes a new instance of the <see cref="GeneralConfiguration"/> <see langword="class"/>.
/// </summary>
public uint InstanceLimit { get; set; }
/// <summary>
/// The maximum number of <see cref="Models.User"/>s allowed.
/// </summary>
public uint UserLimit { get; set; }
public GeneralConfiguration()
{
MinimumPasswordLength = DefaultMinimumPasswordLength;
InstanceLimit = DefaultInstanceLimit;
UserLimit = DefaultUserLimit;
}
}
}
@@ -10,7 +10,6 @@ using System.Threading.Tasks;
using Tgstation.Server.Api;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Host.Database;
using Tgstation.Server.Host.Extensions;
using Tgstation.Server.Host.Security;
namespace Tgstation.Server.Host.Controllers
@@ -58,6 +58,11 @@ namespace Tgstation.Server.Host.Controllers
/// </summary>
readonly IBrowserResolver browserResolver;
/// <summary>
/// The <see cref="GeneralConfiguration"/> for the <see cref="HomeController"/>.
/// </summary>
readonly GeneralConfiguration generalConfiguration;
/// <summary>
/// The <see cref="ControlPanelConfiguration"/> for the <see cref="HomeController"/>
/// </summary>
@@ -74,9 +79,22 @@ namespace Tgstation.Server.Host.Controllers
/// <param name="assemblyInformationProvider">The value of <see cref="assemblyInformationProvider"/></param>
/// <param name="identityCache">The value of <see cref="identityCache"/></param>
/// <param name="browserResolver">The value of <see cref="browserResolver"/></param>
/// <param name="generalConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="generalConfiguration"/>.</param>
/// <param name="controlPanelConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="controlPanelConfiguration"/></param>
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="ApiController"/></param>
public HomeController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ITokenFactory tokenFactory, ISystemIdentityFactory systemIdentityFactory, ICryptographySuite cryptographySuite, IAssemblyInformationProvider assemblyInformationProvider, IIdentityCache identityCache, IBrowserResolver browserResolver, IOptions<ControlPanelConfiguration> controlPanelConfigurationOptions, ILogger<HomeController> logger) : base(databaseContext, authenticationContextFactory, logger, false, false)
public HomeController(
IDatabaseContext databaseContext,
IAuthenticationContextFactory authenticationContextFactory,
ITokenFactory tokenFactory,
ISystemIdentityFactory systemIdentityFactory,
ICryptographySuite cryptographySuite,
IAssemblyInformationProvider assemblyInformationProvider,
IIdentityCache identityCache,
IBrowserResolver browserResolver,
IOptions<GeneralConfiguration> generalConfigurationOptions,
IOptions<ControlPanelConfiguration> controlPanelConfigurationOptions,
ILogger<HomeController> logger)
: base(databaseContext, authenticationContextFactory, logger, false, false)
{
this.tokenFactory = tokenFactory ?? throw new ArgumentNullException(nameof(tokenFactory));
this.systemIdentityFactory = systemIdentityFactory ?? throw new ArgumentNullException(nameof(systemIdentityFactory));
@@ -84,6 +102,7 @@ namespace Tgstation.Server.Host.Controllers
this.assemblyInformationProvider = assemblyInformationProvider ?? throw new ArgumentNullException(nameof(assemblyInformationProvider));
this.identityCache = identityCache ?? throw new ArgumentNullException(nameof(identityCache));
this.browserResolver = browserResolver ?? throw new ArgumentNullException(nameof(browserResolver));
generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
controlPanelConfiguration = controlPanelConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(controlPanelConfigurationOptions));
}
@@ -104,7 +123,10 @@ namespace Tgstation.Server.Host.Controllers
return Json(new Api.Models.ServerInformation
{
Version = assemblyInformationProvider.Version,
ApiVersion = ApiHeaders.Version
ApiVersion = ApiHeaders.Version,
MinimumPasswordLength = generalConfiguration.MinimumPasswordLength,
InstanceLimit = generalConfiguration.InstanceLimit,
UserLimit = generalConfiguration.UserLimit
});
// if we are using a browser and the control panel, soft redirect to the app page
@@ -11,7 +11,6 @@ using Tgstation.Server.Api;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Rights;
using Tgstation.Server.Host.Controllers;
using Tgstation.Server.Host.Extensions;
namespace Tgstation.Server.Host.Core
{
@@ -122,7 +121,7 @@ namespace Tgstation.Server.Host.Core
new OpenApiInfo
{
Title = "TGS API",
Version = ApiHeaders.Version.Semver()
Version = ApiHeaders.Version.Semver().ToString()
});
// Important to do this before applying our own filters
@@ -1,22 +0,0 @@
using System;
namespace Tgstation.Server.Host.Extensions
{
/// <summary>
/// Extensions for the <see cref="Version"/> <see langword="class"/>.
/// </summary>
static class VersionExtensions
{
/// <summary>
/// Converts a given <paramref name="version"/> into a semver <see cref="string"/>.
/// </summary>
/// <param name="version">The <see cref="Version"/> to convert.</param>
/// <returns>A semver <see cref="string"/> base on <paramref name="version"/>.</returns>
public static string Semver(this Version version)
{
if (version == null)
throw new ArgumentNullException(nameof(version));
return $"{version.Major}.{version.Minor}.{version.Build}";
}
}
}
@@ -1,6 +1,6 @@
using System;
using System.Reflection;
using Tgstation.Server.Host.Extensions;
using Tgstation.Server.Api;
namespace Tgstation.Server.Host.System
{
@@ -11,7 +11,7 @@ namespace Tgstation.Server.Host.System
public string VersionPrefix => "tgstation-server";
/// <inheritdoc />
public Version Version => Name.Version!;
public Version Version { get; }
/// <inheritdoc />
public AssemblyName Name { get; }
@@ -30,7 +30,8 @@ namespace Tgstation.Server.Host.System
Assembly assembly = Assembly.GetExecutingAssembly();
Path = assembly.Location;
Name = assembly.GetName();
VersionString = String.Concat(VersionPrefix, '-', Version.Semver());
Version = Name.Version.Semver();
VersionString = String.Concat(VersionPrefix, '-', Version);
}
}
}
@@ -22,7 +22,7 @@ namespace Tgstation.Server.Client.Tests
{
var sample = new Byond
{
Version = new Version(511, 1385)
Version = new Version(511, 1385, 0)
};
var sampleJson = JsonConvert.SerializeObject(sample, new JsonSerializerSettings
@@ -43,6 +43,7 @@ namespace Tgstation.Server.Client.Tests
var result = await client.Read<Byond>(Routes.Byond, default).ConfigureAwait(false);
Assert.AreEqual(sample.Version, result.Version);
Assert.AreEqual(0, result.Version.Build);
}
[TestMethod]
@@ -2,6 +2,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Api;
using Tgstation.Server.Client.Components;
namespace Tgstation.Server.Tests.Instance
@@ -51,7 +52,7 @@ namespace Tgstation.Server.Tests.Instance
Assert.Fail(job.ExceptionDetails);
var currentShit = await byondClient.ActiveVersion(cancellationToken).ConfigureAwait(false);
Assert.AreEqual(newModel.Version, currentShit.Version);
Assert.AreEqual(newModel.Version.Semver(), currentShit.Version);
}
async Task TestNoVersion(CancellationToken cancellationToken)
@@ -15,6 +15,7 @@ using Tgstation.Server.Api.Models;
using Tgstation.Server.Client;
using Tgstation.Server.Host;
using Tgstation.Server.Host.Components.Chat.Providers;
using Tgstation.Server.Host.Extensions;
namespace Tgstation.Server.Tests
{
@@ -81,6 +82,8 @@ namespace Tgstation.Server.Tests
}
}
// Disabled until 4.1.0 due to changes in version handling
[Ignore]
[TestMethod]
public async Task TestServerUpdate()
{
@@ -208,7 +211,11 @@ namespace Tgstation.Server.Tests
var serverInfo = await adminClient.Version(default).ConfigureAwait(false);
Assert.AreEqual(ApiHeaders.Version, serverInfo.ApiVersion);
Assert.AreEqual(typeof(IServer).Assembly.GetName().Version, serverInfo.Version);
var assemblyVersion = typeof(IServer).Assembly.GetName().Version.Semver();
Assert.AreEqual(assemblyVersion, serverInfo.Version);
Assert.AreEqual(15U, serverInfo.MinimumPasswordLength);
Assert.AreEqual(10U, serverInfo.InstanceLimit);
Assert.AreEqual(150U, serverInfo.UserLimit);
//check that modifying the token even slightly fucks up the auth
var newToken = new Token