diff --git a/appveyor.yml b/appveyor.yml index a2436ab181..e403ebb358 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -3,6 +3,7 @@ pull_requests: do_not_increment_build_number: true environment: TGS4_TEST_DATABASE_TYPE: SqlServer + TGS4_TEST_DUMP_API_SPEC: yes TGS4_TEST_CONNECTION_STRING: Server=(local)\SQL2017;Initial Catalog=TGS_Test;User ID=sa;Password=Password12! TGS4_TEST_GITHUB_TOKEN: secure: lJNGAXwiB5HlWdthz3K4PetqpTG5IEAyRgKaiKxFMQ8HW8CcOjRtB97B05op7BsK @@ -35,6 +36,7 @@ services: - mssql2017 install: - choco install doxygen.install codecov graphviz.portable opencover.portable + - npm i -g ibm-openapi-validator - nuget restore tgstation-server.sln - ps: Install-Product node 10 build: @@ -65,6 +67,7 @@ test_script: - OpenCover.Console.exe -returntargetcode -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx /p:DebugType=full tests/Tgstation.Server.Tests/Tgstation.Server.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Tests*]* -[Tgstation.Server.Host]Tgstation.Server.Host.Models.Migrations..*" -output:".\server_coverage.xml" -oldstyle - ps: $wc = New-Object 'System.Net.WebClient' - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\tests\Tgstation.Server.Tests\TestResults\results.trx)) + - lint-openapi -c build/OpenApiValidationSettings.json C:/swagger.json after_test: - ps: build/UploadCoverage.ps1 - ps: build/BuildDox.ps1 diff --git a/build/BuildDox.ps1 b/build/BuildDox.ps1 index a88025e3d7..7ff89fff10 100644 --- a/build/BuildDox.ps1 +++ b/build/BuildDox.ps1 @@ -26,7 +26,7 @@ if($publish_dox){ echo '# THIS BRANCH IS AUTO GENERATED BY APPVEYOR CI' > README.md # Add in the swagger specification - mv C:/swagger.json "$doxdir/swagger.json" + cp C:/swagger.json "$doxdir/swagger.json" # Need to create a .nojekyll file to allow filenames starting with an underscore # to be seen on the gh-pages site. Therefore creating an empty .nojekyll file. diff --git a/build/OpenApiValidationSettings.json b/build/OpenApiValidationSettings.json new file mode 100644 index 0000000000..097d790921 --- /dev/null +++ b/build/OpenApiValidationSettings.json @@ -0,0 +1,80 @@ +{ + "shared": { + "operations": { + "no_operation_id": "error", + "operation_id_case_convention": "off", + "no_summary": "error", + "no_array_responses": "off", + "parameter_order": "error", + "unused_tag": "error" + }, + "pagination": { + "pagination_style": "error" + }, + "parameters": { + "no_parameter_description": "error", + "param_name_case_convention": "off", + "invalid_type_format_pair": "off", + "content_type_parameter": "error", + "accept_type_parameter": "error", + "authorization_parameter": "error", + "required_param_has_default": "error" + }, + "paths": { + "missing_path_parameter": "error", + "duplicate_path_parameter": "error", + "snake_case_only": "off", + "paths_case_convention": "off" + }, + "responses": { + "inline_response_schema": "off" + }, + "security_definitions": { + "unused_security_schemes": "error", + "unused_security_scopes": "error" + }, + "security": { + "invalid_non_empty_security_array": "error" + }, + "schemas": { + "invalid_type_format_pair": "off", + "snake_case_only": "off", + "no_schema_description": "off", + "no_property_description": "off", + "description_mentions_json": "error", + "array_of_arrays": "error", + "property_case_convention": "off", + "enum_case_convention": "error" + }, + "walker": { + "no_empty_descriptions": "error", + "has_circular_references": "off", + "$ref_siblings": "error", + "duplicate_sibling_description": "error", + "incorrect_ref_pattern": "error" + } + }, + "swagger2": { + "operations": { + "no_consumes_for_put_or_post": "error", + "get_op_has_consumes": "error", + "no_produces": "error" + } + }, + "oas3": { + "operations": { + "no_request_body_content": "error", + "no_request_body_name": "off" + }, + "parameters": { + "no_in_property": "error", + "invalid_in_property": "error", + "missing_schema_or_content": "error", + "has_schema_and_content": "error" + }, + "responses": { + "no_response_codes": "error", + "no_success_response_codes": "error" + } + } +} \ No newline at end of file diff --git a/docs/API.dox b/docs/API.dox index 532597365d..4870e91407 100644 --- a/docs/API.dox +++ b/docs/API.dox @@ -38,10 +38,9 @@ This document will reference the canonical C# models in the @ref Tgstation.Serve @section api_header Headers -TGS4 expects this set of headers. Failure to provide them may result in 400 error responses +TGS4 expects this set of headers. Failure to provide them will result in 400 error responses - User-Agent: The user agent product header value of the calling program -- Accept: application/json - Api: Another product header value representing the version of the API to use. Currently this must be: Tgstation.Server.Api/4.0.0.0 For POST, PATCH, and PUT requests you must also include the content type. Currently only json is supported @@ -87,8 +86,7 @@ POST "/" => @ref Tgstation.Server.Api.Models.Token Headers: -- Username: `` -- Authorization:Password `` +- Authorization:basic `` If the provided credentials are valid and your user account is enabled you will recieve a @ref Tgstation.Server.Api.Models.Token object @code{.json} @@ -101,7 +99,7 @@ If your account is disabled, you will recieve a 403 response. You may recognize the bearer value as a Json Web Token. This is a secure representation of your identity to the server. It expires after a set period of time or until your password changes. It must be present for requests made to all other APIs. To do so add the following header to your other requests -- Authorization:Bearer `` +- Authorization:bearer `` Continue to use this token until you begin to recieve 401 responses from the API. Then repeat the process to get a new one if your credentials are still valid diff --git a/docs/Architecture.dox b/docs/Architecture.dox index 59f2f81ed8..b13c7de06e 100644 --- a/docs/Architecture.dox +++ b/docs/Architecture.dox @@ -23,7 +23,7 @@ This is a second process spawned by the Host Watchdog which facilitates the vast The server's entrypoint is in the @ref Tgstation.Server.Host.Program class. This class mainly determines if the Host watchdog is present and creates and runs the @ref Tgstation.Server.Host.Server class. That class then builds an ASP.NET Core web host using the @ref Tgstation.Server.Host.Core.Application class. -The @ref Tgstation.Server.Host.Core.Application class has two methods called by the framework. First the @ref Tgstation.Server.Host.Core.Application.ConfigureServices method sets up dependency injection of interfaces for Controllers, the @ref Tgstation.Server.Host.Models.DatabaseContext, and the component factories of the server. The framework handles constructing these things once the application starts. Configuration is loaded from the appropriate appSettings.json into the @ref Tgstation.Server.Host.Configuration classes for injection as well. Then @ref Tgstation.Server.Host.Core.Application.Configure method is run which sets up the web request pipeline which currently has the following stack of handlers: +The @ref Tgstation.Server.Host.Core.Application class has two methods called by the framework. First the @ref Tgstation.Server.Host.Core.Application.ConfigureServices method sets up dependency injection of interfaces for Controllers, the @ref Tgstation.Server.Host.Database.DatabaseContext, and the component factories of the server. The framework handles constructing these things once the application starts. Configuration is loaded from the appropriate appSettings.json into the @ref Tgstation.Server.Host.Configuration classes for injection as well. Then @ref Tgstation.Server.Host.Core.Application.Configure method is run which sets up the web request pipeline which currently has the following stack of handlers: - Catch any exceptions and respond with 500 and detailed HTML error page - Respond with 503 if the application is still starting or shutting down @@ -38,11 +38,11 @@ The @ref Tgstation.Server.Host.Core.Application class has two methods called by Once the web host starts, the @ref Tgstation.Server.Host.Components.InstanceManager.StartAsync function is called (due to being registered as a IHostedService in @ref Tgstation.Server.Host.Core.Application) this is the only StartAsync implementation that should be called by the framework, others should be called from this to maintain a cohesive initialization order. -The first thing this function does is call @ref Tgstation.Server.Host.Models.DatabaseContext.Initialize which ensures the database is migrated, seeded, and ready to go. Then the @ref Tgstation.Server.Host.Core.JobManager is started, which cleans up any jobs that are considered "still running" in the database. Finally all instances configured to be online are created in parallel (See @ref arch_instance for onlining process) and the @ref Tgstation.Server.Host.Core.Application is signalled to stop blocking requests with 503 responses before they are processed. +The first thing this function does is call @ref Tgstation.Server.Host.Database.DatabaseContext.Initialize which ensures the database is migrated, seeded, and ready to go. Then the @ref Tgstation.Server.Host.Core.JobManager is started, which cleans up any jobs that are considered "still running" in the database. Finally all instances configured to be online are created in parallel (See @ref arch_instance for onlining process) and the @ref Tgstation.Server.Host.Core.Application is signalled to stop blocking requests with 503 responses before they are processed. @section arch_db Database and Context -The database is exposed as a series of DbSet objects through @ref Tgstation.Server.Host.Models.IDatabaseContext . Queries are performed via async LINQ expressions. Inserts, updates, and deletes are done via modifiying the DbSets and then calling @ref Tgstation.Server.Host.Models.IDatabaseContext.Save . Do some reading on Entity Framework Core for a deeper understanding. +The database is exposed as a series of DbSet objects through @ref Tgstation.Server.Host.Database.IDatabaseContext . Queries are performed via async LINQ expressions. Inserts, updates, and deletes are done via modifiying the DbSets and then calling @ref Tgstation.Server.Host.Database.IDatabaseContext.Save . Do some reading on Entity Framework Core for a deeper understanding. @section arch_controllers Controllers @@ -98,9 +98,9 @@ The compilation process is a distinct series of steps: 14. Symlink all `GameStaticFiles` to both the A and B directories 15. Commit the @ref Tgstation.Server.Host.Models.CompileJob to the database -If any of the above steps fail, the target directory is deleted and the deployment is considered a bust. If all went well, after the @ref Tgstation.Server.Host.Models.Job completes the new CompileJob is loaded into the instance's @ref Tgstation.Server.Host.Components.Compiler.IDmbFactory . +If any of the above steps fail, the target directory is deleted and the deployment is considered a bust. If all went well, after the @ref Tgstation.Server.Host.Models.Job completes the new CompileJob is loaded into the instance's @ref Tgstation.Server.Host.Components.Deployment.IDmbFactory . -The DmbFactory is where the @ref arch_watchdog gets the @ref Tgstation.Server.Host.Components.Compiler.IDmbProvider instances to run. Each CompileJob loaded into it is given a lock count. The latest CompileJob holds 1 lock and every DreamDaemon instance running that CompileJob holds another. Loading a new CompileJob releases the initial lock, and when all other locks are released the CompileJob's directory is deleted. Any directories in the `Game` folder not in use are also deleted when the Instance starts. +The DmbFactory is where the @ref arch_watchdog gets the @ref Tgstation.Server.Host.Components.Deployment.IDmbProvider instances to run. Each CompileJob loaded into it is given a lock count. The latest CompileJob holds 1 lock and every DreamDaemon instance running that CompileJob holds another. Loading a new CompileJob releases the initial lock, and when all other locks are released the CompileJob's directory is deleted. Any directories in the `Game` folder not in use are also deleted when the Instance starts. @section arch_chat Chat Bot System @@ -128,7 +128,7 @@ That's a high level view of things, now let's get to the nitty gritty. @subsection arch_wd_launch Launch -First the most recent @ref Tgstation.Server.Host.Components.Compiler.IDmbProvider is retrieved from the @ref Tgstation.Server.Host.Components.Compiler.IDmbFactory twice, adding 2 locks. +First the most recent @ref Tgstation.Server.Host.Components.Deployment.IDmbProvider is retrieved from the @ref Tgstation.Server.Host.Components.Deployment.IDmbFactory twice, adding 2 locks. This is used to launch a @ref Tgstation.Server.Host.Components.Watchdog.ISessionController via the watchdog's @ref Tgstation.Server.Host.Components.Watchdog.ISessionControllerFactory in the `A` directory of dmb providers @ref Tgstation.Server.Host.Models.CompileJob . This will be designated the `Alpha` server. diff --git a/src/DMAPI/tgs.dm b/src/DMAPI/tgs.dm index 5d89b42483..ec0dad2040 100644 --- a/src/DMAPI/tgs.dm +++ b/src/DMAPI/tgs.dm @@ -50,7 +50,7 @@ #define TGS_EVENT_PORT_SWAP -2 //before a port change is about to happen, extra parameter is new port #define TGS_EVENT_REBOOT_MODE_CHANGE -1 //before a reboot mode change, extras parameters are the current and new reboot mode enums -//See the descriptions for these codes here: https://github.com/tgstation/tgstation-server/blob/master/src/Tgstation.Server.Host/Components/EventType.cs +//See the descriptions for the parameters of these codes here: https://github.com/tgstation/tgstation-server/blob/master/src/Tgstation.Server.Host/Components/EventType.cs #define TGS_EVENT_REPO_RESET_ORIGIN 0 #define TGS_EVENT_REPO_CHECKOUT 1 #define TGS_EVENT_REPO_FETCH 2 diff --git a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs index 9f72e17de5..3f0fc07e47 100644 --- a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs @@ -86,7 +86,7 @@ namespace Tgstation.Server.Host.Watchdog Directory.Delete(assemblyStoragePath, true); Directory.CreateDirectory(defaultAssemblyPath); - var sourcePath = "../../../Tgstation.Server.Host/bin/Debug/netcoreapp2.1"; + var sourcePath = "../../../../Tgstation.Server.Host/bin/Debug/netcoreapp2.1"; foreach (string dirPath in Directory.GetDirectories(sourcePath, "*", SearchOption.AllDirectories)) Directory.CreateDirectory(dirPath.Replace(sourcePath, defaultAssemblyPath)); diff --git a/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs b/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs index 73c458f7f0..a4262f12ee 100644 --- a/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs +++ b/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Host.Core; using Tgstation.Server.Host.IO; +using Tgstation.Server.Host.Jobs; namespace Tgstation.Server.Host.Components.Byond { diff --git a/src/Tgstation.Server.Host/Components/Byond/IByondExecutableLock.cs b/src/Tgstation.Server.Host/Components/Byond/IByondExecutableLock.cs index 76e3c04321..c64cb56eec 100644 --- a/src/Tgstation.Server.Host/Components/Byond/IByondExecutableLock.cs +++ b/src/Tgstation.Server.Host/Components/Byond/IByondExecutableLock.cs @@ -8,7 +8,7 @@ namespace Tgstation.Server.Host.Components.Byond public interface IByondExecutableLock : IDisposable { /// - /// The of the locked executables + /// The of the locked executables /// Version Version { get; } diff --git a/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs b/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs index 4b48d6f0bf..c743a08b60 100644 --- a/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs +++ b/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs @@ -6,6 +6,8 @@ using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Host.Core; using Tgstation.Server.Host.IO; +using Tgstation.Server.Host.Jobs; +using Tgstation.Server.Host.System; namespace Tgstation.Server.Host.Components.Byond { diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/CommandFactory.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/CommandFactory.cs index 870bb03b7a..b3dbd72daa 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Commands/CommandFactory.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/CommandFactory.cs @@ -4,6 +4,7 @@ using Tgstation.Server.Host.Components.Byond; using Tgstation.Server.Host.Components.Repository; using Tgstation.Server.Host.Components.Watchdog; using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Database; namespace Tgstation.Server.Host.Components.Chat.Commands { diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/PullRequestsCommand.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/PullRequestsCommand.cs index 8e2b9f9c19..989fc8026d 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Commands/PullRequestsCommand.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/PullRequestsCommand.cs @@ -7,7 +7,7 @@ using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Host.Components.Repository; using Tgstation.Server.Host.Components.Watchdog; -using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Database; namespace Tgstation.Server.Host.Components.Chat.Commands { diff --git a/src/Tgstation.Server.Host/Components/Chat/JsonTrackingContext.cs b/src/Tgstation.Server.Host/Components/Chat/JsonTrackingContext.cs index f551c3046f..e76fdec0dd 100644 --- a/src/Tgstation.Server.Host/Components/Chat/JsonTrackingContext.cs +++ b/src/Tgstation.Server.Host/Components/Chat/JsonTrackingContext.cs @@ -21,7 +21,7 @@ namespace Tgstation.Server.Host.Components.Chat get => active; set { - active = true; + active = value; logger.LogDebug("Tracking {0}activated", !active ? "de" : String.Empty); } } diff --git a/src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs b/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs similarity index 99% rename from src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs rename to src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs index bd6b84fddd..5f4f8fc364 100644 --- a/src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs @@ -5,11 +5,11 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; -using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Database; using Tgstation.Server.Host.IO; using Tgstation.Server.Host.Models; -namespace Tgstation.Server.Host.Components.Compiler +namespace Tgstation.Server.Host.Components.Deployment { /// /// Standard diff --git a/src/Tgstation.Server.Host/Components/Compiler/DmbProvider.cs b/src/Tgstation.Server.Host/Components/Deployment/DmbProvider.cs similarity index 97% rename from src/Tgstation.Server.Host/Components/Compiler/DmbProvider.cs rename to src/Tgstation.Server.Host/Components/Deployment/DmbProvider.cs index bca0d73fbf..dd5c498d5e 100644 --- a/src/Tgstation.Server.Host/Components/Compiler/DmbProvider.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/DmbProvider.cs @@ -2,7 +2,7 @@ using Tgstation.Server.Host.IO; using Tgstation.Server.Host.Models; -namespace Tgstation.Server.Host.Components.Compiler +namespace Tgstation.Server.Host.Components.Deployment { /// sealed class DmbProvider : IDmbProvider diff --git a/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs similarity index 94% rename from src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs rename to src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs index 318243c064..e93de15c20 100644 --- a/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs @@ -12,10 +12,11 @@ using Tgstation.Server.Host.Components.Byond; using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Components.Repository; using Tgstation.Server.Host.Components.Watchdog; -using Tgstation.Server.Host.Core; using Tgstation.Server.Host.IO; +using Tgstation.Server.Host.Jobs; +using Tgstation.Server.Host.System; -namespace Tgstation.Server.Host.Components.Compiler +namespace Tgstation.Server.Host.Components.Deployment { /// sealed class DreamMaker : IDreamMaker @@ -60,16 +61,6 @@ namespace Tgstation.Server.Host.Components.Compiler /// readonly ISessionControllerFactory sessionControllerFactory; - /// - /// The for - /// - readonly ICompileJobConsumer compileJobConsumer; - - /// - /// The for - /// - readonly IApplication application; - /// /// The for /// @@ -100,6 +91,40 @@ namespace Tgstation.Server.Host.Components.Compiler /// bool compiling; + /// + /// Construct + /// + /// The value of + /// The value of + /// The value of + /// The value of + /// The value of + /// The value of + /// The value of + /// The value of + /// The value of + public DreamMaker( + IByondManager byond, + IIOManager ioManager, + StaticFiles.IConfiguration configuration, + ISessionControllerFactory sessionControllerFactory, + IEventConsumer eventConsumer, + IChat chat, + IProcessExecutor processExecutor, + IWatchdog watchdog, + ILogger logger) + { + this.byond = byond ?? throw new ArgumentNullException(nameof(byond)); + this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); + this.sessionControllerFactory = sessionControllerFactory ?? throw new ArgumentNullException(nameof(sessionControllerFactory)); + this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer)); + this.chat = chat ?? throw new ArgumentNullException(nameof(chat)); + this.processExecutor = processExecutor ?? throw new ArgumentNullException(nameof(processExecutor)); + this.watchdog = watchdog ?? throw new ArgumentNullException(nameof(watchdog)); + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + /// /// Gradually triggers a given over a given /// @@ -107,11 +132,12 @@ namespace Tgstation.Server.Host.Components.Compiler /// A representing the duration to give progress over /// The for the operation /// A representing the running operation - static async Task ProgressTask(Action progressReporter, TimeSpan estimatedDuration, CancellationToken cancellationToken) + async Task ProgressTask(Action progressReporter, TimeSpan estimatedDuration, CancellationToken cancellationToken) { progressReporter(0); var sleepInterval = estimatedDuration / 100; + logger.LogDebug("Compile is expected to take: {0}", estimatedDuration); try { for (var I = 0; I < 99; ++I) @@ -123,35 +149,6 @@ namespace Tgstation.Server.Host.Components.Compiler catch (OperationCanceledException) { } } - /// - /// Construct - /// - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of - public DreamMaker(IByondManager byond, IIOManager ioManager, StaticFiles.IConfiguration configuration, ISessionControllerFactory sessionControllerFactory, ICompileJobConsumer compileJobConsumer, IApplication application, IEventConsumer eventConsumer, IChat chat, IProcessExecutor processExecutor, IWatchdog watchdog, ILogger logger) - { - this.byond = byond ?? throw new ArgumentNullException(nameof(byond)); - this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); - this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); - this.sessionControllerFactory = sessionControllerFactory ?? throw new ArgumentNullException(nameof(sessionControllerFactory)); - this.compileJobConsumer = compileJobConsumer ?? throw new ArgumentNullException(nameof(compileJobConsumer)); - this.application = application ?? throw new ArgumentNullException(nameof(application)); - this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer)); - this.chat = chat ?? throw new ArgumentNullException(nameof(chat)); - this.processExecutor = processExecutor ?? throw new ArgumentNullException(nameof(processExecutor)); - this.watchdog = watchdog ?? throw new ArgumentNullException(nameof(watchdog)); - this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - /// /// Run a quick DD instance to test the DMAPI is installed on the target code /// diff --git a/src/Tgstation.Server.Host/Components/Compiler/ICompileJobConsumer.cs b/src/Tgstation.Server.Host/Components/Deployment/ICompileJobConsumer.cs similarity index 93% rename from src/Tgstation.Server.Host/Components/Compiler/ICompileJobConsumer.cs rename to src/Tgstation.Server.Host/Components/Deployment/ICompileJobConsumer.cs index 8095e3321e..a6af72fd99 100644 --- a/src/Tgstation.Server.Host/Components/Compiler/ICompileJobConsumer.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/ICompileJobConsumer.cs @@ -4,7 +4,7 @@ using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Host.Models; -namespace Tgstation.Server.Host.Components.Compiler +namespace Tgstation.Server.Host.Components.Deployment { /// /// Sink for s diff --git a/src/Tgstation.Server.Host/Components/Compiler/IDmbFactory.cs b/src/Tgstation.Server.Host/Components/Deployment/IDmbFactory.cs similarity index 97% rename from src/Tgstation.Server.Host/Components/Compiler/IDmbFactory.cs rename to src/Tgstation.Server.Host/Components/Deployment/IDmbFactory.cs index fc153bb22c..266f3b41e8 100644 --- a/src/Tgstation.Server.Host/Components/Compiler/IDmbFactory.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/IDmbFactory.cs @@ -3,7 +3,7 @@ using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Host.Models; -namespace Tgstation.Server.Host.Components.Compiler +namespace Tgstation.Server.Host.Components.Deployment { /// /// Factory for s diff --git a/src/Tgstation.Server.Host/Components/IDmbProvider.cs b/src/Tgstation.Server.Host/Components/Deployment/IDmbProvider.cs similarity index 93% rename from src/Tgstation.Server.Host/Components/IDmbProvider.cs rename to src/Tgstation.Server.Host/Components/Deployment/IDmbProvider.cs index 75bc533409..ea282ca37e 100644 --- a/src/Tgstation.Server.Host/Components/IDmbProvider.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/IDmbProvider.cs @@ -1,7 +1,7 @@ using System; using Tgstation.Server.Host.Models; -namespace Tgstation.Server.Host.Components +namespace Tgstation.Server.Host.Components.Deployment { /// /// Provides absolute paths to the latest compiled .dmbs diff --git a/src/Tgstation.Server.Host/Components/Compiler/IDreamMaker.cs b/src/Tgstation.Server.Host/Components/Deployment/IDreamMaker.cs similarity index 96% rename from src/Tgstation.Server.Host/Components/Compiler/IDreamMaker.cs rename to src/Tgstation.Server.Host/Components/Deployment/IDreamMaker.cs index b0a489ae84..81f6a6d881 100644 --- a/src/Tgstation.Server.Host/Components/Compiler/IDreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/IDreamMaker.cs @@ -3,7 +3,7 @@ using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Host.Components.Repository; -namespace Tgstation.Server.Host.Components.Compiler +namespace Tgstation.Server.Host.Components.Deployment { /// /// For managing the compiler diff --git a/src/Tgstation.Server.Host/Components/Compiler/TemporaryDmbProvider.cs b/src/Tgstation.Server.Host/Components/Deployment/TemporaryDmbProvider.cs similarity index 95% rename from src/Tgstation.Server.Host/Components/Compiler/TemporaryDmbProvider.cs rename to src/Tgstation.Server.Host/Components/Deployment/TemporaryDmbProvider.cs index 628f77eb0b..7eed521f5c 100644 --- a/src/Tgstation.Server.Host/Components/Compiler/TemporaryDmbProvider.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/TemporaryDmbProvider.cs @@ -1,7 +1,7 @@ using System; using Tgstation.Server.Host.Models; -namespace Tgstation.Server.Host.Components.Compiler +namespace Tgstation.Server.Host.Components.Deployment { /// /// Temporary diff --git a/src/Tgstation.Server.Host/Components/Deployment/WindowsSwappableDmbProvider.cs b/src/Tgstation.Server.Host/Components/Deployment/WindowsSwappableDmbProvider.cs new file mode 100644 index 0000000000..a5577b7a8a --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Deployment/WindowsSwappableDmbProvider.cs @@ -0,0 +1,78 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Host.IO; +using Tgstation.Server.Host.Models; + +namespace Tgstation.Server.Host.Components.Deployment +{ + /// + /// A windows that uses symlinks. + /// + sealed class WindowsSwappableDmbProvider : IDmbProvider + { + /// + /// The directory where the is symlinked to. + /// + const string LiveGameDirectory = "Live"; + + /// + public string DmbName => baseProvider.DmbName; + + /// + public string PrimaryDirectory => ioManager.ResolvePath(LiveGameDirectory); + + /// + public string SecondaryDirectory => throw new NotSupportedException(); + + /// + public CompileJob CompileJob => baseProvider.CompileJob; + + /// + /// The we are swapping for. + /// + readonly IDmbProvider baseProvider; + + /// + /// The to use. + /// + readonly IIOManager ioManager; + + /// + /// The to use. + /// + readonly ISymlinkFactory symlinkFactory; + + /// + /// Initializes a new instance of the . + /// + /// The value of . + /// The value of . + /// The value of . + public WindowsSwappableDmbProvider(IDmbProvider baseProvider, IIOManager ioManager, ISymlinkFactory symlinkFactory) + { + this.baseProvider = baseProvider ?? throw new ArgumentNullException(nameof(baseProvider)); + this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + this.symlinkFactory = symlinkFactory ?? throw new ArgumentNullException(nameof(symlinkFactory)); + } + + /// + public void Dispose() => baseProvider.Dispose(); + + /// + public void KeepAlive() => baseProvider.KeepAlive(); + + /// + /// Make the active by replacing the live link with our . + /// + /// The for the operation. + /// A representing the running operation. + public async Task MakeActive(CancellationToken cancellationToken) + { + // Note this comment from TGS3: + // These next two lines should be atomic but this is the best we can do + await ioManager.DeleteDirectory(LiveGameDirectory, cancellationToken).ConfigureAwait(false); + await symlinkFactory.CreateSymbolicLink(baseProvider.PrimaryDirectory, ioManager.ResolvePath(LiveGameDirectory), cancellationToken).ConfigureAwait(false); + } + } +} diff --git a/src/Tgstation.Server.Host/Components/IInstance.cs b/src/Tgstation.Server.Host/Components/IInstance.cs index 299dbe6fc0..49eaa0b8d1 100644 --- a/src/Tgstation.Server.Host/Components/IInstance.cs +++ b/src/Tgstation.Server.Host/Components/IInstance.cs @@ -7,6 +7,7 @@ using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Components.Repository; using Tgstation.Server.Host.Components.StaticFiles; using Tgstation.Server.Host.Components.Watchdog; +using Tgstation.Server.Host.Database; using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components @@ -61,7 +62,7 @@ namespace Tgstation.Server.Host.Components Task SetAutoUpdateInterval(uint newInterval); /// - /// Run the compile job and insert it into the database. Meant to be called by a + /// Run the compile job and insert it into the database. Meant to be called by a /// /// The running /// The for the operation diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index 5353b9d6f8..a1806e6aa1 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -10,10 +10,12 @@ using System.Threading.Tasks; using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Components.Byond; using Tgstation.Server.Host.Components.Chat; -using Tgstation.Server.Host.Components.Compiler; +using Tgstation.Server.Host.Components.Deployment; using Tgstation.Server.Host.Components.Repository; using Tgstation.Server.Host.Components.Watchdog; using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Database; +using Tgstation.Server.Host.Jobs; using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components @@ -109,7 +111,22 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// The value of - public Instance(Api.Models.Instance metadata, IRepositoryManager repositoryManager, IByondManager byondManager, IDreamMaker dreamMaker, IWatchdog watchdog, IChat chat, StaticFiles.IConfiguration configuration, ICompileJobConsumer compileJobConsumer, IDatabaseContextFactory databaseContextFactory, IDmbFactory dmbFactory, IJobManager jobManager, IEventConsumer eventConsumer, IGitHubClientFactory gitHubClientFactory, ILogger logger) + public Instance( + Api.Models.Instance metadata, + IRepositoryManager repositoryManager, + IByondManager byondManager, + IDreamMaker dreamMaker, + IWatchdog watchdog, + IChat chat, + StaticFiles.IConfiguration + configuration, + ICompileJobConsumer compileJobConsumer, + IDatabaseContextFactory databaseContextFactory, + IDmbFactory dmbFactory, + IJobManager jobManager, + IEventConsumer eventConsumer, + IGitHubClientFactory gitHubClientFactory, + ILogger logger) { this.metadata = metadata ?? throw new ArgumentNullException(nameof(metadata)); RepositoryManager = repositoryManager ?? throw new ArgumentNullException(nameof(repositoryManager)); @@ -324,7 +341,7 @@ namespace Tgstation.Server.Host.Components try { await Task.Delay(TimeSpan.FromMinutes(minutes > Int32.MaxValue ? Int32.MaxValue : (int)minutes), cancellationToken).ConfigureAwait(false); - logger.LogDebug("Beginning auto update..."); + logger.LogInformation("Beginning auto update..."); await eventConsumer.HandleEvent(EventType.InstanceAutoUpdateStart, new List(), cancellationToken).ConfigureAwait(false); try { diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index cbd14f17b6..41a91b5a25 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -6,12 +6,15 @@ using System.Threading.Tasks; using Tgstation.Server.Host.Components.Byond; using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Components.Chat.Commands; -using Tgstation.Server.Host.Components.Compiler; +using Tgstation.Server.Host.Components.Deployment; using Tgstation.Server.Host.Components.Repository; using Tgstation.Server.Host.Components.Watchdog; using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Database; using Tgstation.Server.Host.IO; +using Tgstation.Server.Host.Jobs; using Tgstation.Server.Host.Security; +using Tgstation.Server.Host.System; namespace Tgstation.Server.Host.Components { @@ -129,7 +132,25 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// The value of - public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IByondTopicSender byondTopicSender, ICryptographySuite cryptographySuite, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IByondInstaller byondInstaller, IChatFactory chatFactory, IProcessExecutor processExecutor, IPostWriteHandler postWriteHandler, IWatchdogFactory watchdogFactory, IJobManager jobManager, ICredentialsProvider credentialsProvider, INetworkPromptReaper networkPromptReaper, IGitHubClientFactory gitHubClientFactory, IPlatformIdentifier platformIdentifier) + public InstanceFactory( + IIOManager ioManager, + IDatabaseContextFactory databaseContextFactory, + IApplication application, + ILoggerFactory loggerFactory, + IByondTopicSender byondTopicSender, + ICryptographySuite cryptographySuite, + ISynchronousIOManager synchronousIOManager, + ISymlinkFactory symlinkFactory, + IByondInstaller byondInstaller, + IChatFactory chatFactory, + IProcessExecutor processExecutor, + IPostWriteHandler postWriteHandler, + IWatchdogFactory watchdogFactory, + IJobManager jobManager, + ICredentialsProvider credentialsProvider, + INetworkPromptReaper networkPromptReaper, + IGitHubClientFactory gitHubClientFactory, + IPlatformIdentifier platformIdentifier) { this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); @@ -152,7 +173,7 @@ namespace Tgstation.Server.Host.Components } /// - #pragma warning disable CA1506 // TODO: Decomplexify +#pragma warning disable CA1506 // TODO: Decomplexify public IInstance CreateInstance(Models.Instance metadata) { // Create the ioManager for the instance @@ -166,28 +187,36 @@ namespace Tgstation.Server.Host.Components var configuration = new StaticFiles.Configuration(configurationIoManager, synchronousIOManager, symlinkFactory, processExecutor, postWriteHandler, platformIdentifier, loggerFactory.CreateLogger()); var eventConsumer = new EventConsumer(configuration); - - var dmbFactory = new DmbFactory(databaseContextFactory, gameIoManager, loggerFactory.CreateLogger(), metadata.CloneMetadata()); + var repoManager = new RepositoryManager(metadata.RepositorySettings, repoIoManager, eventConsumer, credentialsProvider, loggerFactory.CreateLogger(), loggerFactory.CreateLogger()); try { - var repoManager = new RepositoryManager(metadata.RepositorySettings, repoIoManager, eventConsumer, credentialsProvider, loggerFactory.CreateLogger(), loggerFactory.CreateLogger()); + var byond = new ByondManager(byondIOManager, byondInstaller, eventConsumer, loggerFactory.CreateLogger()); + + var commandFactory = new CommandFactory(application, byond, repoManager, databaseContextFactory, metadata); + + var chat = chatFactory.CreateChat(instanceIoManager, commandFactory, metadata.ChatSettings); try { - var byond = new ByondManager(byondIOManager, byondInstaller, eventConsumer, loggerFactory.CreateLogger()); + var sessionControllerFactory = new SessionControllerFactory(processExecutor, byond, byondTopicSender, cryptographySuite, application, gameIoManager, chat, networkPromptReaper, platformIdentifier, loggerFactory, metadata.CloneMetadata()); - var commandFactory = new CommandFactory(application, byond, repoManager, databaseContextFactory, metadata); - - var chat = chatFactory.CreateChat(instanceIoManager, commandFactory, metadata.ChatSettings); + var dmbFactory = new DmbFactory(databaseContextFactory, gameIoManager, loggerFactory.CreateLogger(), metadata.CloneMetadata()); try { - var sessionControllerFactory = new SessionControllerFactory(processExecutor, byond, byondTopicSender, cryptographySuite, application, gameIoManager, chat, networkPromptReaper, platformIdentifier, loggerFactory, metadata.CloneMetadata()); var reattachInfoHandler = new ReattachInfoHandler(databaseContextFactory, dmbFactory, loggerFactory.CreateLogger(), metadata.CloneMetadata()); - var watchdog = watchdogFactory.CreateWatchdog(chat, dmbFactory, reattachInfoHandler, configuration, sessionControllerFactory, metadata.CloneMetadata(), metadata.DreamDaemonSettings); + var watchdog = watchdogFactory.CreateWatchdog( + chat, + dmbFactory, + reattachInfoHandler, + configuration, + sessionControllerFactory, + gameIoManager, + metadata.CloneMetadata(), + metadata.DreamDaemonSettings); eventConsumer.SetWatchdog(watchdog); commandFactory.SetWatchdog(watchdog); try { - var dreamMaker = new DreamMaker(byond, gameIoManager, configuration, sessionControllerFactory, dmbFactory, application, eventConsumer, chat, processExecutor, watchdog, loggerFactory.CreateLogger()); + var dreamMaker = new DreamMaker(byond, gameIoManager, configuration, sessionControllerFactory, eventConsumer, chat, processExecutor, watchdog, loggerFactory.CreateLogger()); return new Instance(metadata.CloneMetadata(), repoManager, byond, dreamMaker, watchdog, chat, configuration, dmbFactory, databaseContextFactory, dmbFactory, jobManager, eventConsumer, gitHubClientFactory, loggerFactory.CreateLogger()); } @@ -199,19 +228,19 @@ namespace Tgstation.Server.Host.Components } catch { - chat.Dispose(); + dmbFactory.Dispose(); throw; } } catch { - repoManager.Dispose(); + chat.Dispose(); throw; } } catch { - dmbFactory.Dispose(); + repoManager.Dispose(); throw; } } diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs index f8f63206ff..28fbc31558 100644 --- a/src/Tgstation.Server.Host/Components/InstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs @@ -7,7 +7,9 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Database; using Tgstation.Server.Host.IO; +using Tgstation.Server.Host.Jobs; namespace Tgstation.Server.Host.Components { diff --git a/src/Tgstation.Server.Host/Components/Interop/CommContext.cs b/src/Tgstation.Server.Host/Components/Interop/CommContext.cs index 489053e91b..433714a64e 100644 --- a/src/Tgstation.Server.Host/Components/Interop/CommContext.cs +++ b/src/Tgstation.Server.Host/Components/Interop/CommContext.cs @@ -125,7 +125,7 @@ namespace Tgstation.Server.Host.Components.Interop catch (OperationCanceledException) { } catch (Exception ex) { - logger.LogDebug("Exception while trying to handle command json write: {0}", ex); + logger.LogError("Exception while trying to handle command json write: {0}", ex); } } diff --git a/src/Tgstation.Server.Host/Components/ReattachInfoHandler.cs b/src/Tgstation.Server.Host/Components/ReattachInfoHandler.cs index 6c666e2a7f..299139cdfd 100644 --- a/src/Tgstation.Server.Host/Components/ReattachInfoHandler.cs +++ b/src/Tgstation.Server.Host/Components/ReattachInfoHandler.cs @@ -4,9 +4,9 @@ using System; using System.Linq; using System.Threading; using System.Threading.Tasks; -using Tgstation.Server.Host.Components.Compiler; +using Tgstation.Server.Host.Components.Deployment; using Tgstation.Server.Host.Components.Watchdog; -using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Database; using Z.EntityFramework.Plus; namespace Tgstation.Server.Host.Components diff --git a/src/Tgstation.Server.Host/Components/Repository/CredentialsProvider.cs b/src/Tgstation.Server.Host/Components/Repository/CredentialsProvider.cs index 23c2cdb7f2..80112f3e47 100644 --- a/src/Tgstation.Server.Host/Components/Repository/CredentialsProvider.cs +++ b/src/Tgstation.Server.Host/Components/Repository/CredentialsProvider.cs @@ -2,7 +2,7 @@ using LibGit2Sharp.Handlers; using Microsoft.Extensions.Logging; using System; -using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Jobs; namespace Tgstation.Server.Host.Components.Repository { diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs index 3363f1e8c8..a6408f3c74 100644 --- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs @@ -8,8 +8,8 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; -using Tgstation.Server.Host.Core; using Tgstation.Server.Host.IO; +using Tgstation.Server.Host.Jobs; namespace Tgstation.Server.Host.Components.Repository { @@ -84,6 +84,11 @@ namespace Tgstation.Server.Host.Components.Repository /// readonly Action onDispose; + /// + /// If the was disposed. + /// + bool disposed; + /// /// Converts a given to a /// @@ -120,9 +125,17 @@ namespace Tgstation.Server.Host.Components.Repository /// public void Dispose() { + lock (onDispose) + { + if (disposed) + return; + + disposed = true; + } + logger.LogTrace("Disposing..."); repository.Dispose(); - onDispose.Invoke(); + onDispose(); } void GetRepositoryOwnerName(string remote, out string owner, out string name) diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs index 162741d8bc..6079d6633f 100644 --- a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs +++ b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs @@ -12,6 +12,7 @@ using Tgstation.Server.Api.Models; using Tgstation.Server.Host.Core; using Tgstation.Server.Host.IO; using Tgstation.Server.Host.Security; +using Tgstation.Server.Host.System; namespace Tgstation.Server.Host.Components.StaticFiles { @@ -87,7 +88,14 @@ namespace Tgstation.Server.Host.Components.StaticFiles /// The value of /// The value of /// The value of - public Configuration(IIOManager ioManager, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IProcessExecutor processExecutor, IPostWriteHandler postWriteHandler, IPlatformIdentifier platformIdentifier, ILogger logger) + public Configuration( + IIOManager ioManager, + ISynchronousIOManager synchronousIOManager, + ISymlinkFactory symlinkFactory, + IProcessExecutor processExecutor, + IPostWriteHandler postWriteHandler, + IPlatformIdentifier platformIdentifier, + ILogger logger) { this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.synchronousIOManager = synchronousIOManager ?? throw new ArgumentNullException(nameof(synchronousIOManager)); diff --git a/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs index 2f9f9f5b8f..687b497fcf 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs @@ -1,37 +1,40 @@ using Byond.TopicSender; using Microsoft.Extensions.Logging; using System; +using System.Diagnostics; using System.Globalization; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Components.Chat; -using Tgstation.Server.Host.Components.Compiler; +using Tgstation.Server.Host.Components.Deployment; using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Database; +using Tgstation.Server.Host.Jobs; namespace Tgstation.Server.Host.Components.Watchdog { /// /// A that manages one server. /// - sealed class BasicWatchdog : WatchdogBase + class BasicWatchdog : WatchdogBase { /// - public override bool AlphaIsActive => true; + public sealed override bool AlphaIsActive => true; /// - public override Models.CompileJob ActiveCompileJob => server?.Dmb.CompileJob; + public sealed override Models.CompileJob ActiveCompileJob => Server?.Dmb.CompileJob; /// - public override RebootState? RebootState => server?.RebootState; + public sealed override RebootState? RebootState => Server?.RebootState; /// /// The single . /// - ISessionController server; + protected ISessionController Server { get; private set; } /// - /// Initializes a new instance of the . + /// Initializes a new instance of the . /// /// The for the . /// The for the . @@ -84,8 +87,8 @@ namespace Tgstation.Server.Host.Components.Watchdog switch (reason) { case MonitorActivationReason.ActiveServerCrashed: - string exitWord = server.TerminationWasRequested ? "exited" : "crashed"; - if (server.RebootState == Watchdog.RebootState.Shutdown) + string exitWord = Server.TerminationWasRequested ? "exited" : "crashed"; + if (Server.RebootState == Watchdog.RebootState.Shutdown) { // the time for graceful shutdown is now await Chat.SendWatchdogMessage(String.Format(CultureInfo.InvariantCulture, "Server {0}! Exiting due to graceful termination request...", exitWord), cancellationToken).ConfigureAwait(false); @@ -96,14 +99,13 @@ namespace Tgstation.Server.Host.Components.Watchdog await Chat.SendWatchdogMessage(String.Format(CultureInfo.InvariantCulture, "Server {0}! Rebooting...", exitWord), cancellationToken).ConfigureAwait(false); return MonitorAction.Restart; case MonitorActivationReason.ActiveServerRebooted: - var rebootState = server.RebootState; - server.ResetRebootState(); + var rebootState = Server.RebootState; + Server.ResetRebootState(); switch (rebootState) { case Watchdog.RebootState.Normal: - bool dmbUpdatePending = ActiveLaunchParameters != LastLaunchParameters; - return dmbUpdatePending ? MonitorAction.Restart : MonitorAction.Continue; + return HandleNormalReboot(); case Watchdog.RebootState.Restart: return MonitorAction.Restart; case Watchdog.RebootState.Shutdown: @@ -116,8 +118,10 @@ namespace Tgstation.Server.Host.Components.Watchdog } case MonitorActivationReason.ActiveLaunchParametersUpdated: + await Server.SetRebootState(Watchdog.RebootState.Restart, cancellationToken).ConfigureAwait(false); + return MonitorAction.Continue; case MonitorActivationReason.NewDmbAvailable: - await server.SetRebootState(Watchdog.RebootState.Restart, cancellationToken).ConfigureAwait(false); + await HandleNewDmbAvailable(cancellationToken).ConfigureAwait(false); return MonitorAction.Continue; case MonitorActivationReason.InactiveServerCrashed: case MonitorActivationReason.InactiveServerRebooted: @@ -129,31 +133,28 @@ namespace Tgstation.Server.Host.Components.Watchdog } /// - protected override WatchdogReattachInformation CreateReattachInformation() + protected sealed override WatchdogReattachInformation CreateReattachInformation() => new WatchdogReattachInformation { AlphaIsActive = true, - Alpha = server?.Release() + Alpha = Server?.Release() }; /// protected override void DisposeAndNullControllers() { - server?.Dispose(); - server = null; + Server?.Dispose(); + Server = null; Running = false; } /// - protected override ISessionController GetActiveController() => server; + protected sealed override ISessionController GetActiveController() => Server; /// - protected override async Task InitControllers(Action callBeforeRecurse, Task chatTask, WatchdogReattachInformation reattachInfo, CancellationToken cancellationToken) + protected sealed override async Task InitControllers(Action callBeforeRecurse, Task chatTask, WatchdogReattachInformation reattachInfo, CancellationToken cancellationToken) { - // good ole sanity, should never fucking trigger but i don't trust myself even though I should - // TODO: Unit test this instead? - if (server != null) - throw new InvalidOperationException("Entered LaunchNoLock with server not being null!"); + Debug.Assert(Server == null, "Entered LaunchNoLock with server not being null!"); // don't need a new dmb if reattaching var doesntNeedNewDmb = reattachInfo?.Alpha != null && reattachInfo?.Bravo != null; @@ -179,7 +180,10 @@ namespace Tgstation.Server.Host.Components.Watchdog // The tasks pertaining to server startup times are in the ISessionControllers Task serverLaunchTask, inactiveReattachTask; if (!doesntNeedNewDmb) + { + dmbToUse = await PrepServerForLaunch(dmbToUse, cancellationToken).ConfigureAwait(false); serverLaunchTask = SessionControllerFactory.LaunchNew(ActiveLaunchParameters, dmbToUse, null, true, true, false, cancellationToken); + } else serverLaunchTask = SessionControllerFactory.Reattach(serverToReattach, cancellationToken); @@ -190,31 +194,31 @@ namespace Tgstation.Server.Host.Components.Watchdog inactiveReattachTask = Task.FromResult(null); // retrieve the session controller - server = await serverLaunchTask.ConfigureAwait(false); + Server = await serverLaunchTask.ConfigureAwait(false); // failed reattaches will return null - server?.SetHighPriority(); + Server?.SetHighPriority(); var inactiveServerController = await inactiveReattachTask.ConfigureAwait(false); inactiveServerController?.Dispose(); inactiveServerWasKilled = inactiveServerController != null; // possiblity of null servers due to failed reattaches - if (server == null) + if (Server == null) { callBeforeRecurse(); await NotifyOfFailedReattach(thereIsAnInactiveServerToKill && !inactiveServerWasKilled, cancellationToken).ConfigureAwait(false); return; } - await CheckLaunchResult(server, "Server", cancellationToken).ConfigureAwait(false); + await CheckLaunchResult(Server, "Server", cancellationToken).ConfigureAwait(false); - server.EnableCustomChatCommands(); + Server.EnableCustomChatCommands(); } catch { // kill the controllers - bool serverWasActive = server != null; + bool serverWasActive = Server != null; DisposeAndNullControllers(); // server didn't get control of this dmb @@ -228,7 +232,7 @@ namespace Tgstation.Server.Host.Components.Watchdog } /// - protected override async Task MonitorLifetimes(CancellationToken cancellationToken) + protected sealed override async Task MonitorLifetimes(CancellationToken cancellationToken) { Logger.LogTrace("Entered MonitorLifetimes"); @@ -243,11 +247,11 @@ namespace Tgstation.Server.Host.Components.Watchdog Logger.LogDebug("Iteration {0} of monitor loop", iteration); try { - Logger.LogDebug("Server Compile Job ID: {0}", server.Dmb.CompileJob.Id); + Logger.LogDebug("Server Compile Job ID: {0}", Server.Dmb.CompileJob.Id); // load the activation tasks into local variables - Task activeServerLifetime = server.Lifetime; - var activeServerReboot = server.OnReboot; + Task activeServerLifetime = Server.Lifetime; + var activeServerReboot = Server.OnReboot; Task activeLaunchParametersChanged = ActiveParametersUpdated.Task; var newDmbAvailable = DmbFactory.OnNewerDmb; @@ -291,7 +295,10 @@ namespace Tgstation.Server.Host.Components.Watchdog || CheckActivationReason(ref activeServerReboot, MonitorActivationReason.ActiveServerRebooted) || CheckActivationReason(ref newDmbAvailable, MonitorActivationReason.NewDmbAvailable) || CheckActivationReason(ref activeLaunchParametersChanged, MonitorActivationReason.ActiveLaunchParametersUpdated)) + { + Logger.LogTrace("Monitor activation: {0}", activationReason); nextAction = await HandleMonitorWakeup(activationReason, cancellationToken).ConfigureAwait(false); + } else moreActivationsToProcess = false; } @@ -355,5 +362,30 @@ namespace Tgstation.Server.Host.Components.Watchdog Logger.LogTrace("Monitor exiting..."); } + + /// + /// Handler for when the is . + /// + /// The to take. + protected virtual MonitorAction HandleNormalReboot() + { + bool dmbUpdatePending = ActiveLaunchParameters != LastLaunchParameters; + return dmbUpdatePending ? MonitorAction.Restart : MonitorAction.Continue; + } + + /// + /// Handler for . + /// + /// The for the operation. + /// A representing the running operation. + protected virtual Task HandleNewDmbAvailable(CancellationToken cancellationToken) => Server.SetRebootState(Watchdog.RebootState.Restart, cancellationToken); + + /// + /// Prepare the server to launch a new instance with the and a given . + /// + /// The to be launched. Will not be disposed by this function. + /// The for the operation. + /// A resulting in the modified to be used. + protected virtual Task PrepServerForLaunch(IDmbProvider dmbToUse, CancellationToken cancellationToken) => Task.FromResult(dmbToUse); } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/DeadSessionController.cs b/src/Tgstation.Server.Host/Components/Watchdog/DeadSessionController.cs index 61a8a7cfc2..035708a67c 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/DeadSessionController.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/DeadSessionController.cs @@ -1,6 +1,7 @@ using System; using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Host.Components.Deployment; namespace Tgstation.Server.Host.Components.Watchdog { @@ -96,5 +97,14 @@ namespace Tgstation.Server.Host.Components.Watchdog /// public Task SetRebootState(RebootState newRebootState, CancellationToken cancellationToken) => throw new NotSupportedException(); + + /// + public void ReplaceDmbProvider(IDmbProvider newProvider) => throw new NotSupportedException(); + + /// + public void Suspend() => throw new NotSupportedException(); + + /// + public void Resume() => throw new NotSupportedException(); } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/ExperimentalWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/ExperimentalWatchdog.cs index ed2284241e..7bd0a1235c 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/ExperimentalWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/ExperimentalWatchdog.cs @@ -8,8 +8,10 @@ using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Components.Chat; -using Tgstation.Server.Host.Components.Compiler; +using Tgstation.Server.Host.Components.Deployment; using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Database; +using Tgstation.Server.Host.Jobs; namespace Tgstation.Server.Host.Components.Watchdog { @@ -438,7 +440,10 @@ namespace Tgstation.Server.Host.Components.Watchdog || CheckActivationReason(ref inactiveServerStartup, MonitorActivationReason.InactiveServerStartupComplete) || CheckActivationReason(ref newDmbAvailable, MonitorActivationReason.NewDmbAvailable) || CheckActivationReason(ref activeLaunchParametersChanged, MonitorActivationReason.ActiveLaunchParametersUpdated)) + { + Logger.LogTrace("Monitor activation: {0}", activationReason); await HandlerMonitorWakeup(activationReason, monitorState, cancellationToken).ConfigureAwait(false); + } else moreActivationsToProcess = false; } @@ -513,10 +518,7 @@ namespace Tgstation.Server.Host.Components.Watchdog #pragma warning disable CA1502 // TODO: Decomplexify protected override async Task InitControllers(Action callBeforeRecurse, Task chatTask, WatchdogReattachInformation reattachInfo, CancellationToken cancellationToken) { - // good ole sanity, should never fucking trigger but i don't trust myself even though I should - // TODO: Unit test this instead? - if (alphaServer != null || bravoServer != null) - throw new InvalidOperationException("Entered LaunchNoLock with one or more of the servers not being null!"); + Debug.Assert(alphaServer == null && bravoServer == null, "Entered LaunchNoLock with one or more of the servers not being null!"); // don't need a new dmb if reattaching var doesntNeedNewDmb = reattachInfo?.Alpha != null && reattachInfo?.Bravo != null; diff --git a/src/Tgstation.Server.Host/Components/Watchdog/INetworkPromptReaper.cs b/src/Tgstation.Server.Host/Components/Watchdog/INetworkPromptReaper.cs index eb939218e7..e26800ea37 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/INetworkPromptReaper.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/INetworkPromptReaper.cs @@ -1,4 +1,4 @@ -using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.System; namespace Tgstation.Server.Host.Components.Watchdog { diff --git a/src/Tgstation.Server.Host/Components/Watchdog/ISessionController.cs b/src/Tgstation.Server.Host/Components/Watchdog/ISessionController.cs index 6b21bbd6ca..b1d5e9947b 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/ISessionController.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/ISessionController.cs @@ -1,6 +1,7 @@ using System.Threading; using System.Threading.Tasks; -using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Components.Deployment; +using Tgstation.Server.Host.System; namespace Tgstation.Server.Host.Components.Watchdog { @@ -25,7 +26,7 @@ namespace Tgstation.Server.Host.Components.Watchdog bool TerminationWasRequested { get; } /// - /// The DMAPI + /// The DMAPI /// ApiValidationStatus ApiValidationStatus { get; } @@ -55,7 +56,7 @@ namespace Tgstation.Server.Host.Components.Watchdog Task OnReboot { get; } /// - /// Releases the without terminating it. Also calls + /// Releases the without terminating it. Also calls /// /// which can be used to create a new similar to this one ReattachInformation Release(); @@ -85,7 +86,7 @@ namespace Tgstation.Server.Host.Components.Watchdog Task SetRebootState(RebootState newRebootState, CancellationToken cancellationToken); /// - /// Changes to without telling the DMAPI + /// Changes to without telling the DMAPI /// void ResetRebootState(); @@ -93,5 +94,11 @@ namespace Tgstation.Server.Host.Components.Watchdog /// Enables the reading of custom chat commands from the /// void EnableCustomChatCommands(); + + /// + /// Replace with a given , disposing the old one. + /// + /// The new . + void ReplaceDmbProvider(IDmbProvider newProvider); } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/ISessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/ISessionControllerFactory.cs index 1bad93d59a..28e9d67135 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/ISessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/ISessionControllerFactory.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Components.Byond; +using Tgstation.Server.Host.Components.Deployment; namespace Tgstation.Server.Host.Components.Watchdog { diff --git a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdogFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdogFactory.cs index 7c4091e959..60c2641ebd 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdogFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdogFactory.cs @@ -1,6 +1,7 @@ using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Components.Chat; -using Tgstation.Server.Host.Components.Compiler; +using Tgstation.Server.Host.Components.Deployment; +using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host.Components.Watchdog { @@ -17,9 +18,18 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The for the /// The for the /// The for the + /// The for the . /// The for the /// The initial for the /// A new - IWatchdog CreateWatchdog(IChat chat, IDmbFactory dmbFactory, IReattachInfoHandler reattachInfoHandler, IEventConsumer eventConsumer, ISessionControllerFactory sessionControllerFactory, Api.Models.Instance instance, DreamDaemonSettings settings); + IWatchdog CreateWatchdog( + IChat chat, + IDmbFactory dmbFactory, + IReattachInfoHandler reattachInfoHandler, + IEventConsumer eventConsumer, + ISessionControllerFactory sessionControllerFactory, + IIOManager ioManager, + Api.Models.Instance instance, + DreamDaemonSettings settings); } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/LaunchResult.cs b/src/Tgstation.Server.Host/Components/Watchdog/LaunchResult.cs index feefa00201..15134f80e1 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/LaunchResult.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/LaunchResult.cs @@ -9,12 +9,12 @@ namespace Tgstation.Server.Host.Components.Watchdog public sealed class LaunchResult { /// - /// The time it took for to return. If the startup timed out + /// The time it took for to return. If the startup timed out /// public TimeSpan? StartupTime { get; set; } /// - /// The if it exited + /// The if it exited /// public int? ExitCode { get; set; } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/MonitorState.cs b/src/Tgstation.Server.Host/Components/Watchdog/MonitorState.cs index e6eb14e0b5..79dc4f955d 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/MonitorState.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/MonitorState.cs @@ -18,7 +18,7 @@ namespace Tgstation.Server.Host.Components.Watchdog public bool InactiveServerCritFail { get; set; } /// - /// The next to take in + /// The next to take in /// public MonitorAction NextAction { get; set; } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/PosixNetworkPromptReaper.cs b/src/Tgstation.Server.Host/Components/Watchdog/PosixNetworkPromptReaper.cs index e69e8a360b..df0e4691f1 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/PosixNetworkPromptReaper.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/PosixNetworkPromptReaper.cs @@ -1,4 +1,4 @@ -using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.System; namespace Tgstation.Server.Host.Components.Watchdog { diff --git a/src/Tgstation.Server.Host/Components/Watchdog/ReattachInformation.cs b/src/Tgstation.Server.Host/Components/Watchdog/ReattachInformation.cs index 9570ce7673..065baa9b7a 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/ReattachInformation.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/ReattachInformation.cs @@ -1,4 +1,5 @@ using System; +using Tgstation.Server.Host.Components.Deployment; using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components.Watchdog diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs index ac67ef21d4..d8c806cf74 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs @@ -11,8 +11,9 @@ using System.Threading.Tasks; using Tgstation.Server.Api.Models; using Tgstation.Server.Host.Components.Byond; using Tgstation.Server.Host.Components.Chat; +using Tgstation.Server.Host.Components.Deployment; using Tgstation.Server.Host.Components.Interop; -using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.System; namespace Tgstation.Server.Host.Components.Watchdog { @@ -180,7 +181,17 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The value of /// The value of /// The optional time to wait before failing the - public SessionController(ReattachInformation reattachInformation, IProcess process, IByondExecutableLock byondLock, IByondTopicSender byondTopicSender, IJsonTrackingContext chatJsonTrackingContext, ICommContext interopContext, IChat chat, ILogger logger, DreamDaemonSecurity? launchSecurityLevel, uint? startupTimeout) + public SessionController( + ReattachInformation reattachInformation, + IProcess process, + IByondExecutableLock byondLock, + IByondTopicSender byondTopicSender, + IJsonTrackingContext chatJsonTrackingContext, + ICommContext interopContext, + IChat chat, + ILogger logger, + DreamDaemonSecurity? launchSecurityLevel, + uint? startupTimeout) { this.chatJsonTrackingContext = chatJsonTrackingContext; // null valid this.reattachInformation = reattachInformation ?? throw new ArgumentNullException(nameof(reattachInformation)); @@ -447,6 +458,15 @@ namespace Tgstation.Server.Host.Components.Watchdog async Task SendCommand(string command, ushort? overridePort, CancellationToken cancellationToken) { + if (Lifetime.IsCompleted) + { + logger.LogWarning( + "Attempted to send a command to an inactive SessionController{1}: {0}", + command, + overridePort.HasValue ? $" (Override port: {overridePort.Value})" : String.Empty); + return null; + } + try { var commandString = String.Format(CultureInfo.InvariantCulture, @@ -528,5 +548,23 @@ namespace Tgstation.Server.Host.Components.Watchdog /// public void SetHighPriority() => process.SetHighPriority(); + + /// + public void Suspend() => process.Suspend(); + + /// + public void Resume() => process.Resume(); + + /// + public void ReplaceDmbProvider(IDmbProvider dmbProvider) + { +#pragma warning disable IDE0016 // Use 'throw' expression + if (dmbProvider == null) + throw new ArgumentNullException(nameof(dmbProvider)); +#pragma warning restore IDE0016 // Use 'throw' expression + + reattachInformation.Dmb.Dispose(); + reattachInformation.Dmb = dmbProvider; + } } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs index cfa7c81c0d..fbe01d1a4f 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs @@ -12,10 +12,12 @@ using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Components.Byond; using Tgstation.Server.Host.Components.Chat; +using Tgstation.Server.Host.Components.Deployment; using Tgstation.Server.Host.Components.Interop; using Tgstation.Server.Host.Core; using Tgstation.Server.Host.IO; using Tgstation.Server.Host.Security; +using Tgstation.Server.Host.System; namespace Tgstation.Server.Host.Components.Watchdog { @@ -111,7 +113,18 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The value of /// The value of /// The value of - public SessionControllerFactory(IProcessExecutor processExecutor, IByondManager byond, IByondTopicSender byondTopicSender, ICryptographySuite cryptographySuite, IApplication application, IIOManager ioManager, IChat chat, INetworkPromptReaper networkPromptReaper, IPlatformIdentifier platformIdentifier, ILoggerFactory loggerFactory, Api.Models.Instance instance) + public SessionControllerFactory( + IProcessExecutor processExecutor, + IByondManager byond, + IByondTopicSender byondTopicSender, + ICryptographySuite cryptographySuite, + IApplication application, + IIOManager ioManager, + IChat chat, + INetworkPromptReaper networkPromptReaper, + IPlatformIdentifier platformIdentifier, + ILoggerFactory loggerFactory, + Api.Models.Instance instance) { this.processExecutor = processExecutor ?? throw new ArgumentNullException(nameof(processExecutor)); this.byond = byond ?? throw new ArgumentNullException(nameof(byond)); diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs index 2a6c6ac54a..7050b3b998 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs @@ -14,9 +14,12 @@ using System.Threading.Tasks; using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Components.Chat; -using Tgstation.Server.Host.Components.Compiler; +using Tgstation.Server.Host.Components.Deployment; using Tgstation.Server.Host.Components.Interop; using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Database; +using Tgstation.Server.Host.Extensions; +using Tgstation.Server.Host.Jobs; namespace Tgstation.Server.Host.Components.Watchdog { @@ -255,6 +258,9 @@ namespace Tgstation.Server.Host.Components.Watchdog if (Running) throw new JobException("Watchdog already running!"); + if (!DmbFactory.DmbAvailable) + throw new JobException("Corrupted compilation, please redeploy!"); + // this is necessary, the monitor could be in it's sleep loop trying to restart, if so cancel THAT monitor and start our own with blackjack and hookers Task chatTask; if (startMonitor && await StopMonitor().ConfigureAwait(false)) @@ -423,6 +429,9 @@ namespace Tgstation.Server.Host.Components.Watchdog /// public async Task HandleEvent(EventType eventType, IEnumerable parameters, CancellationToken cancellationToken) { + if (!Running) + return true; + string results; using (await SemaphoreSlimContext.Lock(Semaphore, cancellationToken).ConfigureAwait(false)) { diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs index 46a565bdb7..db2830b18a 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs @@ -4,78 +4,140 @@ using Microsoft.Extensions.Options; using System; using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Components.Chat; -using Tgstation.Server.Host.Components.Compiler; +using Tgstation.Server.Host.Components.Deployment; using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Database; +using Tgstation.Server.Host.IO; +using Tgstation.Server.Host.Jobs; namespace Tgstation.Server.Host.Components.Watchdog { /// - sealed class WatchdogFactory : IWatchdogFactory + class WatchdogFactory : IWatchdogFactory { /// /// The for the /// - readonly IServerControl serverControl; + protected IServerControl ServerControl { get; } /// /// The for the /// - readonly ILoggerFactory loggerFactory; + protected ILoggerFactory LoggerFactory { get; } /// /// The for the /// - readonly IDatabaseContextFactory databaseContextFactory; + protected IDatabaseContextFactory DatabaseContextFactory { get; } /// /// The for the /// - readonly IByondTopicSender byondTopicSender; + protected IByondTopicSender ByondTopicSender { get; } /// /// The for the /// - readonly IJobManager jobManager; + protected IJobManager JobManager { get; } /// /// The for the /// - readonly IAsyncDelayer asyncDelayer; + protected IAsyncDelayer AsyncDelayer { get; } /// - /// The for the + /// The for the /// - readonly GeneralConfiguration generalConfiguration; + protected GeneralConfiguration GeneralConfiguration { get; } /// /// Construct a /// - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of - /// The containing the value of + /// The value of + /// The value of + /// The value of + /// The value of + /// The value of + /// The value of + /// The containing the value of public WatchdogFactory(IServerControl serverControl, ILoggerFactory loggerFactory, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IJobManager jobManager, IAsyncDelayer asyncDelayer, IOptions generalConfigurationOptions) { - this.serverControl = serverControl ?? throw new ArgumentNullException(nameof(serverControl)); - this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); - this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); - this.byondTopicSender = byondTopicSender ?? throw new ArgumentNullException(nameof(byondTopicSender)); - this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); - this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer)); - generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions)); + ServerControl = serverControl ?? throw new ArgumentNullException(nameof(serverControl)); + LoggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); + DatabaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); + ByondTopicSender = byondTopicSender ?? throw new ArgumentNullException(nameof(byondTopicSender)); + JobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); + AsyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer)); + GeneralConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions)); } /// - public IWatchdog CreateWatchdog(IChat chat, IDmbFactory dmbFactory, IReattachInfoHandler reattachInfoHandler, IEventConsumer eventConsumer, ISessionControllerFactory sessionControllerFactory, Api.Models.Instance instance, DreamDaemonSettings settings) + public IWatchdog CreateWatchdog( + IChat chat, + IDmbFactory dmbFactory, + IReattachInfoHandler reattachInfoHandler, + IEventConsumer eventConsumer, + ISessionControllerFactory sessionControllerFactory, + IIOManager ioManager, + Api.Models.Instance instance, + DreamDaemonSettings settings) { - if (generalConfiguration.UseExperimentalWatchdog) - return new ExperimentalWatchdog(chat, sessionControllerFactory, dmbFactory, reattachInfoHandler, databaseContextFactory, byondTopicSender, eventConsumer, jobManager, serverControl, asyncDelayer, loggerFactory.CreateLogger(), settings, instance, settings.AutoStart.Value); + if (GeneralConfiguration.UseExperimentalWatchdog) + return new ExperimentalWatchdog( + chat, + sessionControllerFactory, + dmbFactory, + reattachInfoHandler, + DatabaseContextFactory, + ByondTopicSender, + eventConsumer, + JobManager, + ServerControl, + AsyncDelayer, + LoggerFactory.CreateLogger(), + settings, + instance, + settings.AutoStart.Value); - return new BasicWatchdog(chat, sessionControllerFactory, dmbFactory, reattachInfoHandler, databaseContextFactory, byondTopicSender, eventConsumer, jobManager, serverControl, asyncDelayer, loggerFactory.CreateLogger(), settings, instance, settings.AutoStart.Value); + return CreateNonExperimentalWatchdog(chat, dmbFactory, reattachInfoHandler, eventConsumer, sessionControllerFactory, ioManager, instance, settings); } + + /// + /// Create a that isn't the . + /// + /// The for the + /// The for the with + /// The for the + /// The for the + /// The for the + /// The for the . + /// The for the + /// The initial for the + /// A new + protected virtual IWatchdog CreateNonExperimentalWatchdog( + IChat chat, + IDmbFactory dmbFactory, + IReattachInfoHandler reattachInfoHandler, + IEventConsumer eventConsumer, + ISessionControllerFactory sessionControllerFactory, + IIOManager ioManager, + Api.Models.Instance instance, + DreamDaemonSettings settings) + => new BasicWatchdog( + chat, + sessionControllerFactory, + dmbFactory, + reattachInfoHandler, + DatabaseContextFactory, + ByondTopicSender, + eventConsumer, + JobManager, + ServerControl, + AsyncDelayer, + LoggerFactory.CreateLogger(), + settings, + instance, + settings.AutoStart.Value); } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogReattachInformation.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogReattachInformation.cs index 91ec990b55..0e0c698fa3 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogReattachInformation.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogReattachInformation.cs @@ -1,5 +1,6 @@ using System; using System.Globalization; +using Tgstation.Server.Host.Components.Deployment; using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components.Watchdog diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WindowsNetworkPromptReaper.cs b/src/Tgstation.Server.Host/Components/Watchdog/WindowsNetworkPromptReaper.cs index 737d053e09..c83c74d49a 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WindowsNetworkPromptReaper.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WindowsNetworkPromptReaper.cs @@ -9,6 +9,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.System; namespace Tgstation.Server.Host.Components.Watchdog { diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdog.cs new file mode 100644 index 0000000000..652b372efa --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdog.cs @@ -0,0 +1,180 @@ +using Byond.TopicSender; +using Microsoft.Extensions.Logging; +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Host.Components.Chat; +using Tgstation.Server.Host.Components.Deployment; +using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Database; +using Tgstation.Server.Host.IO; +using Tgstation.Server.Host.Jobs; + +namespace Tgstation.Server.Host.Components.Watchdog +{ + /// + /// A version of the that, instead of killing servers for updates, uses the wonders of symlinks to swap out changes without killing DreamDaemon. + /// + sealed class WindowsWatchdog : BasicWatchdog + { + /// + /// The for the . + /// + readonly IIOManager ioManager; + + /// + /// The for the . + /// + readonly ISymlinkFactory symlinkFactory; + + /// + /// The for . + /// + WindowsSwappableDmbProvider activeSwappable; + + /// + /// The active for . + /// + WindowsSwappableDmbProvider pendingSwappable; + + /// + /// Initializes a new instance of the . + /// + /// The for the . + /// The for the . + /// The for the . + /// The for the . + /// The for the . + /// The for the . + /// The for the . + /// The for the . + /// The for the . + /// The for the . + /// The value of . + /// The value of . + /// The for the . + /// The for the . + /// The for the . + /// The autostart value for the . + public WindowsWatchdog( + IChat chat, + ISessionControllerFactory sessionControllerFactory, + IDmbFactory dmbFactory, + IReattachInfoHandler reattachInfoHandler, + IDatabaseContextFactory databaseContextFactory, + IByondTopicSender byondTopicSender, + IEventConsumer eventConsumer, + IJobManager jobManager, + IServerControl serverControl, + IAsyncDelayer asyncDelayer, + IIOManager ioManager, + ISymlinkFactory symlinkFactory, + ILogger logger, + DreamDaemonLaunchParameters initialLaunchParameters, + Api.Models.Instance instance, bool autoStart) + : base( + chat, + sessionControllerFactory, + dmbFactory, + reattachInfoHandler, + databaseContextFactory, + byondTopicSender, + eventConsumer, + jobManager, + serverControl, + asyncDelayer, + logger, + initialLaunchParameters, + instance, + autoStart) + { + try + { + this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + this.symlinkFactory = symlinkFactory ?? throw new ArgumentNullException(nameof(symlinkFactory)); + } + catch + { + Dispose(); + throw; + } + } + + /// + protected override void DisposeAndNullControllers() + { + base.DisposeAndNullControllers(); + + // If we reach this point, we can guarantee PrepServerForLaunch will be called before starting again. + activeSwappable = null; + pendingSwappable?.Dispose(); + pendingSwappable = null; + } + + /// + protected override MonitorAction HandleNormalReboot() + { + Debug.Assert(activeSwappable != null, "Expected activeSwappable to not be null!"); + if (pendingSwappable != null) + { + Logger.LogTrace("Replacing activeSwappable with pendingSwappable"); + Server.ReplaceDmbProvider(pendingSwappable); + activeSwappable = pendingSwappable; + pendingSwappable = null; + } + + return MonitorAction.Continue; + } + + /// + protected override async Task HandleNewDmbAvailable(CancellationToken cancellationToken) + { + IDmbProvider compileJobProvider = DmbFactory.LockNextDmb(1); + WindowsSwappableDmbProvider windowsProvider = null; + try + { + windowsProvider = new WindowsSwappableDmbProvider(compileJobProvider, ioManager, symlinkFactory); + + Logger.LogDebug("Swapping to compile job {0}...", windowsProvider.CompileJob.Id); + Server.Suspend(); + await windowsProvider.MakeActive(cancellationToken).ConfigureAwait(false); + Server.Resume(); + } + catch + { + IDmbProvider providerToDispose = windowsProvider ?? compileJobProvider; + providerToDispose.Dispose(); + throw; + } + + pendingSwappable?.Dispose(); + pendingSwappable = windowsProvider; + } + + /// + protected override async Task PrepServerForLaunch(IDmbProvider dmbToUse, CancellationToken cancellationToken) + { + Debug.Assert(activeSwappable == null, "Expected swappableDmbProvider to be null!"); + + Logger.LogTrace("Prep for server launch. pendingSwappable is {0}avaiable", pendingSwappable == null ? "not " : String.Empty); + + activeSwappable = pendingSwappable ?? new WindowsSwappableDmbProvider(dmbToUse, ioManager, symlinkFactory); + pendingSwappable = null; + + try + { + await activeSwappable.MakeActive(cancellationToken).ConfigureAwait(false); + } + catch + { + // We won't worry about disposing activeSwappable here as we can't dispose dmbToUse here. + activeSwappable = null; + throw; + } + + return activeSwappable; + } + } +} diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdogFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdogFactory.cs new file mode 100644 index 0000000000..348b885422 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdogFactory.cs @@ -0,0 +1,86 @@ +using Byond.TopicSender; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using System; +using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Host.Components.Chat; +using Tgstation.Server.Host.Components.Deployment; +using Tgstation.Server.Host.Configuration; +using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Database; +using Tgstation.Server.Host.IO; +using Tgstation.Server.Host.Jobs; + +namespace Tgstation.Server.Host.Components.Watchdog +{ + /// + /// for creating s. + /// + sealed class WindowsWatchdogFactory : WatchdogFactory + { + /// + /// The for the . + /// + readonly ISymlinkFactory symlinkFactory; + + /// + /// Initializes a new instance of the . + /// + /// The for the . + /// The for the . + /// The for the . + /// The for the . + /// The for the . + /// The for the . + /// The value of . + /// The for for the . + public WindowsWatchdogFactory( + IServerControl serverControl, + ILoggerFactory loggerFactory, + IDatabaseContextFactory databaseContextFactory, + IByondTopicSender byondTopicSender, + IJobManager jobManager, + IAsyncDelayer asyncDelayer, + ISymlinkFactory symlinkFactory, + IOptions generalConfigurationOptions) + : base( + serverControl, + loggerFactory, + databaseContextFactory, + byondTopicSender, + jobManager, + asyncDelayer, + generalConfigurationOptions) + { + this.symlinkFactory = symlinkFactory ?? throw new ArgumentNullException(nameof(symlinkFactory)); + } + + /// + protected override IWatchdog CreateNonExperimentalWatchdog( + IChat chat, + IDmbFactory dmbFactory, + IReattachInfoHandler reattachInfoHandler, + IEventConsumer eventConsumer, + ISessionControllerFactory sessionControllerFactory, + IIOManager ioManager, + Api.Models.Instance instance, + DreamDaemonSettings settings) + => new WindowsWatchdog( + chat, + sessionControllerFactory, + dmbFactory, + reattachInfoHandler, + DatabaseContextFactory, + ByondTopicSender, + eventConsumer, + JobManager, + ServerControl, + AsyncDelayer, + ioManager, + symlinkFactory, + LoggerFactory.CreateLogger(), + settings, + instance, + settings.AutoStart.Value); + } +} diff --git a/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs b/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs index d1d2bfec97..cba1b03672 100644 --- a/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs +++ b/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs @@ -4,7 +4,7 @@ using Newtonsoft.Json.Converters; namespace Tgstation.Server.Host.Configuration { /// - /// Configuration options for the + /// Configuration options for the /// sealed class DatabaseConfiguration { @@ -35,7 +35,7 @@ namespace Tgstation.Server.Host.Configuration public bool DropDatabase { get; set; } /// - /// The form of the of a target MySQL/MariaDB server + /// The form of the of a target MySQL/MariaDB server /// public string MySqlServerVersion { get; set; } } diff --git a/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs b/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs index 78ac49b33f..293d349a86 100644 --- a/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs +++ b/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs @@ -58,5 +58,10 @@ namespace Tgstation.Server.Host.Configuration /// If the should be used. /// public bool UseExperimentalWatchdog { get; set; } + + /// + /// If the should not be used if it is available. + /// + public bool UseBasicWatchdogOnWindows { get; set; } } } diff --git a/src/Tgstation.Server.Host/Configuration/UpdatesConfiguration.cs b/src/Tgstation.Server.Host/Configuration/UpdatesConfiguration.cs index 66bed5c96a..b7cddee4d3 100644 --- a/src/Tgstation.Server.Host/Configuration/UpdatesConfiguration.cs +++ b/src/Tgstation.Server.Host/Configuration/UpdatesConfiguration.cs @@ -31,7 +31,7 @@ public long GitHubRepositoryId { get; set; } = DefaultGitHubRepositoryId; /// - /// Prefix before the of TGS published in git tags + /// Prefix before the of TGS published in git tags /// public string GitTagPrefix { get; set; } = DefaultGitTagPrefix; diff --git a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs index def822cfb8..4c4807d337 100644 --- a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs +++ b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs @@ -15,9 +15,11 @@ using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Database; +using Tgstation.Server.Host.Extensions; using Tgstation.Server.Host.IO; -using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; +using Tgstation.Server.Host.System; namespace Tgstation.Server.Host.Controllers { @@ -79,7 +81,23 @@ namespace Tgstation.Server.Host.Controllers /// The for the /// The containing value of /// The containing value of - public AdministrationController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IGitHubClientFactory gitHubClientFactory, IServerControl serverUpdater, IApplication application, IIOManager ioManager, IPlatformIdentifier platformIdentifier, ILogger logger, IOptions updatesConfigurationOptions, IOptions generalConfigurationOptions) : base(databaseContext, authenticationContextFactory, logger, false, true) + public AdministrationController( + IDatabaseContext databaseContext, + IAuthenticationContextFactory authenticationContextFactory, + IGitHubClientFactory gitHubClientFactory, + IServerControl serverUpdater, + IApplication application, + IIOManager ioManager, + IPlatformIdentifier platformIdentifier, + ILogger logger, + IOptions updatesConfigurationOptions, + IOptions generalConfigurationOptions) + : base( + databaseContext, + authenticationContextFactory, + logger, + false, + true) { this.gitHubClientFactory = gitHubClientFactory ?? throw new ArgumentNullException(nameof(gitHubClientFactory)); this.serverUpdater = serverUpdater ?? throw new ArgumentNullException(nameof(serverUpdater)); @@ -215,10 +233,18 @@ namespace Tgstation.Server.Host.Controllers /// The model containing the to update to. /// The for the operation. /// A resulting in the for the operation. + /// Update has been started successfully. + /// The requested version could not be found. /// Upgrade operations are unavailable due to the launch configuration of TGS. + /// A GitHub rate limit was encountered. + /// A GitHub API error occurred. [HttpPost] [TgsAuthorize(AdministrationRights.ChangeVersion)] + [ProducesResponseType(202)] + [ProducesResponseType(410)] [ProducesResponseType(typeof(ErrorMessage), 422)] + [ProducesResponseType(424)] + [ProducesResponseType(typeof(ErrorMessage), 429)] public async Task Update([FromBody] Administration model, CancellationToken cancellationToken) { if (model == null) @@ -240,12 +266,12 @@ namespace Tgstation.Server.Host.Controllers } /// - /// Attempts to restart the server + /// Attempts to restart the server. /// /// A resulting in the of the request /// Restart begun successfully. /// Restart operations are unavailable due to the launch configuration of TGS. - [HttpDelete("{id}")] + [HttpDelete] [TgsAuthorize(AdministrationRights.RestartHost)] [ProducesResponseType(200)] [ProducesResponseType(typeof(ErrorMessage), 422)] diff --git a/src/Tgstation.Server.Host/Controllers/ApiController.cs b/src/Tgstation.Server.Host/Controllers/ApiController.cs index c5804466aa..d6e7c2aff9 100644 --- a/src/Tgstation.Server.Host/Controllers/ApiController.cs +++ b/src/Tgstation.Server.Host/Controllers/ApiController.cs @@ -9,7 +9,7 @@ using System.Net; using System.Threading.Tasks; using Tgstation.Server.Api; using Tgstation.Server.Api.Models; -using Tgstation.Server.Host.Models; +using Tgstation.Server.Host.Database; using Tgstation.Server.Host.Security; namespace Tgstation.Server.Host.Controllers diff --git a/src/Tgstation.Server.Host/Controllers/ByondController.cs b/src/Tgstation.Server.Host/Controllers/ByondController.cs index 52872441b6..963007b4d7 100644 --- a/src/Tgstation.Server.Host/Controllers/ByondController.cs +++ b/src/Tgstation.Server.Host/Controllers/ByondController.cs @@ -9,7 +9,8 @@ using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Components; -using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Database; +using Tgstation.Server.Host.Jobs; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; diff --git a/src/Tgstation.Server.Host/Controllers/ChatController.cs b/src/Tgstation.Server.Host/Controllers/ChatController.cs index bbc3ba1f97..7346a52af3 100644 --- a/src/Tgstation.Server.Host/Controllers/ChatController.cs +++ b/src/Tgstation.Server.Host/Controllers/ChatController.cs @@ -13,6 +13,7 @@ using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Components; +using Tgstation.Server.Host.Database; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; using Z.EntityFramework.Plus; diff --git a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs index 363be52b3a..e9ed0a9079 100644 --- a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs +++ b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs @@ -10,6 +10,7 @@ using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Components; +using Tgstation.Server.Host.Database; using Tgstation.Server.Host.IO; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs index d9d8324e25..ec2573ed4c 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs @@ -13,7 +13,8 @@ using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Components; using Tgstation.Server.Host.Components.Watchdog; -using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Database; +using Tgstation.Server.Host.Jobs; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; @@ -150,7 +151,7 @@ namespace Tgstation.Server.Host.Controllers /// The for the operation. /// A resulting in the of the operation. /// Watchdog terminated. - [HttpDelete("{id}")] + [HttpDelete] [TgsAuthorize(DreamDaemonRights.Shutdown)] [ProducesResponseType(200)] public async Task Delete(CancellationToken cancellationToken) diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs index 1a9b4e6bcd..89615d0e47 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs @@ -11,7 +11,8 @@ using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Components; -using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Database; +using Tgstation.Server.Host.Jobs; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; diff --git a/src/Tgstation.Server.Host/Controllers/HomeController.cs b/src/Tgstation.Server.Host/Controllers/HomeController.cs index b936049c8c..6bb12428d7 100644 --- a/src/Tgstation.Server.Host/Controllers/HomeController.cs +++ b/src/Tgstation.Server.Host/Controllers/HomeController.cs @@ -14,6 +14,7 @@ using System.Threading.Tasks; using Tgstation.Server.Api; using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Database; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; using Wangkanai.Detection; @@ -164,25 +165,41 @@ namespace Tgstation.Server.Host.Controllers using (systemIdentity) { + // Get the user from the database IQueryable query; + string canonicalName = ApiHeaders.Username.ToUpperInvariant(); if (systemIdentity == null) - query = DatabaseContext.Users.Where(x => x.CanonicalName == ApiHeaders.Username.ToUpperInvariant()); + query = DatabaseContext.Users.Where(x => x.CanonicalName == canonicalName); else - query = DatabaseContext.Users.Where(x => x.SystemIdentifier == systemIdentity.Uid); - var user = await query.Select(x => new User + query = DatabaseContext.Users.Where(x => x.CanonicalName == canonicalName || x.SystemIdentifier == systemIdentity.Uid); + var users = await query.Select(x => new User { Id = x.Id, PasswordHash = x.PasswordHash, Enabled = x.Enabled, Name = x.Name - }).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); + }).ToListAsync(cancellationToken).ConfigureAwait(false); + // Pick the DB user first + var user = users + .OrderByDescending(dbUser => dbUser.PasswordHash != null) + .FirstOrDefault(); + + // No user? You're not allowed if (user == null) return Unauthorized(); - if (systemIdentity == null) + // A system user may have had their name AND password changed to one in our DB... + // Or a DB user was created that had the same user/pass as a system user + // Dumb admins... + // FALLBACK TO THE DB USER HERE, DO NOT REVEAL A SYSTEM LOGIN!!! + // This of course, allows system users to discover TGS users in this (HIGHLY IMPROBABLE) case but that is not our fault + var originalHash = user.PasswordHash; + var isDbUser = originalHash != null; + bool usingSystemIdentity = systemIdentity != null && !isDbUser; + if (!usingSystemIdentity) { - var originalHash = user.PasswordHash; + // DB User password check and update if (!cryptographySuite.CheckUserPassword(user, ApiHeaders.Password)) return Unauthorized(); if (user.PasswordHash != originalHash) @@ -197,10 +214,9 @@ namespace Tgstation.Server.Host.Controllers await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); } } - - // check if the name changed and updoot accordingly else if (systemIdentity.Username != user.Name) { + // System identity username change update Logger.LogDebug("User ID {0}'s system identity needs a refresh, updating database.", user.Id); DatabaseContext.Users.Attach(user); user.Name = systemIdentity.Username; @@ -216,7 +232,7 @@ namespace Tgstation.Server.Host.Controllers } var token = await tokenFactory.CreateToken(user, cancellationToken).ConfigureAwait(false); - if (systemIdentity != null) + if (usingSystemIdentity) { // expire the identity slightly after the auth token in case of lag var identExpiry = token.ExpiresAt.Value; diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs index 1481b1019f..68df42bba5 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs @@ -16,9 +16,12 @@ using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Components; using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Database; using Tgstation.Server.Host.IO; +using Tgstation.Server.Host.Jobs; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; +using Tgstation.Server.Host.System; namespace Tgstation.Server.Host.Controllers { diff --git a/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs b/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs index dc998b9788..9ad8d65a8c 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Rights; +using Tgstation.Server.Host.Database; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; using Z.EntityFramework.Plus; diff --git a/src/Tgstation.Server.Host/Controllers/JobController.cs b/src/Tgstation.Server.Host/Controllers/JobController.cs index 5dddb4bd01..c0a0bfd80d 100644 --- a/src/Tgstation.Server.Host/Controllers/JobController.cs +++ b/src/Tgstation.Server.Host/Controllers/JobController.cs @@ -8,7 +8,8 @@ using System.Net; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api; -using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Database; +using Tgstation.Server.Host.Jobs; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index c6fd18a741..765388fb4c 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -17,6 +17,8 @@ using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Components; using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Database; +using Tgstation.Server.Host.Jobs; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; @@ -230,7 +232,7 @@ namespace Tgstation.Server.Host.Controllers /// A resulting in the of the operation /// Job to delete the repository created successfully. /// Instance no longer available. - [HttpDelete("{id}")] + [HttpDelete] [TgsAuthorize(RepositoryRights.Delete)] [ProducesResponseType(typeof(Repository), 202)] [ProducesResponseType(410)] diff --git a/src/Tgstation.Server.Host/Controllers/UserController.cs b/src/Tgstation.Server.Host/Controllers/UserController.cs index 8988c62cbb..9e35fdf48d 100644 --- a/src/Tgstation.Server.Host/Controllers/UserController.cs +++ b/src/Tgstation.Server.Host/Controllers/UserController.cs @@ -12,6 +12,7 @@ using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Configuration; +using Tgstation.Server.Host.Database; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; @@ -75,7 +76,7 @@ namespace Tgstation.Server.Host.Controllers /// /// Attempt to change the password of a given . /// - /// The to update. + /// The user to update. /// The new password. /// on success, if is too short. BadRequestObjectResult TrySetPassword(Models.User dbUser, string newPassword) diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 796d929743..40c58d0ff6 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -12,8 +12,6 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; -using Microsoft.Net.Http.Headers; -using Microsoft.OpenApi.Models; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Serilog; @@ -31,10 +29,12 @@ using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Components.Repository; using Tgstation.Server.Host.Components.Watchdog; using Tgstation.Server.Host.Configuration; -using Tgstation.Server.Host.Controllers; +using Tgstation.Server.Host.Database; +using Tgstation.Server.Host.Extensions; using Tgstation.Server.Host.IO; -using Tgstation.Server.Host.Models; +using Tgstation.Server.Host.Jobs; using Tgstation.Server.Host.Security; +using Tgstation.Server.Host.System; namespace Tgstation.Server.Host.Core { @@ -61,6 +61,11 @@ namespace Tgstation.Server.Host.Core /// readonly IAssemblyInformationProvider assemblyInformationProvider; + /// + /// The for the . + /// + readonly IIOManager ioManager; + /// /// The for the /// @@ -82,14 +87,17 @@ namespace Tgstation.Server.Host.Core /// The value of /// The for the . /// The value of + /// The value of . public Application( IConfiguration configuration, IAssemblyInformationProvider assemblyInformationProvider, - Microsoft.AspNetCore.Hosting.IHostingEnvironment hostingEnvironment) + Microsoft.AspNetCore.Hosting.IHostingEnvironment hostingEnvironment, + IIOManager ioManager) { this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); this.assemblyInformationProvider = assemblyInformationProvider ?? throw new ArgumentNullException(nameof(assemblyInformationProvider)); this.hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); + this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); startupTcs = new TaskCompletionSource(); @@ -123,9 +131,8 @@ namespace Tgstation.Server.Host.Core services.AddLogging(); // other stuff needed for for setup wizard and configuration - services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -134,7 +141,6 @@ namespace Tgstation.Server.Host.Core DatabaseConfiguration databaseConfiguration; FileLoggingConfiguration fileLoggingConfiguration; ControlPanelConfiguration controlPanelConfiguration; - IIOManager ioManager; IPlatformIdentifier platformIdentifier; // temporarily build the service provider in it's current state @@ -164,7 +170,6 @@ namespace Tgstation.Server.Host.Core var controlPanelOptions = provider.GetRequiredService>(); controlPanelConfiguration = controlPanelOptions.Value; - ioManager = provider.GetRequiredService(); platformIdentifier = provider.GetRequiredService(); } @@ -254,43 +259,12 @@ namespace Tgstation.Server.Host.Core }); if (hostingEnvironment.IsDevelopment()) - services.AddSwaggerGen( - c => - { - c.SwaggerDoc( - "v1", - new OpenApiInfo - { - Title = "TGS API", - Version = "v4" - }); - - // Important to do this before applying our own filters - // Otherwise we'll get NullReferenceExceptions on parameters to be setup in our document filter - var assemblyLocation = assemblyInformationProvider.Path; - var filePath = ioManager.ConcatPath(ioManager.GetDirectoryName(assemblyLocation), String.Concat(ioManager.GetFileNameWithoutExtension(assemblyLocation), ".xml")); - c.IncludeXmlComments(filePath); - - c.OperationFilter(); - c.DocumentFilter(); - - c.AddSecurityDefinition(TgsOpenApiFilters.PasswordSecuritySchemeId, new OpenApiSecurityScheme - { - In = ParameterLocation.Header, - Type = SecuritySchemeType.Http, - Name = HeaderNames.Authorization, - Scheme = ApiHeaders.BasicAuthenticationScheme - }); - - c.AddSecurityDefinition(TgsOpenApiFilters.TokenSecuritySchemeId, new OpenApiSecurityScheme - { - BearerFormat = "JWT", - In = ParameterLocation.Header, - Type = SecuritySchemeType.Http, - Name = HeaderNames.Authorization, - Scheme = ApiHeaders.JwtAuthenticationScheme - }); - }); + { + string GetDocumentationFilePath(string assemblyLocation) => ioManager.ConcatPath(ioManager.GetDirectoryName(assemblyLocation), String.Concat(ioManager.GetFileNameWithoutExtension(assemblyLocation), ".xml")); + var assemblyDocumentationPath = GetDocumentationFilePath(assemblyInformationProvider.Path); + var apiDocumentationPath = GetDocumentationFilePath(typeof(ApiHeaders).Assembly.Location); + services.AddSwaggerGen(genOptions => SwaggerConfiguration.Configure(genOptions, assemblyDocumentationPath, apiDocumentationPath)); + } // enable browser detection services.AddDetectionCore().AddBrowser(); @@ -338,6 +312,11 @@ namespace Tgstation.Server.Host.Core // configure platform specific services if (platformIdentifier.IsWindows) { + if (generalConfiguration.UseBasicWatchdogOnWindows) + services.AddSingleton(); + else + services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -349,6 +328,7 @@ namespace Tgstation.Server.Host.Core } else { + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -370,7 +350,6 @@ namespace Tgstation.Server.Host.Core services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); services.AddSingleton(); // configure root services diff --git a/src/Tgstation.Server.Host/Core/OpenApiEnumVarNamesExtension.cs b/src/Tgstation.Server.Host/Core/OpenApiEnumVarNamesExtension.cs new file mode 100644 index 0000000000..2955147706 --- /dev/null +++ b/src/Tgstation.Server.Host/Core/OpenApiEnumVarNamesExtension.cs @@ -0,0 +1,57 @@ +using Microsoft.OpenApi; +using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Writers; +using System; + +namespace Tgstation.Server.Host.Core +{ + /// + /// Implements the "x-enum-varnames" OpenAPI 3.0 extension. + /// + sealed class OpenApiEnumVarNamesExtension : IOpenApiExtension + { + /// + /// The of the being described. + /// + readonly Type enumType; + + /// + /// Initializes a new instance of the . + /// + /// The value of , + private OpenApiEnumVarNamesExtension(Type enumType) + { + this.enumType = enumType ?? throw new ArgumentNullException(nameof(enumType)); + } + + /// + /// Applies the extension to a give . + /// + /// The to apply to. + /// The of the being described. + public static void Apply(OpenApiSchema openApiSchema, Type enumType) + { + if (openApiSchema == null) + throw new ArgumentNullException(nameof(openApiSchema)); + + openApiSchema.Extensions.Add("x-enum-varnames", new OpenApiEnumVarNamesExtension(enumType)); + } + + /// + public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion) + { + if (writer == null) + throw new ArgumentNullException(nameof(writer)); + + if (specVersion != OpenApiSpecVersion.OpenApi3_0) + throw new InvalidOperationException("This extension only applies to OpenAPI 3.0!"); + + writer.WriteStartArray(); + foreach (var enumValue in Enum.GetValues(enumType)) + writer.WriteValue(enumValue.ToString()); + + writer.WriteEndArray(); + } + } +} diff --git a/src/Tgstation.Server.Host/Core/SetupWizard.cs b/src/Tgstation.Server.Host/Core/SetupWizard.cs index dff6446387..d685044ec7 100644 --- a/src/Tgstation.Server.Host/Core/SetupWizard.cs +++ b/src/Tgstation.Server.Host/Core/SetupWizard.cs @@ -14,7 +14,9 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Host.Configuration; +using Tgstation.Server.Host.Database; using Tgstation.Server.Host.IO; +using Tgstation.Server.Host.System; namespace Tgstation.Server.Host.Core { @@ -42,9 +44,9 @@ namespace Tgstation.Server.Host.Core readonly IApplication application; /// - /// The for the + /// The for the /// - readonly IDBConnectionFactory dbConnectionFactory; + readonly IDatabaseConnectionFactory dbConnectionFactory; /// /// The for the @@ -78,7 +80,16 @@ namespace Tgstation.Server.Host.Core /// The value of /// The value of /// The containing the value of - public SetupWizard(IIOManager ioManager, IConsole console, IHostingEnvironment hostingEnvironment, IApplication application, IDBConnectionFactory dbConnectionFactory, IPlatformIdentifier platformIdentifier, IAsyncDelayer asyncDelayer, ILogger logger, IOptions generalConfigurationOptions) + public SetupWizard( + IIOManager ioManager, + IConsole console, + IHostingEnvironment hostingEnvironment, + IApplication application, + IDatabaseConnectionFactory dbConnectionFactory, + IPlatformIdentifier platformIdentifier, + IAsyncDelayer asyncDelayer, + ILogger logger, + IOptions generalConfigurationOptions) { this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.console = console ?? throw new ArgumentNullException(nameof(console)); @@ -408,6 +419,11 @@ namespace Tgstation.Server.Host.Core newGeneralConfiguration.GitHubAccessToken = await console.ReadLineAsync(true, cancellationToken).ConfigureAwait(false); if (String.IsNullOrWhiteSpace(newGeneralConfiguration.GitHubAccessToken)) newGeneralConfiguration.GitHubAccessToken = null; + + newGeneralConfiguration.UseExperimentalWatchdog = await PromptYesNo("Use the experimental watchdog (NOT RECOMMENDED)? (y/n): ", cancellationToken).ConfigureAwait(false); + if (!newGeneralConfiguration.UseExperimentalWatchdog && platformIdentifier.IsWindows) + newGeneralConfiguration.UseBasicWatchdogOnWindows = !await PromptYesNo("Use the TGS3 style Windows watchdog (RECOMMENDED)? (y/n): ", cancellationToken).ConfigureAwait(false); + return newGeneralConfiguration; } diff --git a/src/Tgstation.Server.Host/Controllers/TgsOpenApiFilters.cs b/src/Tgstation.Server.Host/Core/SwaggerConfiguration.cs similarity index 53% rename from src/Tgstation.Server.Host/Controllers/TgsOpenApiFilters.cs rename to src/Tgstation.Server.Host/Core/SwaggerConfiguration.cs index b902770af5..de3e3d9f02 100644 --- a/src/Tgstation.Server.Host/Controllers/TgsOpenApiFilters.cs +++ b/src/Tgstation.Server.Host/Core/SwaggerConfiguration.cs @@ -1,4 +1,5 @@ -using Microsoft.Net.Http.Headers; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Net.Http.Headers; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.SwaggerGen; @@ -9,23 +10,140 @@ using System.Net; using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Rights; +using Tgstation.Server.Host.Controllers; -namespace Tgstation.Server.Host.Controllers +namespace Tgstation.Server.Host.Core { /// - /// and for the server. + /// Implements various filters for . /// - sealed class TgsOpenApiFilters : IOperationFilter, IDocumentFilter + sealed class SwaggerConfiguration : IOperationFilter, IDocumentFilter, ISchemaFilter { /// /// The name for password authentication. /// - public const string PasswordSecuritySchemeId = "Password_Login_Scheme"; + const string PasswordSecuritySchemeId = "Password_Login_Scheme"; /// /// The name for token authentication. /// - public const string TokenSecuritySchemeId = "Token_Authorization_Scheme"; + const string TokenSecuritySchemeId = "Token_Authorization_Scheme"; + + static void AddDefaultResponses(OpenApiDocument document) + { + var errorMessageContent = new Dictionary + { + { + ApiHeaders.ApplicationJson, + new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Id = nameof(ErrorMessage), + Type = ReferenceType.Schema + } + } + } + } + }; + + void AddDefaultResponse(HttpStatusCode code, OpenApiResponse concrete) + { + string responseKey = $"{(int)code}"; + + document.Components.Responses.Add(responseKey, concrete); + + var referenceResponse = new OpenApiResponse + { + Reference = new OpenApiReference + { + Type = ReferenceType.Response, + Id = responseKey + } + }; + + foreach (var operation in document.Paths.SelectMany(path => path.Value.Operations)) + operation.Value.Responses.TryAdd(responseKey, referenceResponse); + } + + AddDefaultResponse(HttpStatusCode.BadRequest, new OpenApiResponse + { + Description = "A badly formatted request was made. See error message for details.", + Content = errorMessageContent, + }); + + AddDefaultResponse(HttpStatusCode.Unauthorized, new OpenApiResponse + { + Description = "No/invalid token provided." + }); + + AddDefaultResponse(HttpStatusCode.Forbidden, new OpenApiResponse + { + Description = "User lacks sufficient permissions for the operation." + }); + + AddDefaultResponse(HttpStatusCode.Conflict, new OpenApiResponse + { + Description = "A data integrity check failed while performing the operation. See error message for details.", + Content = errorMessageContent + }); + + AddDefaultResponse(HttpStatusCode.InternalServerError, new OpenApiResponse + { + Description = "The server encountered an unhandled error. See error message for details.", + Content = errorMessageContent + }); + + AddDefaultResponse(HttpStatusCode.ServiceUnavailable, new OpenApiResponse + { + Description = "The server may be starting up or shutting down." + }); + } + + /// + /// Configure the swagger settings. + /// + /// The to use. + /// The path to the XML documentation file for the assembly. + /// The path to the XML documentation file for the assembly. + public static void Configure(SwaggerGenOptions swaggerGenOptions, string assemblyDocumentationPath, string apiDocumentationPath) + { + swaggerGenOptions.SwaggerDoc( + "v1", + new OpenApiInfo + { + Title = "TGS API", + Version = "v4" + }); + + // Important to do this before applying our own filters + // Otherwise we'll get NullReferenceExceptions on parameters to be setup in our document filter + swaggerGenOptions.IncludeXmlComments(assemblyDocumentationPath); + swaggerGenOptions.IncludeXmlComments(apiDocumentationPath); + + swaggerGenOptions.OperationFilter(); + swaggerGenOptions.DocumentFilter(); + swaggerGenOptions.SchemaFilter(); + + swaggerGenOptions.AddSecurityDefinition(PasswordSecuritySchemeId, new OpenApiSecurityScheme + { + In = ParameterLocation.Header, + Type = SecuritySchemeType.Http, + Name = HeaderNames.Authorization, + Scheme = ApiHeaders.BasicAuthenticationScheme + }); + + swaggerGenOptions.AddSecurityDefinition(TokenSecuritySchemeId, new OpenApiSecurityScheme + { + BearerFormat = "JWT", + In = ParameterLocation.Header, + Type = SecuritySchemeType.Http, + Name = HeaderNames.Authorization, + Scheme = ApiHeaders.JwtAuthenticationScheme + }); + } /// public void Apply(OpenApiOperation operation, OperationFilterContext context) @@ -35,6 +153,8 @@ namespace Tgstation.Server.Host.Controllers if (context == null) throw new ArgumentNullException(nameof(context)); + operation.OperationId = $"{context.MethodInfo.DeclaringType.Name}.{context.MethodInfo.Name}"; + var authAttributes = context .MethodInfo .DeclaringType @@ -72,7 +192,7 @@ namespace Tgstation.Server.Host.Controllers { Reference = new OpenApiReference { - Type = ReferenceType.Header, + Type = ReferenceType.Parameter, Id = ApiHeaders.InstanceIdHeader } }); @@ -105,27 +225,50 @@ namespace Tgstation.Server.Host.Controllers /// public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) { - swaggerDoc.Components.Headers.Add(ApiHeaders.InstanceIdHeader, new OpenApiHeader + if (swaggerDoc == null) + throw new ArgumentNullException(nameof(swaggerDoc)); + if (context == null) + throw new ArgumentNullException(nameof(context)); + + swaggerDoc.Components.Parameters.Add(ApiHeaders.InstanceIdHeader, new OpenApiParameter { + In = ParameterLocation.Header, + Name = ApiHeaders.InstanceIdHeader, Description = "The instance ID being accessed", Required = true, - Style = ParameterStyle.Simple + Style = ParameterStyle.Simple, + Schema = new OpenApiSchema + { + Type = "integer" + } }); - swaggerDoc.Components.Headers.Add(ApiHeaders.ApiVersionHeader, new OpenApiHeader + var productHeaderSchema = new OpenApiSchema { + Type = "string", + Format = "productheader" + }; + + swaggerDoc.Components.Parameters.Add(ApiHeaders.ApiVersionHeader, new OpenApiParameter + { + In = ParameterLocation.Header, + Name = ApiHeaders.ApiVersionHeader, Description = "The API version being used in the form \"Tgstation.Server.Api/[API version]\"", Required = true, Style = ParameterStyle.Simple, - Example = new OpenApiString($"Tgstation.Server.Api/{ApiHeaders.Version}") + Example = new OpenApiString($"Tgstation.Server.Api/{ApiHeaders.Version}"), + Schema = productHeaderSchema }); - swaggerDoc.Components.Headers.Add(HeaderNames.UserAgent, new OpenApiHeader + swaggerDoc.Components.Parameters.Add(HeaderNames.UserAgent, new OpenApiParameter { + In = ParameterLocation.Header, + Name = HeaderNames.UserAgent, Description = "The user agent of the calling client.", Required = true, Style = ParameterStyle.Simple, - Example = new OpenApiString("Your-user-agent/1.0.0.0") + Example = new OpenApiString("Your-user-agent/1.0.0.0"), + Schema = productHeaderSchema }); foreach (var operation in swaggerDoc @@ -137,91 +280,41 @@ namespace Tgstation.Server.Host.Controllers { Reference = new OpenApiReference { - Type = ReferenceType.Header, + Type = ReferenceType.Parameter, Id = ApiHeaders.ApiVersionHeader - } + }, }); operation.Parameters.Add(new OpenApiParameter { Reference = new OpenApiReference { - Type = ReferenceType.Header, + Type = ReferenceType.Parameter, Id = HeaderNames.UserAgent } }); } - var errorMessageContent = new Dictionary - { - { - ApiHeaders.ApplicationJson, - new OpenApiMediaType - { - Schema = new OpenApiSchema - { - Reference = new OpenApiReference - { - Id = nameof(ErrorMessage), - Type = ReferenceType.Schema - } - } - } - } - }; + AddDefaultResponses(swaggerDoc); + } - void AddDefaultResponse(HttpStatusCode code, OpenApiResponse concrete) - { - string responseKey = $"{(int)code}"; + /// + public void Apply(OpenApiSchema schema, SchemaFilterContext context) + { + if (schema == null) + throw new ArgumentNullException(nameof(schema)); + if (context == null) + throw new ArgumentNullException(nameof(context)); - swaggerDoc.Components.Responses.Add(responseKey, concrete); + if (!schema.Enum?.Any() ?? false) + return; - var referenceResponse = new OpenApiResponse - { - Reference = new OpenApiReference - { - Type = ReferenceType.Response, - Id = responseKey - } - }; + // Could be nullable type, make sure to get the right one + Type enumType = context.Type.IsConstructedGenericType + ? context.Type.GenericTypeArguments.First() + : context.Type; - foreach (var path in swaggerDoc.Paths) - foreach (var operation in path.Value.Operations) - operation.Value.Responses.TryAdd(responseKey, referenceResponse); - } - - AddDefaultResponse(HttpStatusCode.BadRequest, new OpenApiResponse - { - Description = "A badly formatted request was made. See error message for details.", - Content = errorMessageContent, - }); - - AddDefaultResponse(HttpStatusCode.Unauthorized, new OpenApiResponse - { - Description = "No/invalid token provided." - }); - - AddDefaultResponse(HttpStatusCode.Forbidden, new OpenApiResponse - { - Description = "User lacks sufficient permissions for the operation." - }); - - AddDefaultResponse(HttpStatusCode.Conflict, new OpenApiResponse - { - Description = "A data integrity check failed while performing the operation. See error message for details.", - Content = errorMessageContent - }); - - AddDefaultResponse(HttpStatusCode.InternalServerError, new OpenApiResponse - { - Description = "The server encountered an unhandled error. See error message for details.", - Content = errorMessageContent - }); - - AddDefaultResponse(HttpStatusCode.ServiceUnavailable, new OpenApiResponse - { - Description = "The server may be starting up or shutting down." - }); + OpenApiEnumVarNamesExtension.Apply(schema, enumType); } } } diff --git a/src/Tgstation.Server.Host/Core/DBConnectionFactory.cs b/src/Tgstation.Server.Host/Database/DatabaseConnectionFactory.cs similarity index 86% rename from src/Tgstation.Server.Host/Core/DBConnectionFactory.cs rename to src/Tgstation.Server.Host/Database/DatabaseConnectionFactory.cs index 8378d28d00..91ef82de8d 100644 --- a/src/Tgstation.Server.Host/Core/DBConnectionFactory.cs +++ b/src/Tgstation.Server.Host/Database/DatabaseConnectionFactory.cs @@ -2,13 +2,12 @@ using System; using System.Data.Common; using System.Data.SqlClient; -using System.Globalization; using Tgstation.Server.Host.Configuration; -namespace Tgstation.Server.Host.Core +namespace Tgstation.Server.Host.Database { /// - sealed class DBConnectionFactory : IDBConnectionFactory + sealed class DatabaseConnectionFactory : IDatabaseConnectionFactory { /// public DbConnection CreateConnection(string connectionString, DatabaseType databaseType) diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Database/DatabaseContext.cs similarity index 99% rename from src/Tgstation.Server.Host/Models/DatabaseContext.cs rename to src/Tgstation.Server.Host/Database/DatabaseContext.cs index 5572c9b0e8..5be860c5a5 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Database/DatabaseContext.cs @@ -10,9 +10,10 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Host.Configuration; +using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Models.Migrations; -namespace Tgstation.Server.Host.Models +namespace Tgstation.Server.Host.Database { /// #pragma warning disable CA1506 // TODO: Decomplexify diff --git a/src/Tgstation.Server.Host/Core/DatabaseContextFactory.cs b/src/Tgstation.Server.Host/Database/DatabaseContextFactory.cs similarity index 94% rename from src/Tgstation.Server.Host/Core/DatabaseContextFactory.cs rename to src/Tgstation.Server.Host/Database/DatabaseContextFactory.cs index fc23255d12..2522ca307e 100644 --- a/src/Tgstation.Server.Host/Core/DatabaseContextFactory.cs +++ b/src/Tgstation.Server.Host/Database/DatabaseContextFactory.cs @@ -1,9 +1,8 @@ using Microsoft.Extensions.DependencyInjection; using System; using System.Threading.Tasks; -using Tgstation.Server.Host.Models; -namespace Tgstation.Server.Host.Core +namespace Tgstation.Server.Host.Database { /// sealed class DatabaseContextFactory : IDatabaseContextFactory diff --git a/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs b/src/Tgstation.Server.Host/Database/DatabaseSeeder.cs similarity index 85% rename from src/Tgstation.Server.Host/Models/DatabaseSeeder.cs rename to src/Tgstation.Server.Host/Database/DatabaseSeeder.cs index fbe245ad79..73f5128335 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs +++ b/src/Tgstation.Server.Host/Database/DatabaseSeeder.cs @@ -4,9 +4,10 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Rights; +using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; -namespace Tgstation.Server.Host.Models +namespace Tgstation.Server.Host.Database { /// sealed class DatabaseSeeder : IDatabaseSeeder @@ -20,7 +21,10 @@ namespace Tgstation.Server.Host.Models /// Construct a /// /// The value of - public DatabaseSeeder(ICryptographySuite cryptographySuite) => this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite)); + public DatabaseSeeder(ICryptographySuite cryptographySuite) + { + this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite)); + } /// /// Add a default admin to a given @@ -30,9 +34,9 @@ namespace Tgstation.Server.Host.Models { var admin = new User { - AdministrationRights = (AdministrationRights)~0U, + AdministrationRights = ~AdministrationRights.None, CreatedAt = DateTimeOffset.Now, - InstanceManagerRights = (InstanceManagerRights)~0U, + InstanceManagerRights = ~InstanceManagerRights.None, Name = Api.Models.User.AdminName, CanonicalName = Api.Models.User.AdminName.ToUpperInvariant(), Enabled = true, diff --git a/src/Tgstation.Server.Host/Models/Migrations/DesignTimeDbContextFactoryHelpers.cs b/src/Tgstation.Server.Host/Database/Design/DesignTimeDbContextFactoryHelpers.cs similarity index 94% rename from src/Tgstation.Server.Host/Models/Migrations/DesignTimeDbContextFactoryHelpers.cs rename to src/Tgstation.Server.Host/Database/Design/DesignTimeDbContextFactoryHelpers.cs index 2de82a18bf..5071d72ccf 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/DesignTimeDbContextFactoryHelpers.cs +++ b/src/Tgstation.Server.Host/Database/Design/DesignTimeDbContextFactoryHelpers.cs @@ -1,10 +1,10 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Options; using Tgstation.Server.Host.Configuration; -using Tgstation.Server.Host.Core; using Tgstation.Server.Host.IO; +using Tgstation.Server.Host.System; -namespace Tgstation.Server.Host.Models.Migrations +namespace Tgstation.Server.Host.Database.Design { /// /// Contains helpers for creating design time s diff --git a/src/Tgstation.Server.Host/Models/Migrations/MySqlDesignTimeDbContextFactory.cs b/src/Tgstation.Server.Host/Database/Design/MySqlDesignTimeDbContextFactory.cs similarity index 89% rename from src/Tgstation.Server.Host/Models/Migrations/MySqlDesignTimeDbContextFactory.cs rename to src/Tgstation.Server.Host/Database/Design/MySqlDesignTimeDbContextFactory.cs index 45cbec1e27..1a48bd319f 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/MySqlDesignTimeDbContextFactory.cs +++ b/src/Tgstation.Server.Host/Database/Design/MySqlDesignTimeDbContextFactory.cs @@ -2,9 +2,10 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Design; using Microsoft.Extensions.Logging; +using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; -namespace Tgstation.Server.Host.Models.Migrations +namespace Tgstation.Server.Host.Database.Design { /// sealed class MySqlDesignTimeDbContextFactory : IDesignTimeDbContextFactory diff --git a/src/Tgstation.Server.Host/Models/Migrations/SqlServerDesignTimeDbContextFactory.cs b/src/Tgstation.Server.Host/Database/Design/SqlServerDesignTimeDbContextFactory.cs similarity index 90% rename from src/Tgstation.Server.Host/Models/Migrations/SqlServerDesignTimeDbContextFactory.cs rename to src/Tgstation.Server.Host/Database/Design/SqlServerDesignTimeDbContextFactory.cs index 413bfbc5aa..a935229be5 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/SqlServerDesignTimeDbContextFactory.cs +++ b/src/Tgstation.Server.Host/Database/Design/SqlServerDesignTimeDbContextFactory.cs @@ -2,9 +2,10 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Design; using Microsoft.Extensions.Logging; +using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; -namespace Tgstation.Server.Host.Models.Migrations +namespace Tgstation.Server.Host.Database.Design { /// sealed class SqlServerDesignTimeDbContextFactory : IDesignTimeDbContextFactory diff --git a/src/Tgstation.Server.Host/Core/IDBConnectionFactory.cs b/src/Tgstation.Server.Host/Database/IDatabaseConnectionFactory.cs similarity index 79% rename from src/Tgstation.Server.Host/Core/IDBConnectionFactory.cs rename to src/Tgstation.Server.Host/Database/IDatabaseConnectionFactory.cs index 3f378b88b5..9ebe790587 100644 --- a/src/Tgstation.Server.Host/Core/IDBConnectionFactory.cs +++ b/src/Tgstation.Server.Host/Database/IDatabaseConnectionFactory.cs @@ -1,12 +1,12 @@ using System.Data.Common; using Tgstation.Server.Host.Configuration; -namespace Tgstation.Server.Host.Core +namespace Tgstation.Server.Host.Database { /// - /// For creating + /// For creating raw s. /// - interface IDBConnectionFactory + interface IDatabaseConnectionFactory { /// /// Create a diff --git a/src/Tgstation.Server.Host/Models/IDatabaseContext.cs b/src/Tgstation.Server.Host/Database/IDatabaseContext.cs similarity index 91% rename from src/Tgstation.Server.Host/Models/IDatabaseContext.cs rename to src/Tgstation.Server.Host/Database/IDatabaseContext.cs index e7c8e9dcd2..a40113f5be 100644 --- a/src/Tgstation.Server.Host/Models/IDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Database/IDatabaseContext.cs @@ -2,8 +2,9 @@ using System; using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Host.Models; -namespace Tgstation.Server.Host.Models +namespace Tgstation.Server.Host.Database { /// /// Represents the database @@ -51,7 +52,7 @@ namespace Tgstation.Server.Host.Models DbSet DreamDaemonSettings { get; set; } /// - /// The in the + /// The s in the /// DbSet ChatBots { get; set; } @@ -66,12 +67,12 @@ namespace Tgstation.Server.Host.Models DbSet RepositorySettings { get; set; } /// - /// The for s + /// The for s /// DbSet ReattachInformations { get; set; } /// - /// The for s + /// The for s /// DbSet WatchdogReattachInformations { get; set; } diff --git a/src/Tgstation.Server.Host/Core/IDatabaseContextFactory.cs b/src/Tgstation.Server.Host/Database/IDatabaseContextFactory.cs similarity index 88% rename from src/Tgstation.Server.Host/Core/IDatabaseContextFactory.cs rename to src/Tgstation.Server.Host/Database/IDatabaseContextFactory.cs index 305092416c..7d625a86e7 100644 --- a/src/Tgstation.Server.Host/Core/IDatabaseContextFactory.cs +++ b/src/Tgstation.Server.Host/Database/IDatabaseContextFactory.cs @@ -1,8 +1,7 @@ using System; using System.Threading.Tasks; -using Tgstation.Server.Host.Models; -namespace Tgstation.Server.Host.Core +namespace Tgstation.Server.Host.Database { /// /// Factory for scoping usage of s. Meant for use by diff --git a/src/Tgstation.Server.Host/Models/IDatabaseSeeder.cs b/src/Tgstation.Server.Host/Database/IDatabaseSeeder.cs similarity index 96% rename from src/Tgstation.Server.Host/Models/IDatabaseSeeder.cs rename to src/Tgstation.Server.Host/Database/IDatabaseSeeder.cs index 291f6be3fa..18f9440418 100644 --- a/src/Tgstation.Server.Host/Models/IDatabaseSeeder.cs +++ b/src/Tgstation.Server.Host/Database/IDatabaseSeeder.cs @@ -1,7 +1,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Tgstation.Server.Host.Models +namespace Tgstation.Server.Host.Database { /// /// For initially seeding a database diff --git a/src/Tgstation.Server.Host/Models/MySqlDatabaseContext.cs b/src/Tgstation.Server.Host/Database/MySqlDatabaseContext.cs similarity index 98% rename from src/Tgstation.Server.Host/Models/MySqlDatabaseContext.cs rename to src/Tgstation.Server.Host/Database/MySqlDatabaseContext.cs index 718fc04d98..593aa93519 100644 --- a/src/Tgstation.Server.Host/Models/MySqlDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Database/MySqlDatabaseContext.cs @@ -6,7 +6,7 @@ using Pomelo.EntityFrameworkCore.MySql.Infrastructure; using System; using Tgstation.Server.Host.Configuration; -namespace Tgstation.Server.Host.Models +namespace Tgstation.Server.Host.Database { /// /// for MySQL diff --git a/src/Tgstation.Server.Host/Models/SqlServerDatabaseContext.cs b/src/Tgstation.Server.Host/Database/SqlServerDatabaseContext.cs similarity index 97% rename from src/Tgstation.Server.Host/Models/SqlServerDatabaseContext.cs rename to src/Tgstation.Server.Host/Database/SqlServerDatabaseContext.cs index 94240566d3..0acda9944f 100644 --- a/src/Tgstation.Server.Host/Models/SqlServerDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Database/SqlServerDatabaseContext.cs @@ -3,7 +3,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Tgstation.Server.Host.Configuration; -namespace Tgstation.Server.Host.Models +namespace Tgstation.Server.Host.Database { /// /// for Sqlserver diff --git a/src/Tgstation.Server.Host/Core/ApplicationBuilderExtensions.cs b/src/Tgstation.Server.Host/Extensions/ApplicationBuilderExtensions.cs similarity index 95% rename from src/Tgstation.Server.Host/Core/ApplicationBuilderExtensions.cs rename to src/Tgstation.Server.Host/Extensions/ApplicationBuilderExtensions.cs index a9290b8c01..b79855c412 100644 --- a/src/Tgstation.Server.Host/Core/ApplicationBuilderExtensions.cs +++ b/src/Tgstation.Server.Host/Extensions/ApplicationBuilderExtensions.cs @@ -9,7 +9,7 @@ using System.Globalization; using System.Net; using Tgstation.Server.Api.Models; -namespace Tgstation.Server.Host.Core +namespace Tgstation.Server.Host.Extensions { /// /// Extensions for @@ -21,7 +21,7 @@ namespace Tgstation.Server.Host.Core /// /// The to get the from /// A new - static ILogger GetLogger(HttpContext httpContext) => httpContext.RequestServices.GetRequiredService>(); + static ILogger GetLogger(HttpContext httpContext) => httpContext.RequestServices.GetRequiredService().CreateLogger(typeof(ApplicationBuilderExtensions)); /// /// Return a for s diff --git a/src/Tgstation.Server.Host/Core/ServiceCollectionExtensions.cs b/src/Tgstation.Server.Host/Extensions/ServiceCollectionExtensions.cs similarity index 97% rename from src/Tgstation.Server.Host/Core/ServiceCollectionExtensions.cs rename to src/Tgstation.Server.Host/Extensions/ServiceCollectionExtensions.cs index cd50b10b88..3398d70c90 100644 --- a/src/Tgstation.Server.Host/Core/ServiceCollectionExtensions.cs +++ b/src/Tgstation.Server.Host/Extensions/ServiceCollectionExtensions.cs @@ -4,7 +4,7 @@ using System; using System.Globalization; using Tgstation.Server.Host.Configuration; -namespace Tgstation.Server.Host.Core +namespace Tgstation.Server.Host.Extensions { /// /// Extensions for diff --git a/src/Tgstation.Server.Host/Core/TaskExtensions.cs b/src/Tgstation.Server.Host/Extensions/TaskExtensions.cs similarity index 97% rename from src/Tgstation.Server.Host/Core/TaskExtensions.cs rename to src/Tgstation.Server.Host/Extensions/TaskExtensions.cs index fb1b825313..f2e8802173 100644 --- a/src/Tgstation.Server.Host/Core/TaskExtensions.cs +++ b/src/Tgstation.Server.Host/Extensions/TaskExtensions.cs @@ -2,7 +2,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Tgstation.Server.Host.Core +namespace Tgstation.Server.Host.Extensions { /// /// Extensions for the . diff --git a/src/Tgstation.Server.Host/IO/Console.cs b/src/Tgstation.Server.Host/IO/Console.cs index 0d90d54c25..f8c8162d29 100644 --- a/src/Tgstation.Server.Host/IO/Console.cs +++ b/src/Tgstation.Server.Host/IO/Console.cs @@ -30,7 +30,7 @@ namespace Tgstation.Server.Host.IO public Console() { cancelKeyCts = new CancellationTokenSource(); - System.Console.CancelKeyPress += (sender, e) => + global::System.Console.CancelKeyPress += (sender, e) => { lock (cancelKeyCts) { @@ -60,7 +60,7 @@ namespace Tgstation.Server.Host.IO public Task PressAnyKeyAsync(CancellationToken cancellationToken) => Task.Factory.StartNew(() => { CheckAvailable(); - System.Console.Read(); + global::System.Console.Read(); }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); /// @@ -69,12 +69,12 @@ namespace Tgstation.Server.Host.IO // TODO: Make this better: https://stackoverflow.com/questions/9479573/how-to-interrupt-console-readline CheckAvailable(); if (!usePasswordChar) - return System.Console.ReadLine(); + return global::System.Console.ReadLine(); var passwordBuilder = new StringBuilder(); do { - var keyDescription = System.Console.ReadKey(true); + var keyDescription = global::System.Console.ReadKey(true); if (keyDescription.Key == ConsoleKey.Enter) break; else if (keyDescription.Key == ConsoleKey.Backspace) @@ -82,20 +82,20 @@ namespace Tgstation.Server.Host.IO if (passwordBuilder.Length > 0) { --passwordBuilder.Length; - System.Console.Write("\b \b"); + global::System.Console.Write("\b \b"); } } else if (keyDescription.KeyChar != '\u0000') { // KeyChar == '\u0000' if the key pressed does not correspond to a printable character, e.g. F1, Pause-Break, etc passwordBuilder.Append(keyDescription.KeyChar); - System.Console.Write('*'); + global::System.Console.Write('*'); } } while (!cancellationToken.IsCancellationRequested); cancellationToken.ThrowIfCancellationRequested(); - System.Console.WriteLine(); + global::System.Console.WriteLine(); return passwordBuilder.ToString(); }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); @@ -107,12 +107,12 @@ namespace Tgstation.Server.Host.IO { if (!newLine) throw new InvalidOperationException("Cannot write null text without a new line!"); - System.Console.WriteLine(); + global::System.Console.WriteLine(); } else if (newLine) - System.Console.WriteLine(text); + global::System.Console.WriteLine(text); else - System.Console.Write(text); + global::System.Console.Write(text); }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); } } diff --git a/src/Tgstation.Server.Host/IO/DefaultIOManager.cs b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs index c303d0a008..58b0348172 100644 --- a/src/Tgstation.Server.Host/IO/DefaultIOManager.cs +++ b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs @@ -30,15 +30,17 @@ namespace Tgstation.Server.Host.IO { var tasks = new List(); + // check if we are a symbolic link + if (!dir.Attributes.HasFlag(FileAttributes.Directory) || dir.Attributes.HasFlag(FileAttributes.ReparsePoint)) + { + dir.Delete(); + return; + } + foreach (var subDir in dir.EnumerateDirectories()) { cancellationToken.ThrowIfCancellationRequested(); - - // if below succeeds this is probably a symlink - if (!subDir.Attributes.HasFlag(FileAttributes.Directory) || subDir.Attributes.HasFlag(FileAttributes.ReparsePoint)) - subDir.Delete(); - else - tasks.Add(NormalizeAndDelete(subDir, cancellationToken)); + tasks.Add(NormalizeAndDelete(subDir, cancellationToken)); } foreach (var file in dir.EnumerateFiles()) diff --git a/src/Tgstation.Server.Host/IO/IConsole.cs b/src/Tgstation.Server.Host/IO/IConsole.cs index 405ffb425b..6ec3962bf6 100644 --- a/src/Tgstation.Server.Host/IO/IConsole.cs +++ b/src/Tgstation.Server.Host/IO/IConsole.cs @@ -4,7 +4,7 @@ using System.Threading.Tasks; namespace Tgstation.Server.Host.IO { /// - /// Abstraction for + /// Abstraction for /// interface IConsole { diff --git a/src/Tgstation.Server.Host/IO/IIOManager.cs b/src/Tgstation.Server.Host/IO/IIOManager.cs index 4ce21a7c97..9cd580bd21 100644 --- a/src/Tgstation.Server.Host/IO/IIOManager.cs +++ b/src/Tgstation.Server.Host/IO/IIOManager.cs @@ -139,7 +139,7 @@ namespace Tgstation.Server.Host.IO Task CreateDirectory(string path, CancellationToken cancellationToken); /// - /// Recursively delete a directory + /// Recursively delete a directory, removes and does not enter any symlinks encounterd. /// /// The path to the directory to delete /// A for the operation @@ -183,7 +183,7 @@ namespace Tgstation.Server.Host.IO /// Extract a set of to a given /// /// The path to unzip to - /// The s of the + /// The s of the /// The for the operation /// A representing the running operation Task ZipToDirectory(string path, byte[] zipFileBytes, CancellationToken cancellationToken); diff --git a/src/Tgstation.Server.Host/Core/IJobManager.cs b/src/Tgstation.Server.Host/Jobs/IJobManager.cs similarity index 97% rename from src/Tgstation.Server.Host/Core/IJobManager.cs rename to src/Tgstation.Server.Host/Jobs/IJobManager.cs index 6599b59a03..4dadd50ae6 100644 --- a/src/Tgstation.Server.Host/Core/IJobManager.cs +++ b/src/Tgstation.Server.Host/Jobs/IJobManager.cs @@ -2,9 +2,10 @@ using System; using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Host.Database; using Tgstation.Server.Host.Models; -namespace Tgstation.Server.Host.Core +namespace Tgstation.Server.Host.Jobs { /// /// Manages the runtime of s diff --git a/src/Tgstation.Server.Host/Core/JobException.cs b/src/Tgstation.Server.Host/Jobs/JobException.cs similarity index 96% rename from src/Tgstation.Server.Host/Core/JobException.cs rename to src/Tgstation.Server.Host/Jobs/JobException.cs index fbf4449365..40469a0e59 100644 --- a/src/Tgstation.Server.Host/Core/JobException.cs +++ b/src/Tgstation.Server.Host/Jobs/JobException.cs @@ -1,6 +1,6 @@ using System; -namespace Tgstation.Server.Host.Core +namespace Tgstation.Server.Host.Jobs { /// /// Operation exceptions thrown from the context of a diff --git a/src/Tgstation.Server.Host/Core/JobHandler.cs b/src/Tgstation.Server.Host/Jobs/JobHandler.cs similarity index 98% rename from src/Tgstation.Server.Host/Core/JobHandler.cs rename to src/Tgstation.Server.Host/Jobs/JobHandler.cs index e09f574581..072b075fe2 100644 --- a/src/Tgstation.Server.Host/Core/JobHandler.cs +++ b/src/Tgstation.Server.Host/Jobs/JobHandler.cs @@ -2,7 +2,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Tgstation.Server.Host.Core +namespace Tgstation.Server.Host.Jobs { /// /// Class for pairing s with s diff --git a/src/Tgstation.Server.Host/Core/JobManager.cs b/src/Tgstation.Server.Host/Jobs/JobManager.cs similarity index 99% rename from src/Tgstation.Server.Host/Core/JobManager.cs rename to src/Tgstation.Server.Host/Jobs/JobManager.cs index 42b9bba454..129d740664 100644 --- a/src/Tgstation.Server.Host/Core/JobManager.cs +++ b/src/Tgstation.Server.Host/Jobs/JobManager.cs @@ -5,9 +5,10 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Host.Database; using Tgstation.Server.Host.Models; -namespace Tgstation.Server.Host.Core +namespace Tgstation.Server.Host.Jobs { /// sealed class JobManager : IJobManager, IDisposable diff --git a/src/Tgstation.Server.Host/Models/Migrations/20180906135553_MSInitialCreate.Designer.cs b/src/Tgstation.Server.Host/Models/Migrations/20180906135553_MSInitialCreate.Designer.cs index dae77c6047..f5a3868f47 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/20180906135553_MSInitialCreate.Designer.cs +++ b/src/Tgstation.Server.Host/Models/Migrations/20180906135553_MSInitialCreate.Designer.cs @@ -5,6 +5,7 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Tgstation.Server.Host.Database; namespace Tgstation.Server.Host.Models.Migrations { diff --git a/src/Tgstation.Server.Host/Models/Migrations/20180906143029_MYInitialCreate.Designer.cs b/src/Tgstation.Server.Host/Models/Migrations/20180906143029_MYInitialCreate.Designer.cs index 3c1a424266..fe68368e68 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/20180906143029_MYInitialCreate.Designer.cs +++ b/src/Tgstation.Server.Host/Models/Migrations/20180906143029_MYInitialCreate.Designer.cs @@ -3,6 +3,7 @@ using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; +using Tgstation.Server.Host.Database; namespace Tgstation.Server.Host.Models.Migrations { diff --git a/src/Tgstation.Server.Host/Models/Migrations/20180918020726_MYAddMinimumSecurity.Designer.cs b/src/Tgstation.Server.Host/Models/Migrations/20180918020726_MYAddMinimumSecurity.Designer.cs index 0c4538790b..dad97d04a0 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/20180918020726_MYAddMinimumSecurity.Designer.cs +++ b/src/Tgstation.Server.Host/Models/Migrations/20180918020726_MYAddMinimumSecurity.Designer.cs @@ -3,6 +3,7 @@ using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; +using Tgstation.Server.Host.Database; namespace Tgstation.Server.Host.Models.Migrations { diff --git a/src/Tgstation.Server.Host/Models/Migrations/20180918021228_MSAddMinimumSecurity.Designer.cs b/src/Tgstation.Server.Host/Models/Migrations/20180918021228_MSAddMinimumSecurity.Designer.cs index 6223542618..7fd4819b71 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/20180918021228_MSAddMinimumSecurity.Designer.cs +++ b/src/Tgstation.Server.Host/Models/Migrations/20180918021228_MSAddMinimumSecurity.Designer.cs @@ -5,6 +5,7 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Tgstation.Server.Host.Database; namespace Tgstation.Server.Host.Models.Migrations { diff --git a/src/Tgstation.Server.Host/Models/Migrations/20180918204520_MYNullableAndForeignKeyCleanup.Designer.cs b/src/Tgstation.Server.Host/Models/Migrations/20180918204520_MYNullableAndForeignKeyCleanup.Designer.cs index c267dc1782..bd1692cd78 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/20180918204520_MYNullableAndForeignKeyCleanup.Designer.cs +++ b/src/Tgstation.Server.Host/Models/Migrations/20180918204520_MYNullableAndForeignKeyCleanup.Designer.cs @@ -3,6 +3,7 @@ using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; +using Tgstation.Server.Host.Database; namespace Tgstation.Server.Host.Models.Migrations { diff --git a/src/Tgstation.Server.Host/Models/Migrations/20180918205224_MSNullableAndForeignKeyCleanup.Designer.cs b/src/Tgstation.Server.Host/Models/Migrations/20180918205224_MSNullableAndForeignKeyCleanup.Designer.cs index d8bf97f528..41e7cd3d22 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/20180918205224_MSNullableAndForeignKeyCleanup.Designer.cs +++ b/src/Tgstation.Server.Host/Models/Migrations/20180918205224_MSNullableAndForeignKeyCleanup.Designer.cs @@ -5,6 +5,7 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Tgstation.Server.Host.Database; namespace Tgstation.Server.Host.Models.Migrations { diff --git a/src/Tgstation.Server.Host/Models/Migrations/20180926033145_MSReattachCompileJobRequired.Designer.cs b/src/Tgstation.Server.Host/Models/Migrations/20180926033145_MSReattachCompileJobRequired.Designer.cs index 5c778ac640..bb8f71f024 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/20180926033145_MSReattachCompileJobRequired.Designer.cs +++ b/src/Tgstation.Server.Host/Models/Migrations/20180926033145_MSReattachCompileJobRequired.Designer.cs @@ -5,6 +5,7 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Tgstation.Server.Host.Database; namespace Tgstation.Server.Host.Models.Migrations { diff --git a/src/Tgstation.Server.Host/Models/Migrations/20180926034014_MYReattachCompileJobRequired.Designer.cs b/src/Tgstation.Server.Host/Models/Migrations/20180926034014_MYReattachCompileJobRequired.Designer.cs index 600d1dd972..2428009bdc 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/20180926034014_MYReattachCompileJobRequired.Designer.cs +++ b/src/Tgstation.Server.Host/Models/Migrations/20180926034014_MYReattachCompileJobRequired.Designer.cs @@ -3,6 +3,7 @@ using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; +using Tgstation.Server.Host.Database; namespace Tgstation.Server.Host.Models.Migrations { diff --git a/src/Tgstation.Server.Host/Models/Migrations/20181124231534_MSToggleTestmergeComments.Designer.cs b/src/Tgstation.Server.Host/Models/Migrations/20181124231534_MSToggleTestmergeComments.Designer.cs index 309149de82..e6d62e22c9 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/20181124231534_MSToggleTestmergeComments.Designer.cs +++ b/src/Tgstation.Server.Host/Models/Migrations/20181124231534_MSToggleTestmergeComments.Designer.cs @@ -5,6 +5,7 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Tgstation.Server.Host.Database; namespace Tgstation.Server.Host.Models.Migrations { diff --git a/src/Tgstation.Server.Host/Models/Migrations/20181124231549_MYToggleTestmergeComments.Designer.cs b/src/Tgstation.Server.Host/Models/Migrations/20181124231549_MYToggleTestmergeComments.Designer.cs index ac92efde33..50d60eb5db 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/20181124231549_MYToggleTestmergeComments.Designer.cs +++ b/src/Tgstation.Server.Host/Models/Migrations/20181124231549_MYToggleTestmergeComments.Designer.cs @@ -3,6 +3,7 @@ using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; +using Tgstation.Server.Host.Database; namespace Tgstation.Server.Host.Models.Migrations { diff --git a/src/Tgstation.Server.Host/Models/Migrations/20190219041825_MSFixRevInfoIndex.Designer.cs b/src/Tgstation.Server.Host/Models/Migrations/20190219041825_MSFixRevInfoIndex.Designer.cs index 2bdb6af82f..0097146a76 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/20190219041825_MSFixRevInfoIndex.Designer.cs +++ b/src/Tgstation.Server.Host/Models/Migrations/20190219041825_MSFixRevInfoIndex.Designer.cs @@ -5,6 +5,7 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Tgstation.Server.Host.Database; namespace Tgstation.Server.Host.Models.Migrations { diff --git a/src/Tgstation.Server.Host/Models/Migrations/20190219042440_MYFixRevInfoIndex.Designer.cs b/src/Tgstation.Server.Host/Models/Migrations/20190219042440_MYFixRevInfoIndex.Designer.cs index dffe797032..e037584904 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/20190219042440_MYFixRevInfoIndex.Designer.cs +++ b/src/Tgstation.Server.Host/Models/Migrations/20190219042440_MYFixRevInfoIndex.Designer.cs @@ -3,6 +3,7 @@ using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; +using Tgstation.Server.Host.Database; namespace Tgstation.Server.Host.Models.Migrations { diff --git a/src/Tgstation.Server.Host/Models/Migrations/MySqlDatabaseContextModelSnapshot.cs b/src/Tgstation.Server.Host/Models/Migrations/MySqlDatabaseContextModelSnapshot.cs index 2ddc0fb4e3..47d90d047d 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/MySqlDatabaseContextModelSnapshot.cs +++ b/src/Tgstation.Server.Host/Models/Migrations/MySqlDatabaseContextModelSnapshot.cs @@ -2,6 +2,7 @@ using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; +using Tgstation.Server.Host.Database; namespace Tgstation.Server.Host.Models.Migrations { diff --git a/src/Tgstation.Server.Host/Models/Migrations/SqlServerDatabaseContextModelSnapshot.cs b/src/Tgstation.Server.Host/Models/Migrations/SqlServerDatabaseContextModelSnapshot.cs index 9433a4bf44..57edfcf3a1 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/SqlServerDatabaseContextModelSnapshot.cs +++ b/src/Tgstation.Server.Host/Models/Migrations/SqlServerDatabaseContextModelSnapshot.cs @@ -4,6 +4,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Tgstation.Server.Host.Database; namespace Tgstation.Server.Host.Models.Migrations { diff --git a/src/Tgstation.Server.Host/Models/ReattachInformationBase.cs b/src/Tgstation.Server.Host/Models/ReattachInformationBase.cs index 115b0ae6fe..c057dd8d7b 100644 --- a/src/Tgstation.Server.Host/Models/ReattachInformationBase.cs +++ b/src/Tgstation.Server.Host/Models/ReattachInformationBase.cs @@ -23,7 +23,7 @@ namespace Tgstation.Server.Host.Models public int ProcessId { get; set; } /// - /// If the of the associated dmb is being used + /// If the of the associated dmb is being used /// public bool IsPrimary { get; set; } diff --git a/src/Tgstation.Server.Host/NativeMethods.cs b/src/Tgstation.Server.Host/NativeMethods.cs index 3686e79f9e..98a92c2eac 100644 --- a/src/Tgstation.Server.Host/NativeMethods.cs +++ b/src/Tgstation.Server.Host/NativeMethods.cs @@ -24,6 +24,14 @@ namespace Tgstation.Server.Host AllowUnprivilegedCreate = 2 } + /// + /// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686769(v=vs.85).aspx + /// + public enum ThreadAccess : int + { + SuspendResume = 0x0002, + } + /// /// See https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getwindowthreadprocessid /// @@ -70,5 +78,29 @@ namespace Tgstation.Server.Host /// [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] public static extern bool CreateSymbolicLink(string lpSymlinkFileName, string lpTargetFileName, CreateSymbolicLinkFlags dwFlags); + + /// + /// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms684335(v=vs.85).aspx + /// + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern IntPtr OpenThread(ThreadAccess dwDesiredAccess, bool bInheritHandle, uint dwThreadId); + + /// + /// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms724211(v=vs.85).aspx + /// + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern bool CloseHandle(IntPtr hObject); + + /// + /// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686345(v=vs.85).aspx + /// + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern uint SuspendThread(IntPtr hThread); + + /// + /// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms685086(v=vs.85).aspx + /// + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern uint ResumeThread(IntPtr hThread); } } diff --git a/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs b/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs index bfe70be61d..948a92fa55 100644 --- a/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs +++ b/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs @@ -3,6 +3,7 @@ using System; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Host.Database; using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Security diff --git a/src/Tgstation.Server.Host/Security/ClaimsInjector.cs b/src/Tgstation.Server.Host/Security/ClaimsInjector.cs index c3b67cb489..6894e2c682 100644 --- a/src/Tgstation.Server.Host/Security/ClaimsInjector.cs +++ b/src/Tgstation.Server.Host/Security/ClaimsInjector.cs @@ -9,7 +9,7 @@ using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api; using Tgstation.Server.Api.Rights; -using Tgstation.Server.Host.Models; +using Tgstation.Server.Host.Database; namespace Tgstation.Server.Host.Security { diff --git a/src/Tgstation.Server.Host/Security/IClaimsInjector.cs b/src/Tgstation.Server.Host/Security/IClaimsInjector.cs index 216b03b0f0..f5608a6b21 100644 --- a/src/Tgstation.Server.Host/Security/IClaimsInjector.cs +++ b/src/Tgstation.Server.Host/Security/IClaimsInjector.cs @@ -5,14 +5,14 @@ using System.Threading.Tasks; namespace Tgstation.Server.Host.Security { /// - /// For injecting s that can look for + /// For injecting s that can look for /// interface IClaimsInjector { /// - /// Setup the s for a given + /// Setup the s for a given /// - /// The containing the and of the request and the to add s to + /// The containing the and of the request and the to add s to /// The for the operation /// A representing the running operation Task InjectClaimsIntoContext(TokenValidatedContext tokenValidatedContext, CancellationToken cancellationToken); diff --git a/src/Tgstation.Server.Host/Security/ISystemIdentity.cs b/src/Tgstation.Server.Host/Security/ISystemIdentity.cs index ffa55706e5..9dc72788c8 100644 --- a/src/Tgstation.Server.Host/Security/ISystemIdentity.cs +++ b/src/Tgstation.Server.Host/Security/ISystemIdentity.cs @@ -1,12 +1,11 @@ using System; -using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; namespace Tgstation.Server.Host.Security { /// - /// Represents a user on the current + /// Represents a user on the current /// public interface ISystemIdentity : IDisposable { diff --git a/src/Tgstation.Server.Host/Security/TokenFactory.cs b/src/Tgstation.Server.Host/Security/TokenFactory.cs index 3023189b84..3c8047a443 100644 --- a/src/Tgstation.Server.Host/Security/TokenFactory.cs +++ b/src/Tgstation.Server.Host/Security/TokenFactory.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.System; namespace Tgstation.Server.Host.Security { diff --git a/src/Tgstation.Server.Host/ServerFactory.cs b/src/Tgstation.Server.Host/ServerFactory.cs index 57c46c1ae2..fae7f75146 100644 --- a/src/Tgstation.Server.Host/ServerFactory.cs +++ b/src/Tgstation.Server.Host/ServerFactory.cs @@ -6,6 +6,7 @@ using System; using System.IO; using Tgstation.Server.Host.Core; using Tgstation.Server.Host.IO; +using Tgstation.Server.Host.System; namespace Tgstation.Server.Host { diff --git a/src/Tgstation.Server.Host/Core/AssemblyInformationProvider.cs b/src/Tgstation.Server.Host/System/AssemblyInformationProvider.cs similarity index 93% rename from src/Tgstation.Server.Host/Core/AssemblyInformationProvider.cs rename to src/Tgstation.Server.Host/System/AssemblyInformationProvider.cs index 41fe7919ca..7e6ac8852c 100644 --- a/src/Tgstation.Server.Host/Core/AssemblyInformationProvider.cs +++ b/src/Tgstation.Server.Host/System/AssemblyInformationProvider.cs @@ -1,6 +1,6 @@ using System.Reflection; -namespace Tgstation.Server.Host.Core +namespace Tgstation.Server.Host.System { /// sealed class AssemblyInformationProvider : IAssemblyInformationProvider diff --git a/src/Tgstation.Server.Host/Core/IAssemblyInformationProvider.cs b/src/Tgstation.Server.Host/System/IAssemblyInformationProvider.cs similarity index 90% rename from src/Tgstation.Server.Host/Core/IAssemblyInformationProvider.cs rename to src/Tgstation.Server.Host/System/IAssemblyInformationProvider.cs index 8112deb276..8dbb06ee78 100644 --- a/src/Tgstation.Server.Host/Core/IAssemblyInformationProvider.cs +++ b/src/Tgstation.Server.Host/System/IAssemblyInformationProvider.cs @@ -1,6 +1,6 @@ using System.Reflection; -namespace Tgstation.Server.Host.Core +namespace Tgstation.Server.Host.System { /// /// For retrieving the 's location. diff --git a/src/Tgstation.Server.Host/Core/IPlatformIdentifier.cs b/src/Tgstation.Server.Host/System/IPlatformIdentifier.cs similarity index 89% rename from src/Tgstation.Server.Host/Core/IPlatformIdentifier.cs rename to src/Tgstation.Server.Host/System/IPlatformIdentifier.cs index 300ca29866..2036796994 100644 --- a/src/Tgstation.Server.Host/Core/IPlatformIdentifier.cs +++ b/src/Tgstation.Server.Host/System/IPlatformIdentifier.cs @@ -1,4 +1,4 @@ -namespace Tgstation.Server.Host.Core +namespace Tgstation.Server.Host.System { /// /// For identifying the current platform diff --git a/src/Tgstation.Server.Host/Core/IProcess.cs b/src/Tgstation.Server.Host/System/IProcess.cs similarity index 87% rename from src/Tgstation.Server.Host/Core/IProcess.cs rename to src/Tgstation.Server.Host/System/IProcess.cs index 740b09951f..655374fbfa 100644 --- a/src/Tgstation.Server.Host/Core/IProcess.cs +++ b/src/Tgstation.Server.Host/System/IProcess.cs @@ -1,10 +1,9 @@ -using System; -using System.Threading.Tasks; +using System.Threading.Tasks; -namespace Tgstation.Server.Host.Core +namespace Tgstation.Server.Host.System { /// - /// Abstraction over a + /// Abstraction over a /// interface IProcess : IProcessBase { diff --git a/src/Tgstation.Server.Host/Core/IProcessBase.cs b/src/Tgstation.Server.Host/System/IProcessBase.cs similarity index 50% rename from src/Tgstation.Server.Host/Core/IProcessBase.cs rename to src/Tgstation.Server.Host/System/IProcessBase.cs index cc287208c4..a9013d567a 100644 --- a/src/Tgstation.Server.Host/Core/IProcessBase.cs +++ b/src/Tgstation.Server.Host/System/IProcessBase.cs @@ -1,7 +1,7 @@ using System; using System.Threading.Tasks; -namespace Tgstation.Server.Host.Core +namespace Tgstation.Server.Host.System { /// /// Represents process lifetime @@ -14,8 +14,18 @@ namespace Tgstation.Server.Host.Core Task Lifetime { get; } /// - /// Set's the owned to + /// Set's the owned to /// void SetHighPriority(); + + /// + /// Suspends the process. + /// + void Suspend(); + + /// + /// Resumes the process. + /// + void Resume(); } } diff --git a/src/Tgstation.Server.Host/Core/IProcessExecutor.cs b/src/Tgstation.Server.Host/System/IProcessExecutor.cs similarity index 96% rename from src/Tgstation.Server.Host/Core/IProcessExecutor.cs rename to src/Tgstation.Server.Host/System/IProcessExecutor.cs index 57e81ce382..3d9a001b9e 100644 --- a/src/Tgstation.Server.Host/Core/IProcessExecutor.cs +++ b/src/Tgstation.Server.Host/System/IProcessExecutor.cs @@ -1,4 +1,4 @@ -namespace Tgstation.Server.Host.Core +namespace Tgstation.Server.Host.System { /// /// For launching ' diff --git a/src/Tgstation.Server.Host/Core/PlatformIdentifier.cs b/src/Tgstation.Server.Host/System/PlatformIdentifier.cs similarity index 92% rename from src/Tgstation.Server.Host/Core/PlatformIdentifier.cs rename to src/Tgstation.Server.Host/System/PlatformIdentifier.cs index df59c83356..c2ec1382aa 100644 --- a/src/Tgstation.Server.Host/Core/PlatformIdentifier.cs +++ b/src/Tgstation.Server.Host/System/PlatformIdentifier.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; -namespace Tgstation.Server.Host.Core +namespace Tgstation.Server.Host.System { /// sealed class PlatformIdentifier : IPlatformIdentifier diff --git a/src/Tgstation.Server.Host/Core/Process.cs b/src/Tgstation.Server.Host/System/Process.cs similarity index 61% rename from src/Tgstation.Server.Host/Core/Process.cs rename to src/Tgstation.Server.Host/System/Process.cs index 21856ed710..c064733d1f 100644 --- a/src/Tgstation.Server.Host/Core/Process.cs +++ b/src/Tgstation.Server.Host/System/Process.cs @@ -1,9 +1,11 @@ -using Microsoft.Extensions.Logging; +using BetterWin32Errors; +using Microsoft.Extensions.Logging; using System; +using System.Diagnostics; using System.Text; using System.Threading.Tasks; -namespace Tgstation.Server.Host.Core +namespace Tgstation.Server.Host.System { /// sealed class Process : IProcess @@ -17,7 +19,7 @@ namespace Tgstation.Server.Host.Core /// public Task Lifetime { get; } - readonly System.Diagnostics.Process handle; + readonly global::System.Diagnostics.Process handle; readonly StringBuilder outputStringBuilder; readonly StringBuilder errorStringBuilder; @@ -38,10 +40,16 @@ namespace Tgstation.Server.Host.Core /// The value of /// The value of /// If was NOT just created - public Process(System.Diagnostics.Process handle, Task lifetime, StringBuilder outputStringBuilder, StringBuilder errorStringBuilder, StringBuilder combinedStringBuilder, ILogger logger, bool preExisting) + public Process( + global::System.Diagnostics.Process handle, + Task lifetime, + StringBuilder outputStringBuilder, + StringBuilder errorStringBuilder, + StringBuilder combinedStringBuilder, + ILogger logger, + bool preExisting) { this.handle = handle ?? throw new ArgumentNullException(nameof(handle)); - Lifetime = lifetime ?? throw new ArgumentNullException(nameof(lifetime)); this.outputStringBuilder = outputStringBuilder; this.errorStringBuilder = errorStringBuilder; @@ -49,6 +57,8 @@ namespace Tgstation.Server.Host.Core this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + Lifetime = WrapLifetimeTask(lifetime ?? throw new ArgumentNullException(nameof(lifetime))); + Id = handle.Id; if (preExisting) @@ -72,6 +82,13 @@ namespace Tgstation.Server.Host.Core /// public void Dispose() => handle.Dispose(); + async Task WrapLifetimeTask(Task lifetimeTask) + { + var result = await lifetimeTask.ConfigureAwait(false); + logger.LogTrace("Process {0} ended with code {1}", Id, result); + return result; + } + /// public string GetCombinedOutput() { @@ -118,13 +135,65 @@ namespace Tgstation.Server.Host.Core { try { - handle.PriorityClass = System.Diagnostics.ProcessPriorityClass.AboveNormal; - logger.LogTrace("Set to above normal priority", handle.Id); + handle.PriorityClass = ProcessPriorityClass.AboveNormal; + logger.LogTrace("Set PID {0} to above normal priority", Id); } catch (Exception e) { logger.LogWarning("Unable to raise process priority! Exception: {0}", e); } } + + /// + public void Suspend() + { + try + { + foreach (ProcessThread thread in handle.Threads) + { + var pOpenThread = NativeMethods.OpenThread(NativeMethods.ThreadAccess.SuspendResume, false, (uint)thread.Id); + if (pOpenThread == IntPtr.Zero) + continue; + + if (NativeMethods.SuspendThread(pOpenThread) == UInt32.MaxValue) + throw new Win32Exception(); + + NativeMethods.CloseHandle(pOpenThread); + } + + logger.LogTrace("Suspended PID {0}", Id); + } + catch (Exception e) + { + logger.LogError(e, "Failed to suspend PID {0}!", Id); + throw; + } + } + + /// + public void Resume() + { + try + { + foreach (ProcessThread thread in handle.Threads) + { + var pOpenThread = NativeMethods.OpenThread(NativeMethods.ThreadAccess.SuspendResume, false, (uint)thread.Id); + if (pOpenThread == IntPtr.Zero) + continue; + + if (NativeMethods.ResumeThread(pOpenThread) == UInt32.MaxValue) + throw new Win32Exception(); + + NativeMethods.CloseHandle(pOpenThread); + } + + logger.LogTrace("Resumed PID {0}", Id); + } + catch (Exception e) + { + logger.LogError(e, "Failed to resume PID {0}!", Id); + throw; + } + } } } diff --git a/src/Tgstation.Server.Host/Core/ProcessExecutor.cs b/src/Tgstation.Server.Host/System/ProcessExecutor.cs similarity index 90% rename from src/Tgstation.Server.Host/Core/ProcessExecutor.cs rename to src/Tgstation.Server.Host/System/ProcessExecutor.cs index 7c1d5274eb..00fe0f2c84 100644 --- a/src/Tgstation.Server.Host/Core/ProcessExecutor.cs +++ b/src/Tgstation.Server.Host/System/ProcessExecutor.cs @@ -3,7 +3,7 @@ using System; using System.Text; using System.Threading.Tasks; -namespace Tgstation.Server.Host.Core +namespace Tgstation.Server.Host.System { /// sealed class ProcessExecutor : IProcessExecutor @@ -21,9 +21,9 @@ namespace Tgstation.Server.Host.Core /// /// Create a resulting in the exit code of a given /// - /// The to attach the for + /// The to attach the for /// A new resulting in the exit code of - static Task AttachExitHandler(System.Diagnostics.Process handle) + static Task AttachExitHandler(global::System.Diagnostics.Process handle) { handle.EnableRaisingEvents = true; var tcs = new TaskCompletionSource(); @@ -59,10 +59,10 @@ namespace Tgstation.Server.Host.Core public IProcess GetProcess(int id) { logger.LogDebug("Attaching to process {0}...", id); - System.Diagnostics.Process handle; + global::System.Diagnostics.Process handle; try { - handle = System.Diagnostics.Process.GetProcessById(id); + handle = global::System.Diagnostics.Process.GetProcessById(id); } catch(Exception e) { @@ -85,7 +85,7 @@ namespace Tgstation.Server.Host.Core public IProcess LaunchProcess(string fileName, string workingDirectory, string arguments, bool readOutput, bool readError, bool noShellExecute) { logger.LogDebug("Launching process in {0}: {1} {2}", workingDirectory, fileName, arguments); - var handle = new System.Diagnostics.Process(); + var handle = new global::System.Diagnostics.Process(); try { handle.StartInfo.FileName = fileName; diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index 79a66fa8d5..9e18ee3f8c 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -22,7 +22,6 @@ ClientApp/node_modules - ClientApp/node_modules/.install-stamp @@ -31,15 +30,6 @@ - - - - - - - - - @@ -56,7 +46,7 @@ - + @@ -95,6 +85,26 @@ + + + + + + + + + + + + + + + + + + + + PreserveNewest @@ -104,18 +114,6 @@ - - - - - - - - - - - - PreserveNewest @@ -123,6 +121,6 @@ - + diff --git a/src/Tgstation.Server.Host/appsettings.json b/src/Tgstation.Server.Host/appsettings.json index 5c06b38672..7f9191b101 100644 --- a/src/Tgstation.Server.Host/appsettings.json +++ b/src/Tgstation.Server.Host/appsettings.json @@ -5,7 +5,8 @@ "SetupWizardMode": "AutoDetect", "ByondTopicTimeout": 5000, "RestartTimeout": 10000, - "UseExperimentalWatchdog" : false + "UseExperimentalWatchdog": false, + "UseBasicWatchdogOnWindows": false }, "FileLogging": { "Directory": null, //use the default path diff --git a/tests/Tgstation.Server.Host.Tests/Components/Watchdog/TestExperimentalWatchdog.cs b/tests/Tgstation.Server.Host.Tests/Components/Watchdog/TestExperimentalWatchdog.cs index 28ad98600b..fbff4d209e 100644 --- a/tests/Tgstation.Server.Host.Tests/Components/Watchdog/TestExperimentalWatchdog.cs +++ b/tests/Tgstation.Server.Host.Tests/Components/Watchdog/TestExperimentalWatchdog.cs @@ -8,8 +8,10 @@ using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Components.Chat; -using Tgstation.Server.Host.Components.Compiler; +using Tgstation.Server.Host.Components.Deployment; using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Database; +using Tgstation.Server.Host.Jobs; namespace Tgstation.Server.Host.Components.Watchdog.Tests { @@ -101,6 +103,7 @@ namespace Tgstation.Server.Host.Components.Watchdog.Tests var infiniteTask = new TaskCompletionSource().Task; mockDmbFactory.SetupGet(x => x.OnNewerDmb).Returns(infiniteTask); + mockDmbFactory.SetupGet(x => x.DmbAvailable).Returns(true).Verifiable(); mockDmbFactory.Setup(x => x.LockNextDmb(2)).Returns(mDmbP).Verifiable(); var sessionsToVerify = new List>(); diff --git a/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs b/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs index d19f8c3aa8..2170d75482 100644 --- a/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs +++ b/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs @@ -10,7 +10,9 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Host.Configuration; +using Tgstation.Server.Host.IO; using Tgstation.Server.Host.Security; +using Tgstation.Server.Host.System; namespace Tgstation.Server.Host.Core.Tests { @@ -23,18 +25,19 @@ namespace Tgstation.Server.Host.Core.Tests [TestMethod] public void TestMethodThrows() { - Assert.ThrowsException(() => new Application(null, null, null)); + Assert.ThrowsException(() => new Application(null, null, null, null)); var mockConfiguration = new Mock(); - Assert.ThrowsException(() => new Application(mockConfiguration.Object, null, null)); + Assert.ThrowsException(() => new Application(mockConfiguration.Object, null, null, null)); var mockAssemblyInfo = new Mock(); mockAssemblyInfo.SetupGet(x => x.Name).Returns(typeof(Application).Assembly.GetName()); - Assert.ThrowsException(() => new Application(mockConfiguration.Object, mockAssemblyInfo.Object, null)); + Assert.ThrowsException(() => new Application(mockConfiguration.Object, mockAssemblyInfo.Object, null, null)); var mockHostingEnvironment = new Mock(); + Assert.ThrowsException(() => new Application(mockConfiguration.Object, mockAssemblyInfo.Object, mockHostingEnvironment.Object, null)); - var app = new Application(mockConfiguration.Object, mockAssemblyInfo.Object, mockHostingEnvironment.Object); + var app = new Application(mockConfiguration.Object, mockAssemblyInfo.Object, mockHostingEnvironment.Object, Mock.Of()); Assert.ThrowsException(() => app.ConfigureServices(null)); Assert.ThrowsException(() => app.Configure(null, null, null, null, null)); @@ -78,7 +81,7 @@ namespace Tgstation.Server.Host.Core.Tests mockAssemblyInfo.SetupGet(x => x.Name).Returns(typeof(Application).Assembly.GetName()); var mockHostingEnvironment = new Mock(); - var app = new Application(mockConfiguration.Object, mockAssemblyInfo.Object, mockHostingEnvironment.Object); + var app = new Application(mockConfiguration.Object, mockAssemblyInfo.Object, mockHostingEnvironment.Object, Mock.Of()); var mockOptions = new Mock>(); mockOptions.SetupGet(x => x.Value).Returns(new GeneralConfiguration diff --git a/tests/Tgstation.Server.Host.Tests/Core/TestSetupWizard.cs b/tests/Tgstation.Server.Host.Tests/Core/TestSetupWizard.cs index 5104fd0696..55e06d42ef 100644 --- a/tests/Tgstation.Server.Host.Tests/Core/TestSetupWizard.cs +++ b/tests/Tgstation.Server.Host.Tests/Core/TestSetupWizard.cs @@ -12,7 +12,9 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Host.Configuration; +using Tgstation.Server.Host.Database; using Tgstation.Server.Host.IO; +using Tgstation.Server.Host.System; namespace Tgstation.Server.Host.Core.Tests { @@ -31,7 +33,7 @@ namespace Tgstation.Server.Host.Core.Tests Assert.ThrowsException(() => new SetupWizard(mockIOManager.Object, mockConsole.Object, mockHostingEnvironment.Object, null, null, null, null, null, null)); var mockApplication = new Mock(); Assert.ThrowsException(() => new SetupWizard(mockIOManager.Object, mockConsole.Object, mockHostingEnvironment.Object, mockApplication.Object, null, null, null, null, null)); - var mockDBConnectionFactory = new Mock(); + var mockDBConnectionFactory = new Mock(); Assert.ThrowsException(() => new SetupWizard(mockIOManager.Object, mockConsole.Object, mockHostingEnvironment.Object, mockApplication.Object, mockDBConnectionFactory.Object, null, null, null, null)); var mockPlatformIdentifier = new Mock(); Assert.ThrowsException(() => new SetupWizard(mockIOManager.Object, mockConsole.Object, mockHostingEnvironment.Object, mockApplication.Object, mockDBConnectionFactory.Object, mockPlatformIdentifier.Object, null, null, null)); @@ -48,7 +50,7 @@ namespace Tgstation.Server.Host.Core.Tests var mockConsole = new Mock(); var mockHostingEnvironment = new Mock(); var mockApplication = new Mock(); - var mockDBConnectionFactory = new Mock(); + var mockDBConnectionFactory = new Mock(); var mockLogger = new Mock>(); var mockGeneralConfigurationOptions = new Mock>(); var mockPlatformIdentifier = new Mock(); @@ -142,6 +144,7 @@ namespace Tgstation.Server.Host.Core.Tests "-27", "5000", "fake token", + "y", //logging config "no", //cp config @@ -163,6 +166,8 @@ namespace Tgstation.Server.Host.Core.Tests String.Empty, String.Empty, String.Empty, + "n", + "y", //logging config "y", "not actually verified because lol mocks /../!@#$%^&*()/..///.", @@ -186,6 +191,7 @@ namespace Tgstation.Server.Host.Core.Tests String.Empty, String.Empty, "y", + "y", "will faile", String.Empty, String.Empty, diff --git a/tests/Tgstation.Server.Host.Tests/Core/TestDBConnectionFactory.cs b/tests/Tgstation.Server.Host.Tests/Database/TestDatabaseConnectionFactory.cs similarity index 81% rename from tests/Tgstation.Server.Host.Tests/Core/TestDBConnectionFactory.cs rename to tests/Tgstation.Server.Host.Tests/Database/TestDatabaseConnectionFactory.cs index 39abca29bb..c95662427b 100644 --- a/tests/Tgstation.Server.Host.Tests/Core/TestDBConnectionFactory.cs +++ b/tests/Tgstation.Server.Host.Tests/Database/TestDatabaseConnectionFactory.cs @@ -4,15 +4,15 @@ using System; using System.Data.SqlClient; using Tgstation.Server.Host.Configuration; -namespace Tgstation.Server.Host.Core.Tests +namespace Tgstation.Server.Host.Database.Tests { [TestClass] - public sealed class TestDBConnectionFactory + public sealed class TestDatabaseConnectionFactory { [TestMethod] public void TestBadParameters() { - var factory = new DBConnectionFactory(); + var factory = new DatabaseConnectionFactory(); Assert.ThrowsException(() => factory.CreateConnection(null, default)); Assert.ThrowsException(() => factory.CreateConnection(String.Empty, (DatabaseType)42)); } @@ -20,7 +20,7 @@ namespace Tgstation.Server.Host.Core.Tests [TestMethod] public void TestWorks() { - var factory = new DBConnectionFactory(); + var factory = new DatabaseConnectionFactory(); Assert.IsInstanceOfType(factory.CreateConnection(String.Empty, DatabaseType.MariaDB), typeof(MySqlConnection)); Assert.IsInstanceOfType(factory.CreateConnection(String.Empty, DatabaseType.MySql), typeof(MySqlConnection)); Assert.IsInstanceOfType(factory.CreateConnection(String.Empty, DatabaseType.SqlServer), typeof(SqlConnection)); diff --git a/tests/Tgstation.Server.Host.Tests/Core/TestDatabaseContextFactory.cs b/tests/Tgstation.Server.Host.Tests/Database/TestDatabaseContextFactory.cs similarity index 97% rename from tests/Tgstation.Server.Host.Tests/Core/TestDatabaseContextFactory.cs rename to tests/Tgstation.Server.Host.Tests/Database/TestDatabaseContextFactory.cs index 7585c8a300..14e96f8316 100644 --- a/tests/Tgstation.Server.Host.Tests/Core/TestDatabaseContextFactory.cs +++ b/tests/Tgstation.Server.Host.Tests/Database/TestDatabaseContextFactory.cs @@ -5,7 +5,7 @@ using System; using System.Threading.Tasks; using Tgstation.Server.Host.Models; -namespace Tgstation.Server.Host.Core.Tests +namespace Tgstation.Server.Host.Database.Tests { [TestClass] public sealed class TestDatabaseContextFactory diff --git a/tests/Tgstation.Server.Host.Tests/Core/TestServiceCollectionExtensions.cs b/tests/Tgstation.Server.Host.Tests/Extensions/TestServiceCollectionExtensions.cs similarity index 96% rename from tests/Tgstation.Server.Host.Tests/Core/TestServiceCollectionExtensions.cs rename to tests/Tgstation.Server.Host.Tests/Extensions/TestServiceCollectionExtensions.cs index 3cb14f8dd8..67004ad997 100644 --- a/tests/Tgstation.Server.Host.Tests/Core/TestServiceCollectionExtensions.cs +++ b/tests/Tgstation.Server.Host.Tests/Extensions/TestServiceCollectionExtensions.cs @@ -4,7 +4,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using System; -namespace Tgstation.Server.Host.Core.Tests +namespace Tgstation.Server.Host.Extensions.Tests { [TestClass] public sealed class TestServiceCollectionExtensions diff --git a/tests/Tgstation.Server.Host.Tests/Core/TestJobException.cs b/tests/Tgstation.Server.Host.Tests/Jobs/TestJobException.cs similarity index 87% rename from tests/Tgstation.Server.Host.Tests/Core/TestJobException.cs rename to tests/Tgstation.Server.Host.Tests/Jobs/TestJobException.cs index 9f5abc5235..fa017d17ab 100644 --- a/tests/Tgstation.Server.Host.Tests/Core/TestJobException.cs +++ b/tests/Tgstation.Server.Host.Tests/Jobs/TestJobException.cs @@ -1,7 +1,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using System; -namespace Tgstation.Server.Host.Core.Tests +namespace Tgstation.Server.Host.Jobs.Tests { [TestClass] public sealed class TestJobException diff --git a/tests/Tgstation.Server.Host.Tests/Core/TestJobHandler.cs b/tests/Tgstation.Server.Host.Tests/Jobs/TestJobHandler.cs similarity index 97% rename from tests/Tgstation.Server.Host.Tests/Core/TestJobHandler.cs rename to tests/Tgstation.Server.Host.Tests/Jobs/TestJobHandler.cs index 4aad145fa5..3326de98fd 100644 --- a/tests/Tgstation.Server.Host.Tests/Core/TestJobHandler.cs +++ b/tests/Tgstation.Server.Host.Tests/Jobs/TestJobHandler.cs @@ -3,7 +3,7 @@ using System; using System.Threading; using System.Threading.Tasks; -namespace Tgstation.Server.Host.Core.Tests +namespace Tgstation.Server.Host.Jobs.Tests { [TestClass] public sealed class TestJobHandler diff --git a/tests/Tgstation.Server.Host.Tests/Core/TestPlatformIdentifier.cs b/tests/Tgstation.Server.Host.Tests/System/TestPlatformIdentifier.cs similarity index 93% rename from tests/Tgstation.Server.Host.Tests/Core/TestPlatformIdentifier.cs rename to tests/Tgstation.Server.Host.Tests/System/TestPlatformIdentifier.cs index 26d7459ed6..a21a17cbbc 100644 --- a/tests/Tgstation.Server.Host.Tests/Core/TestPlatformIdentifier.cs +++ b/tests/Tgstation.Server.Host.Tests/System/TestPlatformIdentifier.cs @@ -1,7 +1,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Runtime.InteropServices; -namespace Tgstation.Server.Host.Core.Tests +namespace Tgstation.Server.Host.System.Tests { [TestClass] public sealed class TestPlatformIdentifier diff --git a/tests/Tgstation.Server.Host.Tests/TestServerFactory.cs b/tests/Tgstation.Server.Host.Tests/TestServerFactory.cs index eede3ff41c..ed002bf91f 100644 --- a/tests/Tgstation.Server.Host.Tests/TestServerFactory.cs +++ b/tests/Tgstation.Server.Host.Tests/TestServerFactory.cs @@ -3,6 +3,7 @@ using Moq; using System; using Tgstation.Server.Host.Core; using Tgstation.Server.Host.IO; +using Tgstation.Server.Host.System; namespace Tgstation.Server.Host.Tests { diff --git a/tgstation-server.sln b/tgstation-server.sln index 74735abe47..dc91edb7bd 100644 --- a/tgstation-server.sln +++ b/tgstation-server.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29613.14 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.136 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{DA32568D-1D8D-4A4C-9943-BFD3CE796B3F}" ProjectSection(SolutionItems) = preProject @@ -26,6 +26,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{6FF654E6 build\Dockerfile = build\Dockerfile build\install_byond.sh = build\install_byond.sh build\InstallCodeCoverage.ps1 = build\InstallCodeCoverage.ps1 + build\OpenApiValidationSettings.json = build\OpenApiValidationSettings.json build\prep_deployment.ps1 = build\prep_deployment.ps1 build\stylecop.json = build\stylecop.json build\test_core.sh = build\test_core.sh