Merge pull request #1139 from tgstation/APIDocFix

Fix hosting API documentation in production
This commit is contained in:
Jordan Brown
2020-11-04 12:05:01 -05:00
committed by GitHub
19 changed files with 139 additions and 70 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
<!-- This is the authorative version list -->
<!-- Integration tests will ensure they match across the board -->
<TgsCoreVersion>4.5.4</TgsCoreVersion>
<TgsConfigVersion>2.1.0</TgsConfigVersion>
<TgsConfigVersion>2.1.1</TgsConfigVersion>
<TgsApiVersion>7.4.0</TgsApiVersion>
<TgsClientVersion>8.4.0</TgsClientVersion>
<TgsDmapiVersion>5.2.7</TgsDmapiVersion>
@@ -239,6 +239,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
protected override async Task Connect(CancellationToken cancellationToken)
{
disconnecting = false;
cancellationToken.ThrowIfCancellationRequested();
try
{
client.Connect(address, port);
@@ -1,8 +1,8 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.Reflection;
using Tgstation.Server.Host.Components.Interop.Converters;
using Tgstation.Server.Host.Properties;
namespace Tgstation.Server.Host.Components.Interop
{
@@ -34,11 +34,7 @@ namespace Tgstation.Server.Host.Components.Interop
/// <summary>
/// The DMAPI <see cref="Version"/> being used.
/// </summary>
public static readonly Version Version = Version.Parse(
Assembly
.GetExecutingAssembly()
.GetCustomAttribute<DMApiVersionAtrribute>()
.RawDMApiVersion);
public static readonly Version Version = Version.Parse(MasterVersionsAttribute.Instance.RawDMApiVersion);
/// <summary>
/// <see cref="JsonSerializerSettings"/> for use when communicating with the DMAPI.
@@ -1,25 +0,0 @@
using System;
namespace Tgstation.Server.Host.Components.Interop
{
/// <summary>
/// Attribute for bringing in the <see cref="DMApiConstants.Version"/> from MSBuild.
/// </summary>
[AttributeUsage(AttributeTargets.Assembly)]
sealed class DMApiVersionAtrribute : Attribute
{
/// <summary>
/// The <see cref="Version"/> string of the DMAPI version built.
/// </summary>
public string RawDMApiVersion { get; }
/// <summary>
/// Initializes a new instance of the <see cref="DMApiVersionAtrribute"/> <see langword="class"/>.
/// </summary>
/// <param name="rawDMApiVersion">The value of <see cref="RawDMApiVersion"/>.</param>
public DMApiVersionAtrribute(string rawDMApiVersion)
{
RawDMApiVersion = rawDMApiVersion ?? throw new ArgumentNullException(nameof(rawDMApiVersion));
}
}
}
@@ -3,6 +3,8 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System;
using Tgstation.Server.Api.Models.Internal;
using Tgstation.Server.Host.Properties;
using Tgstation.Server.Host.Setup;
namespace Tgstation.Server.Host.Configuration
{
@@ -24,7 +26,7 @@ namespace Tgstation.Server.Host.Configuration
/// <summary>
/// The current <see cref="ConfigVersion"/>.
/// </summary>
public static readonly Version CurrentConfigVersion = new Version(2, 1, 0);
public static readonly Version CurrentConfigVersion = Version.Parse(MasterVersionsAttribute.Instance.RawConfigurationVersion);
/// <summary>
/// The default value for <see cref="ServerInformation.MinimumPasswordLength"/>.
@@ -90,7 +92,7 @@ namespace Tgstation.Server.Host.Configuration
/// <summary>
/// If the swagger UI should be made avaiable.
/// </summary>
public bool HostApiDocumemtation { get; set; }
public bool HostApiDocumentation { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="GeneralConfiguration"/> <see langword="class"/>.
+10 -5
View File
@@ -35,6 +35,7 @@ using Tgstation.Server.Host.Database;
using Tgstation.Server.Host.Extensions;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.Jobs;
using Tgstation.Server.Host.Properties;
using Tgstation.Server.Host.Security;
using Tgstation.Server.Host.Setup;
using Tgstation.Server.Host.System;
@@ -195,7 +196,7 @@ namespace Tgstation.Server.Host.Core
options.SerializerSettings.Converters = new[] { new VersionConverter() };
});
if (hostingEnvironment.IsDevelopment())
if (postSetupServices.GeneralConfiguration.HostApiDocumentation)
{
static string GetDocumentationFilePath(string assemblyLocation) => IOManager.ConcatPath(IOManager.GetDirectoryName(assemblyLocation), String.Concat(IOManager.GetFileNameWithoutExtension(assemblyLocation), ".xml"));
var assemblyDocumentationPath = GetDocumentationFilePath(typeof(Application).Assembly.Location);
@@ -396,8 +397,7 @@ namespace Tgstation.Server.Host.Core
// suppress OperationCancelledExceptions, they are just aborted HTTP requests
applicationBuilder.UseCancelledRequestSuppression();
if (hostingEnvironment.IsDevelopment()
|| generalConfiguration.HostApiDocumemtation)
if (generalConfiguration.HostApiDocumentation)
{
applicationBuilder.UseSwagger();
applicationBuilder.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "TGS API V4"));
@@ -434,8 +434,6 @@ namespace Tgstation.Server.Host.Core
else
logger.LogDebug("Web control panel disabled!");
logger.LogDebug("Starting hosting...");
// authenticate JWT tokens using our security pipeline if present, returns 401 if bad
applicationBuilder.UseAuthentication();
@@ -446,6 +444,13 @@ namespace Tgstation.Server.Host.Core
applicationBuilder.UseMvc();
// 404 anything that gets this far
// End of request pipeline setup
var masterVersionsAttribute = MasterVersionsAttribute.Instance;
logger.LogTrace("Configuration version: {0}", masterVersionsAttribute.RawConfigurationVersion);
logger.LogTrace("DMAPI version: {0}", masterVersionsAttribute.RawDMApiVersion);
logger.LogTrace("Web control panel version: {0}", masterVersionsAttribute.RawControlPanelVersion);
logger.LogDebug("Starting hosting...");
}
}
}
+2 -2
View File
@@ -1,4 +1,4 @@
using System.Threading;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Host.IO;
@@ -20,7 +20,7 @@ namespace Tgstation.Server.Host
/// <param name="args">The arguments for the <see cref="IServer"/></param>
/// <param name="updatePath">The directory in which to install server updates</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in a new <see cref="IServer"/></returns>
/// <returns>A <see cref="Task{TResult}"/> resulting in a new <see cref="IServer"/> if it should be run, <see langword="null"/> otherwise.</returns>
Task<IServer> CreateServer(string[] args, string updatePath, CancellationToken cancellationToken);
}
}
+2 -3
View File
@@ -207,10 +207,9 @@ namespace Tgstation.Server.Host.Jobs
try
{
lock (synchronizationLock)
{
jobs.Add(job.Id, jobHandler);
jobHandler.Start();
}
jobHandler.Start();
}
catch
{
@@ -0,0 +1,50 @@
using System;
using System.Reflection;
namespace Tgstation.Server.Host.Properties
{
/// <summary>
/// Attribute for bringing in the master versions list from MSBuild that aren't embedded into assemblies by default.
/// </summary>
[AttributeUsage(AttributeTargets.Assembly)]
sealed class MasterVersionsAttribute : Attribute
{
/// <summary>
/// Return the <see cref="Assembly"/>'s instance of the <see cref="MasterVersionsAttribute"/>.
/// </summary>
public static MasterVersionsAttribute Instance => Assembly
.GetExecutingAssembly()
.GetCustomAttribute<MasterVersionsAttribute>();
/// <summary>
/// The <see cref="Version"/> <see cref="string"/> of the <see cref="Configuration"/> version built.
/// </summary>
public string RawConfigurationVersion { get; }
/// <summary>
/// The <see cref="Version"/> <see cref="string"/> of the DMAPI version built.
/// </summary>
public string RawDMApiVersion { get; }
/// <summary>
/// The <see cref="Version"/> <see cref="string"/> of the control panel version built.
/// </summary>
public string RawControlPanelVersion { get; }
/// <summary>
/// Initializes a new instance of the <see cref="MasterVersionsAttribute"/> <see langword="class"/>.
/// </summary>
/// <param name="rawConfigurationVersion">The value of <see cref="RawConfigurationVersion"/>.</param>
/// <param name="rawDMApiVersion">The value of <see cref="RawDMApiVersion"/>.</param>
/// <param name="rawControlPanelVersion">The value of <see cref="RawControlPanelVersion"/>.</param>
public MasterVersionsAttribute(
string rawConfigurationVersion,
string rawDMApiVersion,
string rawControlPanelVersion)
{
RawConfigurationVersion = rawConfigurationVersion ?? throw new ArgumentNullException(nameof(rawConfigurationVersion));
RawDMApiVersion = rawDMApiVersion ?? throw new ArgumentNullException(nameof(rawDMApiVersion));
RawControlPanelVersion = rawControlPanelVersion ?? throw new ArgumentNullException(nameof(rawControlPanelVersion));
}
}
}
+15 -6
View File
@@ -1,8 +1,9 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;
@@ -39,24 +40,31 @@ namespace Tgstation.Server.Host
}
/// <inheritdoc />
// TODO: Decomplexify
#pragma warning disable CA1506
public async Task<IServer> CreateServer(string[] args, string updatePath, CancellationToken cancellationToken)
{
if (args == null)
throw new ArgumentNullException(nameof(args));
var basePath = IOManager.ResolvePath();
IHostBuilder CreateDefaultBuilder() => Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, configuration) => configuration
.SetBasePath(
IOManager.ResolvePath()));
.ConfigureAppConfiguration((context, configuration) => configuration.SetBasePath(basePath));
var setupWizardHostBuilder = CreateDefaultBuilder()
.UseSetupApplication();
IPostSetupServices postSetupServices;
IPostSetupServices<ServerFactory> postSetupServices;
using (var setupHost = setupWizardHostBuilder.Build())
{
postSetupServices = setupHost.Services.GetRequiredService<IPostSetupServices>();
postSetupServices = setupHost.Services.GetRequiredService<IPostSetupServices<ServerFactory>>();
await setupHost.RunAsync(cancellationToken).ConfigureAwait(false);
if (postSetupServices.GeneralConfiguration.SetupWizardMode == SetupWizardMode.Only)
{
postSetupServices.Logger.LogInformation("Shutting down due to only running setup wizard.");
return null;
}
}
var hostBuilder = CreateDefaultBuilder()
@@ -82,5 +90,6 @@ namespace Tgstation.Server.Host
return new Server(hostBuilder, updatePath);
}
#pragma warning restore CA1506
}
}
@@ -0,0 +1,16 @@
using Microsoft.Extensions.Logging;
namespace Tgstation.Server.Host.Setup
{
/// <summary>
/// <see cref="IPostSetupServices"/> with a <see cref="Logger"/>.
/// </summary>
/// <typeparam name="TLoggerType">The category <see cref="global::System.Type"/> for <see cref="Logger"/>.</typeparam>
interface IPostSetupServices<TLoggerType> : IPostSetupServices
{
/// <summary>
/// The <see cref="ILogger"/>.
/// </summary>
ILogger<TLoggerType> Logger { get; }
}
}
@@ -1,4 +1,5 @@
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.System;
@@ -6,7 +7,7 @@ using Tgstation.Server.Host.System;
namespace Tgstation.Server.Host.Setup
{
/// <inheritdoc />
sealed class PostSetupServices : IPostSetupServices
sealed class PostSetupServices<TLoggerType> : IPostSetupServices<TLoggerType>
{
/// <inheritdoc />
public IPlatformIdentifier PlatformIdentifier { get; }
@@ -20,6 +21,9 @@ namespace Tgstation.Server.Host.Setup
/// <inheritdoc />
public FileLoggingConfiguration FileLoggingConfiguration => fileLoggingConfigurationOptions.Value;
/// <inheritdoc />
public ILogger<TLoggerType> Logger { get; }
/// <summary>
/// Backing <see cref="IOptions{TOptions}"/> for <see cref="GeneralConfiguration"/>.
/// </summary>
@@ -36,19 +40,25 @@ namespace Tgstation.Server.Host.Setup
readonly IOptions<FileLoggingConfiguration> fileLoggingConfigurationOptions;
/// <summary>
/// Initializes a new instance of the <see cref="PostSetupServices"/> <see langword="class"/>.
/// Initializes a new instance of the <see cref="PostSetupServices{TLoggerType}"/> <see langword="class"/>.
/// </summary>
/// <param name="platformIdentifier">The value of <see cref="PlatformIdentifier"/>.</param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/> used to create <see cref="Logger"/>.</param>
/// <param name="generalConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="GeneralConfiguration"/>.</param>
/// <param name="databaseConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="DatabaseConfiguration"/>.</param>
/// <param name="fileLoggingConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="FileLoggingConfiguration"/>.</param>
public PostSetupServices(
IPlatformIdentifier platformIdentifier,
ILoggerFactory loggerFactory,
IOptions<GeneralConfiguration> generalConfigurationOptions,
IOptions<DatabaseConfiguration> databaseConfigurationOptions,
IOptions<FileLoggingConfiguration> fileLoggingConfigurationOptions)
{
PlatformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier));
if (loggerFactory == null)
throw new ArgumentNullException(nameof(loggerFactory));
Logger = loggerFactory.CreateLogger<TLoggerType>();
this.generalConfigurationOptions = generalConfigurationOptions ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
this.databaseConfigurationOptions = databaseConfigurationOptions ?? throw new ArgumentNullException(nameof(databaseConfigurationOptions));
this.fileLoggingConfigurationOptions = fileLoggingConfigurationOptions ?? throw new ArgumentNullException(nameof(fileLoggingConfigurationOptions));
@@ -1,4 +1,4 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Serilog.Events;
@@ -73,7 +73,7 @@ namespace Tgstation.Server.Host.Setup
/// <param name="services">The <see cref="IServiceCollection"/> to configure.</param>
protected virtual void ConfigureHostedService(IServiceCollection services)
{
services.AddSingleton<IPostSetupServices, PostSetupServices>();
services.AddSingleton(typeof(IPostSetupServices<>), typeof(PostSetupServices<>));
services.AddSingleton<IHostedService, SetupWizard>();
}
}
@@ -613,7 +613,7 @@ namespace Tgstation.Server.Host.Setup
if (String.IsNullOrWhiteSpace(newGeneralConfiguration.GitHubAccessToken))
newGeneralConfiguration.GitHubAccessToken = null;
newGeneralConfiguration.HostApiDocumemtation = await PromptYesNo("Host API Documentation? (y/n): ", cancellationToken).ConfigureAwait(false);
newGeneralConfiguration.HostApiDocumentation = await PromptYesNo("Host API Documentation? (y/n): ", cancellationToken).ConfigureAwait(false);
return newGeneralConfiguration;
}
@@ -1,27 +1,27 @@
namespace Tgstation.Server.Host.Configuration
namespace Tgstation.Server.Host.Setup
{
/// <summary>
/// Determines if the <see cref="Setup.SetupWizard"/> will run
/// Determines if the <see cref="SetupWizard"/> will run.
/// </summary>
public enum SetupWizardMode
{
/// <summary>
/// Run the wizard if the appsettings.{Environment}.json is not present or empty
/// Run the wizard if the appsettings.{Environment}.json is not present or empty.
/// </summary>
Autodetect,
/// <summary>
/// Force run the wizard
/// Force run the wizard.
/// </summary>
Force,
/// <summary>
/// Only run the wizard and exit
/// Only run the wizard and exit.
/// </summary>
Only,
/// <summary>
/// Never run the wizard
/// Never run the wizard.
/// </summary>
Never
}
@@ -41,14 +41,16 @@
<RemoveDir Directories="wwwroot" />
</Target>
<Target Name="GenerateBuildVersionsAttribute" BeforeTargets="CoreCompile">
<Target Name="ApplyMasterVersionsAttribute" BeforeTargets="CoreCompile">
<ItemGroup>
<AssemblyAttributes Include="Tgstation.Server.Host.Components.Interop.DMApiVersionAtrribute">
<_Parameter1>$(TgsDmapiVersion)</_Parameter1>
<AssemblyAttributes Include="Tgstation.Server.Host.Properties.MasterVersionsAttribute">
<_Parameter1>$(TgsConfigVersion)</_Parameter1>
<_Parameter2>$(TgsDmapiVersion)</_Parameter2>
<_Parameter3>$(TgsControlPanelVersion)</_Parameter3>
</AssemblyAttributes>
</ItemGroup>
<WriteCodeFragment AssemblyAttributes="@(AssemblyAttributes)" Language="C#" OutputDirectory="$(IntermediateOutputPath)" OutputFile="DMApiVersion.cs">
<WriteCodeFragment AssemblyAttributes="@(AssemblyAttributes)" Language="C#" OutputDirectory="$(IntermediateOutputPath)" OutputFile="MasterVersionsAssemblyInfo.cs">
<Output TaskParameter="OutputFile" ItemName="Compile" />
<Output TaskParameter="OutputFile" ItemName="FileWrites" />
</WriteCodeFragment>
+1 -1
View File
@@ -10,7 +10,7 @@
"UserLimit": 100,
"InstanceLimit": 10,
"ValidInstancePaths": null,
"HostApiDocumemtation": false
"HostApiDocumentation": false
},
"FileLogging": {
"Directory": null,
@@ -1,4 +1,4 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using System;
using System.Threading.Tasks;
@@ -30,7 +30,8 @@ namespace Tgstation.Server.Host.Tests
var factory = Application.CreateDefaultServerFactory();
await Assert.ThrowsExceptionAsync<ArgumentNullException>(() => factory.CreateServer(null, null, default));
await factory.CreateServer(new[] { "General:SetupWizardMode=Never" }, null, default);
var result = await factory.CreateServer(new[] { "General:SetupWizardMode=Never" }, null, default);
Assert.IsNotNull(result);
}
[TestMethod]
@@ -41,7 +42,8 @@ namespace Tgstation.Server.Host.Tests
await Assert.ThrowsExceptionAsync<ArgumentNullException>(() => factory.CreateServer(null, null, default));
await Assert.ThrowsExceptionAsync<ArgumentNullException>(() => factory.CreateServer(null, Path, default));
await factory.CreateServer(new[] { "General:SetupWizardMode=Never" }, Path, default);
var result = await factory.CreateServer(new[] { "General:SetupWizardMode=Never" }, Path, default);
Assert.IsNotNull(result);
}
}
}
@@ -9,6 +9,7 @@ using System.Threading.Tasks;
using Tgstation.Server.Host;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.Setup;
namespace Tgstation.Server.Tests
{
@@ -78,6 +79,7 @@ namespace Tgstation.Server.Tests
String.Format(CultureInfo.InvariantCulture, "General:MinimumPasswordLength={0}", 10),
String.Format(CultureInfo.InvariantCulture, "General:InstanceLimit={0}", 11),
String.Format(CultureInfo.InvariantCulture, "General:UserLimit={0}", 150),
String.Format(CultureInfo.InvariantCulture, "General:HostApiDocumentation={0}", DumpOpenApiSpecpath),
String.Format(CultureInfo.InvariantCulture, "FileLogging:Directory={0}", Path.Combine(Directory, "Logs")),
String.Format(CultureInfo.InvariantCulture, "FileLogging:LogLevel={0}", "Trace"),
String.Format(CultureInfo.InvariantCulture, "General:ValidInstancePaths:0={0}", Directory),