diff --git a/.github/workflows/ci-pipeline.yml b/.github/workflows/ci-pipeline.yml index 39bd74ee1a..26ba8af3fe 100644 --- a/.github/workflows/ci-pipeline.yml +++ b/.github/workflows/ci-pipeline.yml @@ -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 diff --git a/build/package/deb/debian/postinst b/build/package/deb/debian/postinst index 9a1cb1b13c..2b98bd204c 100755 --- a/build/package/deb/debian/postinst +++ b/build/package/deb/debian/postinst @@ -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 " _ _ _ _ " diff --git a/src/DMAPI/tgs/core/core.dm b/src/DMAPI/tgs/core/core.dm index f9791fe3f6..8be96f2740 100644 --- a/src/DMAPI/tgs/core/core.dm +++ b/src/DMAPI/tgs/core/core.dm @@ -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]") diff --git a/src/DMAPI/tgs/v5/api.dm b/src/DMAPI/tgs/v5/api.dm index f7afbd5280..25d49b3e3b 100644 --- a/src/DMAPI/tgs/v5/api.dm +++ b/src/DMAPI/tgs/v5/api.dm @@ -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 diff --git a/src/DMAPI/tgs/v5/bridge.dm b/src/DMAPI/tgs/v5/bridge.dm index cc12c1ddc8..60cbcbfb7d 100644 --- a/src/DMAPI/tgs/v5/bridge.dm +++ b/src/DMAPI/tgs/v5/bridge.dm @@ -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) diff --git a/src/Tgstation.Server.Api/Routes.cs b/src/Tgstation.Server.Api/Routes.cs index e4a5f75a0a..4372929ed7 100644 --- a/src/Tgstation.Server.Api/Routes.cs +++ b/src/Tgstation.Server.Api/Routes.cs @@ -8,19 +8,19 @@ namespace Tgstation.Server.Api public static class Routes { /// - /// The root controller. + /// The root of API methods. /// - public const string Root = "/"; + public const string ApiRoot = "/api/"; /// /// The root route of all hubs. /// - public const string HubsRoot = Root + "hubs"; + public const string HubsRoot = ApiRoot + "hubs"; /// /// The server administration controller. /// - public const string Administration = Root + "Administration"; + public const string Administration = ApiRoot + "Administration"; /// /// The endpoint to download server logs. @@ -30,32 +30,32 @@ namespace Tgstation.Server.Api /// /// The user controller. /// - public const string User = Root + "User"; + public const string User = ApiRoot + "User"; /// /// The user group controller. /// - public const string UserGroup = Root + "UserGroup"; + public const string UserGroup = ApiRoot + "UserGroup"; /// /// The controller. /// - public const string InstanceManager = Root + "Instance"; + public const string InstanceManager = ApiRoot + "Instance"; /// /// The engine controller. /// - public const string Engine = Root + "Engine"; + public const string Engine = ApiRoot + "Engine"; /// /// The git repository controller. /// - public const string Repository = Root + "Repository"; + public const string Repository = ApiRoot + "Repository"; /// /// The DreamDaemon controller. /// - public const string DreamDaemon = Root + "DreamDaemon"; + public const string DreamDaemon = ApiRoot + "DreamDaemon"; /// /// For accessing DD diagnostics. @@ -65,7 +65,7 @@ namespace Tgstation.Server.Api /// /// The configuration controller. /// - public const string Configuration = Root + "Config"; + public const string Configuration = ApiRoot + "Config"; /// /// To be paired with for accessing s. @@ -80,27 +80,27 @@ namespace Tgstation.Server.Api /// /// The instance permission set controller. /// - public const string InstancePermissionSet = Root + "InstancePermissionSet"; + public const string InstancePermissionSet = ApiRoot + "InstancePermissionSet"; /// /// The chat bot controller. /// - public const string Chat = Root + "Chat"; + public const string Chat = ApiRoot + "Chat"; /// /// The deployment controller. /// - public const string DreamMaker = Root + "DreamMaker"; + public const string DreamMaker = ApiRoot + "DreamMaker"; /// /// The jobs controller. /// - public const string Jobs = Root + "Job"; + public const string Jobs = ApiRoot + "Job"; /// /// The transfer controller. /// - public const string Transfer = Root + "Transfer"; + public const string Transfer = ApiRoot + "Transfer"; /// /// The postfix for list operations. diff --git a/src/Tgstation.Server.Client/ApiClient.cs b/src/Tgstation.Server.Client/ApiClient.cs index d759ccbbdd..f691dd0e90 100644 --- a/src/Tgstation.Server.Client/ApiClient.cs +++ b/src/Tgstation.Server.Client/ApiClient.cs @@ -349,7 +349,7 @@ namespace Tgstation.Server.Client if (startingToken != headers.Token) return true; - var token = await RunRequest(Routes.Root, new object(), HttpMethod.Post, null, true, cancellationToken).ConfigureAwait(false); + var token = await RunRequest(Routes.ApiRoot, new object(), HttpMethod.Post, null, true, cancellationToken).ConfigureAwait(false); headers = new ApiHeaders(headers.UserAgent!, token); } finally diff --git a/src/Tgstation.Server.Client/IServerClient.cs b/src/Tgstation.Server.Client/IServerClient.cs index 65014f66b1..317e012ed0 100644 --- a/src/Tgstation.Server.Client/IServerClient.cs +++ b/src/Tgstation.Server.Client/IServerClient.cs @@ -16,7 +16,7 @@ namespace Tgstation.Server.Client public interface IServerClient : IAsyncDisposable { /// - /// The connected server . + /// The connected server's root . /// Uri Url { get; } diff --git a/src/Tgstation.Server.Client/ServerClient.cs b/src/Tgstation.Server.Client/ServerClient.cs index 186e32745b..6fce35fcf9 100644 --- a/src/Tgstation.Server.Client/ServerClient.cs +++ b/src/Tgstation.Server.Client/ServerClient.cs @@ -66,7 +66,7 @@ namespace Tgstation.Server.Client public ValueTask DisposeAsync() => apiClient.DisposeAsync(); /// - public ValueTask ServerInformation(CancellationToken cancellationToken) => apiClient.Read(Routes.Root, cancellationToken); + public ValueTask ServerInformation(CancellationToken cancellationToken) => apiClient.Read(Routes.ApiRoot, cancellationToken); /// public void AddRequestLogger(IRequestLogger requestLogger) => apiClient.AddRequestLogger(requestLogger); diff --git a/src/Tgstation.Server.Client/ServerClientFactory.cs b/src/Tgstation.Server.Client/ServerClientFactory.cs index 155198996a..98a51e8dc5 100644 --- a/src/Tgstation.Server.Client/ServerClientFactory.cs +++ b/src/Tgstation.Server.Client/ServerClientFactory.cs @@ -138,7 +138,7 @@ namespace Tgstation.Server.Client if (timeout.HasValue) api.Timeout = timeout.Value; - return await api.Read(Routes.Root, cancellationToken).ConfigureAwait(false); + return await api.Read(Routes.ApiRoot, cancellationToken).ConfigureAwait(false); } /// @@ -169,7 +169,7 @@ namespace Tgstation.Server.Client if (timeout.HasValue) api.Timeout = timeout.Value; - token = await api.Update(Routes.Root, cancellationToken).ConfigureAwait(false); + token = await api.Update(Routes.ApiRoot, cancellationToken).ConfigureAwait(false); } var apiHeaders = new ApiHeaders(productHeaderValue, token); diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs index cae1677f60..6c4a619bf0 100644 --- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs @@ -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) diff --git a/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs b/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs index 0ef30ddac5..f33ef79b11 100644 --- a/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs +++ b/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs @@ -120,7 +120,7 @@ namespace Tgstation.Server.Host.Configuration public bool UseBasicWatchdog { get; set; } /// - /// If the swagger UI should be made avaiable. + /// If the swagger documentation and UI should be made avaiable. /// public bool HostApiDocumentation { get; set; } diff --git a/src/Tgstation.Server.Host/Controllers/HomeController.cs b/src/Tgstation.Server.Host/Controllers/ApiRootController.cs similarity index 90% rename from src/Tgstation.Server.Host/Controllers/HomeController.cs rename to src/Tgstation.Server.Host/Controllers/ApiRootController.cs index 659a379591..fbfc1d4465 100644 --- a/src/Tgstation.Server.Host/Controllers/HomeController.cs +++ b/src/Tgstation.Server.Host/Controllers/ApiRootController.cs @@ -32,66 +32,66 @@ namespace Tgstation.Server.Host.Controllers /// /// Root for the . /// - [Route(Routes.Root)] - public sealed class HomeController : ApiController + [Route(Routes.ApiRoot)] + public sealed class ApiRootController : ApiController { /// - /// The for the . + /// The for the . /// readonly ITokenFactory tokenFactory; /// - /// The for the . + /// The for the . /// readonly ISystemIdentityFactory systemIdentityFactory; /// - /// The for the . + /// The for the . /// readonly ICryptographySuite cryptographySuite; /// - /// The for the . + /// The for the . /// readonly IAssemblyInformationProvider assemblyInformationProvider; /// - /// The for the . + /// The for the . /// readonly IIdentityCache identityCache; /// - /// The for the . + /// The for the . /// readonly IOAuthProviders oAuthProviders; /// - /// The for the . + /// The for the . /// readonly IPlatformIdentifier platformIdentifier; /// - /// The for the . + /// The for the . /// readonly ISwarmService swarmService; /// - /// The for the . + /// The for the . /// readonly IServerControl serverControl; /// - /// The for the . + /// The for the . /// readonly GeneralConfiguration generalConfiguration; /// - /// The for the . + /// The for the . /// readonly ControlPanelConfiguration controlPanelConfiguration; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The for the . /// The for the . @@ -108,7 +108,7 @@ namespace Tgstation.Server.Host.Controllers /// The containing the value of . /// The for the . /// The for the . - public HomeController( + public ApiRootController( IDatabaseContext databaseContext, IAuthenticationContext authenticationContext, ITokenFactory tokenFactory, @@ -122,7 +122,7 @@ namespace Tgstation.Server.Host.Controllers IServerControl serverControl, IOptions generalConfigurationOptions, IOptions controlPanelConfigurationOptions, - ILogger logger, + ILogger 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 /// /// Attempt to authenticate a using . diff --git a/src/Tgstation.Server.Host/Controllers/BridgeController.cs b/src/Tgstation.Server.Host/Controllers/BridgeController.cs index 53e64e08dc..a1acb954a9 100644 --- a/src/Tgstation.Server.Host/Controllers/BridgeController.cs +++ b/src/Tgstation.Server.Host/Controllers/BridgeController.cs @@ -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 /// /// for receiving DMAPI requests from DreamDaemon. /// - [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 { + /// + /// The route to the . + /// + const string RouteExtension = "Bridge"; + /// /// If the content of bridge requests and responses should be logged. /// diff --git a/src/Tgstation.Server.Host/Controllers/RootController.cs b/src/Tgstation.Server.Host/Controllers/RootController.cs new file mode 100644 index 0000000000..fd1bf36b11 --- /dev/null +++ b/src/Tgstation.Server.Host/Controllers/RootController.cs @@ -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 +{ + /// + /// The root path . + /// + [Route("/")] + public sealed class RootController : Controller + { + /// + /// The route to the TGS logo .svg in the on Windows. + /// + public const string ProjectLogoSvgRouteWindows = "/0176d5d8b7d307f158e0.svg"; + + /// + /// The route to the TGS logo .svg in the on Linux. + /// + public const string ProjectLogoSvgRouteLinux = "/b5616c99bf2052a6bbd7.svg"; + + /// + /// The for the . + /// + readonly IAssemblyInformationProvider assemblyInformationProvider; + + /// + /// The for the . + /// + readonly IPlatformIdentifier platformIdentifier; + + /// + /// The for the . + /// + readonly GeneralConfiguration generalConfiguration; + + /// + /// The for the . + /// + readonly ControlPanelConfiguration controlPanelConfiguration; + + /// + /// Initializes a new instance of the class. + /// + /// The value of . + /// The value of . + /// The containing the value of . + /// The containing the value of . + public RootController( + IAssemblyInformationProvider assemblyInformationProvider, + IPlatformIdentifier platformIdentifier, + IOptions generalConfigurationOptions, + IOptions 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)); + } + + /// + /// Gets the server's homepage. + /// + /// The appropriate . + [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 links; + if (panelEnabled) + links = new Dictionary() + { + { "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); + } + } +} diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 795ffe54ce..df49019d05 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -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"); } diff --git a/src/Tgstation.Server.Host/Swarm/SwarmConstants.cs b/src/Tgstation.Server.Host/Swarm/SwarmConstants.cs index fe2b892c32..f5485cc280 100644 --- a/src/Tgstation.Server.Host/Swarm/SwarmConstants.cs +++ b/src/Tgstation.Server.Host/Swarm/SwarmConstants.cs @@ -14,7 +14,7 @@ namespace Tgstation.Server.Host.Swarm /// /// The base route for . /// - public const string ControllerRoute = Routes.Root + "Swarm"; + public const string ControllerRoute = Routes.ApiRoot + "Swarm"; /// /// The header used to pass in the . diff --git a/src/Tgstation.Server.Host/Utils/SwaggerConfiguration.cs b/src/Tgstation.Server.Host/Utils/SwaggerConfiguration.cs index 89f7716732..8f8b0195bd 100644 --- a/src/Tgstation.Server.Host/Utils/SwaggerConfiguration.cs +++ b/src/Tgstation.Server.Host/Utils/SwaggerConfiguration.cs @@ -27,6 +27,16 @@ namespace Tgstation.Server.Host.Utils /// sealed class SwaggerConfiguration : IOperationFilter, IDocumentFilter, ISchemaFilter, IRequestBodyFilter { + /// + /// The name of the swagger document. + /// + public const string DocumentName = "tgs_api"; + + /// + /// The path to the hosted documentation site. + /// + public const string DocumentationSiteRouteExtension = "documentation"; + /// /// The name for password authentication. /// @@ -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 { diff --git a/src/Tgstation.Server.Host/Views/Root/Index.cshtml b/src/Tgstation.Server.Host/Views/Root/Index.cshtml new file mode 100644 index 0000000000..209a6ad5ac --- /dev/null +++ b/src/Tgstation.Server.Host/Views/Root/Index.cshtml @@ -0,0 +1,28 @@ +@{ + var svgPath = Model.Svg; + var title = Model.Title; + + + + @title + + + + + @{ + if (Model.Links != null) + foreach (KeyValuePair kvp in Model.Links) + { +

