From 74aea888470c206c62992ff601e0405b8be0c0e7 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 19 Aug 2018 23:13:26 -0400 Subject: [PATCH 01/23] Remove undefs because who can be assed to give a shit --- src/DMAPI/tgs/v4/api.dm | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/DMAPI/tgs/v4/api.dm b/src/DMAPI/tgs/v4/api.dm index 1736130b67..b665dca9cf 100644 --- a/src/DMAPI/tgs/v4/api.dm +++ b/src/DMAPI/tgs/v4/api.dm @@ -294,21 +294,6 @@ channel.is_private_channel = channel_json["isPrivateChannel"] || FALSE return channel -#undef TGS4_TOPIC_COMMAND -#undef TGS4_TOPIC_TOKEN -#undef TGS4_TOPIC_SUCCESS -#undef TGS4_TOPIC_SWAP -#undef TGS4_TOPIC_SWAP_DELAYED -#undef TGS4_TOPIC_CHAT_COMMAND -#undef TGS4_TOPIC_EVENT - -#undef TGS4_COMM_SERVER_PRIMED -#undef TGS4_COMM_SERVER_REBOOT -#undef TGS4_COMM_END_PROCESS -#undef TGS4_COMM_CHAT - -#undef TGS4_COMM_VALIDATE - /* The MIT License From 506882db8bbc1d6844b89c95957bad19c1889621 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 20 Aug 2018 00:11:43 -0400 Subject: [PATCH 02/23] Sighs --- src/DMAPI/tgs/v4/api.dm | 4 ++-- src/DMAPI/tgs/v4/commands.dm | 10 +++++++--- .../Components/Chat/JsonTrackingContext.cs | 8 +++++++- .../Components/Compiler/DreamMaker.cs | 2 +- .../Components/Interop/ChatCommand.cs | 11 +++++++++++ .../Components/Watchdog/Watchdog.cs | 18 +++++++++++++++++- v4_prototype_TODO.txt | 4 +++- 7 files changed, 48 insertions(+), 9 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/Interop/ChatCommand.cs diff --git a/src/DMAPI/tgs/v4/api.dm b/src/DMAPI/tgs/v4/api.dm index b665dca9cf..c24691e150 100644 --- a/src/DMAPI/tgs/v4/api.dm +++ b/src/DMAPI/tgs/v4/api.dm @@ -135,8 +135,8 @@ switch(command) if(TGS4_TOPIC_CHAT_COMMAND) var/result = HandleCustomCommand(params[TGS4_PARAMETER_DATA]) - if(!result) - return json_encode(list("error" = "Error running chat command!")) + if(result == null) + result = "Error running chat command!" return result if(TGS4_TOPIC_EVENT) intercepted_message_queue = list() diff --git a/src/DMAPI/tgs/v4/commands.dm b/src/DMAPI/tgs/v4/commands.dm index 88ee61e882..1d9951bc04 100644 --- a/src/DMAPI/tgs/v4/commands.dm +++ b/src/DMAPI/tgs/v4/commands.dm @@ -12,7 +12,7 @@ var/datum/other = custom_commands[command_name] TGS_ERROR_LOG("Custom commands [other.type] and [I] have the same name (\"[command_name]\"), only [other.type] will be available!") continue - results[command_name] = list("help_text" = stc.help_text, "admin_only" = stc.admin_only) + results += list(list("name" = command_name, "help_text" = stc.help_text, "admin_only" = stc.admin_only)) custom_commands[command_name] = stc var/commands_file = chat_commands_json_path @@ -33,8 +33,12 @@ u.channel = DecodeChannel(user["channel"]) var/datum/tgs_chat_command/sc = custom_commands[command] - var/result = sc.Run(u, params) - return json_encode(list("result" = result)) + if(sc) + var/result = sc.Run(u, params) + if(result == null) + result = "" + return result + return "Unknown command: [command]!" /* diff --git a/src/Tgstation.Server.Host/Components/Chat/JsonTrackingContext.cs b/src/Tgstation.Server.Host/Components/Chat/JsonTrackingContext.cs index c7482f38ed..e8dc25cb12 100644 --- a/src/Tgstation.Server.Host/Components/Chat/JsonTrackingContext.cs +++ b/src/Tgstation.Server.Host/Components/Chat/JsonTrackingContext.cs @@ -44,7 +44,13 @@ namespace Tgstation.Server.Host.Components.Chat { var resultBytes = await ioManager.ReadAllBytes(commandsPath, cancellationToken).ConfigureAwait(false); var resultJson = Encoding.UTF8.GetString(resultBytes); - var result = JsonConvert.DeserializeObject>(resultJson); + var result = JsonConvert.DeserializeObject>(resultJson, new JsonSerializerSettings + { + ContractResolver = new DefaultContractResolver + { + NamingStrategy = new SnakeCaseNamingStrategy() + } + }); foreach (var I in result) I.SetHandler(customCommandHandler); return result; diff --git a/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs b/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs index c88bc67e91..514b574631 100644 --- a/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs @@ -364,7 +364,7 @@ namespace Tgstation.Server.Host.Components.Compiler { //server never validated or compile failed await eventConsumer.HandleEvent(EventType.CompileFailure, new List { resolvedGameDirectory, exitCode == 0 ? "1" : "0" }, cancellationToken).ConfigureAwait(false); - throw new JobException(exitCode == 0 ? "Validation of the TGS api failed!" : String.Format(CultureInfo.InvariantCulture, "DM exited with a non-zero code: {0}{1}", exitCode, job.Output)); + throw new JobException(exitCode == 0 ? "Validation of the TGS api failed!" : String.Format(CultureInfo.InvariantCulture, "DM exited with a non-zero code: {0}{1}{2}", exitCode, Environment.NewLine, job.Output)); } logger.LogTrace("Running post compile event..."); diff --git a/src/Tgstation.Server.Host/Components/Interop/ChatCommand.cs b/src/Tgstation.Server.Host/Components/Interop/ChatCommand.cs new file mode 100644 index 0000000000..1c49476b35 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Interop/ChatCommand.cs @@ -0,0 +1,11 @@ +using Tgstation.Server.Host.Components.Chat; + +namespace Tgstation.Server.Host.Components.Interop +{ + sealed class ChatCommand + { + public string Command { get; set; } + public string Parameters { get; set; } + public User User { get; set; } + } +} diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index dcda48d43c..bbd5b481bc 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -1,6 +1,7 @@ using Byond.TopicSender; using Microsoft.Extensions.Logging; using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; using System; using System.Collections.Generic; using System.Globalization; @@ -858,7 +859,22 @@ namespace Tgstation.Server.Host.Components.Watchdog if (!Running) return "ERROR: Server offline!"; - var command = String.Format(CultureInfo.InvariantCulture, "{0}&{1}={2}", byondTopicSender.SanitizeString(Constants.DMTopicChatCommand), byondTopicSender.SanitizeString(Constants.DMParameterData), byondTopicSender.SanitizeString(JsonConvert.SerializeObject(arguments))); + var commandObject = new ChatCommand + { + Command = commandName, + Parameters = arguments, + User = sender + }; + + var json = JsonConvert.SerializeObject(arguments, new JsonSerializerSettings + { + ContractResolver = new DefaultContractResolver + { + NamingStrategy = new CamelCaseNamingStrategy() + } + }); + + var command = String.Format(CultureInfo.InvariantCulture, "{0}&{1}={2}", byondTopicSender.SanitizeString(Constants.DMTopicChatCommand), byondTopicSender.SanitizeString(Constants.DMParameterData), byondTopicSender.SanitizeString(json)); var activeServer = AlphaIsActive ? alphaServer : bravoServer; return await activeServer.SendCommand(command, cancellationToken).ConfigureAwait(false) ?? "ERROR: Bad topic exchange!"; diff --git a/v4_prototype_TODO.txt b/v4_prototype_TODO.txt index 641f358e0e..3b97a1ae4d 100644 --- a/v4_prototype_TODO.txt +++ b/v4_prototype_TODO.txt @@ -3,4 +3,6 @@ Verify the byond cache folder location on linux Test watchdog Only show user name and ID when serializing to API -In fact remove IApiConvertable<> altogether, it's not required by anything \ No newline at end of file +In fact remove IApiConvertable<> altogether, it's not required by anything + +Chat channel tagging From ea5527d091c1d79cb29b4e85395e532511009cc7 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 20 Aug 2018 09:41:37 -0400 Subject: [PATCH 03/23] Update package versions --- src/Tgstation.Server.Api/Tgstation.Server.Api.csproj | 2 +- src/Tgstation.Server.Client/Tgstation.Server.Client.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj index 7eed75c01d..9f9426a642 100644 --- a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj +++ b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj @@ -17,7 +17,7 @@ 4.0.0.0 json web api tgstation-server tgstation ss13 byond Prototype release - 4.0.0.0-preview1 + 4.0.0.0-preview2 diff --git a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj index 9701d04cd4..d0957a24a9 100644 --- a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj +++ b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj @@ -3,7 +3,7 @@ netstandard2.0 Full - 4.0.0.0-preview1 + 4.0.0.0-preview2 true Cyberboss /tg/station 13 From dcb1e7ef0b90b80e7e8d51f18f3dfeeaddec23d4 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 20 Aug 2018 11:09:01 -0400 Subject: [PATCH 04/23] Fix catching the wrong exception type --- src/Tgstation.Server.Client/ApiClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Client/ApiClient.cs b/src/Tgstation.Server.Client/ApiClient.cs index c9e16bdca2..ad507b4d7f 100644 --- a/src/Tgstation.Server.Client/ApiClient.cs +++ b/src/Tgstation.Server.Client/ApiClient.cs @@ -102,7 +102,7 @@ namespace Tgstation.Server.Client //check if json serializes to an error message errorMessage = JsonConvert.DeserializeObject(json, serializerSettings); } - catch (JsonSerializationException) { } + catch (JsonException) { } switch (response.StatusCode) { From 88fb2596e00ee156ead29966076260914caf0658 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 20 Aug 2018 11:09:20 -0400 Subject: [PATCH 05/23] Make ServerFactory public --- src/Tgstation.Server.Host/ServerFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/ServerFactory.cs b/src/Tgstation.Server.Host/ServerFactory.cs index 61f2984323..b8b34e0553 100644 --- a/src/Tgstation.Server.Host/ServerFactory.cs +++ b/src/Tgstation.Server.Host/ServerFactory.cs @@ -3,7 +3,7 @@ namespace Tgstation.Server.Host { /// - sealed class ServerFactory : IServerFactory + public sealed class ServerFactory : IServerFactory { /// public IServer CreateServer(string[] args, string updatePath) => new Server(WebHost.CreateDefaultBuilder(args), updatePath); From d5a8f6499d1227d888b89a68d3f2b3e04109f3c5 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 20 Aug 2018 11:16:54 -0400 Subject: [PATCH 06/23] Move default user definitions to the API --- src/Tgstation.Server.Api/Models/User.cs | 10 ++++++++++ .../Models/DatabaseSeeder.cs | 20 +++++-------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Tgstation.Server.Api/Models/User.cs b/src/Tgstation.Server.Api/Models/User.cs index a18856a366..1431dd2db9 100644 --- a/src/Tgstation.Server.Api/Models/User.cs +++ b/src/Tgstation.Server.Api/Models/User.cs @@ -3,6 +3,16 @@ /// public class User : Internal.User { + /// + /// The name of the default admin user + /// + public const string AdminName = "Admin"; + + /// + /// The default admin password + /// + public const string DefaultAdminPassword = "ISolemlySwearToDeleteTheDataDirectory"; + /// /// The who created this /// diff --git a/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs b/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs index 4603c3e09d..5076d67cb8 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs @@ -11,16 +11,6 @@ namespace Tgstation.Server.Host.Models /// sealed class DatabaseSeeder : IDatabaseSeeder { - /// - /// The name of the default admin user - /// - const string AdminName = "Admin"; - - /// - /// The default admin password - /// - const string DefaultAdminPassword = "ISolemlySwearToDeleteTheDataDirectory"; - /// /// The for the /// @@ -43,11 +33,11 @@ namespace Tgstation.Server.Host.Models AdministrationRights = (AdministrationRights)~0U, CreatedAt = DateTimeOffset.Now, InstanceManagerRights = (InstanceManagerRights)~0U, - Name = AdminName, - CanonicalName = AdminName.ToUpperInvariant(), + Name = Api.Models.User.AdminName, + CanonicalName = Api.Models.User.AdminName.ToUpperInvariant(), Enabled = true, }; - cryptographySuite.SetUserPassword(admin, DefaultAdminPassword); + cryptographySuite.SetUserPassword(admin, Api.Models.User.DefaultAdminPassword); databaseContext.Users.Add(admin); } @@ -61,13 +51,13 @@ namespace Tgstation.Server.Host.Models /// public async Task ResetAdminPassword(IDatabaseContext databaseContext, CancellationToken cancellationToken) { - var admin = await databaseContext.Users.Where(x => x.CanonicalName == AdminName.ToUpperInvariant()).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); + var admin = await databaseContext.Users.Where(x => x.CanonicalName == Api.Models.User.AdminName.ToUpperInvariant()).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); if (admin == default) SeedAdminUser(databaseContext); else { admin.Enabled = true; - cryptographySuite.SetUserPassword(admin, DefaultAdminPassword); + cryptographySuite.SetUserPassword(admin, Api.Models.User.DefaultAdminPassword); } await databaseContext.Save(cancellationToken).ConfigureAwait(false); From 24477a8012bbeea16e2d14e151c025bdf8566166 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 20 Aug 2018 11:26:35 -0400 Subject: [PATCH 07/23] Fix API conflict exception not passing an ErrorMessage --- src/Tgstation.Server.Client/ApiConflictException.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Client/ApiConflictException.cs b/src/Tgstation.Server.Client/ApiConflictException.cs index ba3eaa90c6..189fd92009 100644 --- a/src/Tgstation.Server.Client/ApiConflictException.cs +++ b/src/Tgstation.Server.Client/ApiConflictException.cs @@ -14,7 +14,11 @@ namespace Tgstation.Server.Client /// /// The for the /// The for the - public ApiConflictException(ErrorMessage errorMessage, HttpStatusCode statusCode) : base(errorMessage, statusCode) { } + public ApiConflictException(ErrorMessage errorMessage, HttpStatusCode statusCode) : base(errorMessage ?? new ErrorMessage + { + Message = "An unknown API error occurred!", + SeverApiVersion = null + }, statusCode) { } /// /// Construct an From ef0a6978fdfa20315d35c2f7e88fdbb81716ce47 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 20 Aug 2018 11:33:19 -0400 Subject: [PATCH 08/23] Fix sending incorrect Content-Type in client --- src/Tgstation.Server.Client/ApiClient.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Client/ApiClient.cs b/src/Tgstation.Server.Client/ApiClient.cs index ad507b4d7f..d475d976f6 100644 --- a/src/Tgstation.Server.Client/ApiClient.cs +++ b/src/Tgstation.Server.Client/ApiClient.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; +using System.Text; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api; @@ -83,7 +84,7 @@ namespace Tgstation.Server.Client }; if (body != null) - message.Content = new StringContent(JsonConvert.SerializeObject(body, serializerSettings)); + message.Content = new StringContent(JsonConvert.SerializeObject(body, serializerSettings), Encoding.UTF8, ApiHeaders.ApplicationJson); Headers.SetRequestHeaders(message.Headers, instanceId); From e305dc808091293e2dfccdf2fe1500f2956f1fe1 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 20 Aug 2018 11:39:52 -0400 Subject: [PATCH 09/23] Fix setting the incorrect API version header --- src/Tgstation.Server.Api/ApiHeaders.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Api/ApiHeaders.cs b/src/Tgstation.Server.Api/ApiHeaders.cs index 8208b922c8..b62c1a3ae6 100644 --- a/src/Tgstation.Server.Api/ApiHeaders.cs +++ b/src/Tgstation.Server.Api/ApiHeaders.cs @@ -144,7 +144,7 @@ namespace Tgstation.Server.Api //make sure the api header matches ours if (!requestHeaders.Headers.TryGetValue(ApiVersionHeader, out var apiUserAgentHeaderValues) || !ProductInfoHeaderValue.TryParse(apiUserAgentHeaderValues.FirstOrDefault(), out var apiUserAgent) || apiUserAgent.Product.Name != assemblyName.Name) - throw new InvalidOperationException("Missing API user agent!"); + throw new InvalidOperationException("Missing API version!"); if (!Version.TryParse(apiUserAgent.Product.Version, out var apiVersion)) throw new InvalidOperationException("Malformed API version!"); @@ -234,7 +234,7 @@ namespace Tgstation.Server.Api headers.Add(usernameHeader, Username); } headers.UserAgent.Add(new ProductInfoHeaderValue(UserAgent)); - headers.Add(ApiVersionHeader, ApiVersion.ToString()); + headers.Add(ApiVersionHeader, new ProductHeaderValue(assemblyName.Name, ApiVersion.ToString()).ToString()); instanceId = instanceId ?? InstanceId; if (instanceId.HasValue) headers.Add(instanceIdHeader, instanceId.ToString()); From 9625567bc680888f6e12c360e6714e841216886a Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 20 Aug 2018 12:08:37 -0400 Subject: [PATCH 10/23] Home controller now returns a new ServerInformation object --- docs/API.dox | 8 ++++---- src/Tgstation.Server.Api/ApiHeaders.cs | 15 +++++++------- .../Models/ErrorMessage.cs | 2 +- .../Models/ServerInformation.cs | 20 +++++++++++++++++++ .../Controllers/HomeController.cs | 6 +++++- 5 files changed, 38 insertions(+), 13 deletions(-) create mode 100644 src/Tgstation.Server.Api/Models/ServerInformation.cs diff --git a/docs/API.dox b/docs/API.dox index 18c5ec8258..a7be584e94 100644 --- a/docs/API.dox +++ b/docs/API.dox @@ -141,11 +141,11 @@ Users with the permission to modify @ref Tgstation.Server.Api.Models.Instance ob @section api_ver Version -The version of TGS running can be retireved with this request +The versions of the TGS host can be retireved with this request -GET "/" => Version +GET "/" => @ref Tgstation.Server.Api.Models.ServerInformation -The Version model is based on the C# one and looks like this: +The Version model fields are based on the C# one and looks like this: @code{.json} { @@ -156,7 +156,7 @@ The Version model is based on the C# one and looks like this: } @endcode -Other fields may be present but should be ignored. See a description of these version numbers here. +Other fields may be present in the Version model but should be ignored. See a description of these version numbers here. @section api_admin Server-wide Administrative Actions diff --git a/src/Tgstation.Server.Api/ApiHeaders.cs b/src/Tgstation.Server.Api/ApiHeaders.cs index b62c1a3ae6..c4bf1bd255 100644 --- a/src/Tgstation.Server.Api/ApiHeaders.cs +++ b/src/Tgstation.Server.Api/ApiHeaders.cs @@ -48,7 +48,12 @@ namespace Tgstation.Server.Api /// /// The current /// - internal static readonly AssemblyName assemblyName = Assembly.GetExecutingAssembly().GetName(); + static readonly AssemblyName assemblyName = Assembly.GetExecutingAssembly().GetName(); + + /// + /// Get the version of the the caller is using + /// + public static Version Version => assemblyName.Version; /// /// The being accessed @@ -90,11 +95,7 @@ namespace Tgstation.Server.Api /// /// The to test /// if the given version is compatible with the API. otherwise - public static bool CheckCompatibility(Version otherVersion) - { - var ourVersion = assemblyName.Version; - return !(ourVersion.Major != otherVersion.Major || ourVersion.Minor != otherVersion.Minor || ourVersion.Build > otherVersion.Build); - } + public static bool CheckCompatibility(Version otherVersion) => !(Version.Major != otherVersion.Major || Version.Minor != otherVersion.Minor || Version.Build > otherVersion.Build); /// /// Construct for JWT authentication @@ -209,7 +210,7 @@ namespace Tgstation.Server.Api Token = token; Username = username; Password = password; - ApiVersion = assemblyName.Version; + ApiVersion = Version; } /// diff --git a/src/Tgstation.Server.Api/Models/ErrorMessage.cs b/src/Tgstation.Server.Api/Models/ErrorMessage.cs index 05f5b03d86..033ff8b9ad 100644 --- a/src/Tgstation.Server.Api/Models/ErrorMessage.cs +++ b/src/Tgstation.Server.Api/Models/ErrorMessage.cs @@ -15,6 +15,6 @@ namespace Tgstation.Server.Api.Models /// /// The version of the API the server is using /// - public Version SeverApiVersion { get; set; } = ApiHeaders.assemblyName.Version; + public Version SeverApiVersion { get; set; } = ApiHeaders.Version; } } diff --git a/src/Tgstation.Server.Api/Models/ServerInformation.cs b/src/Tgstation.Server.Api/Models/ServerInformation.cs new file mode 100644 index 0000000000..2bfbf9b6b6 --- /dev/null +++ b/src/Tgstation.Server.Api/Models/ServerInformation.cs @@ -0,0 +1,20 @@ +using System; + +namespace Tgstation.Server.Api.Models +{ + /// + /// Represents basic server information + /// + public sealed class ServerInformation + { + /// + /// The version of the host + /// + public Version Version { get; set; } + + /// + /// The version of the host + /// + public Version ApiVersion { get; set; } + } +} diff --git a/src/Tgstation.Server.Host/Controllers/HomeController.cs b/src/Tgstation.Server.Host/Controllers/HomeController.cs index 9619335070..ca67ed2d09 100644 --- a/src/Tgstation.Server.Host/Controllers/HomeController.cs +++ b/src/Tgstation.Server.Host/Controllers/HomeController.cs @@ -3,6 +3,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using System; using System.Linq; +using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api; @@ -65,7 +66,10 @@ namespace Tgstation.Server.Host.Controllers /// [TgsAuthorize] [HttpGet] - public JsonResult Home() => Json(new { application.Version }); + public JsonResult Home() => Json(new Api.Models.ServerInformation { + Version = application.Version, + ApiVersion = ApiHeaders.Version + }); /// /// Attempt to authenticate a using From 048dc486d774ee3363e413facde30f07599d0698 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 20 Aug 2018 12:10:58 -0400 Subject: [PATCH 11/23] CurrentVersion renamed to NewVersion. No longer returned on Read --- docs/API.dox | 2 ++ .../Models/Administration.cs | 4 ++-- .../Rights/AdministrationRights.cs | 2 +- .../Controllers/AdministrationController.cs | 21 ++++++++----------- 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/docs/API.dox b/docs/API.dox index a7be584e94..1407bc59a4 100644 --- a/docs/API.dox +++ b/docs/API.dox @@ -168,6 +168,8 @@ If you want to perform a live update of the server use POST "/Administration" @ref Tgstation.Server.Api.Models.Administration => OK +With the @ref Tgstation.Server.Api.Models.Administration.NewVersion field set + Any DreamDaemon servers running will persist while the server installs the new version from the official tgstation-server GitHub (If it exists) If the server is otherwise acting funky and you wish to restart it, use this request: diff --git a/src/Tgstation.Server.Api/Models/Administration.cs b/src/Tgstation.Server.Api/Models/Administration.cs index 7c8873d04b..33f7aa5e5f 100644 --- a/src/Tgstation.Server.Api/Models/Administration.cs +++ b/src/Tgstation.Server.Api/Models/Administration.cs @@ -19,7 +19,7 @@ namespace Tgstation.Server.Api.Models public Uri TrackedRepositoryUrl { get; set; } /// - /// The latest available version of the Tgstation.Server.Host assembly from the upstream repository. If is higher than 's the update cannot be applied due to API changes + /// The latest available version of the Tgstation.Server.Host assembly from the upstream repository. If is higher than 's the update cannot be applied due to API changes /// [Permissions(DenyWrite = true)] public Version LatestVersion { get; set; } @@ -28,6 +28,6 @@ namespace Tgstation.Server.Api.Models /// Changes the version of Tgstation.Server.Host to the given version from the upstream repository /// [Permissions(WriteRight = AdministrationRights.ChangeVersion)] - public Version CurrentVersion { get; set; } + public Version NewVersion { get; set; } } } diff --git a/src/Tgstation.Server.Api/Rights/AdministrationRights.cs b/src/Tgstation.Server.Api/Rights/AdministrationRights.cs index dc8c6f9dfa..6f546020aa 100644 --- a/src/Tgstation.Server.Api/Rights/AdministrationRights.cs +++ b/src/Tgstation.Server.Api/Rights/AdministrationRights.cs @@ -21,7 +21,7 @@ namespace Tgstation.Server.Api.Rights /// RestartHost = 2, /// - /// User can change + /// User can change /// ChangeVersion = 4, /// diff --git a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs index 45aadcca9a..3c438ca6d1 100644 --- a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs +++ b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs @@ -90,10 +90,6 @@ namespace Tgstation.Server.Host.Controllers [TgsAuthorize] public override async Task Read(CancellationToken cancellationToken) { - var model = new Administration - { - CurrentVersion = application.Version - }; try { var repositoryTask = gitHubClient.Repository.Get(updatesConfiguration.GitHubRepositoryId); @@ -105,16 +101,17 @@ namespace Tgstation.Server.Host.Controllers && version.Major == application.Version.Major && (greatestVersion == null || version > greatestVersion)) greatestVersion = version; - - model.LatestVersion = greatestVersion; - model.TrackedRepositoryUrl = new Uri((await repositoryTask.ConfigureAwait(false)).HtmlUrl); - model.WindowsHost = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + return Json(new Administration + { + LatestVersion = greatestVersion, + TrackedRepositoryUrl = new Uri((await repositoryTask.ConfigureAwait(false)).HtmlUrl), + WindowsHost = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + }); } catch (RateLimitExceededException e) { return RateLimit(e); } - return Json(model); } /// @@ -124,10 +121,10 @@ namespace Tgstation.Server.Host.Controllers if (model == null) throw new ArgumentNullException(nameof(model)); - if (model.CurrentVersion == null) + if (model.NewVersion == null) return BadRequest(new ErrorMessage { Message = "Missing new version!" }); - if (model.CurrentVersion.Major != application.Version.Major) + if (model.NewVersion.Major != application.Version.Major) return BadRequest(new ErrorMessage { Message = "Cannot update to a different suite version!" }); IEnumerable releases; @@ -141,7 +138,7 @@ namespace Tgstation.Server.Host.Controllers } foreach (var release in releases) - if (Version.TryParse(release.TagName.Replace(updatesConfiguration.GitTagPrefix, String.Empty, StringComparison.Ordinal), out var version) && version == model.CurrentVersion) + if (Version.TryParse(release.TagName.Replace(updatesConfiguration.GitTagPrefix, String.Empty, StringComparison.Ordinal), out var version) && version == model.NewVersion) { var asset = release.Assets.Where(x => x.Name == updatesConfiguration.UpdatePackageAssetName).FirstOrDefault(); if (asset == default) From 855a17a54b715195dd98cf7bbb97325689c7c2e2 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 20 Aug 2018 12:13:40 -0400 Subject: [PATCH 12/23] Fix client trying to read the wrong type of object for Version() --- src/Tgstation.Server.Client/IServerClient.cs | 4 ++-- src/Tgstation.Server.Client/ServerClient.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Tgstation.Server.Client/IServerClient.cs b/src/Tgstation.Server.Client/IServerClient.cs index 3bd57d287e..23e44a36b2 100644 --- a/src/Tgstation.Server.Client/IServerClient.cs +++ b/src/Tgstation.Server.Client/IServerClient.cs @@ -36,9 +36,9 @@ namespace Tgstation.Server.Client IUsersClient Users { get; } /// - /// The of the + /// The of the /// - Task Version(CancellationToken cancellationToken); + Task Version(CancellationToken cancellationToken); /// /// Adds a to the request pipeline diff --git a/src/Tgstation.Server.Client/ServerClient.cs b/src/Tgstation.Server.Client/ServerClient.cs index 7e34062840..afc19af371 100644 --- a/src/Tgstation.Server.Client/ServerClient.cs +++ b/src/Tgstation.Server.Client/ServerClient.cs @@ -55,7 +55,7 @@ namespace Tgstation.Server.Client public void Dispose() => apiClient.Dispose(); /// - public Task Version(CancellationToken cancellationToken) => apiClient.Read(Routes.Root, cancellationToken); + public Task Version(CancellationToken cancellationToken) => apiClient.Read(Routes.Root, cancellationToken); /// public void AddRequestLogger(IRequestLogger requestLogger) => apiClient.AddRequestLogger(requestLogger); From 361a3ff92c8fe0f500f5a88506aed84b681c85d0 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 20 Aug 2018 12:23:23 -0400 Subject: [PATCH 13/23] Fix for badly configured upstream repo ID --- .../Controllers/AdministrationController.cs | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs index 3c438ca6d1..a9c21a8d08 100644 --- a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs +++ b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs @@ -92,19 +92,28 @@ namespace Tgstation.Server.Host.Controllers { try { - var repositoryTask = gitHubClient.Repository.Get(updatesConfiguration.GitHubRepositoryId); - var releases = (await gitHubClient.Repository.Release.GetAll(updatesConfiguration.GitHubRepositoryId).ConfigureAwait(false)).Where(x => x.TagName.StartsWith(updatesConfiguration.GitTagPrefix, StringComparison.InvariantCulture)); - Version greatestVersion = null; - foreach (var I in releases) - if (Version.TryParse(I.TagName.Replace(updatesConfiguration.GitTagPrefix, String.Empty, StringComparison.Ordinal), out var version) - && version.Major == application.Version.Major - && (greatestVersion == null || version > greatestVersion)) - greatestVersion = version; + Uri repoUrl = null; + try + { + var repositoryTask = gitHubClient.Repository.Get(updatesConfiguration.GitHubRepositoryId); + var releases = (await gitHubClient.Repository.Release.GetAll(updatesConfiguration.GitHubRepositoryId).ConfigureAwait(false)).Where(x => x.TagName.StartsWith(updatesConfiguration.GitTagPrefix, StringComparison.InvariantCulture)); + + foreach (var I in releases) + if (Version.TryParse(I.TagName.Replace(updatesConfiguration.GitTagPrefix, String.Empty, StringComparison.Ordinal), out var version) + && version.Major == application.Version.Major + && (greatestVersion == null || version > greatestVersion)) + greatestVersion = version; + repoUrl = new Uri((await repositoryTask.ConfigureAwait(false)).HtmlUrl); + } + catch (NotFoundException e) + { + Logger.LogWarning("Not found exception while retrieving upstream repository info: {0}", e); + } return Json(new Administration { LatestVersion = greatestVersion, - TrackedRepositoryUrl = new Uri((await repositoryTask.ConfigureAwait(false)).HtmlUrl), + TrackedRepositoryUrl = repoUrl, WindowsHost = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) }); } From 6c8e7bae9e30b954899c199f56177a31a3252f69 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 20 Aug 2018 12:24:01 -0400 Subject: [PATCH 14/23] Skeleton integration test. Easy to expand --- appveyor.yml | 3 ++ .../AdministrationTest.cs | 31 +++++++++++ .../Tgstation.Server.Tests/IntegrationTest.cs | 53 +++++++++++++++++++ tests/Tgstation.Server.Tests/TestingServer.cs | 41 ++++++++++++++ .../Tgstation.Server.Tests.csproj | 28 ++++++++++ tgstation-server.sln | 10 +++- 6 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 tests/Tgstation.Server.Tests/AdministrationTest.cs create mode 100644 tests/Tgstation.Server.Tests/IntegrationTest.cs create mode 100644 tests/Tgstation.Server.Tests/TestingServer.cs create mode 100644 tests/Tgstation.Server.Tests/Tgstation.Server.Tests.csproj diff --git a/appveyor.yml b/appveyor.yml index 0397e3682f..b5a69eab3e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -53,6 +53,9 @@ test_script: - vstest.console /logger:trx;LogFileName=results.trx "tests\Tgstation.Server.Host.Watchdog.Tests\bin\%CONFIGURATION%\netcoreapp2.0\Tgstation.Server.Host.Watchdog.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 - ps: $wc = New-Object 'System.Net.WebClient' - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\TestResults\results.trx)) + - vstest.console /logger:trx;LogFileName=results.trx "tests\Tgstation.Server.Tests\bin\%CONFIGURATION%\netcoreapp2.0\Tgstation.Server.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 + - ps: $wc = New-Object 'System.Net.WebClient' + - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\TestResults\results.trx)) after_test: - ps: build/UploadCoverage.ps1 - ps: build/BuildDox.ps1 diff --git a/tests/Tgstation.Server.Tests/AdministrationTest.cs b/tests/Tgstation.Server.Tests/AdministrationTest.cs new file mode 100644 index 0000000000..ef20efe158 --- /dev/null +++ b/tests/Tgstation.Server.Tests/AdministrationTest.cs @@ -0,0 +1,31 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using Tgstation.Server.Client; + +namespace Tgstation.Server.Tests +{ + sealed class AdministrationTest + { + readonly IAdministrationClient client; + + public AdministrationTest(IAdministrationClient client) + { + this.client = client ?? throw new ArgumentNullException(nameof(client)); + } + + public async Task Run() + { + await TestRead().ConfigureAwait(false); + } + + async Task TestRead() + { + var model = await client.Read(default).ConfigureAwait(false); + Assert.AreEqual(RuntimeInformation.IsOSPlatform(OSPlatform.Windows), model.WindowsHost); + + //uhh not much else to do + } + } +} diff --git a/tests/Tgstation.Server.Tests/IntegrationTest.cs b/tests/Tgstation.Server.Tests/IntegrationTest.cs new file mode 100644 index 0000000000..3011fa7ae3 --- /dev/null +++ b/tests/Tgstation.Server.Tests/IntegrationTest.cs @@ -0,0 +1,53 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Net.Http.Headers; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api; +using Tgstation.Server.Api.Models; +using Tgstation.Server.Client; +using Tgstation.Server.Host; + +namespace Tgstation.Server.Tests +{ + /// + /// Integration test for + /// + [TestClass] + public sealed class IntegrationTest + { + readonly IServerClientFactory clientFactory = new ServerClientFactory(new ProductHeaderValue(Assembly.GetExecutingAssembly().GetName().Name, Assembly.GetExecutingAssembly().GetName().Version.ToString())); + + [TestMethod] + public async Task FullMonty() + { + using (var server = new TestingServer()) + using (var serverCts = new CancellationTokenSource()) + { + var serverTask = server.RunAsync(serverCts.Token); + try + { + using (var adminClient = await clientFactory.CreateServerClient(server.Url, User.AdminName, User.DefaultAdminPassword).ConfigureAwait(false)) + { + var serverInfo = await adminClient.Version(default).ConfigureAwait(false); + + Assert.AreEqual(ApiHeaders.Version, serverInfo.ApiVersion); + Assert.AreEqual(typeof(IServer).Assembly.GetName().Version, serverInfo.Version); + + await new AdministrationTest(adminClient.Administration).Run().ConfigureAwait(false); + } + } + finally + { + serverCts.Cancel(); + try + { + await serverTask.ConfigureAwait(false); + } + catch (OperationCanceledException) { } + } + } + } + } +} diff --git a/tests/Tgstation.Server.Tests/TestingServer.cs b/tests/Tgstation.Server.Tests/TestingServer.cs new file mode 100644 index 0000000000..c943f1d821 --- /dev/null +++ b/tests/Tgstation.Server.Tests/TestingServer.cs @@ -0,0 +1,41 @@ +using System; +using System.Globalization; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Host; + +namespace Tgstation.Server.Tests +{ + sealed class TestingServer : IServer + { + public Uri Url { get; } + public bool RestartRequested => realServer.RestartRequested; + + readonly IServer realServer; + readonly string databasePath; + + public TestingServer() + { + databasePath = Path.GetTempFileName(); + File.Delete(databasePath); + Url = new Uri("http://localhost:5001"); + realServer = new ServerFactory().CreateServer(new string[] + { + "--urls", + Url.ToString(), + "Database:DatabaseType=Sqlite", + String.Format(CultureInfo.InvariantCulture, "Database:ConnectionString=Data Source={0}", databasePath) + ,"Database:NoMigrations=true" //TODO: remove this when migrations are added + }, null); + } + + public void Dispose() + { + realServer.Dispose(); + File.Delete(databasePath); + } + + public Task RunAsync(CancellationToken cancellationToken) => realServer.RunAsync(cancellationToken); + } +} diff --git a/tests/Tgstation.Server.Tests/Tgstation.Server.Tests.csproj b/tests/Tgstation.Server.Tests/Tgstation.Server.Tests.csproj new file mode 100644 index 0000000000..1a89f581ca --- /dev/null +++ b/tests/Tgstation.Server.Tests/Tgstation.Server.Tests.csproj @@ -0,0 +1,28 @@ + + + + netcoreapp2.0 + + false + + + + 7.1 + + + + 7.1 + + + + + + + + + + + + + + diff --git a/tgstation-server.sln b/tgstation-server.sln index 41c53999f5..48791368cf 100644 --- a/tgstation-server.sln +++ b/tgstation-server.sln @@ -101,8 +101,8 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{DCC75431-7913-4306-9FAB-70998D440BCE}" ProjectSection(SolutionItems) = preProject docs\API.dox = docs\API.dox - docs\ArchitectureOverview.png = docs\ArchitectureOverview.png docs\Architecture.dox = docs\Architecture.dox + docs\ArchitectureOverview.png = docs\ArchitectureOverview.png EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{E82104F4-F5C4-4786-ACD4-B635166CDB21}" @@ -112,6 +112,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{E821 .github\ISSUE_TEMPLATE.md = .github\ISSUE_TEMPLATE.md EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tgstation.Server.Tests", "tests\Tgstation.Server.Tests\Tgstation.Server.Tests.csproj", "{09056964-1C74-445A-96EC-33F6DFC07916}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -191,6 +193,12 @@ Global {AA80A190-52E2-4BE3-BFEB-1F148D9E9007}.Docker|Any CPU.Build.0 = Release|Any CPU {AA80A190-52E2-4BE3-BFEB-1F148D9E9007}.Release|Any CPU.ActiveCfg = Release|Any CPU {AA80A190-52E2-4BE3-BFEB-1F148D9E9007}.Release|Any CPU.Build.0 = Release|Any CPU + {09056964-1C74-445A-96EC-33F6DFC07916}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {09056964-1C74-445A-96EC-33F6DFC07916}.Debug|Any CPU.Build.0 = Debug|Any CPU + {09056964-1C74-445A-96EC-33F6DFC07916}.Docker|Any CPU.ActiveCfg = Debug|Any CPU + {09056964-1C74-445A-96EC-33F6DFC07916}.Docker|Any CPU.Build.0 = Debug|Any CPU + {09056964-1C74-445A-96EC-33F6DFC07916}.Release|Any CPU.ActiveCfg = Release|Any CPU + {09056964-1C74-445A-96EC-33F6DFC07916}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 2c60a40bfce8631f3946f2aafca0faa679833b45 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 20 Aug 2018 12:28:38 -0400 Subject: [PATCH 15/23] Commenting slows down testing --- tests/Tgstation.Server.Tests/IntegrationTest.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/Tgstation.Server.Tests/IntegrationTest.cs b/tests/Tgstation.Server.Tests/IntegrationTest.cs index 3011fa7ae3..e630207f2b 100644 --- a/tests/Tgstation.Server.Tests/IntegrationTest.cs +++ b/tests/Tgstation.Server.Tests/IntegrationTest.cs @@ -11,9 +11,6 @@ using Tgstation.Server.Host; namespace Tgstation.Server.Tests { - /// - /// Integration test for - /// [TestClass] public sealed class IntegrationTest { From dd6a53731121168a76d8fa5c69098e376c064593 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 20 Aug 2018 12:30:53 -0400 Subject: [PATCH 16/23] Add integration test to dockerfile --- build/Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build/Dockerfile b/build/Dockerfile index af174c8317..a0da9591bd 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -29,6 +29,9 @@ RUN dotnet test -c Release WORKDIR /src/tests/Tgstation.Server.Host.Console.Tests RUN dotnet test -c Release +WORKDIR /src/tests/Tgstation.Server.Tests +RUN dotnet test -c Release + WORKDIR /src/src/Tgstation.Server.Host.Console RUN dotnet publish -c Release -o /app From df88cbe6d1c419e0c254c84f349c2b995c3998d5 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 20 Aug 2018 12:32:57 -0400 Subject: [PATCH 17/23] Removed Docker solution configuration --- tgstation-server.sln | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/tgstation-server.sln b/tgstation-server.sln index 48791368cf..bf94dec0a7 100644 --- a/tgstation-server.sln +++ b/tgstation-server.sln @@ -117,86 +117,59 @@ EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU - Docker|Any CPU = Docker|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {A09C947F-4B9B-4AEE-AB50-47055EA05F18}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A09C947F-4B9B-4AEE-AB50-47055EA05F18}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A09C947F-4B9B-4AEE-AB50-47055EA05F18}.Docker|Any CPU.ActiveCfg = Release|Any CPU - {A09C947F-4B9B-4AEE-AB50-47055EA05F18}.Docker|Any CPU.Build.0 = Release|Any CPU {A09C947F-4B9B-4AEE-AB50-47055EA05F18}.Release|Any CPU.ActiveCfg = Release|Any CPU {A09C947F-4B9B-4AEE-AB50-47055EA05F18}.Release|Any CPU.Build.0 = Release|Any CPU {D36B99C4-E771-42D6-A95F-1102B3E236DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D36B99C4-E771-42D6-A95F-1102B3E236DF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D36B99C4-E771-42D6-A95F-1102B3E236DF}.Docker|Any CPU.ActiveCfg = Release|Any CPU - {D36B99C4-E771-42D6-A95F-1102B3E236DF}.Docker|Any CPU.Build.0 = Release|Any CPU {D36B99C4-E771-42D6-A95F-1102B3E236DF}.Release|Any CPU.ActiveCfg = Release|Any CPU {D36B99C4-E771-42D6-A95F-1102B3E236DF}.Release|Any CPU.Build.0 = Release|Any CPU {2B69AD6D-2B5A-4023-8EAD-0BD1B18E028A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2B69AD6D-2B5A-4023-8EAD-0BD1B18E028A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2B69AD6D-2B5A-4023-8EAD-0BD1B18E028A}.Docker|Any CPU.ActiveCfg = Release|Any CPU - {2B69AD6D-2B5A-4023-8EAD-0BD1B18E028A}.Docker|Any CPU.Build.0 = Release|Any CPU {2B69AD6D-2B5A-4023-8EAD-0BD1B18E028A}.Release|Any CPU.ActiveCfg = Release|Any CPU {2B69AD6D-2B5A-4023-8EAD-0BD1B18E028A}.Release|Any CPU.Build.0 = Release|Any CPU {8B4A208D-A48A-4A5D-8B94-E2661138865D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8B4A208D-A48A-4A5D-8B94-E2661138865D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8B4A208D-A48A-4A5D-8B94-E2661138865D}.Docker|Any CPU.ActiveCfg = Release|Any CPU - {8B4A208D-A48A-4A5D-8B94-E2661138865D}.Docker|Any CPU.Build.0 = Release|Any CPU {8B4A208D-A48A-4A5D-8B94-E2661138865D}.Release|Any CPU.ActiveCfg = Release|Any CPU {8B4A208D-A48A-4A5D-8B94-E2661138865D}.Release|Any CPU.Build.0 = Release|Any CPU {29927416-3B78-49A7-A560-5CCAA638B6B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {29927416-3B78-49A7-A560-5CCAA638B6B4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {29927416-3B78-49A7-A560-5CCAA638B6B4}.Docker|Any CPU.ActiveCfg = Release|Any CPU - {29927416-3B78-49A7-A560-5CCAA638B6B4}.Docker|Any CPU.Build.0 = Release|Any CPU {29927416-3B78-49A7-A560-5CCAA638B6B4}.Release|Any CPU.ActiveCfg = Release|Any CPU {29927416-3B78-49A7-A560-5CCAA638B6B4}.Release|Any CPU.Build.0 = Release|Any CPU {E0AC911F-7675-4A91-9499-D8A2E2390AAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E0AC911F-7675-4A91-9499-D8A2E2390AAD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E0AC911F-7675-4A91-9499-D8A2E2390AAD}.Docker|Any CPU.ActiveCfg = Release|Any CPU - {E0AC911F-7675-4A91-9499-D8A2E2390AAD}.Docker|Any CPU.Build.0 = Release|Any CPU {E0AC911F-7675-4A91-9499-D8A2E2390AAD}.Release|Any CPU.ActiveCfg = Release|Any CPU {E0AC911F-7675-4A91-9499-D8A2E2390AAD}.Release|Any CPU.Build.0 = Release|Any CPU {BE0E8F49-334F-49D7-A04D-D82E9AB7AA36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {BE0E8F49-334F-49D7-A04D-D82E9AB7AA36}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BE0E8F49-334F-49D7-A04D-D82E9AB7AA36}.Docker|Any CPU.ActiveCfg = Release|Any CPU - {BE0E8F49-334F-49D7-A04D-D82E9AB7AA36}.Docker|Any CPU.Build.0 = Release|Any CPU {BE0E8F49-334F-49D7-A04D-D82E9AB7AA36}.Release|Any CPU.ActiveCfg = Release|Any CPU {BE0E8F49-334F-49D7-A04D-D82E9AB7AA36}.Release|Any CPU.Build.0 = Release|Any CPU {E5301AF1-4F74-4982-BF24-95F23CC5D5B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E5301AF1-4F74-4982-BF24-95F23CC5D5B2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E5301AF1-4F74-4982-BF24-95F23CC5D5B2}.Docker|Any CPU.ActiveCfg = Release|Any CPU - {E5301AF1-4F74-4982-BF24-95F23CC5D5B2}.Docker|Any CPU.Build.0 = Release|Any CPU {E5301AF1-4F74-4982-BF24-95F23CC5D5B2}.Release|Any CPU.ActiveCfg = Release|Any CPU {E5301AF1-4F74-4982-BF24-95F23CC5D5B2}.Release|Any CPU.Build.0 = Release|Any CPU {A3362FF6-550F-480F-859E-8EC1EB6EAB31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A3362FF6-550F-480F-859E-8EC1EB6EAB31}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A3362FF6-550F-480F-859E-8EC1EB6EAB31}.Docker|Any CPU.ActiveCfg = Release|Any CPU - {A3362FF6-550F-480F-859E-8EC1EB6EAB31}.Docker|Any CPU.Build.0 = Release|Any CPU {A3362FF6-550F-480F-859E-8EC1EB6EAB31}.Release|Any CPU.ActiveCfg = Release|Any CPU {A3362FF6-550F-480F-859E-8EC1EB6EAB31}.Release|Any CPU.Build.0 = Release|Any CPU {5D2D682C-6BF0-439C-850B-6AB945BBEAEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5D2D682C-6BF0-439C-850B-6AB945BBEAEA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5D2D682C-6BF0-439C-850B-6AB945BBEAEA}.Docker|Any CPU.ActiveCfg = Release|Any CPU - {5D2D682C-6BF0-439C-850B-6AB945BBEAEA}.Docker|Any CPU.Build.0 = Release|Any CPU {5D2D682C-6BF0-439C-850B-6AB945BBEAEA}.Release|Any CPU.ActiveCfg = Release|Any CPU {5D2D682C-6BF0-439C-850B-6AB945BBEAEA}.Release|Any CPU.Build.0 = Release|Any CPU {7500F776-4384-4B5F-A8D8-22461CAD108B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7500F776-4384-4B5F-A8D8-22461CAD108B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7500F776-4384-4B5F-A8D8-22461CAD108B}.Docker|Any CPU.ActiveCfg = Release|Any CPU - {7500F776-4384-4B5F-A8D8-22461CAD108B}.Docker|Any CPU.Build.0 = Release|Any CPU {7500F776-4384-4B5F-A8D8-22461CAD108B}.Release|Any CPU.ActiveCfg = Release|Any CPU {7500F776-4384-4B5F-A8D8-22461CAD108B}.Release|Any CPU.Build.0 = Release|Any CPU {AA80A190-52E2-4BE3-BFEB-1F148D9E9007}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AA80A190-52E2-4BE3-BFEB-1F148D9E9007}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AA80A190-52E2-4BE3-BFEB-1F148D9E9007}.Docker|Any CPU.ActiveCfg = Release|Any CPU - {AA80A190-52E2-4BE3-BFEB-1F148D9E9007}.Docker|Any CPU.Build.0 = Release|Any CPU {AA80A190-52E2-4BE3-BFEB-1F148D9E9007}.Release|Any CPU.ActiveCfg = Release|Any CPU {AA80A190-52E2-4BE3-BFEB-1F148D9E9007}.Release|Any CPU.Build.0 = Release|Any CPU {09056964-1C74-445A-96EC-33F6DFC07916}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {09056964-1C74-445A-96EC-33F6DFC07916}.Debug|Any CPU.Build.0 = Debug|Any CPU - {09056964-1C74-445A-96EC-33F6DFC07916}.Docker|Any CPU.ActiveCfg = Debug|Any CPU - {09056964-1C74-445A-96EC-33F6DFC07916}.Docker|Any CPU.Build.0 = Debug|Any CPU {09056964-1C74-445A-96EC-33F6DFC07916}.Release|Any CPU.ActiveCfg = Release|Any CPU {09056964-1C74-445A-96EC-33F6DFC07916}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection From 2f5bcda9587e2cffbf672a0329090be5be00fdeb Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 20 Aug 2018 12:35:52 -0400 Subject: [PATCH 18/23] Don't use DefaultContractResolver where unnecessary --- .../Components/Chat/JsonTrackingContext.cs | 5 +---- .../Components/Watchdog/SessionControllerFactory.cs | 5 +---- src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs | 5 +---- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Chat/JsonTrackingContext.cs b/src/Tgstation.Server.Host/Components/Chat/JsonTrackingContext.cs index e8dc25cb12..f5bbcd558f 100644 --- a/src/Tgstation.Server.Host/Components/Chat/JsonTrackingContext.cs +++ b/src/Tgstation.Server.Host/Components/Chat/JsonTrackingContext.cs @@ -67,10 +67,7 @@ namespace Tgstation.Server.Host.Components.Chat using (await SemaphoreSlimContext.Lock(channelsSemaphore, cancellationToken).ConfigureAwait(false)) await ioManager.WriteAllBytes(channelsPath, Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(channels, Formatting.Indented, new JsonSerializerSettings { - ContractResolver = new DefaultContractResolver - { - NamingStrategy = new CamelCaseNamingStrategy() - } + ContractResolver = new CamelCasePropertyNamesContractResolver() })), cancellationToken).ConfigureAwait(false); } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs index 69fbe5cd85..fd7209bb41 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs @@ -153,10 +153,7 @@ namespace Tgstation.Server.Host.Components.Watchdog var interopJson = JsonConvert.SerializeObject(interopInfo, Formatting.Indented, new JsonSerializerSettings { - ContractResolver = new DefaultContractResolver - { - NamingStrategy = new CamelCaseNamingStrategy() - }, + ContractResolver = new CamelCasePropertyNamesContractResolver(), ReferenceLoopHandling = ReferenceLoopHandling.Ignore }); diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index bbd5b481bc..bec6c74822 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -868,10 +868,7 @@ namespace Tgstation.Server.Host.Components.Watchdog var json = JsonConvert.SerializeObject(arguments, new JsonSerializerSettings { - ContractResolver = new DefaultContractResolver - { - NamingStrategy = new CamelCaseNamingStrategy() - } + ContractResolver = new CamelCasePropertyNamesContractResolver() }); var command = String.Format(CultureInfo.InvariantCulture, "{0}&{1}={2}", byondTopicSender.SanitizeString(Constants.DMTopicChatCommand), byondTopicSender.SanitizeString(Constants.DMParameterData), byondTopicSender.SanitizeString(json)); From f58c71adc7aad1a1f59f10f3f693afe9de847008 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 20 Aug 2018 13:14:33 -0400 Subject: [PATCH 19/23] Add returns for 201, 202, 422, and 426 HTTP status codes --- docs/API.dox | 8 +++-- src/Tgstation.Server.Api/ApiHeaders.cs | 9 +++-- src/Tgstation.Server.Client/ApiClient.cs | 13 ++++---- .../Controllers/AdministrationController.cs | 33 ++++++++++++++----- .../Controllers/ApiController.cs | 10 ++++++ .../Controllers/ByondController.cs | 2 +- .../Controllers/ChatController.cs | 11 ++----- .../Controllers/ConfigurationController.cs | 2 +- .../Controllers/DreamDaemonController.cs | 2 +- .../Controllers/DreamMakerController.cs | 2 +- .../Controllers/InstanceController.cs | 2 +- .../Controllers/InstanceUserController.cs | 2 +- .../Controllers/RepositoryController.cs | 6 ++-- .../Controllers/UserController.cs | 2 +- v4_prototype_TODO.txt | 2 ++ 15 files changed, 66 insertions(+), 40 deletions(-) diff --git a/docs/API.dox b/docs/API.dox index 1407bc59a4..16af967651 100644 --- a/docs/API.dox +++ b/docs/API.dox @@ -52,7 +52,9 @@ An Authentication header is also required. See @ref api_auth TGS will only every return the response codes listed here - 200: General OK status. Unless the HTTP DELETE verb was used to make the request (In which case the response body will be empty), the response body will contain a json model or array depending on the API called. -- 400: Bad request made. The response body will contain a @ref Tgstation.Server.Api.Models.ErrorMessage model detailing the error +- 201: Created: Returned when the request created an entity, 202 trumps this +- 202: Accepted. Used when a response triggers a long running operation such as a @ref Tgstation.Server.Api.Models.Job or server restart +- 400: Bad request made. The response body will contain an @ref Tgstation.Server.Api.Models.ErrorMessage model detailing the error - 401: User unauthorized. Invalid or expired credentials were provided. Check rights APIs for updates. See @ref api_auth for details - 403: Usage forbidden. User tried to make a request they were not allowed to perform. - 404: Not found. A resource was requested that had never existed. In the case of retrieving a resource by ID, it could potentially exist in the future @@ -60,9 +62,11 @@ TGS will only every return the response codes listed here - 408: Request timeout. The client took to long to continue a request - 409: Conflict. Documented in the requests that use them - 410: Gone. Attempted to access/modify a resource that ideally should have been ready, but isn't or no longer is +- 422: Unprocessable Entity: Used specifically when an operation that requires a server restart is unable to be performed due to the @ref Tgstation.Server.Host.Watchdog not being present in the deployment. Blame MSO. Response body contains an @ref Tgstation.Server.Api.Models.ErrorMessage +- 426: Upgrade required: Used when the client's API version is not compatible with the server's. Response body contains an @ref Tgstation.Server.Api.Models.ErrorMessage - 429: Rate limited. Used with operations that rely on GitHub.com. If a rate limit is hit for an operation this will be returned. Response will contain a Retry-After header - 500: Server error. Please report the request and response body to the code repository -- 501: Not implemented. Currently used in two places: 1. Endpoints that trigger a server restart but the server is not running in a restartable configuration. 2. +- 501: Not implemented. Functionality not available in the current server version - 503: Service unavailable. The server is either starting up or shutting down and isn't ready to respond to requests. You can try again soon and a response/lack thereof will indicate which of the two events it was @section api_auth Authentication diff --git a/src/Tgstation.Server.Api/ApiHeaders.cs b/src/Tgstation.Server.Api/ApiHeaders.cs index c4bf1bd255..1160e258ca 100644 --- a/src/Tgstation.Server.Api/ApiHeaders.cs +++ b/src/Tgstation.Server.Api/ApiHeaders.cs @@ -153,9 +153,6 @@ namespace Tgstation.Server.Api ApiVersion = apiVersion; UserAgent = clientUserAgent.Product; - if(!CheckCompatibility(ApiVersion)) - throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Given API version is incompatible with version {0}!", ApiVersion)); - if (!requestHeaders.Headers.TryGetValue(HeaderNames.Authorization, out StringValues authorization)) throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Missing {0} header!", HeaderNames.Authorization)); var auth = authorization.First(); @@ -213,6 +210,12 @@ namespace Tgstation.Server.Api ApiVersion = Version; } + /// + /// Checks if the is compatible with + /// + /// if the API is compatible, otherwise + public bool Compatible() => CheckCompatibility(ApiVersion); + /// /// Set using the . This initially clears /// diff --git a/src/Tgstation.Server.Client/ApiClient.cs b/src/Tgstation.Server.Client/ApiClient.cs index d475d976f6..176253e06d 100644 --- a/src/Tgstation.Server.Client/ApiClient.cs +++ b/src/Tgstation.Server.Client/ApiClient.cs @@ -96,7 +96,8 @@ namespace Tgstation.Server.Client var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - if (!response.IsSuccessStatusCode) { + if (!response.IsSuccessStatusCode) + { ErrorMessage errorMessage = null; try { @@ -107,11 +108,8 @@ namespace Tgstation.Server.Client switch (response.StatusCode) { - case HttpStatusCode.BadRequest: - //validate our api version is compatible - if(errorMessage != null && ApiHeaders.CheckCompatibility(errorMessage.SeverApiVersion)) - throw new ApiMismatchException(errorMessage); - goto default; + case HttpStatusCode.UpgradeRequired: + throw new ApiMismatchException(errorMessage); case HttpStatusCode.Unauthorized: throw new UnauthorizedException(); case HttpStatusCode.RequestTimeout: @@ -125,10 +123,11 @@ namespace Tgstation.Server.Client case HttpStatusCode.Conflict: throw new ConflictException(errorMessage, response.StatusCode); case HttpStatusCode.NotImplemented: + case (HttpStatusCode)422: //unprocessable entity throw new MethodNotSupportedException(); case HttpStatusCode.InternalServerError: //response - throw new ServerErrorException(json); //json is html + throw new ServerErrorException(json); //json is html case (HttpStatusCode)429: //rate limited response.Headers.TryGetValues("Retry-After", out var values); throw new RateLimitException(values?.FirstOrDefault()); diff --git a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs index a9c21a8d08..24ace9d9b9 100644 --- a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs +++ b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs @@ -28,10 +28,7 @@ namespace Tgstation.Server.Host.Controllers [Route(Routes.Administration)] public sealed class AdministrationController : ModelController { - /// - /// HTTP 429 status code - /// - const int RateLimitHttpStatusCode = 429; + const string RestartNotSupportedException = "This deployment of tgstation-server is lacking the Tgstation.Server.Host.Watchdog component. Restarts and version changes cannot be completed!"; /// /// The for the @@ -83,7 +80,7 @@ namespace Tgstation.Server.Host.Controllers Logger.LogWarning("Exceeded GitHub rate limit!"); var secondsString = Math.Ceiling((exception.Reset - DateTimeOffset.Now).TotalSeconds).ToString(CultureInfo.InvariantCulture); Response.Headers.Add("Retry-After", new StringValues(secondsString)); - return StatusCode(RateLimitHttpStatusCode); + return StatusCode(429); } /// @@ -157,10 +154,16 @@ namespace Tgstation.Server.Host.Controllers try { if (!await serverUpdater.ApplyUpdate(assetBytes, ioManager, cancellationToken).ConfigureAwait(false)) - return StatusCode((int)HttpStatusCode.NotImplemented); + return UnprocessableEntity(new ErrorMessage + { + Message = RestartNotSupportedException + }); //unprocessable entity } - catch (InvalidOperationException) { } //we were beat to the punch - return Ok(); //gtfo of here before all the cancellation tokens fire + catch (InvalidOperationException) + { + return StatusCode((int)HttpStatusCode.ServiceUnavailable); //we were beat to the punch, really shouldn't happen but heat death of the universe and what not + } + return Accepted(); //gtfo of here before all the cancellation tokens fire } return StatusCode((int)HttpStatusCode.Gone); @@ -169,6 +172,18 @@ namespace Tgstation.Server.Host.Controllers /// [HttpDelete] [TgsAuthorize(AdministrationRights.RestartHost)] - public Task Delete() => Task.FromResult(serverUpdater.Restart() ? (IActionResult)Ok() : StatusCode((int)HttpStatusCode.NotImplemented)); + public Task Delete() { + try + { + return Task.FromResult(serverUpdater.Restart() ? (IActionResult)Ok() : UnprocessableEntity(new ErrorMessage + { + Message = RestartNotSupportedException + })); + } + catch (InvalidOperationException) + { + return Task.FromResult(StatusCode((int)HttpStatusCode.ServiceUnavailable)); + } + } } } diff --git a/src/Tgstation.Server.Host/Controllers/ApiController.cs b/src/Tgstation.Server.Host/Controllers/ApiController.cs index ffa84c64a3..faf1cad40e 100644 --- a/src/Tgstation.Server.Host/Controllers/ApiController.cs +++ b/src/Tgstation.Server.Host/Controllers/ApiController.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System.Globalization; using System.IdentityModel.Tokens.Jwt; using System.Linq; +using System.Net; using System.Security.Claims; using System.Threading.Tasks; using Tgstation.Server.Api; @@ -150,6 +151,15 @@ namespace Tgstation.Server.Host.Controllers { ApiHeaders = new ApiHeaders(Request.GetTypedHeaders()); + if(!ApiHeaders.Compatible()) + { + await StatusCode((int)HttpStatusCode.UpgradeRequired, new ErrorMessage + { + Message = "Provided API version is incompatible with server version!" + }).ExecuteResultAsync(context).ConfigureAwait(false); + return; + } + if (requireInstance) { if(!ApiHeaders.InstanceId.HasValue) diff --git a/src/Tgstation.Server.Host/Controllers/ByondController.cs b/src/Tgstation.Server.Host/Controllers/ByondController.cs index 11c0191d24..566aaae2b0 100644 --- a/src/Tgstation.Server.Host/Controllers/ByondController.cs +++ b/src/Tgstation.Server.Host/Controllers/ByondController.cs @@ -99,7 +99,7 @@ namespace Tgstation.Server.Host.Controllers result.InstallJob = job.ToApi(); } result.Version = byondManager.ActiveVersion; - return Json(result); + return result.InstallJob != null ? (IActionResult)Accepted(result) : Json(result); } } } diff --git a/src/Tgstation.Server.Host/Controllers/ChatController.cs b/src/Tgstation.Server.Host/Controllers/ChatController.cs index f1cb99d06c..297bfb0bb0 100644 --- a/src/Tgstation.Server.Host/Controllers/ChatController.cs +++ b/src/Tgstation.Server.Host/Controllers/ChatController.cs @@ -100,14 +100,7 @@ namespace Tgstation.Server.Host.Controllers DatabaseContext.ChatBots.Add(dbModel); - try - { - await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); - } - catch (DbUpdateException) - { - return Conflict(); - } + await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); try { @@ -132,7 +125,7 @@ namespace Tgstation.Server.Host.Controllers { return BadRequest(new ErrorMessage { Message = e.Message }); } - return Json(dbModel.ToApi()); + return StatusCode((int)HttpStatusCode.Created, dbModel.ToApi()); } /// diff --git a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs index e724238caa..e2b9620bab 100644 --- a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs +++ b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs @@ -58,7 +58,7 @@ namespace Tgstation.Server.Host.Controllers newFile.Content = null; - return Json(newFile); + return model.LastReadHash == null ? (IActionResult)StatusCode((int)HttpStatusCode.Created, newFile) : Json(newFile); } catch(NotImplementedException) { diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs index 2872f37787..c61672848f 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs @@ -74,7 +74,7 @@ namespace Tgstation.Server.Host.Controllers throw new InvalidOperationException("Watchdog already running!"); }, cancellationToken).ConfigureAwait(false); - return Json(job.ToApi()); + return Accepted(job.ToApi()); } /// diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs index 657e7fe3dc..bcabfd014e 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs @@ -93,7 +93,7 @@ namespace Tgstation.Server.Host.Controllers Instance = Instance }; await jobManager.RegisterOperation(job, (paramJob, serviceProvider, progressReporter, ct) => RunCompile(paramJob, serviceProvider, Instance, ct), cancellationToken).ConfigureAwait(false); - return Json(job.ToApi()); + return Accepted(job.ToApi()); } /// diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs index 902d1c54c6..a417dbe86c 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs @@ -183,7 +183,7 @@ namespace Tgstation.Server.Host.Controllers Logger.LogInformation("{0} created instance {1}: {2}", AuthenticationContext.User.Name, newInstance.Name, newInstance.Id); - return Json(newInstance.ToApi()); + return StatusCode((int)HttpStatusCode.Created, newInstance.ToApi()); } /// diff --git a/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs b/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs index 5a37a37c3f..26412f7509 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs @@ -70,7 +70,7 @@ namespace Tgstation.Server.Host.Controllers DatabaseContext.InstanceUsers.Add(dbUser); await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); - return Json(dbUser.ToApi()); + return StatusCode((int)HttpStatusCode.Created, dbUser.ToApi()); } /// diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index 77e7cfb95e..91b1a36a7c 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -174,7 +174,7 @@ namespace Tgstation.Server.Host.Controllers api.IsGitHub = model.Origin.ToUpperInvariant().Contains(uiGitHub); api.ActiveJob = job.ToApi(); - return Json(api); + return StatusCode((int)HttpStatusCode.Created, api); } } @@ -207,7 +207,7 @@ namespace Tgstation.Server.Host.Controllers var api = currentModel.ToApi(); await jobManager.RegisterOperation(job, (paramJob, serviceProvider, progressReporter, ct) => instanceManager.GetInstance(Instance).RepositoryManager.DeleteRepository(cancellationToken), cancellationToken).ConfigureAwait(false); api.ActiveJob = job.ToApi(); - return Ok(); + return Accepted(api); } /// @@ -595,7 +595,7 @@ namespace Tgstation.Server.Host.Controllers } }, cancellationToken).ConfigureAwait(false); - return Json(job.ToApi()); + return Accepted(job.ToApi()); } } } diff --git a/src/Tgstation.Server.Host/Controllers/UserController.cs b/src/Tgstation.Server.Host/Controllers/UserController.cs index 961e9a99e7..8e0b574398 100644 --- a/src/Tgstation.Server.Host/Controllers/UserController.cs +++ b/src/Tgstation.Server.Host/Controllers/UserController.cs @@ -118,7 +118,7 @@ namespace Tgstation.Server.Host.Controllers await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); - return Json(dbUser.ToApi()); + return StatusCode((int)HttpStatusCode.Created, dbUser.ToApi()); } /// diff --git a/v4_prototype_TODO.txt b/v4_prototype_TODO.txt index 3b97a1ae4d..830b59ef57 100644 --- a/v4_prototype_TODO.txt +++ b/v4_prototype_TODO.txt @@ -6,3 +6,5 @@ Only show user name and ID when serializing to API In fact remove IApiConvertable<> altogether, it's not required by anything Chat channel tagging + +Directory create function \ No newline at end of file From d1d78ba3a7d43e1a2705658eba81219ea5bd5d37 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 20 Aug 2018 13:26:32 -0400 Subject: [PATCH 20/23] Implement CreateDirectory API --- docs/API.dox | 4 +++ .../Components/StaticFiles/Configuration.cs | 16 ++++++++++ .../Components/StaticFiles/IConfiguration.cs | 9 ++++++ .../Controllers/ConfigurationController.cs | 32 +++++++++++++++++++ .../IO/ISynchronousIOManager.cs | 8 +++++ .../IO/SynchronousIOManager.cs | 10 ++++++ 6 files changed, 79 insertions(+) diff --git a/docs/API.dox b/docs/API.dox index 16af967651..d1fbde97e6 100644 --- a/docs/API.dox +++ b/docs/API.dox @@ -396,6 +396,10 @@ If the path is empty, the root directory will be retrieved. The @ref Tgstation.S If you do not have access to list the requested directory, a 403 response will be returned. If the path actually represents a file or the directory no longer exists a 410 response will be returned. +To create an empty config directory use the following request + +I POST "/Config/List/" => @ref Tgstation.Server.Api.Models.ConfigurationFile + To get the content of a static file use the following method I GET "/Config/File/" => @ref Tgstation.Server.Api.Models.ConfigurationFile diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs index 260e3447fe..0f431bea96 100644 --- a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs +++ b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs @@ -347,6 +347,22 @@ namespace Tgstation.Server.Host.Components.StaticFiles return result; } + /// + public async Task CreateDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken) + { + await EnsureDirectories(cancellationToken).ConfigureAwait(false); + var path = ValidateConfigRelativePath(configurationRelativePath); + + bool? result = null; + void DoCreate() => result = synchronousIOManager.CreateDirectory(path, cancellationToken); + if (systemIdentity == null) + await Task.Factory.StartNew(DoCreate, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); + else + await systemIdentity.RunImpersonated(DoCreate, cancellationToken).ConfigureAwait(false); + + return result.Value; + } + /// public Task StartAsync(CancellationToken cancellationToken) => EnsureDirectories(cancellationToken); diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs b/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs index 3fdce2aa9b..ebccc9c24e 100644 --- a/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs +++ b/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs @@ -48,6 +48,15 @@ namespace Tgstation.Server.Host.Components.StaticFiles /// A resulting in the of the file Task Read(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken); + /// + /// Create an empty directory at + /// + /// The relative path in the Configuration directory + /// The for the operation. If , the operation will be performed as the user of the + /// The for the operation. Usage may result in partial writes + /// A resulting in if the directory already existed, otherwise + Task CreateDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken); + /// /// Writes to a given /// diff --git a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs index e2b9620bab..f942863b37 100644 --- a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs +++ b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs @@ -127,5 +127,37 @@ namespace Tgstation.Server.Host.Controllers /// [TgsAuthorize(ConfigurationRights.List)] public override Task List(CancellationToken cancellationToken) => Directory(null, cancellationToken); + + /// + /// Create an empty directory at a + /// + /// The path of the directory to get + /// The for the operation + /// A resulting in the for the operation + [HttpPut("List/{*directoryPath}")] + [TgsAuthorize(ConfigurationRights.List)] + public async Task CreateDirectory(string directoryPath, CancellationToken cancellationToken) + { + if (ForbidDueToModeConflicts()) + return Forbid(); + + try + { + var result = new ConfigurationFile + { + IsDirectory = true, + Path = directoryPath + }; + return await instanceManager.GetInstance(Instance).Configuration.CreateDirectory(directoryPath, AuthenticationContext.SystemIdentity, cancellationToken).ConfigureAwait(false) ? (IActionResult)Json(result) : StatusCode((int)HttpStatusCode.Created); + } + catch (NotImplementedException) + { + return StatusCode((int)HttpStatusCode.NotImplemented); + } + catch (UnauthorizedAccessException) + { + return Forbid(); + } + } } } diff --git a/src/Tgstation.Server.Host/IO/ISynchronousIOManager.cs b/src/Tgstation.Server.Host/IO/ISynchronousIOManager.cs index 72b241645e..f361004dfe 100644 --- a/src/Tgstation.Server.Host/IO/ISynchronousIOManager.cs +++ b/src/Tgstation.Server.Host/IO/ISynchronousIOManager.cs @@ -24,6 +24,14 @@ namespace Tgstation.Server.Host.IO /// A of directory names in IEnumerable GetDirectories(string path, CancellationToken cancellationToken); + /// + /// Create an empty directory at + /// + /// The path to create + /// The for the operation. Usage may result in partial writes + /// if the directory already existed, otherwise + bool CreateDirectory(string path, CancellationToken cancellationToken); + /// /// Read the s of a file at a given /// diff --git a/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs b/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs index 4cd4b175a0..9a60da2313 100644 --- a/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs +++ b/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs @@ -11,6 +11,16 @@ namespace Tgstation.Server.Host.IO /// sealed class SynchronousIOManager : ISynchronousIOManager { + /// + public bool CreateDirectory(string path, CancellationToken cancellationToken) + { + if (IsDirectory(path)) + return true; + cancellationToken.ThrowIfCancellationRequested(); + Directory.CreateDirectory(path); + return false; + } + /// public IEnumerable GetDirectories(string path, CancellationToken cancellationToken) { From 2e7c159d75b2e22ae505a770e78cd0a5eef6737e Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 20 Aug 2018 13:30:08 -0400 Subject: [PATCH 21/23] Stops reinventing the wheel --- .../Controllers/ConfigurationController.cs | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs index f942863b37..aecf50f624 100644 --- a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs +++ b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs @@ -128,27 +128,15 @@ namespace Tgstation.Server.Host.Controllers [TgsAuthorize(ConfigurationRights.List)] public override Task List(CancellationToken cancellationToken) => Directory(null, cancellationToken); - /// - /// Create an empty directory at a - /// - /// The path of the directory to get - /// The for the operation - /// A resulting in the for the operation - [HttpPut("List/{*directoryPath}")] - [TgsAuthorize(ConfigurationRights.List)] - public async Task CreateDirectory(string directoryPath, CancellationToken cancellationToken) + /// + public override async Task Create(ConfigurationFile model, CancellationToken cancellationToken) { if (ForbidDueToModeConflicts()) return Forbid(); try { - var result = new ConfigurationFile - { - IsDirectory = true, - Path = directoryPath - }; - return await instanceManager.GetInstance(Instance).Configuration.CreateDirectory(directoryPath, AuthenticationContext.SystemIdentity, cancellationToken).ConfigureAwait(false) ? (IActionResult)Json(result) : StatusCode((int)HttpStatusCode.Created); + return await instanceManager.GetInstance(Instance).Configuration.CreateDirectory(model.Path, AuthenticationContext.SystemIdentity, cancellationToken).ConfigureAwait(false) ? (IActionResult)Json(model) : StatusCode((int)HttpStatusCode.Created, model); } catch (NotImplementedException) { From 66cf86c0b485b3732f62d7ed0ec551b14dcac2e3 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 20 Aug 2018 13:41:37 -0400 Subject: [PATCH 22/23] Fix create dir API --- docs/API.dox | 2 +- .../Controllers/ConfigurationController.cs | 3 ++- v4_prototype_TODO.txt | 2 -- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/API.dox b/docs/API.dox index d1fbde97e6..4d5c8a51fc 100644 --- a/docs/API.dox +++ b/docs/API.dox @@ -398,7 +398,7 @@ If you do not have access to list the requested directory, a 403 response will b To create an empty config directory use the following request -I POST "/Config/List/" => @ref Tgstation.Server.Api.Models.ConfigurationFile +I PUT "/Config" @ref Tgstation.Server.Api.Models.ConfigurationFile => @ref Tgstation.Server.Api.Models.ConfigurationFile To get the content of a static file use the following method diff --git a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs index aecf50f624..8823ee4de5 100644 --- a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs +++ b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs @@ -129,7 +129,8 @@ namespace Tgstation.Server.Host.Controllers public override Task List(CancellationToken cancellationToken) => Directory(null, cancellationToken); /// - public override async Task Create(ConfigurationFile model, CancellationToken cancellationToken) + [TgsAuthorize(ConfigurationRights.Write)] + public override async Task Create([FromBody] ConfigurationFile model, CancellationToken cancellationToken) { if (ForbidDueToModeConflicts()) return Forbid(); diff --git a/v4_prototype_TODO.txt b/v4_prototype_TODO.txt index 830b59ef57..3b97a1ae4d 100644 --- a/v4_prototype_TODO.txt +++ b/v4_prototype_TODO.txt @@ -6,5 +6,3 @@ Only show user name and ID when serializing to API In fact remove IApiConvertable<> altogether, it's not required by anything Chat channel tagging - -Directory create function \ No newline at end of file From 760b6f6661c84f4bdf09a553f0e71e61b5486a20 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 20 Aug 2018 13:42:15 -0400 Subject: [PATCH 23/23] Update postman scripts --- tools/TGS.postman_collection.json | 45 ++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/tools/TGS.postman_collection.json b/tools/TGS.postman_collection.json index c7f7374670..635c67dc90 100644 --- a/tools/TGS.postman_collection.json +++ b/tools/TGS.postman_collection.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "b7e61e38-6891-473e-8ab4-869e5cf41a96", + "_postman_id": "bb44d5ca-de20-4da3-880d-1ceacd54fe4a", "name": "TGS", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, @@ -2624,6 +2624,49 @@ } }, "response": [] + }, + { + "name": "Create NewDir", + "request": { + "method": "PUT", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "Api", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Instance", + "value": "1" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"path\": \"NewDir\"\n}" + }, + "url": { + "raw": "localhost:5000/Config", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Config" + ] + } + }, + "response": [] } ], "_postman_isSubFolder": true