mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-26 14:37:44 +01:00
Merge pull request #1017 from tgstation/FixMultiMerging [TGSDeploy][APIDeploy][NugetDeploy]
v4.2.5
This commit is contained in:
@@ -38,6 +38,7 @@ The following dependencies are required to run tgstation-server on Linux alongsi
|
||||
|
||||
- libc6-i386
|
||||
- libstdc++6:i386
|
||||
- libssl1.0.0
|
||||
- gcc-multilib (Only on 64-bit systems)
|
||||
|
||||
Note that tgstation-server has only ever been tested on Linux via it's [docker environment](build/Dockerfile#L22). If you are having trouble with something in a native installation, or figure out a required workaround, please contact project maintainers so this documentation may be better updated.
|
||||
|
||||
+2
-2
@@ -3,8 +3,8 @@
|
||||
<!-- This is the authorative version list -->
|
||||
<!-- Integration tests will ensure they match across the board -->
|
||||
<TgsCoreVersion>4.2.5</TgsCoreVersion>
|
||||
<TgsApiVersion>6.4.0</TgsApiVersion>
|
||||
<TgsClientVersion>6.3.0</TgsClientVersion>
|
||||
<TgsApiVersion>6.4.1</TgsApiVersion>
|
||||
<TgsClientVersion>7.0.0</TgsClientVersion>
|
||||
<TgsDmapiVersion>5.2.1</TgsDmapiVersion>
|
||||
<TgsControlPanelVersion>0.4.0</TgsControlPanelVersion>
|
||||
<TgsHostWatchdogVersion>1.1.0</TgsHostWatchdogVersion>
|
||||
|
||||
@@ -69,12 +69,12 @@ namespace Tgstation.Server.Api
|
||||
/// <summary>
|
||||
/// The client's user agent as a <see cref="ProductHeaderValue"/> if valid
|
||||
/// </summary>
|
||||
public ProductHeaderValue UserAgent => ProductInfoHeaderValue.TryParse(RawUserAgent, out var userAgent) ? userAgent.Product : null;
|
||||
public ProductHeaderValue? UserAgent => ProductInfoHeaderValue.TryParse(RawUserAgent, out var userAgent) ? userAgent.Product : null;
|
||||
|
||||
/// <summary>
|
||||
/// The client's raw user agent
|
||||
/// </summary>
|
||||
public string RawUserAgent { get; }
|
||||
public string? RawUserAgent { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The client's API version
|
||||
@@ -84,17 +84,17 @@ namespace Tgstation.Server.Api
|
||||
/// <summary>
|
||||
/// The client's JWT
|
||||
/// </summary>
|
||||
public string Token { get; }
|
||||
public string? Token { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The client's username
|
||||
/// </summary>
|
||||
public string Username { get; }
|
||||
public string? Username { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The client's password
|
||||
/// </summary>
|
||||
public string Password { get; }
|
||||
public string? Password { get; }
|
||||
|
||||
/// <summary>
|
||||
/// If the header uses password or JWT authentication
|
||||
@@ -236,7 +236,7 @@ namespace Tgstation.Server.Api
|
||||
/// <param name="token">The value of <see cref="Token"/></param>
|
||||
/// <param name="username">The value of <see cref="Username"/></param>
|
||||
/// <param name="password">The value of <see cref="Password"/></param>
|
||||
ApiHeaders(ProductHeaderValue userAgent, string token, string username, string password)
|
||||
ApiHeaders(ProductHeaderValue userAgent, string? token, string? username, string? password)
|
||||
{
|
||||
RawUserAgent = userAgent?.ToString();
|
||||
Token = token;
|
||||
@@ -274,7 +274,7 @@ namespace Tgstation.Server.Api
|
||||
|
||||
headers.UserAgent.Add(new ProductInfoHeaderValue(UserAgent));
|
||||
headers.Add(ApiVersionHeader, new ProductHeaderValue(AssemblyName.Name, ApiVersion.ToString()).ToString());
|
||||
instanceId = instanceId ?? InstanceId;
|
||||
instanceId ??= InstanceId;
|
||||
if (instanceId.HasValue)
|
||||
headers.Add(InstanceIdHeader, instanceId.ToString());
|
||||
}
|
||||
|
||||
@@ -15,16 +15,16 @@ namespace Tgstation.Server.Api.Models
|
||||
/// <summary>
|
||||
/// The GitHub repository the server is built to recieve updates from
|
||||
/// </summary>
|
||||
public Uri TrackedRepositoryUrl { get; set; }
|
||||
public Uri? TrackedRepositoryUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The latest available version of the Tgstation.Server.Host assembly from the upstream repository. If <see cref="Version.Major"/> is higher than <see cref="NewVersion"/>'s the update cannot be applied due to API changes
|
||||
/// </summary>
|
||||
public Version LatestVersion { get; set; }
|
||||
public Version? LatestVersion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Changes the version of Tgstation.Server.Host to the given version from the upstream repository
|
||||
/// </summary>
|
||||
public Version NewVersion { get; set; }
|
||||
public Version? NewVersion { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,11 +10,11 @@ namespace Tgstation.Server.Api.Models
|
||||
/// <summary>
|
||||
/// The <see cref="System.Version"/> of the <see cref="Byond"/> installation used for new compiles. Will be <see langword="null"/> if the user does not have permission to view it or there is no BYOND version installed. Only considers the <see cref="Version.Major"/> and <see cref="Version.Minor"/> numbers.
|
||||
/// </summary>
|
||||
public Version Version { get; set; }
|
||||
public Version? Version { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Job"/> being used to install a new <see cref="Version"/>
|
||||
/// </summary>
|
||||
public Job InstallJob { get; set; }
|
||||
public Job? InstallJob { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Tgstation.Server.Api.Models
|
||||
/// <summary>
|
||||
/// Channels the Discord bot should listen/announce in
|
||||
/// </summary>
|
||||
public ICollection<ChatChannel> Channels { get; set; }
|
||||
public ICollection<ChatChannel>? Channels { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Validates <see cref="Channels"/> are correct for the <see cref="Internal.ChatBot.Provider"/>
|
||||
@@ -20,15 +20,12 @@ namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
if (!Provider.HasValue)
|
||||
return true;
|
||||
switch (Provider.Value)
|
||||
return Provider.Value switch
|
||||
{
|
||||
case ChatProvider.Discord:
|
||||
return Channels?.Select(x => x.DiscordChannelId.HasValue && x.IrcChannel == null).All(x => x) ?? true;
|
||||
case ChatProvider.Irc:
|
||||
return Channels?.Select(x => !x.DiscordChannelId.HasValue && x.IrcChannel != null).All(x => x) ?? true;
|
||||
default:
|
||||
throw new InvalidOperationException("Invalid provider type!");
|
||||
}
|
||||
ChatProvider.Discord => Channels?.Select(x => x.DiscordChannelId.HasValue && x.IrcChannel == null).All(x => x) ?? true,
|
||||
ChatProvider.Irc => Channels?.Select(x => !x.DiscordChannelId.HasValue && x.IrcChannel != null).All(x => x) ?? true,
|
||||
_ => throw new InvalidOperationException("Invalid provider type!"),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Tgstation.Server.Api.Models
|
||||
/// If multiple copies of the same channel with different keys are added to the server, the one that will be used is undefined.
|
||||
/// </summary>
|
||||
[StringLength(Limits.MaximumIndexableStringLength)]
|
||||
public string IrcChannel { get; set; }
|
||||
public string? IrcChannel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Discord channel ID
|
||||
@@ -41,6 +41,6 @@ namespace Tgstation.Server.Api.Models
|
||||
/// A custom tag users can define to group channels together
|
||||
/// </summary>
|
||||
[StringLength(Limits.MaximumStringLength)]
|
||||
public string Tag { get; set; }
|
||||
public string? Tag { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,16 +8,16 @@ namespace Tgstation.Server.Api.Models
|
||||
/// <summary>
|
||||
/// The <see cref="Job"/> relating to this job
|
||||
/// </summary>
|
||||
public Job Job { get; set; }
|
||||
public Job? Job { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Git revision the compiler ran on. Not modifiable
|
||||
/// </summary>
|
||||
public RevisionInformation RevisionInformation { get; set; }
|
||||
public RevisionInformation? RevisionInformation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Byond.Version"/> the <see cref="CompileJob"/> was made with
|
||||
/// </summary>
|
||||
public Version ByondVersion { get; set; }
|
||||
public Version? ByondVersion { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Tgstation.Server.Api.Models
|
||||
/// The path to the <see cref="ConfigurationFile"/> file
|
||||
/// </summary>
|
||||
[StringLength(Limits.MaximumStringLength)]
|
||||
public string Path { get; set; }
|
||||
public string? Path { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If access to the <see cref="ConfigurationFile"/> file was denied for the operation
|
||||
@@ -26,13 +26,13 @@ namespace Tgstation.Server.Api.Models
|
||||
/// <summary>
|
||||
/// The MD5 hash of the file when last read by the user. If this doesn't match during update actions, the write will be denied with <see cref="System.Net.HttpStatusCode.Conflict"/>
|
||||
/// </summary>
|
||||
public string LastReadHash { get; set; }
|
||||
public string? LastReadHash { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The content of the <see cref="ConfigurationFile"/>. Will be <see langword="null"/> if <see cref="AccessDenied"/> is <see langword="true"/> or during listing and write operations
|
||||
/// </summary>
|
||||
#pragma warning disable CA1819 // Properties should not return arrays
|
||||
public byte[] Content { get; set; }
|
||||
#pragma warning restore CA1819 // Properties should not return arrays
|
||||
#pragma warning disable CA1819, SA1011 // Properties should not return arrays, Closing square bracket should be followed by a space
|
||||
public byte[]? Content { get; set; }
|
||||
#pragma warning restore CA1819, SA1011 // Properties should not return arrays, Closing square bracket should be followed by a space
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Tgstation.Server.Api.Models
|
||||
/// The Discord bot token
|
||||
/// </summary>
|
||||
/// <remarks>See https://discordapp.com/developers/docs/topics/oauth2#bots</remarks>
|
||||
public string BotToken { get; set; }
|
||||
public string? BotToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="DiscordConnectionStringBuilder"/>
|
||||
@@ -32,6 +32,6 @@ namespace Tgstation.Server.Api.Models
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString() => BotToken;
|
||||
public override string ToString() => BotToken ?? "(null)";
|
||||
}
|
||||
}
|
||||
@@ -11,12 +11,12 @@ namespace Tgstation.Server.Api.Models
|
||||
/// <summary>
|
||||
/// The live revision
|
||||
/// </summary>
|
||||
public CompileJob ActiveCompileJob { get; set; }
|
||||
public CompileJob? ActiveCompileJob { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The next revision to go live
|
||||
/// </summary>
|
||||
public CompileJob StagedCompileJob { get; set; }
|
||||
public CompileJob? StagedCompileJob { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The current status of <see cref="DreamDaemon"/>
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Tgstation.Server.Api.Models
|
||||
/// The .dme file <see cref="DreamMaker"/> tries to compile with without the extension
|
||||
/// </summary>
|
||||
[StringLength(Limits.MaximumStringLength)]
|
||||
public string ProjectName { get; set; }
|
||||
public string? ProjectName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The port used during compilation to validate the DMAPI
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.ComponentModel;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace Tgstation.Server.Api.Models
|
||||
@@ -13,9 +14,9 @@ namespace Tgstation.Server.Api.Models
|
||||
/// </summary>
|
||||
/// <param name="errorCode">The <see cref="ErrorCode"/> to describe.</param>
|
||||
/// <returns>A description of the <paramref name="errorCode"/> on success, <see langword="null"/> on failure.</returns>
|
||||
public static string Describe(this ErrorCode errorCode)
|
||||
public static string? Describe(this ErrorCode errorCode)
|
||||
{
|
||||
var attributes = (DescriptionAttribute[])typeof(ErrorCode)
|
||||
var attributes = (IEnumerable<DescriptionAttribute>?)typeof(ErrorCode)
|
||||
.GetField(errorCode.ToString())
|
||||
?.GetCustomAttributes(typeof(DescriptionAttribute), false);
|
||||
|
||||
|
||||
@@ -12,18 +12,18 @@ namespace Tgstation.Server.Api.Models
|
||||
/// The version of the API the server is using
|
||||
/// </summary>
|
||||
[Required]
|
||||
public Version ServerApiVersion { get; set; }
|
||||
public Version? ServerApiVersion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A human readable description of the error
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string Message { get; set; }
|
||||
public string? Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Additional data associated with the error message.
|
||||
/// </summary>
|
||||
public string AdditionalData { get; set; }
|
||||
public string? AdditionalData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ErrorCode"/> of the <see cref="ErrorMessage"/>.
|
||||
@@ -31,6 +31,11 @@ namespace Tgstation.Server.Api.Models
|
||||
[EnumDataType(typeof(ErrorCode))]
|
||||
public ErrorCode ErrorCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ErrorMessage"/> <see langword="class"/>.
|
||||
/// </summary>
|
||||
public ErrorMessage() { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ErrorMessage"/> <see langword="class"/>.
|
||||
/// </summary>
|
||||
|
||||
@@ -18,13 +18,13 @@ namespace Tgstation.Server.Api.Models
|
||||
/// </summary>
|
||||
[Required]
|
||||
[StringLength(Limits.MaximumStringLength)]
|
||||
public string Name { get; set; }
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The path to where the <see cref="Instance"/> is located. Can only be changed while the <see cref="Instance"/> is offline. Must not exist when the instance is created
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string Path { get; set; }
|
||||
public string? Path { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the <see cref="Instance"/> is online
|
||||
@@ -56,7 +56,7 @@ namespace Tgstation.Server.Api.Models
|
||||
/// </summary>
|
||||
/// <remarks>Due to how <see cref="Job"/>s are children of <see cref="Instance"/>s but moving one requires the <see cref="Instance"/> to be offline, interactions with this <see cref="Job"/> are performed in a non-standard fashion. The <see cref="Job"/> is read by querying the <see cref="Instance"/> again (either via list or ID lookup) and cancelled by making any sort of update to the <see cref="Instance"/>. Once the <see cref="Instance"/> comes back <see cref="Online"/> it can be queried like a normal job</remarks>
|
||||
[NotMapped]
|
||||
public Job MoveJob { get; set; }
|
||||
public Job? MoveJob { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a clone of the essential <see cref="Instance"/> metadata
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Tgstation.Server.Api.Models.Internal
|
||||
/// </summary>
|
||||
[Required]
|
||||
[StringLength(Limits.MaximumIndexableStringLength)]
|
||||
public string Name { get; set; }
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the connection is enabled
|
||||
@@ -50,25 +50,22 @@ namespace Tgstation.Server.Api.Models.Internal
|
||||
/// </summary>
|
||||
[Required]
|
||||
[StringLength(Limits.MaximumStringLength)]
|
||||
public string ConnectionString { get; set; }
|
||||
public string? ConnectionString { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="ChatConnectionStringBuilder"/> which maps to the <see cref="ConnectionString"/>.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="ChatConnectionStringBuilder"/> for the <see cref="ChatBot"/>.</returns>
|
||||
public ChatConnectionStringBuilder CreateConnectionStringBuilder()
|
||||
public ChatConnectionStringBuilder? CreateConnectionStringBuilder()
|
||||
{
|
||||
if (ConnectionString == null)
|
||||
return null;
|
||||
switch (Provider)
|
||||
return Provider switch
|
||||
{
|
||||
case ChatProvider.Discord:
|
||||
return new DiscordConnectionStringBuilder(ConnectionString);
|
||||
case ChatProvider.Irc:
|
||||
return new IrcConnectionStringBuilder(ConnectionString);
|
||||
default:
|
||||
throw new InvalidOperationException("Invalid Provider!");
|
||||
}
|
||||
ChatProvider.Discord => new DiscordConnectionStringBuilder(ConnectionString),
|
||||
ChatProvider.Irc => new IrcConnectionStringBuilder(ConnectionString),
|
||||
_ => throw new InvalidOperationException("Invalid Provider!"),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -77,7 +74,7 @@ namespace Tgstation.Server.Api.Models.Internal
|
||||
/// <param name="stringBuilder">The optional <see cref="ChatConnectionStringBuilder"/>.</param>
|
||||
public void SetConnectionStringBuilder(ChatConnectionStringBuilder stringBuilder)
|
||||
{
|
||||
ConnectionString = stringBuilder?.ToString();
|
||||
ConnectionString = stringBuilder?.ToString() ?? throw new ArgumentNullException(nameof(stringBuilder));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,13 +13,13 @@ namespace Tgstation.Server.Api.Models.Internal
|
||||
/// The .dme file used for compilation
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string DmeName { get; set; }
|
||||
public string? DmeName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Textual output of DM
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string Output { get; set; }
|
||||
public string? Output { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Game folder the results were compiled into
|
||||
@@ -37,6 +37,6 @@ namespace Tgstation.Server.Api.Models.Internal
|
||||
/// The DMAPI <see cref="Version"/>.
|
||||
/// </summary>
|
||||
[NotMapped]
|
||||
public virtual Version DMApiVersion { get; set; }
|
||||
public virtual Version? DMApiVersion { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Tgstation.Server.Api.Models.Internal
|
||||
/// English description of the <see cref="Job"/>
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string Description { get; set; }
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Models.ErrorCode"/> associated with the <see cref="Job"/> if any.
|
||||
@@ -23,7 +23,7 @@ namespace Tgstation.Server.Api.Models.Internal
|
||||
/// <summary>
|
||||
/// Details of any exceptions caught during the <see cref="Job"/>
|
||||
/// </summary>
|
||||
public string ExceptionDetails { get; set; }
|
||||
public string? ExceptionDetails { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When the <see cref="Job"/> was started
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Tgstation.Server.Api.Models.Internal
|
||||
/// </summary>
|
||||
[Required]
|
||||
[StringLength(Limits.MaximumStringLength)]
|
||||
public string CommitterName { get; set; }
|
||||
public string? CommitterName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The e-mail of the committer
|
||||
@@ -20,19 +20,19 @@ namespace Tgstation.Server.Api.Models.Internal
|
||||
[Required]
|
||||
[StringLength(Limits.MaximumStringLength)]
|
||||
[EmailAddress]
|
||||
public string CommitterEmail { get; set; }
|
||||
public string? CommitterEmail { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The username to access the git repository with
|
||||
/// </summary>
|
||||
[StringLength(Limits.MaximumStringLength)]
|
||||
public string AccessUser { get; set; }
|
||||
public string? AccessUser { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The token/password to access the git repository with
|
||||
/// </summary>
|
||||
[StringLength(Limits.MaximumStringLength)]
|
||||
public string AccessToken { get; set; }
|
||||
public string? AccessToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If commits created from testmerges are pushed to the remote
|
||||
|
||||
@@ -12,13 +12,13 @@ namespace Tgstation.Server.Api.Models.Internal
|
||||
/// </summary>
|
||||
[Required]
|
||||
[StringLength(40)]
|
||||
public string CommitSha { get; set; }
|
||||
public string? CommitSha { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The sha of the most recent remote commit
|
||||
/// </summary>
|
||||
[Required]
|
||||
[StringLength(40)]
|
||||
public string OriginCommitSha { get; set; }
|
||||
public string? OriginCommitSha { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,6 @@ namespace Tgstation.Server.Api.Models.Internal
|
||||
/// <summary>
|
||||
/// Limits the locations instances may be created or attached from.
|
||||
/// </summary>
|
||||
public ICollection<string> ValidInstancePaths { get; set; }
|
||||
public ICollection<string>? ValidInstancePaths { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,27 +12,27 @@ namespace Tgstation.Server.Api.Models.Internal
|
||||
/// The title of the pull request
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string TitleAtMerge { get; set; }
|
||||
public string? TitleAtMerge { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The body of the pull request
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string BodyAtMerge { get; set; }
|
||||
public string? BodyAtMerge { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The URL of the pull request
|
||||
/// </summary>
|
||||
[Required]
|
||||
#pragma warning disable CA1056 // Uri properties should not be strings
|
||||
public string Url { get; set; }
|
||||
public string? Url { get; set; }
|
||||
#pragma warning restore CA1056 // Uri properties should not be strings
|
||||
|
||||
/// <summary>
|
||||
/// The author of the pull request
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string Author { get; set; }
|
||||
public string? Author { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="TestMergeBase"/>
|
||||
|
||||
@@ -31,14 +31,14 @@ namespace Tgstation.Server.Api.Models.Internal
|
||||
/// The SID/UID of the <see cref="User"/> on Windows/POSIX respectively
|
||||
/// </summary>
|
||||
// No need for StringLength as the server MUST validate it.
|
||||
public string SystemIdentifier { get; set; }
|
||||
public string? SystemIdentifier { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the <see cref="User"/>
|
||||
/// </summary>
|
||||
[Required]
|
||||
[StringLength(Limits.MaximumStringLength)]
|
||||
public string Name { get; set; }
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Rights.AdministrationRights"/> for the <see cref="User"/>
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Tgstation.Server.Api.Models
|
||||
/// <summary>
|
||||
/// The IP address or URL of the IRC server
|
||||
/// </summary>
|
||||
public string Address { get; set; }
|
||||
public string? Address { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The port the server runs on
|
||||
@@ -26,7 +26,7 @@ namespace Tgstation.Server.Api.Models
|
||||
/// <summary>
|
||||
/// The nickname for the bot to use
|
||||
/// </summary>
|
||||
public string Nickname { get; set; }
|
||||
public string? Nickname { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the connection should be made using SSL
|
||||
@@ -41,7 +41,7 @@ namespace Tgstation.Server.Api.Models
|
||||
/// <summary>
|
||||
/// The optional password to use
|
||||
/// </summary>
|
||||
public string Password { get; set; }
|
||||
public string? Password { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="IrcConnectionStringBuilder"/>
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
/// <summary>
|
||||
/// The <see cref="User"/> that started the job
|
||||
/// </summary>
|
||||
public User StartedBy { get; set; }
|
||||
public User? StartedBy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="User"/> that cancelled the job
|
||||
/// </summary>
|
||||
public User CancelledBy { get; set; }
|
||||
public User? CancelledBy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional progress between 0 and 100 inclusive
|
||||
|
||||
@@ -10,32 +10,32 @@ namespace Tgstation.Server.Api.Models
|
||||
/// <summary>
|
||||
/// The origin URL. If <see langword="null"/>, the <see cref="Repository"/> does not exist
|
||||
/// </summary>
|
||||
public string Origin { get; set; }
|
||||
public string? Origin { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The commit HEAD should point to. Not populated in responses, use <see cref="RevisionInformation"/> instead for retrieval
|
||||
/// </summary>
|
||||
public string CheckoutSha { get; set; }
|
||||
public string? CheckoutSha { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The current <see cref="Models.RevisionInformation"/> for the <see cref="Repository"/>
|
||||
/// </summary>
|
||||
public RevisionInformation RevisionInformation { get; set; }
|
||||
public RevisionInformation? RevisionInformation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the repository was cloned from GitHub.com this will be set with the owner of the repository
|
||||
/// </summary>
|
||||
public string GitHubOwner { get; set; }
|
||||
public string? GitHubOwner { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the repository was cloned from GitHub.com this will be set with the name of the repository
|
||||
/// </summary>
|
||||
public string GitHubName { get; set; }
|
||||
public string? GitHubName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Job"/> started by the <see cref="Repository"/> if any
|
||||
/// </summary>
|
||||
public Job ActiveJob { get; set; }
|
||||
public Job? ActiveJob { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Do the equivalent of a git pull. Will attempt to merge unless <see cref="Reference"/> is also specified in which case a hard reset will be performed after checking out
|
||||
@@ -45,11 +45,11 @@ namespace Tgstation.Server.Api.Models
|
||||
/// <summary>
|
||||
/// The branch or tag HEAD points to
|
||||
/// </summary>
|
||||
public string Reference { get; set; }
|
||||
public string? Reference { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="TestMergeParameters"/> for new <see cref="TestMerge"/>s. Note that merges that conflict will not be performed
|
||||
/// </summary>
|
||||
public List<TestMergeParameters> NewTestMerges { get; set; }
|
||||
public ICollection<TestMergeParameters>? NewTestMerges { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -8,16 +8,16 @@ namespace Tgstation.Server.Api.Models
|
||||
/// <summary>
|
||||
/// The <see cref="TestMerge"/> that was created with this <see cref="RevisionInformation"/>
|
||||
/// </summary>
|
||||
public TestMerge PrimaryTestMerge { get; set; }
|
||||
public TestMerge? PrimaryTestMerge { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="TestMerge"/>s active in the <see cref="RevisionInformation"/>
|
||||
/// </summary>
|
||||
public ICollection<TestMerge> ActiveTestMerges { get; set; }
|
||||
public ICollection<TestMerge>? ActiveTestMerges { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="CompileJob"/>s made from the <see cref="RevisionInformation"/>
|
||||
/// </summary>
|
||||
public ICollection<CompileJob> CompileJobs { get; set; }
|
||||
public ICollection<CompileJob>? CompileJobs { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,16 +10,16 @@ namespace Tgstation.Server.Api.Models
|
||||
/// <summary>
|
||||
/// The version of the host
|
||||
/// </summary>
|
||||
public Version Version { get; set; }
|
||||
public Version? Version { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Api"/> version of the host
|
||||
/// </summary>
|
||||
public Version ApiVersion { get; set; }
|
||||
public Version? ApiVersion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The DMAPI version of the host.
|
||||
/// </summary>
|
||||
public Version DMApiVersion { get; set; }
|
||||
public Version? DMApiVersion { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,6 @@
|
||||
/// <summary>
|
||||
/// The <see cref="User"/> who created the <see cref="TestMerge"/>
|
||||
/// </summary>
|
||||
public User MergedBy { get; set; }
|
||||
public User? MergedBy { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -17,12 +17,12 @@ namespace Tgstation.Server.Api.Models
|
||||
/// </summary>
|
||||
[Required]
|
||||
[StringLength(40)]
|
||||
public string PullRequestRevision { get; set; }
|
||||
public string? PullRequestRevision { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional comment about the test
|
||||
/// </summary>
|
||||
[StringLength(Limits.MaximumStringLength)]
|
||||
public string Comment { get; set; }
|
||||
public string? Comment { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ namespace Tgstation.Server.Api.Models
|
||||
/// <summary>
|
||||
/// The value of the JWT
|
||||
/// </summary>
|
||||
public string Bearer { get; set; }
|
||||
public string? Bearer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When the <see cref="Token"/> expires
|
||||
|
||||
@@ -6,16 +6,16 @@
|
||||
/// <summary>
|
||||
/// The name of the default admin user
|
||||
/// </summary>
|
||||
public const string AdminName = "Admin";
|
||||
public static readonly string AdminName = "Admin";
|
||||
|
||||
/// <summary>
|
||||
/// The default admin password
|
||||
/// </summary>
|
||||
public const string DefaultAdminPassword = "ISolemlySwearToDeleteTheDataDirectory";
|
||||
public static readonly string DefaultAdminPassword = "ISolemlySwearToDeleteTheDataDirectory";
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="User"/> who created this <see cref="User"/>
|
||||
/// </summary>
|
||||
public User CreatedBy { get; set; }
|
||||
public User? CreatedBy { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,6 @@ namespace Tgstation.Server.Api.Models
|
||||
/// Cleartext password of the <see cref="User"/>
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string Password { get; set; }
|
||||
public string? Password { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,15 +2,16 @@
|
||||
<Import Project="../../build/Version.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<DebugType>Full</DebugType>
|
||||
<Version>$(TgsApiVersion)</Version>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<Authors>Cyberboss</Authors>
|
||||
<Company>/tg/station</Company>
|
||||
<Company>/tg/station 13</Company>
|
||||
<Description>API definitions for tgstation-server</Description>
|
||||
<PackageProjectUrl>https://tgstation.github.io/tgstation-server</PackageProjectUrl>
|
||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||
<PackageIcon>tgs.png</PackageIcon>
|
||||
<PackageProjectUrl>https://tgstation.github.io/tgstation-server</PackageProjectUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<RepositoryUrl>https://github.com/tgstation/tgstation-server</RepositoryUrl>
|
||||
<Copyright>2018</Copyright>
|
||||
@@ -18,21 +19,16 @@
|
||||
<PackageReleaseNotes>See https://github.com/tgstation/tgstation-server/releases/tag/api-v$(TgsApiVersion)</PackageReleaseNotes>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
<Version>$(TgsApiVersion)</Version>
|
||||
<CodeAnalysisRuleSet>../../build/analyzers.ruleset</CodeAnalysisRuleSet>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<DocumentationFile>bin\$(Configuration)\netstandard2.1\Tgstation.Server.Api.xml</DocumentationFile>
|
||||
<NoWarn>CA1028</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Release'">
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<WarningsAsErrors />
|
||||
<DocumentationFile>bin\Release\netstandard2.1\Tgstation.Server.Api.xml</DocumentationFile>
|
||||
<NoWarn>1701;1702;CA1028</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<NoWarn>1701;1702;CA1028</NoWarn>
|
||||
<DocumentationFile>bin\Debug\netstandard2.1\Tgstation.Server.Api.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -44,6 +44,16 @@ namespace Tgstation.Server.Client
|
||||
/// </summary>
|
||||
readonly List<IRequestLogger> requestLoggers;
|
||||
|
||||
/// <summary>
|
||||
/// Backing field for <see cref="Headers"/>
|
||||
/// </summary>
|
||||
readonly ApiHeaders? tokenRefreshHeaders;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="SemaphoreSlim"/> for <see cref="Token"/> refreshes.
|
||||
/// </summary>
|
||||
readonly SemaphoreSlim semaphoreSlim;
|
||||
|
||||
/// <summary>
|
||||
/// Backing field for <see cref="Headers"/>
|
||||
/// </summary>
|
||||
@@ -57,7 +67,7 @@ namespace Tgstation.Server.Client
|
||||
|
||||
static void HandleBadResponse(HttpResponseMessage response, string json)
|
||||
{
|
||||
ErrorMessage errorMessage = null;
|
||||
ErrorMessage? errorMessage = null;
|
||||
try
|
||||
{
|
||||
// check if json serializes to an error message
|
||||
@@ -101,18 +111,25 @@ namespace Tgstation.Server.Client
|
||||
/// </summary>
|
||||
/// <param name="httpClient">The value of <see cref="httpClient"/></param>
|
||||
/// <param name="url">The value of <see cref="Url"/></param>
|
||||
/// <param name="apiHeaders">The value of <see cref="ApiHeaders"/></param>
|
||||
public ApiClient(IHttpClient httpClient, Uri url, ApiHeaders apiHeaders)
|
||||
/// <param name="apiHeaders">The value of <see cref="Headers"/></param>
|
||||
/// <param name="tokenRefreshHeaders">The value of <see cref="tokenRefreshHeaders"/></param>
|
||||
public ApiClient(IHttpClient httpClient, Uri url, ApiHeaders apiHeaders, ApiHeaders? tokenRefreshHeaders)
|
||||
{
|
||||
this.httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
|
||||
Url = url ?? throw new ArgumentNullException(nameof(url));
|
||||
headers = apiHeaders ?? throw new ArgumentNullException(nameof(apiHeaders));
|
||||
this.tokenRefreshHeaders = tokenRefreshHeaders;
|
||||
|
||||
requestLoggers = new List<IRequestLogger>();
|
||||
semaphoreSlim = new SemaphoreSlim(1);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() => httpClient.Dispose();
|
||||
public void Dispose()
|
||||
{
|
||||
httpClient.Dispose();
|
||||
semaphoreSlim.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Main request method
|
||||
@@ -122,9 +139,10 @@ namespace Tgstation.Server.Client
|
||||
/// <param name="body">The body of the request</param>
|
||||
/// <param name="method">The method of the request</param>
|
||||
/// <param name="instanceId">The optional <see cref="Instance.Id"/> for the request</param>
|
||||
/// <param name="tokenRefresh">If this is a token refresh operation.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the response on success</returns>
|
||||
async Task<TResult> RunRequest<TResult>(string route, object body, HttpMethod method, long? instanceId, CancellationToken cancellationToken)
|
||||
async Task<TResult> RunRequest<TResult>(string route, object? body, HttpMethod method, long? instanceId, bool tokenRefresh, CancellationToken cancellationToken)
|
||||
{
|
||||
if (route == null)
|
||||
throw new ArgumentNullException(nameof(route));
|
||||
@@ -141,11 +159,25 @@ namespace Tgstation.Server.Client
|
||||
if (body != null)
|
||||
request.Content = new StringContent(JsonConvert.SerializeObject(body, serializerSettings), Encoding.UTF8, ApiHeaders.ApplicationJson);
|
||||
|
||||
headers.SetRequestHeaders(request.Headers, instanceId);
|
||||
var headersToUse = tokenRefresh ? tokenRefreshHeaders! : headers;
|
||||
headersToUse.SetRequestHeaders(request.Headers, instanceId);
|
||||
|
||||
await Task.WhenAll(requestLoggers.Select(x => x.LogRequest(request, cancellationToken))).ConfigureAwait(false);
|
||||
// This is meant to be a gate against token refresh operations
|
||||
await semaphoreSlim.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
if(!tokenRefresh)
|
||||
semaphoreSlim.Release();
|
||||
|
||||
response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await Task.WhenAll(requestLoggers.Select(x => x.LogRequest(request, cancellationToken))).ConfigureAwait(false);
|
||||
|
||||
response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (tokenRefresh)
|
||||
semaphoreSlim.Release();
|
||||
}
|
||||
}
|
||||
|
||||
using (response)
|
||||
@@ -155,14 +187,20 @@ namespace Tgstation.Server.Client
|
||||
var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
if (!tokenRefresh
|
||||
&& response.StatusCode == HttpStatusCode.Unauthorized
|
||||
&& await RefreshToken(cancellationToken).ConfigureAwait(false))
|
||||
return await RunRequest<TResult>(route, body, method, instanceId, false, cancellationToken).ConfigureAwait(false);
|
||||
HandleBadResponse(response, json);
|
||||
}
|
||||
|
||||
if (String.IsNullOrWhiteSpace(json))
|
||||
json = JsonConvert.SerializeObject(new object());
|
||||
|
||||
try
|
||||
{
|
||||
return JsonConvert.DeserializeObject<TResult>(json, serializerSettings);
|
||||
return JsonConvert.DeserializeObject<TResult>(json, serializerSettings) !;
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
@@ -171,50 +209,68 @@ namespace Tgstation.Server.Client
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<TResult> Create<TResult>(string route, CancellationToken cancellationToken) => RunRequest<TResult>(route, new object(), HttpMethod.Put, null, cancellationToken);
|
||||
async Task<bool> RefreshToken(CancellationToken cancellationToken)
|
||||
{
|
||||
if (tokenRefreshHeaders == null)
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
var token = await RunRequest<Token>(Routes.Root, null, HttpMethod.Post, null, true, cancellationToken);
|
||||
headers = new ApiHeaders(headers.UserAgent!, token.Bearer!);
|
||||
}
|
||||
catch (ClientException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<TResult> Read<TResult>(string route, CancellationToken cancellationToken) => RunRequest<TResult>(route, null, HttpMethod.Get, null, cancellationToken);
|
||||
public Task<TResult> Create<TResult>(string route, CancellationToken cancellationToken) => RunRequest<TResult>(route, new object(), HttpMethod.Put, null, false, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<TResult> Update<TResult>(string route, CancellationToken cancellationToken) => RunRequest<TResult>(route, new object(), HttpMethod.Post, null, cancellationToken);
|
||||
public Task<TResult> Read<TResult>(string route, CancellationToken cancellationToken) => RunRequest<TResult>(route, null, HttpMethod.Get, null, false, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<TResult> Update<TBody, TResult>(string route, TBody body, CancellationToken cancellationToken) => RunRequest<TResult>(route, body, HttpMethod.Post, null, cancellationToken);
|
||||
public Task<TResult> Update<TResult>(string route, CancellationToken cancellationToken) => RunRequest<TResult>(route, new object(), HttpMethod.Post, null, false, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Update<TBody>(string route, TBody body, CancellationToken cancellationToken) => RunRequest<object>(route, body, HttpMethod.Post, null, cancellationToken);
|
||||
public Task<TResult> Update<TBody, TResult>(string route, TBody body, CancellationToken cancellationToken) => RunRequest<TResult>(route, body, HttpMethod.Post, null, false, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<TResult> Create<TBody, TResult>(string route, TBody body, CancellationToken cancellationToken) => RunRequest<TResult>(route, body, HttpMethod.Put, null, cancellationToken);
|
||||
public Task Update<TBody>(string route, TBody body, CancellationToken cancellationToken) => RunRequest<object>(route, body, HttpMethod.Post, null, false, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Delete(string route, CancellationToken cancellationToken) => RunRequest<object>(route, null, HttpMethod.Delete, null, cancellationToken);
|
||||
public Task<TResult> Create<TBody, TResult>(string route, TBody body, CancellationToken cancellationToken) => RunRequest<TResult>(route, body, HttpMethod.Put, null, false, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<TResult> Create<TBody, TResult>(string route, TBody body, long instanceId, CancellationToken cancellationToken) => RunRequest<TResult>(route, body, HttpMethod.Put, instanceId, cancellationToken);
|
||||
public Task Delete(string route, CancellationToken cancellationToken) => RunRequest<object>(route, null, HttpMethod.Delete, null, false, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<TResult> Read<TResult>(string route, long instanceId, CancellationToken cancellationToken) => RunRequest<TResult>(route, null, HttpMethod.Get, instanceId, cancellationToken);
|
||||
public Task<TResult> Create<TBody, TResult>(string route, TBody body, long instanceId, CancellationToken cancellationToken) => RunRequest<TResult>(route, body, HttpMethod.Put, instanceId, false, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<TResult> Update<TBody, TResult>(string route, TBody body, long instanceId, CancellationToken cancellationToken) => RunRequest<TResult>(route, body, HttpMethod.Post, instanceId, cancellationToken);
|
||||
public Task<TResult> Read<TResult>(string route, long instanceId, CancellationToken cancellationToken) => RunRequest<TResult>(route, null, HttpMethod.Get, instanceId, false, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Delete(string route, long instanceId, CancellationToken cancellationToken) => RunRequest<object>(route, null, HttpMethod.Delete, instanceId, cancellationToken);
|
||||
public Task<TResult> Update<TBody, TResult>(string route, TBody body, long instanceId, CancellationToken cancellationToken) => RunRequest<TResult>(route, body, HttpMethod.Post, instanceId, false, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Delete<TBody>(string route, TBody body, long instanceId, CancellationToken cancellationToken) => RunRequest<object>(route, body, HttpMethod.Delete, instanceId, cancellationToken);
|
||||
public Task Delete(string route, long instanceId, CancellationToken cancellationToken) => RunRequest<object>(route, null, HttpMethod.Delete, instanceId, false, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<TResult> Delete<TResult>(string route, long instanceId, CancellationToken cancellationToken) => RunRequest<TResult>(route, null, HttpMethod.Delete, instanceId, cancellationToken);
|
||||
public Task Delete<TBody>(string route, TBody body, long instanceId, CancellationToken cancellationToken) => RunRequest<object>(route, body, HttpMethod.Delete, instanceId, false, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<TResult> Create<TResult>(string route, long instanceId, CancellationToken cancellationToken) => RunRequest<TResult>(route, new object(), HttpMethod.Put, instanceId, cancellationToken);
|
||||
public Task<TResult> Delete<TResult>(string route, long instanceId, CancellationToken cancellationToken) => RunRequest<TResult>(route, null, HttpMethod.Delete, instanceId, false, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<TResult> Patch<TResult>(string route, long instanceId, CancellationToken cancellationToken) => RunRequest<TResult>(route, new object(), new HttpMethod("PATCH"), instanceId, cancellationToken);
|
||||
public Task<TResult> Create<TResult>(string route, long instanceId, CancellationToken cancellationToken) => RunRequest<TResult>(route, new object(), HttpMethod.Put, instanceId, false, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<TResult> Patch<TResult>(string route, long instanceId, CancellationToken cancellationToken) => RunRequest<TResult>(route, new object(), HttpMethod.Patch, instanceId, false, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AddRequestLogger(IRequestLogger requestLogger) => requestLoggers.Add(requestLogger ?? throw new ArgumentNullException(nameof(requestLogger)));
|
||||
|
||||
@@ -7,6 +7,6 @@ namespace Tgstation.Server.Client
|
||||
sealed class ApiClientFactory : IApiClientFactory
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public IApiClient CreateApiClient(Uri url, ApiHeaders apiHeaders) => new ApiClient(new HttpClient(), url, apiHeaders);
|
||||
public IApiClient CreateApiClient(Uri url, ApiHeaders apiHeaders, ApiHeaders? tokenRefreshHeaders) => new ApiClient(new HttpClient(), url, apiHeaders, tokenRefreshHeaders);
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ namespace Tgstation.Server.Client
|
||||
/// </summary>
|
||||
/// <param name="errorMessage">The <see cref="ErrorMessage"/> for the <see cref="ApiException"/></param>
|
||||
/// <param name="responseMessage">The <see cref="HttpResponseMessage"/> for the <see cref="ClientException"/></param>
|
||||
public ApiConflictException(ErrorMessage errorMessage, HttpResponseMessage responseMessage) : base(errorMessage, responseMessage)
|
||||
public ApiConflictException(ErrorMessage? errorMessage, HttpResponseMessage responseMessage) : base(errorMessage, responseMessage)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -12,12 +12,12 @@ namespace Tgstation.Server.Client
|
||||
/// <summary>
|
||||
/// The <see cref="Version"/> of the server's API.
|
||||
/// </summary>
|
||||
public Version ServerApiVersion { get; }
|
||||
public Version? ServerApiVersion { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Additional error data from the server.
|
||||
/// </summary>
|
||||
public string AdditionalServerData { get; }
|
||||
public string? AdditionalServerData { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The API <see cref="ErrorCode"/> if applicable.
|
||||
@@ -29,7 +29,7 @@ namespace Tgstation.Server.Client
|
||||
/// </summary>
|
||||
/// <param name="errorMessage">The <see cref="ErrorMessage"/> returned from the API.</param>
|
||||
/// <param name="responseMessage">The <see cref="HttpResponseMessage"/>.</param>
|
||||
protected ApiException(ErrorMessage errorMessage, HttpResponseMessage responseMessage) : base(
|
||||
protected ApiException(ErrorMessage? errorMessage, HttpResponseMessage responseMessage) : base(
|
||||
responseMessage,
|
||||
errorMessage?.Message ?? $"HTTP {responseMessage.StatusCode}. Unknown API error, ErrorMessage payload not present!")
|
||||
{
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Tgstation.Server.Client
|
||||
/// <summary>
|
||||
/// The <see cref="HttpStatusCode"/> of the <see cref="ClientException"/>
|
||||
/// </summary>
|
||||
public HttpResponseMessage ResponseMessage { get; }
|
||||
public HttpResponseMessage? ResponseMessage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initialize a new instance of the <see cref="ClientException"/> <see langword="class"/>.
|
||||
|
||||
@@ -59,7 +59,10 @@ namespace Tgstation.Server.Client.Components
|
||||
{
|
||||
if (file == null)
|
||||
throw new ArgumentNullException(nameof(file));
|
||||
return apiClient.Read<ConfigurationFile>(Routes.ConfigurationFile + SanitizeGetPath(file.Path), instance.Id, cancellationToken);
|
||||
return apiClient.Read<ConfigurationFile>(
|
||||
Routes.ConfigurationFile + SanitizeGetPath(file.Path ?? throw new ArgumentException("file.Path should not be null!", nameof(file))),
|
||||
instance.Id,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -35,7 +35,12 @@ namespace Tgstation.Server.Client.Components
|
||||
public Task<InstanceUser> Create(InstanceUser instanceUser, CancellationToken cancellationToken) => apiClient.Create<InstanceUser, InstanceUser>(Routes.InstanceUser, instanceUser ?? throw new ArgumentNullException(nameof(instanceUser)), instance.Id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Delete(InstanceUser instanceUser, CancellationToken cancellationToken) => apiClient.Delete(Routes.SetID(Routes.InstanceUser, instanceUser.UserId.Value), instance.Id, cancellationToken);
|
||||
public Task Delete(InstanceUser instanceUser, CancellationToken cancellationToken) => apiClient.Delete(
|
||||
Routes.SetID(
|
||||
Routes.InstanceUser,
|
||||
instanceUser.UserId ?? throw new ArgumentException("Missing instanceUser.UserId!", nameof(instanceUser))),
|
||||
instance.Id,
|
||||
cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<InstanceUser> Read(CancellationToken cancellationToken) => apiClient.Read<InstanceUser>(Routes.InstanceUser, instance.Id, cancellationToken);
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Tgstation.Server.Client
|
||||
/// </summary>
|
||||
/// <param name="errorMessage">The <see cref="ErrorMessage"/> for the <see cref="ApiException"/>.</param>
|
||||
/// <param name="responseMessage">The <see cref="HttpResponseMessage"/> for the <see cref="ClientException"/>.</param>
|
||||
public ConflictException(ErrorMessage errorMessage, HttpResponseMessage responseMessage) : base(errorMessage, responseMessage)
|
||||
public ConflictException(ErrorMessage? errorMessage, HttpResponseMessage responseMessage) : base(errorMessage, responseMessage)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -13,7 +13,8 @@ namespace Tgstation.Server.Client
|
||||
/// </summary>
|
||||
/// <param name="url">The base <see cref="Uri"/></param>
|
||||
/// <param name="apiHeaders">The <see cref="ApiHeaders"/> for the <see cref="IApiClient"/></param>
|
||||
/// <param name="tokenRefreshHeaders">The <see cref="ApiHeaders"/> to use to generate a new <see cref="Api.Models.Token"/>.</param>
|
||||
/// <returns>A new <see cref="IApiClient"/></returns>
|
||||
IApiClient CreateApiClient(Uri url, ApiHeaders apiHeaders);
|
||||
IApiClient CreateApiClient(Uri url, ApiHeaders apiHeaders, ApiHeaders? tokenRefreshHeaders);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models;
|
||||
@@ -16,18 +17,28 @@ namespace Tgstation.Server.Client
|
||||
/// <param name="host">The URL to access TGS</param>
|
||||
/// <param name="username">The username to for the <see cref="IServerClient"/></param>
|
||||
/// <param name="password">The password for the <see cref="IServerClient"/></param>
|
||||
/// <param name="timeout">The <see cref="TimeSpan"/> representing timeout for the connection</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <param name="requestLoggers">Optional initial <see cref="IRequestLogger"/>s to add to the <see cref="IServerClient"/>.</param>
|
||||
/// <param name="timeout">Optional <see cref="TimeSpan"/> representing timeout for the connection</param>
|
||||
/// <param name="attemptLoginRefresh">Attempt to refresh the received <see cref="Token"/> when it expires or becomes invalid. <paramref name="username"/> and <paramref name="password"/> will be stored in memory if this is <see langword="true"/>.</param>
|
||||
/// <param name="cancellationToken">Optional <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in a new <see cref="IServerClient"/></returns>
|
||||
Task<IServerClient> CreateServerClient(Uri host, string username, string password, TimeSpan timeout = default, CancellationToken cancellationToken = default);
|
||||
Task<IServerClient> CreateFromLogin(
|
||||
Uri host,
|
||||
string username,
|
||||
string password,
|
||||
IEnumerable<IRequestLogger>? requestLoggers = null,
|
||||
TimeSpan? timeout = null,
|
||||
bool attemptLoginRefresh = true,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="IServerClient"/>
|
||||
/// </summary>
|
||||
/// <param name="host">The URL to access TGS</param>
|
||||
/// <param name="token">The <see cref="Token"/> to access the API with</param>
|
||||
/// <param name="timeout">The <see cref="TimeSpan"/> representing timeout for the connection</param>
|
||||
/// <returns>A new <see cref="IServerClient"/></returns>
|
||||
IServerClient CreateServerClient(Uri host, Token token, TimeSpan timeout = default);
|
||||
IServerClient CreateFromToken(
|
||||
Uri host,
|
||||
Token token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Tgstation.Server.Client
|
||||
/// </summary>
|
||||
/// <param name="errorMessage">The <see cref="ErrorMessage"/> for the <see cref="ApiException"/>.</param>
|
||||
/// <param name="responseMessage">The <see cref="HttpResponseMessage"/> for the <see cref="ClientException"/>.</param>
|
||||
public MethodNotSupportedException(ErrorMessage errorMessage, HttpResponseMessage responseMessage) : base(errorMessage, responseMessage)
|
||||
public MethodNotSupportedException(ErrorMessage? errorMessage, HttpResponseMessage responseMessage) : base(errorMessage, responseMessage)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Tgstation.Server.Client
|
||||
/// </summary>
|
||||
/// <param name="errorMessage">The <see cref="ErrorMessage"/> for the <see cref="ApiException"/>.</param>
|
||||
/// <param name="responseMessage">The <see cref="HttpResponseMessage"/> for the <see cref="ClientException"/>.</param>
|
||||
public RateLimitException(ErrorMessage errorMessage, HttpResponseMessage responseMessage) : base(errorMessage, responseMessage)
|
||||
public RateLimitException(ErrorMessage? errorMessage, HttpResponseMessage responseMessage) : base(errorMessage, responseMessage)
|
||||
{
|
||||
if (!responseMessage.Headers.TryGetValues(HeaderNames.RetryAfter, out var values))
|
||||
return;
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Tgstation.Server.Client
|
||||
set
|
||||
{
|
||||
token = value ?? throw new InvalidOperationException("Cannot set a null Token!");
|
||||
apiClient.Headers = new ApiHeaders(apiClient.Headers.UserAgent, token.Bearer);
|
||||
apiClient.Headers = new ApiHeaders(apiClient.Headers.UserAgent!, token.Bearer!);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -30,7 +32,14 @@ namespace Tgstation.Server.Client
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IServerClient> CreateServerClient(Uri host, string username, string password, TimeSpan timeout, CancellationToken cancellationToken)
|
||||
public async Task<IServerClient> CreateFromLogin(
|
||||
Uri host,
|
||||
string username,
|
||||
string password,
|
||||
IEnumerable<IRequestLogger>? requestLoggers = null,
|
||||
TimeSpan? timeout = null,
|
||||
bool attemptRefreshLogin = true,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (host == null)
|
||||
throw new ArgumentNullException(nameof(host));
|
||||
@@ -39,28 +48,40 @@ namespace Tgstation.Server.Client
|
||||
if (password == null)
|
||||
throw new ArgumentNullException(nameof(password));
|
||||
|
||||
requestLoggers ??= Enumerable.Empty<IRequestLogger>();
|
||||
|
||||
Token token;
|
||||
using (var api = ApiClientFactory.CreateApiClient(host, new ApiHeaders(productHeaderValue, username, password)))
|
||||
var loginHeaders = new ApiHeaders(productHeaderValue, username, password);
|
||||
using (var api = ApiClientFactory.CreateApiClient(host, loginHeaders, null))
|
||||
{
|
||||
if (timeout != default)
|
||||
api.Timeout = timeout;
|
||||
foreach (var requestLogger in requestLoggers)
|
||||
api.AddRequestLogger(requestLogger);
|
||||
|
||||
if (timeout.HasValue)
|
||||
api.Timeout = timeout.Value;
|
||||
token = await api.Update<Token>(Routes.Root, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return CreateServerClient(host, token, timeout);
|
||||
var client = CreateFromToken(host, token);
|
||||
if (timeout.HasValue)
|
||||
client.Timeout = timeout.Value;
|
||||
|
||||
var apiHeaders = new ApiHeaders(productHeaderValue, token.Bearer!);
|
||||
|
||||
return new ServerClient(ApiClientFactory.CreateApiClient(host, apiHeaders, loginHeaders), token);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IServerClient CreateServerClient(Uri host, Token token, TimeSpan timeout)
|
||||
public IServerClient CreateFromToken(Uri host, Token token)
|
||||
{
|
||||
if (host == null)
|
||||
throw new ArgumentNullException(nameof(host));
|
||||
if (token == null)
|
||||
throw new ArgumentNullException(nameof(token));
|
||||
var result = new ServerClient(ApiClientFactory.CreateApiClient(host, new ApiHeaders(productHeaderValue, token.Bearer)), token);
|
||||
if (timeout != default)
|
||||
result.Timeout = timeout;
|
||||
return result;
|
||||
if (token.Bearer == null)
|
||||
throw new ArgumentException("token.Bearer should not be null!", nameof(token));
|
||||
|
||||
return new ServerClient(ApiClientFactory.CreateApiClient(host, new ApiHeaders(productHeaderValue, token.Bearer), null), token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Tgstation.Server.Client
|
||||
/// </summary>
|
||||
/// <param name="errorMessage">The <see cref="ErrorMessage"/> for the <see cref="ApiException"/></param>
|
||||
/// <param name="responseMessage">The <see cref="HttpResponseMessage"/> for the <see cref="ClientException"/></param>
|
||||
public ServerErrorException(ErrorMessage errorMessage, HttpResponseMessage responseMessage) : base(errorMessage, responseMessage)
|
||||
public ServerErrorException(ErrorMessage? errorMessage, HttpResponseMessage responseMessage) : base(errorMessage, responseMessage)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -2,41 +2,34 @@
|
||||
<Import Project="../../build/Version.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<DebugType>Full</DebugType>
|
||||
<Version>$(TgsClientVersion)</Version>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<Authors>Cyberboss</Authors>
|
||||
<Company>/tg/station 13</Company>
|
||||
<Product />
|
||||
<Description>Client library for tgstation-server</Description>
|
||||
<PackageProjectUrl>https://tgstation.github.io/tgstation-server</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/tgstation/tgstation-server</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<NeutralLanguage>en-CA</NeutralLanguage>
|
||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||
<PackageIcon>tgs.png</PackageIcon>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<RepositoryUrl>https://github.com/tgstation/tgstation-server</RepositoryUrl>
|
||||
<Copyright>2018</Copyright>
|
||||
<PackageTags>json web api tgstation-server tgstation ss13 byond client</PackageTags>
|
||||
<PackageReleaseNotes>Updated to API version $(TgsApiVersion)</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>.Net Standard 2.1, allow login requests to be logged.</PackageReleaseNotes>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
<Copyright>2018</Copyright>
|
||||
<CodeAnalysisRuleSet>../../build/analyzers.ruleset</CodeAnalysisRuleSet>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<DocumentationFile>bin\$(Configuration)\netstandard2.1\Tgstation.Server.Client.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Release'">
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<WarningsAsErrors />
|
||||
<DocumentationFile>bin\Release\netstandard2.1\Tgstation.Server.Client.xml</DocumentationFile>
|
||||
<NoWarn>1701;1702</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<NoWarn>1701;1702</NoWarn>
|
||||
<DocumentationFile>bin\Debug\netstandard2.1\Tgstation.Server.Client.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Tgstation.Server.Client
|
||||
/// </summary>
|
||||
/// <param name="errorMessage">The <see cref="ErrorMessage"/> returned by the API.</param>
|
||||
/// <param name="responseMessage">The <see cref="HttpResponseMessage"/>.</param>
|
||||
public UnauthorizedException(ErrorMessage errorMessage, HttpResponseMessage responseMessage) : base(errorMessage, responseMessage)
|
||||
public UnauthorizedException(ErrorMessage? errorMessage, HttpResponseMessage responseMessage) : base(errorMessage, responseMessage)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Tgstation.Server.Client
|
||||
/// </summary>
|
||||
/// <param name="errorMessage">The <see cref="ErrorMessage"/> for the <see cref="ApiException"/>.</param>
|
||||
/// <param name="responseMessage">The <see cref="HttpResponseMessage"/> for the <see cref="ClientException"/>.</param>
|
||||
public VersionMismatchException(ErrorMessage errorMessage, HttpResponseMessage responseMessage) : base(errorMessage, responseMessage)
|
||||
public VersionMismatchException(ErrorMessage? errorMessage, HttpResponseMessage responseMessage) : base(errorMessage, responseMessage)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Components.Interop;
|
||||
using Tgstation.Server.Host.Components.Interop.Bridge;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.IO;
|
||||
@@ -78,6 +81,11 @@ namespace Tgstation.Server.Host.Components
|
||||
/// </summary>
|
||||
readonly IDictionary<string, IBridgeHandler> bridgeHandlers;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="GeneralConfiguration"/> for the <see cref="InstanceManager"/>.
|
||||
/// </summary>
|
||||
readonly GeneralConfiguration generalConfiguration;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="TaskCompletionSource{TResult}"/> for <see cref="Ready"/>.
|
||||
/// </summary>
|
||||
@@ -104,6 +112,7 @@ namespace Tgstation.Server.Host.Components
|
||||
/// <param name="serverControl">The value of <see cref="serverControl"/></param>
|
||||
/// <param name="systemIdentityFactory">The value of <see cref="systemIdentityFactory"/>.</param>
|
||||
/// <param name="asyncDelayer">The value of <see cref="asyncDelayer"/>.</param>
|
||||
/// <param name="generalConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="generalConfiguration"/>.</param>
|
||||
/// <param name="logger">The value of <see cref="logger"/></param>
|
||||
public InstanceManager(
|
||||
IInstanceFactory instanceFactory,
|
||||
@@ -114,6 +123,7 @@ namespace Tgstation.Server.Host.Components
|
||||
IServerControl serverControl,
|
||||
ISystemIdentityFactory systemIdentityFactory,
|
||||
IAsyncDelayer asyncDelayer,
|
||||
IOptions<GeneralConfiguration> generalConfigurationOptions,
|
||||
ILogger<InstanceManager> logger)
|
||||
{
|
||||
this.instanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory));
|
||||
@@ -124,6 +134,7 @@ namespace Tgstation.Server.Host.Components
|
||||
this.serverControl = serverControl ?? throw new ArgumentNullException(nameof(serverControl));
|
||||
this.systemIdentityFactory = systemIdentityFactory ?? throw new ArgumentNullException(nameof(systemIdentityFactory));
|
||||
this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer));
|
||||
generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
serverControl.RegisterForRestart(this);
|
||||
@@ -319,6 +330,10 @@ namespace Tgstation.Server.Host.Components
|
||||
/// </summary>
|
||||
private void CheckSystemCompatibility()
|
||||
{
|
||||
if (generalConfiguration.UseExperimentalWatchdog && !Debugger.IsAttached)
|
||||
throw new InvalidOperationException(
|
||||
"The experimental watchdog is currently non-functional! Please disable the '{nameof(generalConfiguration.UseExperimentalWatchdog)}' option in your configuration!");
|
||||
|
||||
using var systemIdentity = systemIdentityFactory.GetCurrent();
|
||||
if (!systemIdentity.CanCreateSymlinks)
|
||||
throw new InvalidOperationException("The user running tgstation-server cannot create symlinks! Please try running as an administrative user!");
|
||||
|
||||
@@ -265,12 +265,12 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
if (progressReporter == null)
|
||||
throw new ArgumentNullException(nameof(progressReporter));
|
||||
|
||||
logger.LogDebug("Begin AddTestMerge: #{0} at {1} ({4}) by <{2} ({3})>",
|
||||
logger.LogDebug("Begin AddTestMerge: #{0} at {1} ({2}) by <{3} ({4})>",
|
||||
testMergeParameters.Number,
|
||||
testMergeParameters.PullRequestRevision?.Substring(0, 7),
|
||||
testMergeParameters.Comment,
|
||||
committerName,
|
||||
committerEmail,
|
||||
testMergeParameters.Comment);
|
||||
committerEmail);
|
||||
|
||||
if (!IsGitHubRepository)
|
||||
throw new InvalidOperationException("Test merging is only available on GitHub hosted origin repositories!");
|
||||
|
||||
@@ -491,6 +491,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
async databaseContext =>
|
||||
{
|
||||
databaseContext.Instances.Attach(attachedInstance);
|
||||
var previousRevInfo = lastRevisionInfo;
|
||||
var needsUpdate = await LoadRevisionInformation(
|
||||
repo,
|
||||
databaseContext,
|
||||
@@ -516,13 +517,14 @@ namespace Tgstation.Server.Host.Controllers
|
||||
|
||||
testMergeToAdd.MergedBy = mergedBy;
|
||||
|
||||
lastRevisionInfo.ActiveTestMerges.AddRange(previousRevInfo.ActiveTestMerges);
|
||||
lastRevisionInfo.ActiveTestMerges.Add(new RevInfoTestMerge
|
||||
{
|
||||
TestMerge = testMergeToAdd
|
||||
});
|
||||
lastRevisionInfo.PrimaryTestMerge = testMergeToAdd;
|
||||
|
||||
databaseContext.Users.Attach(testMergeToAdd.MergedBy);
|
||||
needsUpdate = true;
|
||||
}
|
||||
|
||||
if (needsUpdate)
|
||||
@@ -532,12 +534,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
await CallLoadRevInfo().ConfigureAwait(false);
|
||||
|
||||
// apply new rev info, tracking applied test merges
|
||||
async Task UpdateRevInfo(Models.TestMerge testMergeToAdd = null)
|
||||
{
|
||||
var last = lastRevisionInfo;
|
||||
await CallLoadRevInfo(testMergeToAdd, last.OriginCommitSha).ConfigureAwait(false);
|
||||
lastRevisionInfo.ActiveTestMerges.AddRange(last.ActiveTestMerges);
|
||||
}
|
||||
Task UpdateRevInfo(Models.TestMerge testMergeToAdd = null) => CallLoadRevInfo(testMergeToAdd, lastRevisionInfo.OriginCommitSha);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -814,7 +811,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
doneSteps = 0;
|
||||
numSteps = 2;
|
||||
|
||||
// the stuff didn't make it into the db, forget what we've done and abort
|
||||
// Forget what we've done and abort
|
||||
await repo.CheckoutObject(startReference ?? startSha, NextProgressReporter(), default).ConfigureAwait(false);
|
||||
if (startReference != null && repo.Head != startSha)
|
||||
await repo.ResetToSha(startSha, NextProgressReporter(), default).ConfigureAwait(false);
|
||||
|
||||
@@ -556,10 +556,7 @@ namespace Tgstation.Server.Host.Setup
|
||||
if (String.IsNullOrWhiteSpace(newGeneralConfiguration.GitHubAccessToken))
|
||||
newGeneralConfiguration.GitHubAccessToken = null;
|
||||
|
||||
newGeneralConfiguration.UseExperimentalWatchdog = await PromptYesNo("Use the experimental watchdog (NOT RECOMMENDED)? (y/n): ", cancellationToken).ConfigureAwait(false);
|
||||
|
||||
newGeneralConfiguration.UseBasicWatchdogOnWindows = true;
|
||||
|
||||
// newGeneralConfiguration.UseExperimentalWatchdog = await PromptYesNo("Use the experimental watchdog (NOT RECOMMENDED)? (y/n): ", cancellationToken).ConfigureAwait(false);
|
||||
return newGeneralConfiguration;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,18 +9,13 @@
|
||||
<CodeAnalysisRuleSet>../../build/analyzers.ruleset</CodeAnalysisRuleSet>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IncludeOpenAPIAnalyzers>true</IncludeOpenAPIAnalyzers>
|
||||
<DocumentationFile>bin\$(Configuration)\netcoreapp3.1\Tgstation.Server.Host.xml</DocumentationFile>
|
||||
<NoWarn>API1000</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Release'">
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<WarningsAsErrors />
|
||||
<DocumentationFile>bin\Release\netcoreapp2.1\Tgstation.Server.Host.xml</DocumentationFile>
|
||||
<NoWarn>API1000</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<NoWarn>API1000</NoWarn>
|
||||
<DocumentationFile>bin\Debug\netcoreapp2.1\Tgstation.Server.Host.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -39,7 +39,7 @@ namespace Tgstation.Server.Client.Tests
|
||||
var httpClient = new Mock<IHttpClient>();
|
||||
httpClient.Setup(x => x.SendAsync(It.IsNotNull<HttpRequestMessage>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(response));
|
||||
|
||||
var client = new ApiClient(httpClient.Object, new Uri("http://fake.com"), new ApiHeaders(new ProductHeaderValue("fake"), "fake"));
|
||||
var client = new ApiClient(httpClient.Object, new Uri("http://fake.com"), new ApiHeaders(new ProductHeaderValue("fake"), "fake"), null);
|
||||
|
||||
var result = await client.Read<Byond>(Routes.Byond, default).ConfigureAwait(false);
|
||||
Assert.AreEqual(sample.Version, result.Version);
|
||||
@@ -64,7 +64,7 @@ namespace Tgstation.Server.Client.Tests
|
||||
var httpClient = new Mock<IHttpClient>();
|
||||
httpClient.Setup(x => x.SendAsync(It.IsNotNull<HttpRequestMessage>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(response));
|
||||
|
||||
var client = new ApiClient(httpClient.Object, new Uri("http://fake.com"), new ApiHeaders(new ProductHeaderValue("fake"), "fake"));
|
||||
var client = new ApiClient(httpClient.Object, new Uri("http://fake.com"), new ApiHeaders(new ProductHeaderValue("fake"), "fake"), null);
|
||||
|
||||
await Assert.ThrowsExceptionAsync<UnrecognizedResponseException>(() => client.Read<Byond>(Routes.Byond, default)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ namespace Tgstation.Server.Host.Setup.Tests
|
||||
var mockFailCommand = new Mock<DbCommand>();
|
||||
mockFailCommand.Setup(x => x.ExecuteNonQueryAsync(It.IsAny<CancellationToken>())).Throws(new Exception()).Verifiable();
|
||||
|
||||
void SetDbCommandCreator(Mock<DbConnection> mock, Func<DbCommand> creator) => mock.Protected().Setup<DbCommand>("CreateDbCommand").Returns(creator).Verifiable();
|
||||
static void SetDbCommandCreator(Mock<DbConnection> mock, Func<DbCommand> creator) => mock.Protected().Setup<DbCommand>("CreateDbCommand").Returns(creator).Verifiable();
|
||||
|
||||
var mockGoodDbConnection = new Mock<DbConnection>();
|
||||
mockGoodDbConnection.Setup(x => x.OpenAsync(It.IsAny<CancellationToken>())).Returns(Task.CompletedTask).Verifiable();
|
||||
@@ -144,7 +144,6 @@ namespace Tgstation.Server.Host.Setup.Tests
|
||||
"-27",
|
||||
"5000",
|
||||
"fake token",
|
||||
"y",
|
||||
//logging config
|
||||
"no",
|
||||
//cp config
|
||||
@@ -167,7 +166,6 @@ namespace Tgstation.Server.Host.Setup.Tests
|
||||
String.Empty,
|
||||
String.Empty,
|
||||
"n",
|
||||
"y",
|
||||
//logging config
|
||||
"y",
|
||||
"not actually verified because lol mocks /../!@#$%^&*()/..///.",
|
||||
@@ -191,7 +189,6 @@ namespace Tgstation.Server.Host.Setup.Tests
|
||||
String.Empty,
|
||||
String.Empty,
|
||||
"y",
|
||||
"y",
|
||||
"will faile",
|
||||
String.Empty,
|
||||
String.Empty,
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace Tgstation.Server.Tests
|
||||
{
|
||||
try
|
||||
{
|
||||
adminClient = await clientFactory.CreateServerClient(server.Url, User.AdminName, User.DefaultAdminPassword).ConfigureAwait(false);
|
||||
adminClient = await clientFactory.CreateFromLogin(server.Url, User.AdminName, User.DefaultAdminPassword).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
catch (HttpRequestException)
|
||||
@@ -144,7 +144,7 @@ namespace Tgstation.Server.Tests
|
||||
{
|
||||
try
|
||||
{
|
||||
return await clientFactory.CreateServerClient(server.Url, User.AdminName, User.DefaultAdminPassword).ConfigureAwait(false);
|
||||
return await clientFactory.CreateFromLogin(server.Url, User.AdminName, User.DefaultAdminPassword, attemptLoginRefresh: false).ConfigureAwait(false);
|
||||
}
|
||||
catch (HttpRequestException)
|
||||
{
|
||||
@@ -193,7 +193,7 @@ namespace Tgstation.Server.Tests
|
||||
Bearer = adminClient.Token.Bearer + '0'
|
||||
};
|
||||
|
||||
var badClient = clientFactory.CreateServerClient(server.Url, newToken);
|
||||
var badClient = clientFactory.CreateFromToken(server.Url, newToken);
|
||||
await Assert.ThrowsExceptionAsync<UnauthorizedException>(() => badClient.Version(cancellationToken)).ConfigureAwait(false);
|
||||
|
||||
var adminTest = new AdministrationTest(adminClient.Administration).Run(cancellationToken);
|
||||
|
||||
Reference in New Issue
Block a user