mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-24 21:46:52 +01:00
Merge branch 'V6' into OpenDream
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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 " _ _ _ _ "
|
||||
|
||||
@@ -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]")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
+17
-35
@@ -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>
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user