Merge branch 'V6' into OpenDream

This commit is contained in:
Jordan Dominion
2023-11-12 08:39:33 -05:00
28 changed files with 425 additions and 125 deletions
+4 -4
View File
@@ -526,7 +526,7 @@ jobs:
uses: actions/upload-artifact@v3
with:
name: openapi-spec
path: C:/swagger.json
path: C:/tgs_api.json
- name: Package Server Service
if: ${{ matrix.configuration == 'Release' && matrix.watchdog-type == 'Basic' }}
@@ -755,7 +755,7 @@ jobs:
path: ./swagger
- name: Lint OpenAPI Spec
run: npx lint-openapi -v -p -c build/OpenApiValidationSettings.json ./swagger/swagger.json
run: npx lint-openapi -v -p -c build/OpenApiValidationSettings.json ./swagger/tgs_api.json
upload-code-coverage:
name: Upload Code Coverage
@@ -1363,7 +1363,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.DEV_PUSH_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./swagger/swagger.json
asset_path: ./swagger/tgs_api.json
asset_name: swagger.json
asset_content_type: application/json
@@ -1671,7 +1671,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.DEV_PUSH_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./swagger/swagger.json
asset_path: ./swagger/tgs_api.json
asset_name: swagger.json
asset_content_type: application/json
+8 -6
View File
@@ -1,14 +1,16 @@
#!/bin/sh -e
adduser --system tgstation-server
mkdir -m 754 -p /var/log/tgstation-server
chown -R tgstation-server /etc/tgstation-server
chown -R tgstation-server /opt/tgstation-server
chown -R tgstation-server /var/log/tgstation-server
if [ -z "$2" ]; then
adduser --system tgstation-server
mkdir -m 754 -p /var/log/tgstation-server
chown -R tgstation-server /etc/tgstation-server
chown -R tgstation-server /opt/tgstation-server/lib
chown -R tgstation-server /var/log/tgstation-server
fi
#DEBHELPER#
if [ "$1" = "configure" ]; then
if [ -z "$2" ]; then
deb-systemd-helper stop 'tgstation-server.service' >/dev/null || true
echo " _ _ _ _ "
+2 -2
View File
@@ -42,11 +42,11 @@
var/datum/tgs_version/max_api_version = TgsMaximumApiVersion();
if(version.suite != null && version.minor != null && version.patch != null && version.deprecated_patch != null && version.deprefixed_parameter > max_api_version.deprefixed_parameter)
TGS_ERROR_LOG("Detected unknown API version! Defaulting to latest. Update the DMAPI to fix this problem.")
TGS_ERROR_LOG("Detected unknown Interop API version! Defaulting to latest. Update the DMAPI to fix this problem.")
api_datum = /datum/tgs_api/latest
if(!api_datum)
TGS_ERROR_LOG("Found unsupported API version: [raw_parameter]. If this is a valid version please report this, backporting is done on demand.")
TGS_ERROR_LOG("Found unsupported Interop API version: [raw_parameter]. If this is a valid version please report this, backporting is done on demand.")
return
TGS_INFO_LOG("Activating API for version [version.deprefixed_parameter]")
+8 -4
View File
@@ -17,6 +17,8 @@
var/list/chat_channels
var/initialized = FALSE
var/initial_bridge_request_received = FALSE
var/datum/tgs_version/interop_version
var/chunked_requests = 0
var/list/chunked_topics = list()
@@ -25,7 +27,8 @@
/datum/tgs_api/v5/New()
. = ..()
TGS_DEBUG_LOG("V5 API created")
interop_version = version
TGS_DEBUG_LOG("V5 API created: [json_encode(args)]")
/datum/tgs_api/v5/ApiVersion()
return new /datum/tgs_version(
@@ -38,7 +41,7 @@
access_identifier = world.params[DMAPI5_PARAM_ACCESS_IDENTIFIER]
var/datum/tgs_version/api_version = ApiVersion()
version = null
version = null // we want this to be the TGS version, not the interop version
var/list/bridge_response = Bridge(DMAPI5_BRIDGE_COMMAND_STARTUP, list(DMAPI5_BRIDGE_PARAMETER_MINIMUM_SECURITY_LEVEL = minimum_required_security_level, DMAPI5_BRIDGE_PARAMETER_VERSION = api_version.raw_parameter, DMAPI5_PARAMETER_CUSTOM_COMMANDS = ListCustomCommands(), DMAPI5_PARAMETER_TOPIC_PORT = GetTopicPort()))
if(!istype(bridge_response))
TGS_ERROR_LOG("Failed initial bridge request!")
@@ -53,7 +56,8 @@
TGS_INFO_LOG("DMAPI validation, exiting...")
TerminateWorld()
version = new /datum/tgs_version(runtime_information[DMAPI5_RUNTIME_INFORMATION_SERVER_VERSION])
initial_bridge_request_received = TRUE
version = new /datum/tgs_version(runtime_information[DMAPI5_RUNTIME_INFORMATION_SERVER_VERSION]) // reassigning this because it can change if TGS updates
security_level = runtime_information[DMAPI5_RUNTIME_INFORMATION_SECURITY_LEVEL]
visibility = runtime_information[DMAPI5_RUNTIME_INFORMATION_VISIBILITY]
instance_name = runtime_information[DMAPI5_RUNTIME_INFORMATION_INSTANCE_NAME]
@@ -112,7 +116,7 @@
/datum/tgs_api/v5/proc/RequireInitialBridgeResponse()
TGS_DEBUG_LOG("RequireInitialBridgeResponse()")
var/logged = FALSE
while(!version)
while(!initial_bridge_request_received)
if(!logged)
TGS_DEBUG_LOG("RequireInitialBridgeResponse: Starting sleep")
logged = TRUE
+3 -1
View File
@@ -48,7 +48,9 @@
var/json = CreateBridgeData(command, data, TRUE)
var/encoded_json = url_encode(json)
var/url = "http://127.0.0.1:[server_port]/Bridge?[DMAPI5_BRIDGE_DATA]=[encoded_json]"
var/api_prefix = interop_version.minor >= 7 ? "api/" : ""
var/url = "http://127.0.0.1:[server_port]/[api_prefix]Bridge?[DMAPI5_BRIDGE_DATA]=[encoded_json]"
return url
/datum/tgs_api/v5/proc/CreateBridgeData(command, list/data, needs_auth)
+16 -16
View File
@@ -8,19 +8,19 @@ namespace Tgstation.Server.Api
public static class Routes
{
/// <summary>
/// The root controller.
/// The root of API methods.
/// </summary>
public const string Root = "/";
public const string ApiRoot = "/api/";
/// <summary>
/// The root route of all hubs.
/// </summary>
public const string HubsRoot = Root + "hubs";
public const string HubsRoot = ApiRoot + "hubs";
/// <summary>
/// The server administration controller.
/// </summary>
public const string Administration = Root + "Administration";
public const string Administration = ApiRoot + "Administration";
/// <summary>
/// The endpoint to download server logs.
@@ -30,32 +30,32 @@ namespace Tgstation.Server.Api
/// <summary>
/// The user controller.
/// </summary>
public const string User = Root + "User";
public const string User = ApiRoot + "User";
/// <summary>
/// The user group controller.
/// </summary>
public const string UserGroup = Root + "UserGroup";
public const string UserGroup = ApiRoot + "UserGroup";
/// <summary>
/// The <see cref="Models.Instance"/> controller.
/// </summary>
public const string InstanceManager = Root + "Instance";
public const string InstanceManager = ApiRoot + "Instance";
/// <summary>
/// The engine controller.
/// </summary>
public const string Engine = Root + "Engine";
public const string Engine = ApiRoot + "Engine";
/// <summary>
/// The git repository controller.
/// </summary>
public const string Repository = Root + "Repository";
public const string Repository = ApiRoot + "Repository";
/// <summary>
/// The DreamDaemon controller.
/// </summary>
public const string DreamDaemon = Root + "DreamDaemon";
public const string DreamDaemon = ApiRoot + "DreamDaemon";
/// <summary>
/// For accessing DD diagnostics.
@@ -65,7 +65,7 @@ namespace Tgstation.Server.Api
/// <summary>
/// The configuration controller.
/// </summary>
public const string Configuration = Root + "Config";
public const string Configuration = ApiRoot + "Config";
/// <summary>
/// To be paired with <see cref="Configuration"/> for accessing <see cref="Models.IConfigurationFile"/>s.
@@ -80,27 +80,27 @@ namespace Tgstation.Server.Api
/// <summary>
/// The instance permission set controller.
/// </summary>
public const string InstancePermissionSet = Root + "InstancePermissionSet";
public const string InstancePermissionSet = ApiRoot + "InstancePermissionSet";
/// <summary>
/// The chat bot controller.
/// </summary>
public const string Chat = Root + "Chat";
public const string Chat = ApiRoot + "Chat";
/// <summary>
/// The deployment controller.
/// </summary>
public const string DreamMaker = Root + "DreamMaker";
public const string DreamMaker = ApiRoot + "DreamMaker";
/// <summary>
/// The jobs controller.
/// </summary>
public const string Jobs = Root + "Job";
public const string Jobs = ApiRoot + "Job";
/// <summary>
/// The transfer controller.
/// </summary>
public const string Transfer = Root + "Transfer";
public const string Transfer = ApiRoot + "Transfer";
/// <summary>
/// The postfix for list operations.
+1 -1
View File
@@ -349,7 +349,7 @@ namespace Tgstation.Server.Client
if (startingToken != headers.Token)
return true;
var token = await RunRequest<object, TokenResponse>(Routes.Root, new object(), HttpMethod.Post, null, true, cancellationToken).ConfigureAwait(false);
var token = await RunRequest<object, TokenResponse>(Routes.ApiRoot, new object(), HttpMethod.Post, null, true, cancellationToken).ConfigureAwait(false);
headers = new ApiHeaders(headers.UserAgent!, token);
}
finally
+1 -1
View File
@@ -16,7 +16,7 @@ namespace Tgstation.Server.Client
public interface IServerClient : IAsyncDisposable
{
/// <summary>
/// The connected server <see cref="Uri"/>.
/// The connected server's root <see cref="Uri"/>.
/// </summary>
Uri Url { get; }
+1 -1
View File
@@ -66,7 +66,7 @@ namespace Tgstation.Server.Client
public ValueTask DisposeAsync() => apiClient.DisposeAsync();
/// <inheritdoc />
public ValueTask<ServerInformationResponse> ServerInformation(CancellationToken cancellationToken) => apiClient.Read<ServerInformationResponse>(Routes.Root, cancellationToken);
public ValueTask<ServerInformationResponse> ServerInformation(CancellationToken cancellationToken) => apiClient.Read<ServerInformationResponse>(Routes.ApiRoot, cancellationToken);
/// <inheritdoc />
public void AddRequestLogger(IRequestLogger requestLogger) => apiClient.AddRequestLogger(requestLogger);
@@ -138,7 +138,7 @@ namespace Tgstation.Server.Client
if (timeout.HasValue)
api.Timeout = timeout.Value;
return await api.Read<ServerInformationResponse>(Routes.Root, cancellationToken).ConfigureAwait(false);
return await api.Read<ServerInformationResponse>(Routes.ApiRoot, cancellationToken).ConfigureAwait(false);
}
/// <summary>
@@ -169,7 +169,7 @@ namespace Tgstation.Server.Client
if (timeout.HasValue)
api.Timeout = timeout.Value;
token = await api.Update<TokenResponse>(Routes.Root, cancellationToken).ConfigureAwait(false);
token = await api.Update<TokenResponse>(Routes.ApiRoot, cancellationToken).ConfigureAwait(false);
}
var apiHeaders = new ApiHeaders(productHeaderValue, token);
@@ -442,6 +442,7 @@ namespace Tgstation.Server.Host.Components.Repository
OnProgress = (a) => !cancellationToken.IsCancellationRequested,
OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested,
CredentialsProvider = credentialsProvider.GenerateCredentialsHandler(username, password),
TagFetchMode = TagFetchMode.All,
};
if (progressReporter != null)
@@ -120,7 +120,7 @@ namespace Tgstation.Server.Host.Configuration
public bool UseBasicWatchdog { get; set; }
/// <summary>
/// If the swagger UI should be made avaiable.
/// If the swagger documentation and UI should be made avaiable.
/// </summary>
public bool HostApiDocumentation { get; set; }
@@ -32,66 +32,66 @@ namespace Tgstation.Server.Host.Controllers
/// <summary>
/// Root <see cref="ApiController"/> for the <see cref="Application"/>.
/// </summary>
[Route(Routes.Root)]
public sealed class HomeController : ApiController
[Route(Routes.ApiRoot)]
public sealed class ApiRootController : ApiController
{
/// <summary>
/// The <see cref="ITokenFactory"/> for the <see cref="HomeController"/>.
/// The <see cref="ITokenFactory"/> for the <see cref="ApiRootController"/>.
/// </summary>
readonly ITokenFactory tokenFactory;
/// <summary>
/// The <see cref="ISystemIdentityFactory"/> for the <see cref="HomeController"/>.
/// The <see cref="ISystemIdentityFactory"/> for the <see cref="ApiRootController"/>.
/// </summary>
readonly ISystemIdentityFactory systemIdentityFactory;
/// <summary>
/// The <see cref="ICryptographySuite"/> for the <see cref="HomeController"/>.
/// The <see cref="ICryptographySuite"/> for the <see cref="ApiRootController"/>.
/// </summary>
readonly ICryptographySuite cryptographySuite;
/// <summary>
/// The <see cref="IAssemblyInformationProvider"/> for the <see cref="HomeController"/>.
/// The <see cref="IAssemblyInformationProvider"/> for the <see cref="ApiRootController"/>.
/// </summary>
readonly IAssemblyInformationProvider assemblyInformationProvider;
/// <summary>
/// The <see cref="IIdentityCache"/> for the <see cref="HomeController"/>.
/// The <see cref="IIdentityCache"/> for the <see cref="ApiRootController"/>.
/// </summary>
readonly IIdentityCache identityCache;
/// <summary>
/// The <see cref="IOAuthProviders"/> for the <see cref="HomeController"/>.
/// The <see cref="IOAuthProviders"/> for the <see cref="ApiRootController"/>.
/// </summary>
readonly IOAuthProviders oAuthProviders;
/// <summary>
/// The <see cref="IPlatformIdentifier"/> for the <see cref="HomeController"/>.
/// The <see cref="IPlatformIdentifier"/> for the <see cref="ApiRootController"/>.
/// </summary>
readonly IPlatformIdentifier platformIdentifier;
/// <summary>
/// The <see cref="ISwarmService"/> for the <see cref="HomeController"/>.
/// The <see cref="ISwarmService"/> for the <see cref="ApiRootController"/>.
/// </summary>
readonly ISwarmService swarmService;
/// <summary>
/// The <see cref="IServerControl"/> for the <see cref="HomeController"/>.
/// The <see cref="IServerControl"/> for the <see cref="ApiRootController"/>.
/// </summary>
readonly IServerControl serverControl;
/// <summary>
/// The <see cref="GeneralConfiguration"/> for the <see cref="HomeController"/>.
/// The <see cref="GeneralConfiguration"/> for the <see cref="ApiRootController"/>.
/// </summary>
readonly GeneralConfiguration generalConfiguration;
/// <summary>
/// The <see cref="ControlPanelConfiguration"/> for the <see cref="HomeController"/>.
/// The <see cref="ControlPanelConfiguration"/> for the <see cref="ApiRootController"/>.
/// </summary>
readonly ControlPanelConfiguration controlPanelConfiguration;
/// <summary>
/// Initializes a new instance of the <see cref="HomeController"/> class.
/// Initializes a new instance of the <see cref="ApiRootController"/> class.
/// </summary>
/// <param name="databaseContext">The <see cref="IDatabaseContext"/> for the <see cref="ApiController"/>.</param>
/// <param name="authenticationContext">The <see cref="IAuthenticationContext"/> for the <see cref="ApiController"/>.</param>
@@ -108,7 +108,7 @@ namespace Tgstation.Server.Host.Controllers
/// <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>
/// <param name="apiHeadersProvider">The <see cref="IApiHeadersProvider"/> for the <see cref="ApiController"/>.</param>
public HomeController(
public ApiRootController(
IDatabaseContext databaseContext,
IAuthenticationContext authenticationContext,
ITokenFactory tokenFactory,
@@ -122,7 +122,7 @@ namespace Tgstation.Server.Host.Controllers
IServerControl serverControl,
IOptions<GeneralConfiguration> generalConfigurationOptions,
IOptions<ControlPanelConfiguration> controlPanelConfigurationOptions,
ILogger<HomeController> logger,
ILogger<ApiRootController> logger,
IApiHeadersProvider apiHeadersProvider)
: base(
databaseContext,
@@ -154,29 +154,12 @@ namespace Tgstation.Server.Host.Controllers
[HttpGet]
[AllowAnonymous]
[ProducesResponseType(typeof(ServerInformationResponse), 200)]
#pragma warning disable CA1506
public IActionResult Home()
public IActionResult ServerInfo()
{
if (controlPanelConfiguration.Enable)
Response.Headers.Add(
HeaderNames.Vary,
new StringValues(ApiHeaders.ApiVersionHeader));
// if they tried to authenticate in any form and failed, let them know immediately
bool failIfUnauthed;
if (ApiHeaders == null)
{
if (controlPanelConfiguration.Enable && !Request.Headers.TryGetValue(ApiHeaders.ApiVersionHeader, out _))
{
Logger.LogDebug("No API headers on request, redirecting to control panel...");
var controlPanelRoute = controlPanelConfiguration.PublicPath;
if (String.IsNullOrWhiteSpace(controlPanelRoute))
controlPanelRoute = ControlPanelController.ControlPanelRoute;
return Redirect(controlPanelRoute);
}
try
{
// we only allow authorization header issues
@@ -211,7 +194,6 @@ namespace Tgstation.Server.Host.Controllers
UpdateInProgress = serverControl.UpdateInProgress,
});
}
#pragma warning restore CA1506
/// <summary>
/// Attempt to authenticate a <see cref="User"/> using <see cref="ApiController.ApiHeaders"/>.
@@ -8,9 +8,12 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Serilog.Context;
using Tgstation.Server.Api;
using Tgstation.Server.Host.Components.Interop;
using Tgstation.Server.Host.Components.Interop.Bridge;
using Tgstation.Server.Host.Utils;
@@ -20,10 +23,16 @@ namespace Tgstation.Server.Host.Controllers
/// <summary>
/// <see cref="Controller"/> for receiving DMAPI requests from DreamDaemon.
/// </summary>
[Route("/Bridge")]
[Route("/" + RouteExtension)] // obsolete route, but BYOND can't handle a simple fucking 301
[Route(Routes.ApiRoot + RouteExtension)]
[ApiExplorerSettings(IgnoreApi = true)]
public sealed class BridgeController : ApiControllerBase
{
/// <summary>
/// The route to the <see cref="BridgeController"/>.
/// </summary>
const string RouteExtension = "Bridge";
/// <summary>
/// If the content of bridge requests and responses should be logged.
/// </summary>
@@ -0,0 +1,109 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.System;
using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Controllers
{
/// <summary>
/// The root path <see cref="Controller"/>.
/// </summary>
[Route("/")]
public sealed class RootController : Controller
{
/// <summary>
/// The route to the TGS logo .svg in the <see cref="Microsoft.AspNetCore.Hosting.IWebHostEnvironment.WebRootPath"/> on Windows.
/// </summary>
public const string ProjectLogoSvgRouteWindows = "/0176d5d8b7d307f158e0.svg";
/// <summary>
/// The route to the TGS logo .svg in the <see cref="Microsoft.AspNetCore.Hosting.IWebHostEnvironment.WebRootPath"/> on Linux.
/// </summary>
public const string ProjectLogoSvgRouteLinux = "/b5616c99bf2052a6bbd7.svg";
/// <summary>
/// The <see cref="IAssemblyInformationProvider"/> for the <see cref="RootController"/>.
/// </summary>
readonly IAssemblyInformationProvider assemblyInformationProvider;
/// <summary>
/// The <see cref="IPlatformIdentifier"/> for the <see cref="RootController"/>.
/// </summary>
readonly IPlatformIdentifier platformIdentifier;
/// <summary>
/// The <see cref="GeneralConfiguration"/> for the <see cref="RootController"/>.
/// </summary>
readonly GeneralConfiguration generalConfiguration;
/// <summary>
/// The <see cref="ControlPanelConfiguration"/> for the <see cref="RootController"/>.
/// </summary>
readonly ControlPanelConfiguration controlPanelConfiguration;
/// <summary>
/// Initializes a new instance of the <see cref="RootController"/> class.
/// </summary>
/// <param name="assemblyInformationProvider">The value of <see cref="assemblyInformationProvider"/>.</param>
/// <param name="platformIdentifier">The value of <see cref="platformIdentifier"/>.</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>
public RootController(
IAssemblyInformationProvider assemblyInformationProvider,
IPlatformIdentifier platformIdentifier,
IOptions<GeneralConfiguration> generalConfigurationOptions,
IOptions<ControlPanelConfiguration> controlPanelConfigurationOptions)
{
this.assemblyInformationProvider = assemblyInformationProvider ?? throw new ArgumentNullException(nameof(assemblyInformationProvider));
this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier));
generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
controlPanelConfiguration = controlPanelConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(controlPanelConfigurationOptions));
}
/// <summary>
/// Gets the server's homepage.
/// </summary>
/// <returns>The appropriate <see cref="IActionResult"/>.</returns>
[HttpGet]
[AllowAnonymous]
public IActionResult Index()
{
const string ApiDocumentationRoute = "/" + SwaggerConfiguration.DocumentationSiteRouteExtension;
var panelEnabled = controlPanelConfiguration.Enable;
var apiDocsEnabled = generalConfiguration.HostApiDocumentation;
if (panelEnabled ^ apiDocsEnabled)
if (panelEnabled)
return Redirect(ControlPanelController.ControlPanelRoute);
else
return Redirect(ApiDocumentationRoute);
Dictionary<string, string> links;
if (panelEnabled)
links = new Dictionary<string, string>()
{
{ "Web Control Panel", ControlPanelController.ControlPanelRoute.TrimStart('/') },
{ "API Documentation", SwaggerConfiguration.DocumentationSiteRouteExtension },
};
else
links = null;
var model = new
{
Links = links,
Svg = platformIdentifier.IsWindows // these are different because of motherfucking line endings -_-
? ProjectLogoSvgRouteWindows
: ProjectLogoSvgRouteLinux,
Title = assemblyInformationProvider.VersionString,
};
return View(model);
}
}
}
@@ -498,8 +498,15 @@ namespace Tgstation.Server.Host.Core
if (generalConfiguration.HostApiDocumentation)
{
applicationBuilder.UseSwagger();
applicationBuilder.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "TGS API"));
applicationBuilder.UseSwagger(options =>
{
options.RouteTemplate = Routes.ApiRoot + "doc/{documentName}.{json|yaml}";
});
applicationBuilder.UseSwaggerUI(options =>
{
options.RoutePrefix = SwaggerConfiguration.DocumentationSiteRouteExtension;
options.SwaggerEndpoint(Routes.ApiRoot + $"doc/{SwaggerConfiguration.DocumentName}.json", "TGS API");
});
logger.LogTrace("Swagger API generation enabled");
}
@@ -14,7 +14,7 @@ namespace Tgstation.Server.Host.Swarm
/// <summary>
/// The base route for <see cref="Controllers.SwarmController"/>.
/// </summary>
public const string ControllerRoute = Routes.Root + "Swarm";
public const string ControllerRoute = Routes.ApiRoot + "Swarm";
/// <summary>
/// The header used to pass in the <see cref="Configuration.SwarmConfiguration.PrivateKey"/>.
@@ -27,6 +27,16 @@ namespace Tgstation.Server.Host.Utils
/// </summary>
sealed class SwaggerConfiguration : IOperationFilter, IDocumentFilter, ISchemaFilter, IRequestBodyFilter
{
/// <summary>
/// The name of the swagger document.
/// </summary>
public const string DocumentName = "tgs_api";
/// <summary>
/// The path to the hosted documentation site.
/// </summary>
public const string DocumentationSiteRouteExtension = "documentation";
/// <summary>
/// The <see cref="OpenApiSecurityScheme"/> name for password authentication.
/// </summary>
@@ -51,7 +61,7 @@ namespace Tgstation.Server.Host.Utils
public static void Configure(SwaggerGenOptions swaggerGenOptions, string assemblyDocumentationPath, string apiDocumentationPath)
{
swaggerGenOptions.SwaggerDoc(
"v1",
DocumentName,
new OpenApiInfo
{
Title = "TGS API",
@@ -405,7 +415,7 @@ namespace Tgstation.Server.Host.Utils
twoHundredResponseContents.Add(MediaTypeNames.Application.Octet, fileContent);
}
}
else if (context.MethodInfo.Name == nameof(HomeController.CreateToken))
else if (context.MethodInfo.Name == nameof(ApiRootController.CreateToken))
{
var passwordScheme = new OpenApiSecurityScheme
{
@@ -0,0 +1,28 @@
@{
var svgPath = Model.Svg;
var title = Model.Title;
<!DOCTYPE html>
<html>
<head>
<title>@title</title>
<style>
body {
background-color: #212529;
text-align: center;
}
</style>
</head>
<body>
<img src="@svgPath"/>
@{
if (Model.Links != null)
foreach (KeyValuePair<string, string> kvp in Model.Links)
{
<h1>
<a href="@kvp.Value">@kvp.Key</a>
</h1>
}
}
</body>
</html>
}
+1 -1
View File
@@ -13,7 +13,7 @@ General:
UserGroupLimit: 25 # Maximum number of allowed groups
InstanceLimit: 10 # Maximum number of allowed instances
ValidInstancePaths: # An array of directories instances may be created in (either directly or as a subdirectory). null removes the restriction
HostApiDocumentation: false # Make HTTP API documentation available at /swagger/v1/swagger.json
HostApiDocumentation: false # Make HTTP API documentation available at /api/doc/tgs_api.json
SkipAddingByondFirewallException: false # Windows Only: Prevent running netsh.exe to add a firewall exception for installed engine binaries
DeploymentDirectoryCopyTasksPerCore: 100 # Maximum number of concurrent file copy operations PER available CPU core
OpenDreamGitUrl: https://github.com/OpenDreamProject/OpenDream # The repository to retrieve OpenDream from
+30
View File
@@ -182,6 +182,11 @@ var/run_bridge_test
kajigger_test = TRUE
return "we love casting spells"
var/its_sad = data["im_out_of_memes"]
if(its_sad)
TestLegacyBridge()
return "yeah gimmie a sec"
TgsChatBroadcast(new /datum/tgs_message_content("Received non-tgs topic: `[T]`"))
return "feck"
@@ -351,3 +356,28 @@ var/suppress_bridge_spam = FALSE
FailTest("Failed to end bridge limit test! [(istype(final_result) ? json_encode(final_result): (final_result || "null"))]")
api.access_identifier = old_ai
/proc/TestLegacyBridge()
set waitfor = FALSE
sleep(10)
var/datum/tgs_api/v5/api = TGS_READ_GLOBAL(tgs)
if(api.interop_version.suite != 5)
FailTest("Legacy bridge test not required anymore?")
var/old_minor_version = api.interop_version.minor
api.interop_version.minor = 6 // before api repath
var/result
var/bridge_request = api.CreateBridgeRequest(5, list("chatMessage" = list("text" = "legacy bridge test", "channelIds" = list())))
try
result = api.PerformBridgeRequest(bridge_request)
catch(var/exception/e2)
world.log << "Caught exception: [e2]"
result = null
if(!result || lastTgsError)
FailTest("Failed bridge request redirect test!")
api.interop_version.minor = old_minor_version
@@ -88,7 +88,7 @@ namespace Tgstation.Server.Tests.Live.Instance
Assert.AreEqual("payload", coreMessage);
var serializedRequest = JsonConvert.SerializeObject(parameters, DMApiConstants.SerializerSettings);
var actualLastRequest = $"http://127.0.0.1:{serverPort}/Bridge?data=" + HttpUtility.UrlEncode(serializedRequest);
var actualLastRequest = $"http://127.0.0.1:{serverPort}/api/Bridge?data=" + HttpUtility.UrlEncode(serializedRequest);
lastBridgeRequestSize = actualLastRequest.Length;
return new BridgeResponseHack
{
@@ -233,6 +233,8 @@ namespace Tgstation.Server.Tests.Live.Instance
await RegressionTest1550(cancellationToken);
await TestLegacyBridgeEndpoint(cancellationToken);
var deleteJobTask = TestDeleteByondInstallErrorCasesAndQueing(cancellationToken);
SessionController.LogTopicRequests = false;
@@ -1381,5 +1383,13 @@ namespace Tgstation.Server.Tests.Live.Instance
var logtext = await File.ReadAllTextAsync(logfile.FullName, cancellationToken);
Assert.IsFalse(String.IsNullOrWhiteSpace(logtext));
}
async ValueTask TestLegacyBridgeEndpoint(CancellationToken cancellationToken)
{
var result = await topicClient.SendTopic(IPAddress.Loopback, "im_out_of_memes=1", ddPort, cancellationToken);
Assert.AreEqual("yeah gimmie a sec", result.StringData);
await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken);
await CheckDMApiFail((await instanceClient.DreamDaemon.Read(cancellationToken)).ActiveCompileJob, cancellationToken);
}
}
}
@@ -10,6 +10,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Api;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Host;
using Tgstation.Server.Host.Configuration;
@@ -51,7 +52,9 @@ namespace Tgstation.Server.Tests.Live
}
}
public Uri Url { get; }
public Uri ApiUrl { get; }
public Uri RootUrl { get; }
public string Directory { get; }
@@ -89,7 +92,8 @@ namespace Tgstation.Server.Tests.Live
Directory = Path.Combine(Directory, swarmConfiguration?.Identifier ?? "default");
System.IO.Directory.CreateDirectory(Directory);
string urlString = $"http://localhost:{port}";
Url = new Uri(urlString);
RootUrl = new Uri(urlString);
ApiUrl = new Uri(urlString + Routes.ApiRoot);
//so we need a db
//we have to rely on env vars
@@ -37,7 +37,7 @@ namespace Tgstation.Server.Tests.Live
var token = serverClient.Token.Bearer;
// check that 400s are returned appropriately
using var httpClient = new HttpClient();
using (var request = new HttpRequestMessage(HttpMethod.Get, url.ToString()))
using (var request = new HttpRequestMessage(HttpMethod.Get, url.ToString() + Routes.ApiRoot.TrimStart('/')))
{
request.Headers.Accept.Clear();
request.Headers.UserAgent.Add(new ProductInfoHeaderValue("RootTest", "1.0.0"));
@@ -45,7 +45,7 @@ namespace Tgstation.Server.Tests.Live
Assert.AreEqual(HttpStatusCode.NotAcceptable, response.StatusCode);
}
using (var request = new HttpRequestMessage(HttpMethod.Get, url.ToString()))
using (var request = new HttpRequestMessage(HttpMethod.Get, url.ToString() + Routes.ApiRoot.TrimStart('/')))
{
request.Headers.Accept.Clear();
request.Headers.UserAgent.Add(new ProductInfoHeaderValue("RootTest", "1.0.0"));
@@ -54,7 +54,7 @@ namespace Tgstation.Server.Tests.Live
Assert.AreEqual(HttpStatusCode.NotAcceptable, response.StatusCode);
}
using (var request = new HttpRequestMessage(HttpMethod.Get, url.ToString()))
using (var request = new HttpRequestMessage(HttpMethod.Get, url.ToString() + Routes.ApiRoot.TrimStart('/')))
{
request.Headers.Accept.Clear();
request.Headers.UserAgent.Add(new ProductInfoHeaderValue("RootTest", "1.0.0"));
@@ -66,7 +66,7 @@ namespace Tgstation.Server.Tests.Live
Assert.AreEqual(ErrorCode.BadHeaders, message.ErrorCode);
}
using (var request = new HttpRequestMessage(HttpMethod.Get, url.ToString()))
using (var request = new HttpRequestMessage(HttpMethod.Get, url.ToString() + Routes.ApiRoot.TrimStart('/')))
{
request.Headers.Accept.Clear();
request.Headers.UserAgent.Add(new ProductInfoHeaderValue("RootTest", "1.0.0"));
@@ -79,7 +79,7 @@ namespace Tgstation.Server.Tests.Live
Assert.AreEqual(ApiHeaders.Version, message.ApiVersion);
}
using (var request = new HttpRequestMessage(HttpMethod.Get, url.ToString()))
using (var request = new HttpRequestMessage(HttpMethod.Get, url.ToString() + Routes.ApiRoot.TrimStart('/')))
{
request.Headers.Accept.Clear();
request.Headers.UserAgent.Add(new ProductInfoHeaderValue("RootTest", "1.0.0"));
@@ -149,7 +149,7 @@ namespace Tgstation.Server.Tests.Live
Assert.AreEqual(ErrorCode.InstanceHeaderRequired, message.ErrorCode);
}
using (var request = new HttpRequestMessage(HttpMethod.Get, url.ToString()))
using (var request = new HttpRequestMessage(HttpMethod.Get, url.ToString() + Routes.ApiRoot.TrimStart('/')))
{
request.Headers.Accept.Clear();
request.Headers.UserAgent.Add(new ProductInfoHeaderValue("RootTest", "1.0.0"));
@@ -163,7 +163,7 @@ namespace Tgstation.Server.Tests.Live
Assert.AreEqual(ErrorCode.BadHeaders, message.ErrorCode);
}
using (var request = new HttpRequestMessage(HttpMethod.Post, url.ToString()))
using (var request = new HttpRequestMessage(HttpMethod.Post, url.ToString() + Routes.ApiRoot.TrimStart('/')))
{
request.Headers.Accept.Clear();
request.Headers.UserAgent.Add(new ProductInfoHeaderValue("RootTest", "1.0.0"));
@@ -178,7 +178,7 @@ namespace Tgstation.Server.Tests.Live
Assert.AreEqual(ErrorCode.BadHeaders, message.ErrorCode);
}
using (var request = new HttpRequestMessage(HttpMethod.Get, url.ToString()))
using (var request = new HttpRequestMessage(HttpMethod.Get, url.ToString() + Routes.ApiRoot.TrimStart('/')))
{
request.Headers.Accept.Clear();
request.Headers.UserAgent.Add(new ProductInfoHeaderValue("RootTest", "1.0.0"));
@@ -232,7 +232,7 @@ namespace Tgstation.Server.Tests.Live
// just hitting each type of oauth provider for coverage
foreach (var I in Enum.GetValues(typeof(OAuthProvider)))
using (var request = new HttpRequestMessage(HttpMethod.Post, url.ToString()))
using (var request = new HttpRequestMessage(HttpMethod.Post, url.ToString() + Routes.ApiRoot.TrimStart('/')))
{
request.Headers.Accept.Clear();
request.Headers.UserAgent.Add(new ProductInfoHeaderValue("RootTest", "1.0.0"));
@@ -291,11 +291,11 @@ namespace Tgstation.Server.Tests.Live
return await action();
}
await using (var adminClient = await CreateAdminClient(server.Url, cancellationToken))
await using (var adminClient = await CreateAdminClient(server.ApiUrl, cancellationToken))
{
// Disabled OAuth test
using (var httpClient = new HttpClient())
using (var request = new HttpRequestMessage(HttpMethod.Post, server.Url.ToString()))
using (var request = new HttpRequestMessage(HttpMethod.Post, server.ApiUrl.ToString()))
{
request.Headers.Accept.Clear();
request.Headers.UserAgent.Add(new ProductInfoHeaderValue("RootTest", "1.0.0"));
@@ -372,7 +372,7 @@ namespace Tgstation.Server.Tests.Live
await new Host.IO.DefaultIOManager().DeleteDirectory(server.UpdatePath, cancellationToken);
serverTask = server.Run(cancellationToken).AsTask();
await using (var adminClient = await CreateAdminClient(server.Url, cancellationToken))
await using (var adminClient = await CreateAdminClient(server.ApiUrl, cancellationToken))
{
// test we can't do this without the correct permission
@@ -428,7 +428,7 @@ namespace Tgstation.Server.Tests.Live
try
{
var testUpdateVersion = new Version(5, 11, 20);
await using var adminClient = await CreateAdminClient(server.Url, cancellationToken);
await using var adminClient = await CreateAdminClient(server.ApiUrl, cancellationToken);
await ApiAssert.ThrowsException<ConflictException, ServerUpdateResponse>(
() => adminClient.Administration.Update(
new ServerUpdateRequest
@@ -478,7 +478,7 @@ namespace Tgstation.Server.Tests.Live
try
{
await using var controllerClient = await CreateAdminClient(controller.Url, cancellationToken);
await using var controllerClient = await CreateAdminClient(controller.ApiUrl, cancellationToken);
var controllerInfo = await controllerClient.ServerInformation(cancellationToken);
@@ -579,9 +579,9 @@ namespace Tgstation.Server.Tests.Live
try
{
await using var controllerClient = await CreateAdminClient(controller.Url, cancellationToken);
await using var node1Client = await CreateAdminClient(node1.Url, cancellationToken);
await using var node2Client = await CreateAdminClient(node2.Url, cancellationToken);
await using var controllerClient = await CreateAdminClient(controller.ApiUrl, cancellationToken);
await using var node1Client = await CreateAdminClient(node1.ApiUrl, cancellationToken);
await using var node2Client = await CreateAdminClient(node2.ApiUrl, cancellationToken);
var controllerInfo = await controllerClient.ServerInformation(cancellationToken);
@@ -650,7 +650,7 @@ namespace Tgstation.Server.Tests.Live
newUser.Name,
"asdfasdfasdfasdf");
await using var node1BadClient = clientFactory.CreateFromToken(node1.Url, controllerUserClient.Token);
await using var node1BadClient = clientFactory.CreateFromToken(node1.RootUrl, controllerUserClient.Token);
await ApiAssert.ThrowsException<UnauthorizedException, AdministrationResponse>(() => node1BadClient.Administration.Read(cancellationToken));
// check instance info is not shared
@@ -720,8 +720,8 @@ namespace Tgstation.Server.Tests.Live
controller.Run(cancellationToken).AsTask(),
node1.Run(cancellationToken).AsTask());
await using var controllerClient2 = await CreateAdminClient(controller.Url, cancellationToken);
await using var node1Client2 = await CreateAdminClient(node1.Url, cancellationToken);
await using var controllerClient2 = await CreateAdminClient(controller.ApiUrl, cancellationToken);
await using var node1Client2 = await CreateAdminClient(node1.ApiUrl, cancellationToken);
await ApiAssert.ThrowsException<ApiConflictException, ServerUpdateResponse>(() => controllerClient2.Administration.Update(
new ServerUpdateRequest
@@ -736,7 +736,7 @@ namespace Tgstation.Server.Tests.Live
serverTask,
node2.Run(cancellationToken).AsTask());
await using var node2Client2 = await CreateAdminClient(node2.Url, cancellationToken);
await using var node2Client2 = await CreateAdminClient(node2.ApiUrl, cancellationToken);
async Task WaitForSwarmServerUpdate2()
{
@@ -850,9 +850,9 @@ namespace Tgstation.Server.Tests.Live
try
{
await using var controllerClient = await CreateAdminClient(controller.Url, cancellationToken);
await using var node1Client = await CreateAdminClient(node1.Url, cancellationToken);
await using var node2Client = await CreateAdminClient(node2.Url, cancellationToken);
await using var controllerClient = await CreateAdminClient(controller.ApiUrl, cancellationToken);
await using var node1Client = await CreateAdminClient(node1.ApiUrl, cancellationToken);
await using var node2Client = await CreateAdminClient(node2.ApiUrl, cancellationToken);
var controllerInfo = await controllerClient.ServerInformation(cancellationToken);
@@ -932,7 +932,7 @@ namespace Tgstation.Server.Tests.Live
Assert.IsTrue(controllerTask.IsCompleted);
controllerTask = controller.Run(cancellationToken).AsTask();
await using var controllerClient2 = await CreateAdminClient(controller.Url, cancellationToken);
await using var controllerClient2 = await CreateAdminClient(controller.ApiUrl, cancellationToken);
// node 2 should reconnect once it's health check triggers
await Task.WhenAny(
@@ -969,7 +969,7 @@ namespace Tgstation.Server.Tests.Live
ErrorCode.SwarmIntegrityCheckFailed);
node2Task = node2.Run(cancellationToken).AsTask();
await using var node2Client2 = await CreateAdminClient(node2.Url, cancellationToken);
await using var node2Client2 = await CreateAdminClient(node2.ApiUrl, cancellationToken);
// should re-register
await Task.WhenAny(
@@ -1026,7 +1026,7 @@ namespace Tgstation.Server.Tests.Live
var serverTask = server.Run(cancellationToken);
try
{
await using var adminClient = await CreateAdminClient(server.Url, cancellationToken);
await using var adminClient = await CreateAdminClient(server.ApiUrl, cancellationToken);
var instanceManagerTest = new InstanceManagerTest(adminClient, server.Directory);
var instance = await instanceManagerTest.CreateTestInstance("TgTestInstance", cancellationToken);
@@ -1319,7 +1319,7 @@ namespace Tgstation.Server.Tests.Live
{
Api.Models.Instance instance;
long initialStaged, initialActive;
await using var firstAdminClient = await CreateAdminClient(server.Url, cancellationToken);
await using var firstAdminClient = await CreateAdminClient(server.ApiUrl, cancellationToken);
async ValueTask<IServerClient> CreateUserWithNoInstancePerms()
{
@@ -1337,7 +1337,7 @@ namespace Tgstation.Server.Tests.Live
var user = await firstAdminClient.Users.Create(createRequest, cancellationToken);
Assert.IsTrue(user.Enabled);
return await clientFactory.CreateFromLogin(server.Url, createRequest.Name, createRequest.Password, cancellationToken: cancellationToken);
return await clientFactory.CreateFromLogin(server.RootUrl, createRequest.Name, createRequest.Password, cancellationToken: cancellationToken);
}
var jobsHubTest = new JobsHubTests(firstAdminClient, await CreateUserWithNoInstancePerms());
@@ -1348,11 +1348,11 @@ namespace Tgstation.Server.Tests.Live
// Dump swagger to disk
// This is purely for CI
using var httpClient = new HttpClient();
var webRequestTask = httpClient.GetAsync(server.Url.ToString() + "swagger/v1/swagger.json", cancellationToken);
var webRequestTask = httpClient.GetAsync(server.ApiUrl.ToString() + "doc/tgs_api.json", cancellationToken);
using var response = await webRequestTask;
response.EnsureSuccessStatusCode();
await using var content = await response.Content.ReadAsStreamAsync(cancellationToken);
await using var output = new FileStream(@"C:\swagger.json", FileMode.Create);
await using var output = new FileStream(@"C:\tgs_api.json", FileMode.Create);
await content.CopyToAsync(output, cancellationToken);
}
@@ -1393,7 +1393,7 @@ namespace Tgstation.Server.Tests.Live
firstAdminClient.Instances,
fileDownloader,
GetInstanceManager(),
(ushort)server.Url.Port);
(ushort)server.ApiUrl.Port);
async Task RunInstanceTests()
{
@@ -1503,7 +1503,7 @@ namespace Tgstation.Server.Tests.Live
using var blockingSocket = new Socket(SocketType.Stream, ProtocolType.Tcp);
blockingSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ExclusiveAddressUse, true);
blockingSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, false);
blockingSocket.Bind(new IPEndPoint(IPAddress.Any, server.Url.Port));
blockingSocket.Bind(new IPEndPoint(IPAddress.Any, server.ApiUrl.Port));
// bind test run
await server.Run(cancellationToken);
Assert.Fail("Expected server task to end with a SocketException");
@@ -1525,7 +1525,7 @@ namespace Tgstation.Server.Tests.Live
// chat bot start and DD reattach test
serverTask = server.Run(cancellationToken).AsTask();
await using (var adminClient = await CreateAdminClient(server.Url, cancellationToken))
await using (var adminClient = await CreateAdminClient(server.ApiUrl, cancellationToken))
{
await jobsHubTest.WaitForReconnect(cancellationToken);
var instanceClient = adminClient.Instances.CreateClient(instance);
@@ -1629,7 +1629,7 @@ namespace Tgstation.Server.Tests.Live
serverTask = server.Run(cancellationToken).AsTask();
long expectedCompileJobId, expectedStaged;
var edgeVersion = await EngineTest.GetEdgeVersion(EngineType.Byond, fileDownloader, cancellationToken);
await using (var adminClient = await CreateAdminClient(server.Url, cancellationToken))
await using (var adminClient = await CreateAdminClient(server.ApiUrl, cancellationToken))
{
var instanceClient = adminClient.Instances.CreateClient(instance);
await WaitForInitialJobs(instanceClient);
@@ -1640,7 +1640,7 @@ namespace Tgstation.Server.Tests.Live
Assert.AreEqual(WatchdogStatus.Online, dd.Status.Value);
var compileJob = await instanceClient.DreamMaker.Compile(cancellationToken);
var wdt = new WatchdogTest(edgeVersion, instanceClient, GetInstanceManager(), (ushort)server.Url.Port, server.HighPriorityDreamDaemon, mainDDPort, server.UsingBasicWatchdog);
var wdt = new WatchdogTest(edgeVersion, instanceClient, GetInstanceManager(), (ushort)server.ApiUrl.Port, server.HighPriorityDreamDaemon, mainDDPort, server.UsingBasicWatchdog);
await wdt.WaitForJob(compileJob, 30, false, null, cancellationToken);
dd = await instanceClient.DreamDaemon.Read(cancellationToken);
@@ -1678,7 +1678,7 @@ namespace Tgstation.Server.Tests.Live
// post/entity deletion tests
serverTask = server.Run(cancellationToken).AsTask();
await using (var adminClient = await CreateAdminClient(server.Url, cancellationToken))
await using (var adminClient = await CreateAdminClient(server.ApiUrl, cancellationToken))
{
var instanceClient = adminClient.Instances.CreateClient(instance);
await WaitForInitialJobs(instanceClient);
@@ -1689,7 +1689,7 @@ namespace Tgstation.Server.Tests.Live
Assert.AreEqual(WatchdogStatus.Online, currentDD.Status);
Assert.AreEqual(expectedStaged, currentDD.StagedCompileJob.Job.Id.Value);
var wdt = new WatchdogTest(edgeVersion, instanceClient, GetInstanceManager(), (ushort)server.Url.Port, server.HighPriorityDreamDaemon, mainDDPort, server.UsingBasicWatchdog);
var wdt = new WatchdogTest(edgeVersion, instanceClient, GetInstanceManager(), (ushort)server.ApiUrl.Port, server.HighPriorityDreamDaemon, mainDDPort, server.UsingBasicWatchdog);
currentDD = await wdt.TellWorldToReboot(cancellationToken);
Assert.AreEqual(expectedStaged, currentDD.ActiveCompileJob.Job.Id.Value);
Assert.IsNull(currentDD.StagedCompileJob);
@@ -1738,6 +1738,7 @@ namespace Tgstation.Server.Tests.Live
async Task<IServerClient> CreateAdminClient(Uri url, CancellationToken cancellationToken)
{
url = new Uri(url.ToString().Replace(Routes.ApiRoot, String.Empty));
var giveUpAt = DateTimeOffset.UtcNow.AddMinutes(2);
for (var I = 1; ; ++I)
{
+78 -2
View File
@@ -1,8 +1,11 @@
using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using LibGit2Sharp;
using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -28,7 +31,7 @@ namespace Tgstation.Server.Tests
{
LibGit2Sharp.Repository.Clone("https://github.com/Cyberboss/test", tempPath);
var libGit2Repo = new LibGit2Sharp.Repository(tempPath);
using var repo = new Repository(
using var repo = new Host.Components.Repository.Repository(
libGit2Repo,
new LibGit2Commands(),
Mock.Of<IIOManager>(),
@@ -36,7 +39,7 @@ namespace Tgstation.Server.Tests
Mock.Of<ICredentialsProvider>(),
Mock.Of<IPostWriteHandler>(),
Mock.Of<IGitRemoteFeaturesFactory>(),
Mock.Of<ILogger<Repository>>(),
Mock.Of<ILogger<Host.Components.Repository.Repository>>(),
new GeneralConfiguration(),
() => { });
@@ -56,5 +59,78 @@ namespace Tgstation.Server.Tests
CancellationToken.None);
}
}
[TestMethod]
public async Task TestFetchingAdditionalCommits()
{
var tempPath = Path.Combine(Path.GetTempPath(), "TGS-Repository-Integration-Test", Guid.NewGuid().ToString());
var repoFac =
new LibGit2RepositoryFactory(
Mock.Of<ILogger<LibGit2RepositoryFactory>>());
var commands = new LibGit2Commands();
using var manager = new RepositoryManager(
repoFac,
commands,
new ResolvingIOManager(
new DefaultIOManager(),
tempPath),
Mock.Of<IEventConsumer>(),
new WindowsPostWriteHandler(),
Mock.Of<IGitRemoteFeaturesFactory>(),
Mock.Of<ILogger<Host.Components.Repository.Repository>>(),
Mock.Of<ILogger<RepositoryManager>>(),
new GeneralConfiguration());
try
{
using (await manager.CloneRepository(
new Uri("https://github.com/Cyberboss/common_core"),
null,
null,
null,
new JobProgressReporter(Mock.Of<ILogger<JobProgressReporter>>(), null, (_, _) => { }),
true,
default))
{
}
using (var repo = await repoFac.CreateFromPath(tempPath, default))
{
repo.Network.Remotes.Update("origin", updater =>
{
updater.Url = "https://github.com/tgstation/common_core";
});
var targetCommit = repo.Lookup<Commit>("5b0d0a38057a2c8306a852ccbd6cd6f4ae766a33");
Assert.IsNull(targetCommit);
}
using (var repo2 = await manager.LoadRepository(default))
{
await repo2.FetchOrigin(
new JobProgressReporter(Mock.Of<ILogger<JobProgressReporter>>(), null, (_, _) => { }),
null,
null,
false,
default);
}
using var repo3 = await repoFac.CreateFromPath(tempPath, default);
var remote = repo3.Network.Remotes.First();
commands.Fetch(repo3, remote.FetchRefSpecs.Select(x => x.Specification), remote, new FetchOptions
{
TagFetchMode = TagFetchMode.All,
Prune = true,
}, "test");
var targetCommit2 = repo3.Lookup<Commit>("5b0d0a38057a2c8306a852ccbd6cd6f4ae766a33");
Assert.IsNotNull(targetCommit2);
}
finally
{
await new DefaultIOManager().DeleteDirectory(
Path.GetDirectoryName(tempPath),
CancellationToken.None);
}
}
}
}
+26 -1
View File
@@ -1,9 +1,11 @@
using System;
using System.IO;
using System.IO.Compression;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;
@@ -24,10 +26,10 @@ using Tgstation.Server.Host;
using Tgstation.Server.Host.Components.Engine;
using Tgstation.Server.Host.Components.Interop;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.Controllers;
using Tgstation.Server.Host.Database;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.System;
using System.Net;
using Tgstation.Server.Api.Models.Internal;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Tests.Live;
@@ -406,6 +408,29 @@ namespace Tgstation.Server.Tests
Assert.AreEqual(latestMigrationSL, DatabaseContext.SLLatestMigration);
}
[TestMethod]
public async Task CheckWebRootPathForTgsLogo()
{
var directory = Path.GetFullPath("../../../../../src/Tgstation.Server.Host/wwwroot");
if (!Directory.Exists(directory))
Assert.Inconclusive("Webpanel not built?");
var logo = new PlatformIdentifier().IsWindows
? RootController.ProjectLogoSvgRouteWindows
: RootController.ProjectLogoSvgRouteLinux;
var path = $"../../../../../src/Tgstation.Server.Host/wwwroot{logo}";
Assert.IsTrue(File.Exists(path));
var content = await File.ReadAllBytesAsync(path);
var hash = String.Join(String.Empty, SHA1.HashData(content).Select(b => b.ToString("x2", CultureInfo.InvariantCulture)));
Assert.AreEqual(
new PlatformIdentifier().IsWindows
? "c5e4709774c14a6f376dbb5100bd80a0114a2287"
: "9eba2fac24c5c7e0008721690d07c3df575a00d6",
hash);
}
static async Task<Tuple<Stream, EngineVersion>> GetByondVersionPriorTo(ByondInstallerBase byondInstaller, Version version)
{
var minusOneMinor = new Version(version.Major, version.Minor - 1);