+ @kvp.Key +

+ } + } + + +} diff --git a/src/Tgstation.Server.Host/appsettings.yml b/src/Tgstation.Server.Host/appsettings.yml index 82cb960bf3..60624d3617 100644 --- a/src/Tgstation.Server.Host/appsettings.yml +++ b/src/Tgstation.Server.Host/appsettings.yml @@ -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 diff --git a/tests/DMAPI/LongRunning/Test.dm b/tests/DMAPI/LongRunning/Test.dm index e2656485d9..0a80378b7e 100644 --- a/tests/DMAPI/LongRunning/Test.dm +++ b/tests/DMAPI/LongRunning/Test.dm @@ -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 diff --git a/tests/Tgstation.Server.Tests/Live/Instance/TestBridgeHandler.cs b/tests/Tgstation.Server.Tests/Live/Instance/TestBridgeHandler.cs index 53e0c43a51..4276067b19 100644 --- a/tests/Tgstation.Server.Tests/Live/Instance/TestBridgeHandler.cs +++ b/tests/Tgstation.Server.Tests/Live/Instance/TestBridgeHandler.cs @@ -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 { diff --git a/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs b/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs index 31b64c65ba..de4dea0073 100644 --- a/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs +++ b/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs @@ -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); + } } } diff --git a/tests/Tgstation.Server.Tests/Live/LiveTestingServer.cs b/tests/Tgstation.Server.Tests/Live/LiveTestingServer.cs index cd00cc2f96..aed4357b72 100644 --- a/tests/Tgstation.Server.Tests/Live/LiveTestingServer.cs +++ b/tests/Tgstation.Server.Tests/Live/LiveTestingServer.cs @@ -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 diff --git a/tests/Tgstation.Server.Tests/Live/RawRequestTests.cs b/tests/Tgstation.Server.Tests/Live/RawRequestTests.cs index bf0d409e61..50e9b21169 100644 --- a/tests/Tgstation.Server.Tests/Live/RawRequestTests.cs +++ b/tests/Tgstation.Server.Tests/Live/RawRequestTests.cs @@ -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")); diff --git a/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs b/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs index 681f4ea902..8dfe8058cd 100644 --- a/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs +++ b/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs @@ -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( () => 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(() => 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(() => 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 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 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) { diff --git a/tests/Tgstation.Server.Tests/TestRepository.cs b/tests/Tgstation.Server.Tests/TestRepository.cs index 8a16f0ddd9..ad99eb2603 100644 --- a/tests/Tgstation.Server.Tests/TestRepository.cs +++ b/tests/Tgstation.Server.Tests/TestRepository.cs @@ -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(), @@ -36,7 +39,7 @@ namespace Tgstation.Server.Tests Mock.Of(), Mock.Of(), Mock.Of(), - Mock.Of>(), + Mock.Of>(), 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>()); + var commands = new LibGit2Commands(); + using var manager = new RepositoryManager( + repoFac, + commands, + new ResolvingIOManager( + new DefaultIOManager(), + tempPath), + Mock.Of(), + new WindowsPostWriteHandler(), + Mock.Of(), + Mock.Of>(), + Mock.Of>(), + new GeneralConfiguration()); + try + { + using (await manager.CloneRepository( + new Uri("https://github.com/Cyberboss/common_core"), + null, + null, + null, + new JobProgressReporter(Mock.Of>(), 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("5b0d0a38057a2c8306a852ccbd6cd6f4ae766a33"); + Assert.IsNull(targetCommit); + } + + using (var repo2 = await manager.LoadRepository(default)) + { + await repo2.FetchOrigin( + new JobProgressReporter(Mock.Of>(), 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("5b0d0a38057a2c8306a852ccbd6cd6f4ae766a33"); + Assert.IsNotNull(targetCommit2); + } + finally + { + await new DefaultIOManager().DeleteDirectory( + Path.GetDirectoryName(tempPath), + CancellationToken.None); + } + } } } diff --git a/tests/Tgstation.Server.Tests/TestVersions.cs b/tests/Tgstation.Server.Tests/TestVersions.cs index 78390936df..80382f2fc2 100644 --- a/tests/Tgstation.Server.Tests/TestVersions.cs +++ b/tests/Tgstation.Server.Tests/TestVersions.cs @@ -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> GetByondVersionPriorTo(ByondInstallerBase byondInstaller, Version version) { var minusOneMinor = new Version(version.Major, version.Minor - 1);