diff --git a/.travis.yml b/.travis.yml
index 38e1a220ac..b5de2be669 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -11,6 +11,97 @@ branches:
jobs:
include:
+ - env:
+ - DoxGeneration=false
+ - DockerBuild=false
+ - DMAPI=false
+ - CONFIG=Release
+ - TGS4_TEST_DATABASE_TYPE=MySql
+ - TGS4_TEST_CONNECTION_STRING="server=127.0.0.1;uid=root;pwd=;database=tgs_test"
+ name: "MySql Integration Test"
+ language: csharp
+ mono: none
+ services:
+ - mysql
+ dotnet: 3.1
+ cache:
+ directories:
+ - $HOME/.nuget/packages:
+ addons:
+ apt:
+ packages:
+ - libc6-i386
+ - libstdc++6:i386
+ - env:
+ - DoxGeneration=false
+ - DockerBuild=false
+ - DMAPI=false
+ - CONFIG=Release
+ - TGS4_TEST_DATABASE_TYPE=Sqlite
+ - TGS4_TEST_CONNECTION_STRING="Data Source=TravisTestDB.sqlite3;Mode=ReadWriteCreate"
+ name: "Sqlite Integration Test"
+ language: csharp
+ mono: none
+ dotnet: 3.1
+ cache:
+ directories:
+ - $HOME/.nuget/packages:
+ addons:
+ apt:
+ packages:
+ - libc6-i386
+ - libstdc++6:i386
+# - env:
+# - DoxGeneration=false
+# - DockerBuild=false
+# - DMAPI=false
+# - CONFIG=Release
+# - TGS4_TEST_DATABASE_TYPE=PostgresSql
+# - TGS4_TEST_CONNECTION_STRING="Application Name=tgstation-server;Host=127.0.0.1;Username=postgres;Password=;Database=TGS_Test"
+# name: "PostgresSql Integration Test"
+# language: csharp
+# mono: none
+# dotnet: 3.1
+# services:
+# - postgresql
+# cache:
+# directories:
+# - $HOME/.nuget/packages:
+# addons:
+# apt:
+# packages:
+# - libc6-i386
+# - libstdc++6:i386
+ - env:
+ - DoxGeneration=false
+ - DockerBuild=false
+ - DMAPI=false
+ - CONFIG=Debug
+ name: "Debug Unit Tests"
+ language: csharp
+ mono: none
+ dotnet: 3.1
+ cache:
+ directories:
+ - $HOME/.nuget/packages:
+ - env:
+ - DoxGeneration=false
+ - DockerBuild=false
+ - DMAPI=false
+ - CONFIG=Release
+ name: "Release Unit Tests"
+ language: csharp
+ mono: none
+ dotnet: 3.1
+ cache:
+ directories:
+ - $HOME/.nuget/packages:
+ - env:
+ - DoxGeneration=false
+ - DockerBuild=true
+ name: "Docker Build"
+ services:
+ - docker
- env:
- DoxGeneration=true
name: "Dox Generation"
@@ -30,56 +121,11 @@ jobs:
cache:
directories:
- $HOME/BYOND-${BYOND_MAJOR}.${BYOND_MINOR}
-
addons:
apt:
packages:
- libc6-i386
- libstdc++6:i386
- - env:
- - DoxGeneration=false
- - DockerBuild=false
- - DMAPI=false
- - CONFIG=Debug
- name: "Test Server Debug"
- language: csharp
- mono: none
- dotnet: 3.1
- services:
- - mysql
- cache:
- directories:
- - $HOME/.nuget/packages:
- addons:
- apt:
- packages:
- - libc6-i386
- - libstdc++6:i386
- - env:
- - DoxGeneration=false
- - DockerBuild=false
- - DMAPI=false
- - CONFIG=Release
- name: "Test Server Release"
- language: csharp
- mono: none
- dotnet: 3.1
- services:
- - mysql
- cache:
- directories:
- - $HOME/.nuget/packages:
- addons:
- apt:
- packages:
- - libc6-i386
- - libstdc++6:i386
- - env:
- - DoxGeneration=false
- - DockerBuild=true
- name: "Docker Build"
- services:
- - docker
install:
- if [ $DoxGeneration = false ] && [ $DockerBuild = false ] && [ $DMAPI = true ]; then build/install_byond.sh; fi
diff --git a/appveyor.yml b/appveyor.yml
index 3ba2f056f9..13d2da0ad0 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -73,7 +73,7 @@ test_script:
- OpenCover.Console.exe -returntargetcode -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx /p:DebugType=full tests/Tgstation.Server.Host.Watchdog.Tests/Tgstation.Server.Host.Watchdog.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Host.Watchdog.Tests*]*" -output:".\watchdog_coverage.xml" -oldstyle
- ps: $wc = New-Object 'System.Net.WebClient'
- ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\tests\Tgstation.Server.Host.Watchdog.Tests\TestResults\results.trx))
- - OpenCover.Console.exe -returntargetcode -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx /p:DebugType=full tests/Tgstation.Server.Tests/Tgstation.Server.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Tests*]* -[Tgstation.Server.Host]Tgstation.Server.Host.Database.Migrations..*" -output:".\server_coverage.xml" -oldstyle
+ - OpenCover.Console.exe -returntargetcode -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx --logger:console;noprogress=true /p:DebugType=full tests/Tgstation.Server.Tests/Tgstation.Server.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Tests*]* -[Tgstation.Server.Host]Tgstation.Server.Host.Database.Migrations..*" -output:".\server_coverage.xml" -oldstyle
- ps: $wc = New-Object 'System.Net.WebClient'
- ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\tests\Tgstation.Server.Tests\TestResults\results.trx))
- lint-openapi -p -c build/OpenApiValidationSettings.json C:/swagger.json
diff --git a/build/Version.props b/build/Version.props
index 3d1ad36d6e..bda24b2315 100644
--- a/build/Version.props
+++ b/build/Version.props
@@ -2,10 +2,10 @@
- 4.2.8
- 6.4.1
- 7.0.1
- 5.2.1
+ 4.3.0
+ 6.5.0
+ 7.1.0
+ 5.2.2
0.4.0
1.1.0
diff --git a/build/integration_test.sh b/build/integration_test.sh
new file mode 100755
index 0000000000..8492301359
--- /dev/null
+++ b/build/integration_test.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+set -e
+
+export TGS4_TEST_DISCORD_CHANNEL=493119635319947269
+export TGS4_TEST_IRC_CHANNEL=\#botbus
+export TGS4_TEST_TEMP_DIRECTORY=~/tgs4_test
+#token set in CI settings
+
+cd tests/Tgstation.Server.Tests
+
+dotnet build -c $CONFIG
+
+$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp3.1/Tgstation.Server.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG --no-build --logger:\"console;noprogress=true\"" --format opencover --output "../../TestResults/integration_test.xml" --include "[Tgstation.Server*]*" --exclude "[Tgstation.Server.Tests*]*" --exclude "[Tgstation.Server.Host]Tgstation.Server.Host.Database.Migrations.*"
+
+cd ../../TestResults
+
+bash <(curl -s https://codecov.io/bash) -f integration_test.xml -F integration
diff --git a/build/test_core.sh b/build/test_core.sh
index 59577f26b6..0775ff7246 100755
--- a/build/test_core.sh
+++ b/build/test_core.sh
@@ -7,6 +7,11 @@ mkdir TestResults
source ~/.nvm/nvm.sh && nvm install 10
+if [[ ! -z "${TGS4_TEST_CONNECTION_STRING}" ]]; then
+ build/integration_test.sh
+ exit
+fi
+
cd tests/Tgstation.Server.Api.Tests
dotnet build -c $CONFIG /p:CopyLocalLockFileAssemblies=true
@@ -32,22 +37,6 @@ cd ../Tgstation.Server.Host.Console.Tests
dotnet build -c $CONFIG /p:CopyLocalLockFileAssemblies=true
$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp3.1/Tgstation.Server.Host.Console.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG --no-build" --format opencover --output "../../TestResults/console.xml" --include "[Tgstation.Server*]*" --exclude "[Tgstation.Server.Host.Console.Tests*]*"
-cd ../Tgstation.Server.Tests
-export TGS4_TEST_DATABASE_TYPE=MySql
-export TGS4_TEST_DISCORD_CHANNEL=493119635319947269
-export TGS4_TEST_CONNECTION_STRING="server=127.0.0.1;uid=root;pwd=;database=tgs_test"
-export TGS4_TEST_IRC_CHANNEL=\#botbus
-export TGS4_TEST_TEMP_DIRECTORY=~/tgs4_test
-#token set in CI settings
-dotnet build -c $CONFIG /p:CopyLocalLockFileAssemblies=true
-
-$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp3.1/Tgstation.Server.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG --no-build" --format opencover --output "../../TestResults/servermy.xml" --include "[Tgstation.Server*]*" --exclude "[Tgstation.Server.Tests*]*" --exclude "[Tgstation.Server.Host]Tgstation.Server.Host.Database.Migrations.*"
-
-#Run again for Sqlite
-export TGS4_TEST_DATABASE_TYPE=Sqlite
-export TGS4_TEST_CONNECTION_STRING="Data Source=TravisTestDB.sqlite3;Mode=ReadWriteCreate"
-$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp3.1/Tgstation.Server.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG --no-build" --format opencover --output "../../TestResults/serversl.xml" --include "[Tgstation.Server*]*" --exclude "[Tgstation.Server.Tests*]*" --exclude "[Tgstation.Server.Host]Tgstation.Server.Host.Database.Migrations.*"
-
cd ../../TestResults
bash <(curl -s https://codecov.io/bash) -f api.xml -F unittests
@@ -55,5 +44,3 @@ bash <(curl -s https://codecov.io/bash) -f client.xml -F unittests
bash <(curl -s https://codecov.io/bash) -f host.xml -F unittests
bash <(curl -s https://codecov.io/bash) -f watchdog.xml -F unittests
bash <(curl -s https://codecov.io/bash) -f console.xml -F unittests
-bash <(curl -s https://codecov.io/bash) -f servermy.xml -F integration
-bash <(curl -s https://codecov.io/bash) -f serversl.xml -F integration
diff --git a/src/DMAPI/tgs.dm b/src/DMAPI/tgs.dm
index 7c0811ec59..4493aef72c 100644
--- a/src/DMAPI/tgs.dm
+++ b/src/DMAPI/tgs.dm
@@ -1,6 +1,6 @@
// tgstation-server DMAPI
-#define TGS_DMAPI_VERSION "5.2.1"
+#define TGS_DMAPI_VERSION "5.2.2"
// All functions and datums outside this document are subject to change with any version and should not be relied on.
@@ -127,6 +127,7 @@
* Call this when your initializations are complete and your game is ready to play before any player interactions happen.
*
* This may use [/world/var/sleep_offline] to make this happen so ensure no changes are made to it while this call is running.
+ * Afterwards, consider explicitly setting it to what you want to avoid this BYOND bug: http://www.byond.com/forum/post/2575184
* Before this point, note that any static files or directories may be in use by another server. Your code should account for this.
* This function should not be called before ..() in [/world/proc/New].
*/
diff --git a/src/DMAPI/tgs/v4/api.dm b/src/DMAPI/tgs/v4/api.dm
index 1dc98f811e..5e7f3c60be 100644
--- a/src/DMAPI/tgs/v4/api.dm
+++ b/src/DMAPI/tgs/v4/api.dm
@@ -114,12 +114,14 @@
/datum/tgs_api/v4/OnInitializationComplete()
Export(TGS4_COMM_SERVER_PRIMED)
- var/tgs4_secret_sleep_offline_sauce = 24051994
+ var/tgs4_secret_sleep_offline_sauce = 29051994
var/old_sleep_offline = world.sleep_offline
world.sleep_offline = tgs4_secret_sleep_offline_sauce
sleep(1)
if(world.sleep_offline == tgs4_secret_sleep_offline_sauce) //if not someone changed it
world.sleep_offline = old_sleep_offline
+ else
+ TGS_WARNING_LOG("world.sleep_offline unexpectedly changed!")
/datum/tgs_api/v4/OnTopic(T)
var/list/params = params2list(T)
diff --git a/src/DMAPI/tgs/v5/api.dm b/src/DMAPI/tgs/v5/api.dm
index 8ca85faf05..3791cbde84 100644
--- a/src/DMAPI/tgs/v5/api.dm
+++ b/src/DMAPI/tgs/v5/api.dm
@@ -16,7 +16,7 @@
var/list/chat_channels
/datum/tgs_api/v5/ApiVersion()
- return new /datum/tgs_version("5.2.1")
+ return new /datum/tgs_version("5.2.2")
/datum/tgs_api/v5/OnWorldNew(minimum_required_security_level)
server_port = world.params[DMAPI5_PARAM_SERVER_PORT]
@@ -94,6 +94,8 @@
sleep(1)
if(world.sleep_offline == tgs4_secret_sleep_offline_sauce) //if not someone changed it
world.sleep_offline = old_sleep_offline
+ else
+ TGS_WARNING_LOG("world.sleep_offline unexpectedly changed!")
/datum/tgs_api/v5/proc/TopicResponse(error_message = null)
var/list/response = list()
diff --git a/src/Tgstation.Server.Api/Models/RevisionInformation.cs b/src/Tgstation.Server.Api/Models/RevisionInformation.cs
index 1d05a22755..8e115fb922 100644
--- a/src/Tgstation.Server.Api/Models/RevisionInformation.cs
+++ b/src/Tgstation.Server.Api/Models/RevisionInformation.cs
@@ -18,6 +18,6 @@ namespace Tgstation.Server.Api.Models
///
/// The s made from the
///
- public ICollection? CompileJobs { get; set; }
+ public ICollection? CompileJobs { get; set; }
}
}
diff --git a/src/Tgstation.Server.Api/Models/User.cs b/src/Tgstation.Server.Api/Models/User.cs
index e6f50acb27..60ecb7225d 100644
--- a/src/Tgstation.Server.Api/Models/User.cs
+++ b/src/Tgstation.Server.Api/Models/User.cs
@@ -16,6 +16,6 @@
///
/// The who created this
///
- public User? CreatedBy { get; set; }
+ public Internal.User? CreatedBy { get; set; }
}
}
\ No newline at end of file
diff --git a/src/Tgstation.Server.Client/ApiClient.cs b/src/Tgstation.Server.Client/ApiClient.cs
index 539b3ce319..b9443b8897 100644
--- a/src/Tgstation.Server.Client/ApiClient.cs
+++ b/src/Tgstation.Server.Client/ApiClient.cs
@@ -162,22 +162,9 @@ namespace Tgstation.Server.Client
var headersToUse = tokenRefresh ? tokenRefreshHeaders! : headers;
headersToUse.SetRequestHeaders(request.Headers, instanceId);
- // This is meant to be a gate against token refresh operations
- await semaphoreSlim.WaitAsync(cancellationToken).ConfigureAwait(false);
- if(!tokenRefresh)
- semaphoreSlim.Release();
+ await Task.WhenAll(requestLoggers.Select(x => x.LogRequest(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();
- }
+ response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
}
using (response)
@@ -214,8 +201,13 @@ namespace Tgstation.Server.Client
if (tokenRefreshHeaders == null)
return false;
+ var startingToken = headers.Token;
+ await semaphoreSlim.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
+ if (startingToken != headers.Token)
+ return true;
+
var token = await RunRequest(Routes.Root, new object(), HttpMethod.Post, null, true, cancellationToken);
headers = new ApiHeaders(headers.UserAgent!, token.Bearer!);
}
@@ -223,6 +215,10 @@ namespace Tgstation.Server.Client
{
return false;
}
+ finally
+ {
+ semaphoreSlim.Release();
+ }
return true;
}
diff --git a/src/Tgstation.Server.Host/.config/dotnet-tools.json b/src/Tgstation.Server.Host/.config/dotnet-tools.json
index 22ec38229b..1bbb1a456c 100644
--- a/src/Tgstation.Server.Host/.config/dotnet-tools.json
+++ b/src/Tgstation.Server.Host/.config/dotnet-tools.json
@@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"dotnet-ef": {
- "version": "3.1.3",
+ "version": "3.1.4",
"commands": [
"dotnet-ef"
]
diff --git a/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs b/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs
index 94e01c8af5..dd4caa7835 100644
--- a/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs
@@ -620,13 +620,61 @@ namespace Tgstation.Server.Host.Components.Chat
}
///
- public Task SendUpdateMessage(string message, CancellationToken cancellationToken)
+ public async Task> SendDeploymentMessage(
+ Models.RevisionInformation revisionInformation,
+ Version byondVersion,
+ DateTimeOffset? estimatedCompletionTime,
+ string gitHubOwner,
+ string gitHubRepo,
+ bool localCommitPushed,
+ CancellationToken cancellationToken)
{
List wdChannels;
- message = String.Format(CultureInfo.InvariantCulture, "DM: {0}", message);
lock (mappedChannels) // so it doesn't change while we're using it
wdChannels = mappedChannels.Where(x => x.Value.IsUpdatesChannel).Select(x => x.Key).ToList();
- return SendMessage(message, wdChannels, cancellationToken);
+
+ logger.LogTrace("Sending deployment message for RevisionInformation: {0}", revisionInformation.Id);
+
+ var callbacks = new List>();
+
+ await Task.WhenAll(
+ wdChannels.Select(
+ async x =>
+ {
+ ChannelMapping channelMapping;
+ lock (mappedChannels)
+ if (!mappedChannels.TryGetValue(x, out channelMapping))
+ return;
+ IProvider provider;
+ lock (providers)
+ if (!providers.TryGetValue(channelMapping.ProviderId, out provider))
+ return;
+ try
+ {
+ var callback = await provider.SendUpdateMessage(
+ revisionInformation,
+ byondVersion,
+ estimatedCompletionTime,
+ gitHubOwner,
+ gitHubRepo,
+ channelMapping.ProviderChannelId,
+ localCommitPushed,
+ cancellationToken)
+ .ConfigureAwait(false);
+
+ callbacks.Add(callback);
+ }
+ catch (Exception ex)
+ {
+ logger.LogWarning(
+ "Error sending deploy message to provider {0}! Exception: {1}",
+ channelMapping.ProviderId,
+ ex);
+ }
+ }))
+ .ConfigureAwait(false);
+
+ return (errorMessage, dreamMakerOutput) => Task.WhenAll(callbacks.Select(x => x(errorMessage, dreamMakerOutput)));
}
///
diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/PullRequestsCommand.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/PullRequestsCommand.cs
index 15e992da83..5e63fa62a3 100644
--- a/src/Tgstation.Server.Host/Components/Chat/Commands/PullRequestsCommand.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/Commands/PullRequestsCommand.cs
@@ -76,14 +76,21 @@ namespace Tgstation.Server.Host.Components.Chat.Commands
head = repo.Head;
}
- await databaseContextFactory.UseContext(async db => results = await db.RevisionInformations.Where(x => x.Instance.Id == instance.Id && x.CommitSha == head)
- .SelectMany(x => x.ActiveTestMerges)
- .Select(x => x.TestMerge)
- .Select(x => new Models.TestMerge
- {
- Number = x.Number,
- PullRequestRevision = x.PullRequestRevision
- }).ToListAsync(cancellationToken).ConfigureAwait(false)).ConfigureAwait(false);
+ await databaseContextFactory.UseContext(
+ async db => results = await db
+ .RevisionInformations
+ .AsQueryable()
+ .Where(x => x.Instance.Id == instance.Id && x.CommitSha == head)
+ .SelectMany(x => x.ActiveTestMerges)
+ .Select(x => x.TestMerge)
+ .Select(x => new Models.TestMerge
+ {
+ Number = x.Number,
+ PullRequestRevision = x.PullRequestRevision
+ })
+ .ToListAsync(cancellationToken)
+ .ConfigureAwait(false))
+ .ConfigureAwait(false);
}
else
{
diff --git a/src/Tgstation.Server.Host/Components/Chat/IChatManager.cs b/src/Tgstation.Server.Host/Components/Chat/IChatManager.cs
index 2c9b286c30..062e8fe1bd 100644
--- a/src/Tgstation.Server.Host/Components/Chat/IChatManager.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/IChatManager.cs
@@ -62,12 +62,24 @@ namespace Tgstation.Server.Host.Components.Chat
Task SendWatchdogMessage(string message, bool adminOnly, CancellationToken cancellationToken);
///
- /// Send a chat to configured update channels
+ /// Send the message for a deployment to configured deployment channels.
///
- /// The message being sent
- /// The for the operation
- /// A representing the running operation
- Task SendUpdateMessage(string message, CancellationToken cancellationToken);
+ /// The of the deployment.
+ /// The BYOND of the deployment.
+ /// The optional the deployment is expected to be completed at.
+ /// The repository GitHub owner, if any.
+ /// The repository GitHub name, if any.
+ /// if the local deployment commit was pushed to the remote repository.
+ /// The for the operation.
+ /// A resulting in a to call to update the message at the deployment's conclusion. Parameters: Error message if any, DreamMaker output if any.
+ Task> SendDeploymentMessage(
+ Models.RevisionInformation revisionInformation,
+ Version byondVersion,
+ DateTimeOffset? estimatedCompletionTime,
+ string gitHubOwner,
+ string gitHubRepo,
+ bool localCommitPushed,
+ CancellationToken cancellationToken);
///
/// Start tracking s and s.
diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs
index 9e7052bcfb..ba23173db1 100644
--- a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs
@@ -6,6 +6,8 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using Tgstation.Server.Host.Models;
+using Tgstation.Server.Host.System;
namespace Tgstation.Server.Host.Components.Chat.Providers
{
@@ -28,6 +30,11 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
}
}
+ ///
+ /// The for the .
+ ///
+ readonly IAssemblyInformationProvider assemblyInformationProvider;
+
///
/// The for the
///
@@ -53,12 +60,18 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
///
/// Construct a
///
+ /// The value of .
/// The value of
/// The value of
/// The initial reconnect interval in minutes.
- public DiscordProvider(ILogger logger, string botToken, uint reconnectInterval)
+ public DiscordProvider(
+ IAssemblyInformationProvider assemblyInformationProvider,
+ ILogger logger,
+ string botToken,
+ uint reconnectInterval)
: base(logger, reconnectInterval)
{
+ this.assemblyInformationProvider = assemblyInformationProvider ?? throw new ArgumentNullException(nameof(assemblyInformationProvider));
this.botToken = botToken ?? throw new ArgumentNullException(nameof(botToken));
client = new DiscordSocketClient();
client.MessageReceived += Client_MessageReceived;
@@ -260,5 +273,138 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
Logger.LogWarning("Error sending discord message: {0}", e);
}
}
+
+ ///
+ public override async Task> SendUpdateMessage(
+ RevisionInformation revisionInformation,
+ Version byondVersion,
+ DateTimeOffset? estimatedCompletionTime,
+ string gitHubOwner,
+ string gitHubRepo,
+ ulong channelId,
+ bool localCommitPushed,
+ CancellationToken cancellationToken)
+ {
+ bool gitHub = gitHubOwner != null && gitHubRepo != null;
+
+ localCommitPushed |= revisionInformation.CommitSha == revisionInformation.OriginCommitSha;
+
+ var fields = new List
+ {
+ new EmbedFieldBuilder
+ {
+ Name = "BYOND Version",
+ Value = $"{byondVersion.Major}.{byondVersion.Minor}",
+ IsInline = true
+ },
+ new EmbedFieldBuilder
+ {
+ Name = "Local Commit",
+ Value = localCommitPushed && gitHub
+ ? $"[{revisionInformation.CommitSha.Substring(0, 7)}](https://github.com/{gitHubOwner}/{gitHubRepo}/commit/{revisionInformation.CommitSha})"
+ : revisionInformation.CommitSha.Substring(0, 7),
+ IsInline = true
+ },
+ new EmbedFieldBuilder
+ {
+ Name = "Branch Commit",
+ Value = gitHub
+ ? $"[{revisionInformation.OriginCommitSha.Substring(0, 7)}](https://github.com/{gitHubOwner}/{gitHubRepo}/commit/{revisionInformation.OriginCommitSha})"
+ : revisionInformation.OriginCommitSha.Substring(0, 7),
+ IsInline = true
+ }
+ };
+
+ fields.AddRange((revisionInformation.ActiveTestMerges ?? Enumerable.Empty())
+ .Select(x => x.TestMerge)
+ .Select(x => new EmbedFieldBuilder
+ {
+ Name = $"#{x.Number}",
+ Value = $"[{x.TitleAtMerge}]({x.Url}) by _[@{x.Author}](https://github.com/{x.Author})_{Environment.NewLine}Commit: [{x.PullRequestRevision.Substring(0, 7)}](https://github.com/{gitHubOwner}/{gitHubRepo}/commit/{x.PullRequestRevision}){(String.IsNullOrWhiteSpace(x.Comment) ? String.Empty : $"{Environment.NewLine}_**{x.Comment}**_")}"
+ }));
+
+ var builder = new EmbedBuilder
+ {
+ Author = new EmbedAuthorBuilder
+ {
+ Name = assemblyInformationProvider.VersionPrefix,
+ Url = "https://github.com/tgstation/tgstation-server",
+ IconUrl = "https://avatars0.githubusercontent.com/u/1363778?s=280&v=4"
+ },
+ Color = Color.Gold,
+ Description = "TGS has begun deploying active repository code to production.",
+ Fields = fields,
+ Title = "Code Deployment",
+ Footer = new EmbedFooterBuilder
+ {
+ Text = "In progress... ETA"
+ },
+ Timestamp = estimatedCompletionTime
+ };
+
+ Logger.LogTrace("Attempting to post deploy embed to channel {0}...", channelId);
+ if (!(client.GetChannel(channelId) is IMessageChannel channel))
+ {
+ Logger.LogTrace("Channel ID {0} does not exist or is not an IMessageChannel!", channelId);
+ return (errorMessage, dreamMakerOutput) => Task.CompletedTask;
+ }
+
+ var message = await channel.SendMessageAsync(
+ String.Empty,
+ false,
+ builder.Build(),
+ new RequestOptions
+ {
+ CancelToken = cancellationToken
+ })
+ .ConfigureAwait(false);
+
+ return async (errorMessage, dreamMakerOutput) =>
+ {
+ builder.Footer.Text = errorMessage == null ? "Succeeded" : "Failed";
+ builder.Color = errorMessage == null ? Color.Green : Color.Red;
+ builder.Timestamp = DateTimeOffset.Now;
+ builder.Description = errorMessage == null
+ ? "The deployment completed successfully and will be available at the next server reboot."
+ : "The deployment failed.";
+
+ if (dreamMakerOutput != null)
+ builder.AddField(new EmbedFieldBuilder
+ {
+ Name = "DreamMaker Output",
+ Value = $"```{Environment.NewLine}{dreamMakerOutput}{Environment.NewLine}```"
+ });
+
+ if (errorMessage != null)
+ builder.AddField(new EmbedFieldBuilder
+ {
+ Name = "Error Message",
+ Value = errorMessage
+ });
+
+ try
+ {
+ await message.ModifyAsync(
+ props => props.Embed = builder.Build())
+ .ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ Logger.LogWarning("Updating deploy embed {0} failed, attempting new post! Exception: {1}", message.Id, ex);
+ try
+ {
+ await channel.SendMessageAsync(
+ String.Empty,
+ false,
+ builder.Build())
+ .ConfigureAwait(false);
+ }
+ catch (Exception ex2)
+ {
+ Logger.LogWarning("Posting completion deploy embed failed! Exception: {0}", ex2);
+ }
+ }
+ };
+ }
}
}
diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs
index d4dd515150..c06c22844f 100644
--- a/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
+using Tgstation.Server.Host.Models;
namespace Tgstation.Server.Host.Components.Chat.Providers
{
@@ -65,5 +66,27 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
/// The reconnection interval in minutes.
/// A representing the running operation.
Task SetReconnectInterval(uint reconnectInterval);
+
+ ///
+ /// Send the message for a deployment.
+ ///
+ /// The of the deployment.
+ /// The BYOND of the deployment.
+ /// The optional the deployment is expected to be completed at.
+ /// The repository GitHub owner, if any.
+ /// The repository GitHub name, if any.
+ /// The to send to
+ /// if the local deployment commit was pushed to the remote repository.
+ /// The for the operation.
+ /// A resulting in a to call to update the message at the deployment's conclusion. Parameters: Error message if any, DreamMaker output if any.
+ Task> SendUpdateMessage(
+ RevisionInformation revisionInformation,
+ Version byondVersion,
+ DateTimeOffset? estimatedCompletionTime,
+ string gitHubOwner,
+ string gitHubRepo,
+ ulong channelId,
+ bool localCommitPushed,
+ CancellationToken cancellationToken);
}
}
diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs
index 9d66fc0f38..67e85e152a 100644
--- a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs
@@ -467,5 +467,55 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
Logger.LogWarning("Unable to send to channel: {0}", e);
}
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
+
+ ///
+ public override async Task> SendUpdateMessage(
+ Models.RevisionInformation revisionInformation,
+ Version byondVersion,
+ DateTimeOffset? estimatedCompletionTime,
+ string gitHubOwner,
+ string gitHubRepo,
+ ulong channelId,
+ bool localCommitPushed,
+ CancellationToken cancellationToken)
+ {
+ var commitInsert = revisionInformation.CommitSha.Substring(0, 7);
+ string remoteCommitInsert;
+ if (revisionInformation.CommitSha == revisionInformation.OriginCommitSha)
+ {
+ commitInsert = String.Format(CultureInfo.InvariantCulture, localCommitPushed ? "^{0}" : "{0}", commitInsert);
+ remoteCommitInsert = String.Empty;
+ }
+ else
+ remoteCommitInsert = String.Format(CultureInfo.InvariantCulture, ". Remote commit: ^{0}", revisionInformation.OriginCommitSha.Substring(0, 7));
+
+ var testmergeInsert = (revisionInformation.ActiveTestMerges?.Count ?? 0) == 0 ? String.Empty : String.Format(CultureInfo.InvariantCulture, " (Test Merges: {0})",
+ String.Join(", ", revisionInformation.ActiveTestMerges.Select(x => x.TestMerge).Select(x =>
+ {
+ var result = String.Format(CultureInfo.InvariantCulture, "#{0} at {1}", x.Number, x.PullRequestRevision.Substring(0, 7));
+ if (x.Comment != null)
+ result += String.Format(CultureInfo.InvariantCulture, " ({0})", x.Comment);
+ return result;
+ })));
+
+ await SendMessage(
+ channelId,
+ String.Format(
+ CultureInfo.InvariantCulture,
+ "DM: Deploying revision: {0}{1}{2} BYOND Version: {3}{4}",
+ commitInsert,
+ testmergeInsert,
+ remoteCommitInsert,
+ byondVersion,
+ estimatedCompletionTime.HasValue
+ ? $" ETA: {estimatedCompletionTime - DateTimeOffset.Now}"
+ : String.Empty),
+ cancellationToken).ConfigureAwait(false);
+
+ return (errorMessage, dreamMakerOutput) => SendMessage(
+ channelId,
+ $"DM: Deployment {(errorMessage == null ? "complete" : "failed")}!",
+ cancellationToken);
+ }
}
}
diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/Provider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/Provider.cs
index 9a4fcae488..9f686bedca 100644
--- a/src/Tgstation.Server.Host/Components/Chat/Providers/Provider.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/Providers/Provider.cs
@@ -3,6 +3,7 @@ using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
+using Tgstation.Server.Host.Models;
namespace Tgstation.Server.Host.Components.Chat.Providers
{
@@ -180,5 +181,16 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
///
public abstract Task SendMessage(ulong channelId, string message, CancellationToken cancellationToken);
+
+ ///
+ public abstract Task> SendUpdateMessage(
+ RevisionInformation revisionInformation,
+ Version byondVersion,
+ DateTimeOffset? estimatedCompletionTime,
+ string gitHubOwner,
+ string gitHubRepo,
+ ulong channelId,
+ bool localCommitPushed,
+ CancellationToken cancellationToken);
}
}
diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/ProviderFactory.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/ProviderFactory.cs
index 8d0bc2c832..3a23549cba 100644
--- a/src/Tgstation.Server.Host/Components/Chat/Providers/ProviderFactory.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/Providers/ProviderFactory.cs
@@ -56,7 +56,11 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
return new IrcProvider(assemblyInformationProvider, asyncDelayer, loggerFactory.CreateLogger(), ircBuilder.Address, ircBuilder.Port.Value, ircBuilder.Nickname, ircBuilder.Password, ircBuilder.PasswordType, settings.ReconnectionInterval.Value, ircBuilder.UseSsl.Value);
case ChatProvider.Discord:
var discordBuilder = (DiscordConnectionStringBuilder)builder;
- return new DiscordProvider(loggerFactory.CreateLogger(), discordBuilder.BotToken, settings.ReconnectionInterval.Value);
+ return new DiscordProvider(
+ assemblyInformationProvider,
+ loggerFactory.CreateLogger(),
+ discordBuilder.BotToken,
+ settings.ReconnectionInterval.Value);
default:
throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid ChatProvider: {0}", settings.Provider));
}
diff --git a/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs b/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs
index 281bb892e9..978586af70 100644
--- a/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs
+++ b/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs
@@ -197,11 +197,17 @@ namespace Tgstation.Server.Host.Components.Deployment
// ensure we have the entire compile job tree
logger.LogTrace("Loading compile job {0}...", compileJob.Id);
- await databaseContextFactory.UseContext(async db => compileJob = await db.CompileJobs.Where(x => x.Id == compileJob.Id)
- .Include(x => x.Job).ThenInclude(x => x.StartedBy)
- .Include(x => x.RevisionInformation).ThenInclude(x => x.PrimaryTestMerge).ThenInclude(x => x.MergedBy)
- .Include(x => x.RevisionInformation).ThenInclude(x => x.ActiveTestMerges).ThenInclude(x => x.TestMerge).ThenInclude(x => x.MergedBy)
- .FirstAsync(cancellationToken).ConfigureAwait(false)).ConfigureAwait(false); // can't wait to see that query
+ await databaseContextFactory.UseContext(
+ async db => compileJob = await db
+ .CompileJobs
+ .AsQueryable()
+ .Where(x => x.Id == compileJob.Id)
+ .Include(x => x.Job).ThenInclude(x => x.StartedBy)
+ .Include(x => x.RevisionInformation).ThenInclude(x => x.PrimaryTestMerge).ThenInclude(x => x.MergedBy)
+ .Include(x => x.RevisionInformation).ThenInclude(x => x.ActiveTestMerges).ThenInclude(x => x.TestMerge).ThenInclude(x => x.MergedBy)
+ .FirstAsync(cancellationToken)
+ .ConfigureAwait(false))
+ .ConfigureAwait(false); // can't wait to see that query
if (!compileJob.Job.StoppedAt.HasValue)
{
@@ -269,8 +275,12 @@ namespace Tgstation.Server.Host.Components.Deployment
// find the uids of locked directories
await databaseContextFactory.UseContext(async db =>
{
- jobUidsToNotErase = (await db.CompileJobs.Where(
- x => x.Job.Instance.Id == instance.Id && jobIdsToSkip.Contains(x.Id))
+ jobUidsToNotErase = (await db
+ .CompileJobs
+ .AsQueryable()
+ .Where(
+ x => x.Job.Instance.Id == instance.Id
+ && jobIdsToSkip.Contains(x.Id))
.Select(x => x.DirectoryName.Value)
.ToListAsync(cancellationToken)
.ConfigureAwait(false))
diff --git a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs
index aab15c7d26..e35cfd5b15 100644
--- a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs
+++ b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs
@@ -14,7 +14,6 @@ using Tgstation.Server.Host.Components.Byond;
using Tgstation.Server.Host.Components.Chat;
using Tgstation.Server.Host.Components.Repository;
using Tgstation.Server.Host.Components.Session;
-using Tgstation.Server.Host.Components.Watchdog;
using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.Database;
using Tgstation.Server.Host.IO;
@@ -82,11 +81,6 @@ namespace Tgstation.Server.Host.Components.Deployment
///
readonly IProcessExecutor processExecutor;
- ///
- /// The for
- ///
- readonly IWatchdog watchdog;
-
///
/// The for .
///
@@ -113,14 +107,18 @@ namespace Tgstation.Server.Host.Components.Deployment
readonly Api.Models.Instance metadata;
///
- /// for .
+ /// for .
///
- readonly object compilingLock;
+ readonly object deploymentLock;
+
+ Func currentChatCallback;
+
+ string currentDreamMakerOutput;
///
/// If a compile job is running
///
- bool compiling;
+ bool deploying;
///
/// Construct
@@ -132,7 +130,6 @@ namespace Tgstation.Server.Host.Components.Deployment
/// The value of
/// The value of
/// The value of
- /// The value of
/// The value of .
/// The value of .
/// The value of .
@@ -146,7 +143,6 @@ namespace Tgstation.Server.Host.Components.Deployment
IEventConsumer eventConsumer,
IChatManager chatManager,
IProcessExecutor processExecutor,
- IWatchdog watchdog,
IGitHubClientFactory gitHubClientFactory,
ICompileJobSink compileJobConsumer,
IRepositoryManager repositoryManager,
@@ -160,14 +156,13 @@ namespace Tgstation.Server.Host.Components.Deployment
this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer));
this.chatManager = chatManager ?? throw new ArgumentNullException(nameof(chatManager));
this.processExecutor = processExecutor ?? throw new ArgumentNullException(nameof(processExecutor));
- this.watchdog = watchdog ?? throw new ArgumentNullException(nameof(watchdog));
this.gitHubClientFactory = gitHubClientFactory ?? throw new ArgumentNullException(nameof(gitHubClientFactory));
this.compileJobConsumer = compileJobConsumer ?? throw new ArgumentNullException(nameof(compileJobConsumer));
this.repositoryManager = repositoryManager ?? throw new ArgumentNullException(nameof(repositoryManager));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
this.metadata = metadata ?? throw new ArgumentNullException(nameof(metadata));
- compilingLock = new object();
+ deploymentLock = new object();
}
///
@@ -289,7 +284,7 @@ namespace Tgstation.Server.Host.Components.Deployment
cancellationToken.ThrowIfCancellationRequested();
logger.LogDebug("DreamMaker exit code: {0}", exitCode);
- job.Output = dm.GetCombinedOutput();
+ currentDreamMakerOutput = job.Output = dm.GetCombinedOutput();
logger.LogDebug("DreamMaker output: {0}{1}", Environment.NewLine, job.Output);
return exitCode;
}
@@ -352,13 +347,10 @@ namespace Tgstation.Server.Host.Components.Deployment
/// Cleans up a failed compile
///
/// The running
- /// If the was cancelled
- /// The for the operation
/// A representing the running operation
- async Task CleanupFailedCompile(Models.CompileJob job, bool cancelled, CancellationToken cancellationToken)
+ async Task CleanupFailedCompile(Models.CompileJob job)
{
logger.LogTrace("Cleaning compile directory...");
- var chatTask = chatManager.SendUpdateMessage(cancelled ? "Deploy cancelled!" : "Deploy failed!", cancellationToken);
var jobPath = job.DirectoryName.ToString();
try
{
@@ -368,66 +360,6 @@ namespace Tgstation.Server.Host.Components.Deployment
{
logger.LogWarning("Error cleaning up compile directory {0}! Exception: {1}", ioManager.ResolvePath(jobPath), e);
}
-
- await chatTask.ConfigureAwait(false);
- }
-
- ///
- /// Send a message to about a deployment
- ///
- /// The for the deployment
- /// The for the deployment
- /// The for the operation
- /// A representing the running operation
- async Task SendDeploymentMessage(Models.RevisionInformation revisionInformation, IByondExecutableLock byondLock, CancellationToken cancellationToken)
- {
- var commitInsert = revisionInformation.CommitSha.Substring(0, 7);
- string remoteCommitInsert;
- if (revisionInformation.CommitSha == revisionInformation.OriginCommitSha)
- {
- commitInsert = String.Format(CultureInfo.InvariantCulture, "^{0}", commitInsert);
- remoteCommitInsert = String.Empty;
- }
- else
- remoteCommitInsert = String.Format(CultureInfo.InvariantCulture, ". Remote commit: ^{0}", revisionInformation.OriginCommitSha.Substring(0, 7));
-
- var testmergeInsert = (revisionInformation.ActiveTestMerges?.Count ?? 0) == 0
- ? String.Empty
- : String.Format(
- CultureInfo.InvariantCulture,
- "{0}Test Merges:{1}",
- Environment.NewLine,
- String.Join(
- Environment.NewLine,
- revisionInformation
- .ActiveTestMerges
- .Select(x => x.TestMerge)
- .Select(x =>
- {
- var result = String.Format(
- CultureInfo.InvariantCulture,
- "- #{0} at {1}",
- x.Number,
- x.PullRequestRevision.Substring(0, 7));
-
- if (x.Comment != null)
- result += $": {x.Comment}";
-
- return result;
- })));
-
- await chatManager.SendUpdateMessage(
- String.Format(
- CultureInfo.InvariantCulture,
- "*Deployment Triggered*{0}Revision: {1}{2}{3}{0}BYOND Version: {4}.{5}",
- Environment.NewLine,
- commitInsert,
- testmergeInsert,
- remoteCommitInsert,
- byondLock.Version.Major,
- byondLock.Version.Minor),
- cancellationToken)
- .ConfigureAwait(false);
}
///
@@ -523,9 +455,9 @@ namespace Tgstation.Server.Host.Components.Deployment
logger.LogDebug("Compile complete!");
}
- catch (Exception e)
+ catch
{
- await CleanupFailedCompile(job, e is OperationCanceledException, cancellationToken).ConfigureAwait(false);
+ await CleanupFailedCompile(job).ConfigureAwait(false);
throw;
}
}
@@ -547,174 +479,209 @@ namespace Tgstation.Server.Host.Components.Deployment
if (progressReporter == null)
throw new ArgumentNullException(nameof(progressReporter));
- string repoOwner = null;
- string repoName = null;
- TimeSpan? averageSpan = null;
- Models.RepositorySettings repositorySettings = null;
- Models.DreamDaemonSettings ddSettings = null;
- DreamMakerSettings dreamMakerSettings = null;
- IRepository repo = null;
+ lock (deploymentLock)
+ {
+ if (deploying)
+ throw new JobException(ErrorCode.DreamMakerCompileJobInProgress);
+ deploying = true;
+ }
+
+ currentChatCallback = null;
+ currentDreamMakerOutput = null;
Models.CompileJob compileJob = null;
- Models.RevisionInformation revInfo = null;
- await databaseContextFactory.UseContext(
- async databaseContext =>
- {
- averageSpan = await CalculateExpectedDeploymentTime(databaseContext, cancellationToken).ConfigureAwait(false);
-
- ddSettings = await databaseContext
- .DreamDaemonSettings
- .Where(x => x.InstanceId == metadata.Id)
- .Select(x => new Models.DreamDaemonSettings
- {
- StartupTimeout = x.StartupTimeout,
- })
- .FirstOrDefaultAsync(cancellationToken)
- .ConfigureAwait(false);
- if (ddSettings == default)
- throw new JobException(ErrorCode.InstanceMissingDreamDaemonSettings);
-
- dreamMakerSettings = await databaseContext.DreamMakerSettings.Where(x => x.InstanceId == metadata.Id).FirstAsync(cancellationToken).ConfigureAwait(false);
- if (dreamMakerSettings == default)
- throw new JobException(ErrorCode.InstanceMissingDreamMakerSettings);
-
- repositorySettings = await databaseContext
- .RepositorySettings
- .Where(x => x.InstanceId == metadata.Id)
- .Select(x => new Models.RepositorySettings
- {
- AccessToken = x.AccessToken,
- ShowTestMergeCommitters = x.ShowTestMergeCommitters,
- PushTestMergeCommits = x.PushTestMergeCommits,
- PostTestMergeComment = x.PostTestMergeComment
- })
- .FirstOrDefaultAsync(cancellationToken)
- .ConfigureAwait(false);
- if (repositorySettings == default)
- throw new JobException(ErrorCode.InstanceMissingRepositorySettings);
-
- repo = await repositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false);
- try
- {
- if (repo == null)
- throw new JobException(ErrorCode.RepoMissing);
-
- if (repo.IsGitHubRepository)
- {
- repoOwner = repo.GitHubOwner;
- repoName = repo.GitHubRepoName;
- }
-
- var repoSha = repo.Head;
- revInfo = await databaseContext
- .RevisionInformations
- .Where(x => x.CommitSha == repoSha && x.Instance.Id == metadata.Id)
- .Include(x => x.ActiveTestMerges)
- .ThenInclude(x => x.TestMerge)
- .ThenInclude(x => x.MergedBy)
- .FirstOrDefaultAsync(cancellationToken)
- .ConfigureAwait(false);
-
- if (revInfo == default)
- {
- revInfo = new Models.RevisionInformation
- {
- CommitSha = repoSha,
- OriginCommitSha = repoSha,
- Instance = new Models.Instance
- {
- Id = metadata.Id
- },
- ActiveTestMerges = new List()
- };
-
- logger.LogWarning(Repository.Repository.OriginTrackingErrorTemplate, repoSha);
- databaseContext.Instances.Attach(revInfo.Instance);
- await databaseContext.Save(cancellationToken).ConfigureAwait(false);
- }
- }
- catch
- {
- repo.Dispose();
- throw;
- }
- })
- .ConfigureAwait(false);
-
- using (repo)
- compileJob = await Compile(
- revInfo,
- dreamMakerSettings,
- ddSettings.StartupTimeout.Value,
- repo,
- progressReporter,
- averageSpan,
- cancellationToken)
- .ConfigureAwait(false);
-
- var activeCompileJob = compileJobConsumer.LatestCompileJob();
try
{
+ string repoOwner = null;
+ string repoName = null;
+ TimeSpan? averageSpan = null;
+ Models.RepositorySettings repositorySettings = null;
+ Models.DreamDaemonSettings ddSettings = null;
+ DreamMakerSettings dreamMakerSettings = null;
+ IRepository repo = null;
+ Models.RevisionInformation revInfo = null;
await databaseContextFactory.UseContext(
async databaseContext =>
{
- compileJob.Job = new Models.Job
- {
- Id = job.Id
- };
- compileJob.RevisionInformation = new Models.RevisionInformation
- {
- Id = revInfo.Id
- };
+ averageSpan = await CalculateExpectedDeploymentTime(databaseContext, cancellationToken).ConfigureAwait(false);
- databaseContext.Jobs.Attach(compileJob.Job);
- databaseContext.RevisionInformations.Attach(compileJob.RevisionInformation);
- databaseContext.CompileJobs.Add(compileJob);
+ ddSettings = await databaseContext
+ .DreamDaemonSettings
+ .AsQueryable()
+ .Where(x => x.InstanceId == metadata.Id)
+ .Select(x => new Models.DreamDaemonSettings
+ {
+ StartupTimeout = x.StartupTimeout,
+ })
+ .FirstOrDefaultAsync(cancellationToken)
+ .ConfigureAwait(false);
+ if (ddSettings == default)
+ throw new JobException(ErrorCode.InstanceMissingDreamDaemonSettings);
- // The difficulty with compile jobs is they have a two part commit
- await databaseContext.Save(cancellationToken).ConfigureAwait(false);
+ dreamMakerSettings = await databaseContext
+ .DreamMakerSettings
+ .AsQueryable()
+ .Where(x => x.InstanceId == metadata.Id)
+ .FirstAsync(cancellationToken)
+ .ConfigureAwait(false);
+ if (dreamMakerSettings == default)
+ throw new JobException(ErrorCode.InstanceMissingDreamMakerSettings);
+
+ repositorySettings = await databaseContext
+ .RepositorySettings
+ .AsQueryable()
+ .Where(x => x.InstanceId == metadata.Id)
+ .Select(x => new Models.RepositorySettings
+ {
+ AccessToken = x.AccessToken,
+ ShowTestMergeCommitters = x.ShowTestMergeCommitters,
+ PushTestMergeCommits = x.PushTestMergeCommits,
+ PostTestMergeComment = x.PostTestMergeComment
+ })
+ .FirstOrDefaultAsync(cancellationToken)
+ .ConfigureAwait(false);
+ if (repositorySettings == default)
+ throw new JobException(ErrorCode.InstanceMissingRepositorySettings);
+
+ repo = await repositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false);
try
{
- await compileJobConsumer.LoadCompileJob(compileJob, cancellationToken).ConfigureAwait(false);
+ if (repo == null)
+ throw new JobException(ErrorCode.RepoMissing);
+
+ if (repo.IsGitHubRepository)
+ {
+ repoOwner = repo.GitHubOwner;
+ repoName = repo.GitHubRepoName;
+ }
+
+ var repoSha = repo.Head;
+ revInfo = await databaseContext
+ .RevisionInformations
+ .AsQueryable()
+ .Where(x => x.CommitSha == repoSha && x.Instance.Id == metadata.Id)
+ .Include(x => x.ActiveTestMerges)
+ .ThenInclude(x => x.TestMerge)
+ .ThenInclude(x => x.MergedBy)
+ .FirstOrDefaultAsync(cancellationToken)
+ .ConfigureAwait(false);
+
+ if (revInfo == default)
+ {
+ revInfo = new Models.RevisionInformation
+ {
+ CommitSha = repoSha,
+ OriginCommitSha = repoSha,
+ Instance = new Models.Instance
+ {
+ Id = metadata.Id
+ },
+ ActiveTestMerges = new List()
+ };
+
+ logger.LogWarning(Repository.Repository.OriginTrackingErrorTemplate, repoSha);
+ databaseContext.Instances.Attach(revInfo.Instance);
+ await databaseContext.Save(cancellationToken).ConfigureAwait(false);
+ }
}
catch
{
- // So we need to un-commit the compile job if the above throws
- databaseContext.CompileJobs.Remove(compileJob);
- await databaseContext.Save(default).ConfigureAwait(false);
+ repo.Dispose();
throw;
}
})
.ConfigureAwait(false);
+
+ var likelyPushedTestMergeCommit =
+ repositorySettings.PushTestMergeCommits.Value
+ && repositorySettings.AccessToken != null
+ && repositorySettings.AccessUser != null;
+ using (repo)
+ compileJob = await Compile(
+ revInfo,
+ dreamMakerSettings,
+ ddSettings.StartupTimeout.Value,
+ repo,
+ progressReporter,
+ averageSpan,
+ likelyPushedTestMergeCommit,
+ cancellationToken)
+ .ConfigureAwait(false);
+
+ var activeCompileJob = compileJobConsumer.LatestCompileJob();
+ try
+ {
+ await databaseContextFactory.UseContext(
+ async databaseContext =>
+ {
+ compileJob.Job = new Models.Job
+ {
+ Id = job.Id
+ };
+ compileJob.RevisionInformation = new Models.RevisionInformation
+ {
+ Id = revInfo.Id
+ };
+
+ databaseContext.Jobs.Attach(compileJob.Job);
+ databaseContext.RevisionInformations.Attach(compileJob.RevisionInformation);
+ databaseContext.CompileJobs.Add(compileJob);
+
+ // The difficulty with compile jobs is they have a two part commit
+ await databaseContext.Save(cancellationToken).ConfigureAwait(false);
+ try
+ {
+ await compileJobConsumer.LoadCompileJob(compileJob, cancellationToken).ConfigureAwait(false);
+ }
+ catch
+ {
+ // So we need to un-commit the compile job if the above throws
+ databaseContext.CompileJobs.Remove(compileJob);
+ await databaseContext.Save(default).ConfigureAwait(false);
+ throw;
+ }
+ })
+ .ConfigureAwait(false);
+ }
+ catch
+ {
+ await CleanupFailedCompile(compileJob).ConfigureAwait(false);
+ throw;
+ }
+
+ var commentsTask = PostDeploymentComments(
+ revInfo,
+ activeCompileJob?.RevisionInformation,
+ repositorySettings,
+ repoOwner,
+ repoName);
+
+ var eventTask = eventConsumer.HandleEvent(EventType.DeploymentComplete, null, cancellationToken);
+
+ var chatTask = currentChatCallback(null, compileJob.Output);
+ currentChatCallback = null;
+
+ try
+ {
+ await Task.WhenAll(commentsTask, eventTask, chatTask).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ throw new JobException(ErrorCode.PostDeployFailure, ex);
+ }
}
catch (Exception ex)
{
- await CleanupFailedCompile(compileJob, ex is OperationCanceledException, default).ConfigureAwait(false);
+ if (currentChatCallback != null)
+ await currentChatCallback(
+ ex.Message,
+ currentDreamMakerOutput)
+ .ConfigureAwait(false);
+
throw;
}
-
- var commentsTask = PostDeploymentComments(
- revInfo,
- activeCompileJob?.RevisionInformation,
- repositorySettings,
- repoOwner,
- repoName);
-
- var eventTask = eventConsumer.HandleEvent(EventType.DeploymentComplete, null, cancellationToken);
-
- var chatTask = chatManager.SendUpdateMessage(
- String.Format(
- CultureInfo.InvariantCulture,
- "Deployment complete! Changes will be applied when DreamDaemon {0}.",
- watchdog.Running ? "reboots" : "is launched"),
- cancellationToken);
-
- try
+ finally
{
- await Task.WhenAll(commentsTask, eventTask, chatTask).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- throw new JobException(ErrorCode.PostDeployFailure, ex);
+ deploying = false;
}
}
#pragma warning restore CA1506
@@ -727,7 +694,9 @@ namespace Tgstation.Server.Host.Components.Deployment
/// A resulting in the average of the 10 previous deployments or if there are none.
async Task CalculateExpectedDeploymentTime(IDatabaseContext databaseContext, CancellationToken cancellationToken)
{
- var previousCompileJobs = await databaseContext.CompileJobs
+ var previousCompileJobs = await databaseContext
+ .CompileJobs
+ .AsQueryable()
.Where(x => x.Job.Instance.Id == metadata.Id)
.OrderByDescending(x => x.Job.StoppedAt)
.Take(10)
@@ -751,23 +720,32 @@ namespace Tgstation.Server.Host.Components.Deployment
return averageSpan;
}
- async Task Compile(Models.RevisionInformation revisionInformation, Api.Models.DreamMaker dreamMakerSettings, uint apiValidateTimeout, IRepository repository, Action progressReporter, TimeSpan? estimatedDuration, CancellationToken cancellationToken)
+ async Task Compile(
+ Models.RevisionInformation revisionInformation,
+ Api.Models.DreamMaker dreamMakerSettings,
+ uint apiValidateTimeout,
+ IRepository repository,
+ Action progressReporter,
+ TimeSpan? estimatedDuration,
+ bool localCommitExistsOnRemote,
+ CancellationToken cancellationToken)
{
logger.LogTrace("Begin Compile");
- lock (compilingLock)
- {
- if (compiling)
- throw new JobException(ErrorCode.DreamMakerCompileJobInProgress);
- compiling = true;
- }
-
using var progressCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
var progressTask = estimatedDuration.HasValue ? ProgressTask(progressReporter, estimatedDuration.Value, progressCts.Token) : Task.CompletedTask;
try
{
using var byondLock = await byond.UseExecutables(null, cancellationToken).ConfigureAwait(false);
- await SendDeploymentMessage(revisionInformation, byondLock, cancellationToken).ConfigureAwait(false);
+ currentChatCallback = await chatManager.SendDeploymentMessage(
+ revisionInformation,
+ byondLock.Version,
+ DateTimeOffset.Now + estimatedDuration,
+ repository.GitHubOwner,
+ repository.GitHubRepoName,
+ localCommitExistsOnRemote,
+ cancellationToken)
+ .ConfigureAwait(false);
var job = new Models.CompileJob
{
@@ -788,7 +766,6 @@ namespace Tgstation.Server.Host.Components.Deployment
}
finally
{
- compiling = false;
progressCts.Cancel();
await progressTask.ConfigureAwait(false);
}
diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs
index d1c281352b..2782ee9934 100644
--- a/src/Tgstation.Server.Host/Components/Instance.cs
+++ b/src/Tgstation.Server.Host/Components/Instance.cs
@@ -162,6 +162,7 @@ namespace Tgstation.Server.Host.Components
await databaseContextFactory.UseContext(
async (db) => user = await db
.Users
+ .AsQueryable()
.Where(x => x.CanonicalName == User.CanonicalizeName(Api.Models.User.AdminName))
.FirstAsync(cancellationToken)
.ConfigureAwait(false))
@@ -189,7 +190,11 @@ namespace Tgstation.Server.Host.Components
await databaseContextFactory.UseContext(
async databaseContext =>
{
- var repositorySettingsTask = databaseContext.RepositorySettings.Where(x => x.InstanceId == metadata.Id).FirstAsync(jobCancellationToken);
+ var repositorySettingsTask = databaseContext
+ .RepositorySettings
+ .AsQueryable()
+ .Where(x => x.InstanceId == metadata.Id)
+ .FirstAsync(jobCancellationToken);
const int NumSteps = 3;
var doneSteps = 0;
@@ -225,6 +230,7 @@ namespace Tgstation.Server.Host.Components
bool hasDbChanges = false;
Task LoadRevInfo() => databaseContext.RevisionInformations
+ .AsQueryable()
.Where(x => x.CommitSha == startSha && x.Instance.Id == metadata.Id)
.Include(x => x.ActiveTestMerges).ThenInclude(x => x.TestMerge)
.FirstOrDefaultAsync(cancellationToken);
@@ -294,6 +300,7 @@ namespace Tgstation.Server.Host.Components
var currentHead = repo.Head;
currentRevInfo = await databaseContext.RevisionInformations
+ .AsQueryable()
.Where(x => x.CommitSha == currentHead && x.Instance.Id == metadata.Id)
.FirstOrDefaultAsync(jobCancellationToken).ConfigureAwait(false);
diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs
index 9be0802cc7..23aa1da429 100644
--- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs
+++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs
@@ -262,7 +262,6 @@ namespace Tgstation.Server.Host.Components
eventConsumer,
chatManager,
processExecutor,
- watchdog,
gitHubClientFactory,
dmbFactory,
repoManager,
diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs
index 3e61925a27..062dca8163 100644
--- a/src/Tgstation.Server.Host/Components/InstanceManager.cs
+++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs
@@ -213,10 +213,14 @@ namespace Tgstation.Server.Host.Components
var tasks = new List();
await databaseContextFactory.UseContext(async db =>
{
- var jobs = db.Jobs.Where(x => x.Instance.Id == metadata.Id).Select(x => new Models.Job
- {
- Id = x.Id
- }).ToAsyncEnumerable();
+ var jobs = db
+ .Jobs
+ .AsQueryable()
+ .Where(x => x.Instance.Id == metadata.Id)
+ .Select(x => new Models.Job
+ {
+ Id = x.Id
+ });
await jobs.ForEachAsync(job =>
{
lock (tasks)
@@ -271,7 +275,10 @@ namespace Tgstation.Server.Host.Components
var factoryStartup = instanceFactory.StartAsync(cancellationToken);
await databaseContext.Initialize(cancellationToken).ConfigureAwait(false);
await jobManager.StartAsync(cancellationToken).ConfigureAwait(false);
- var dbInstances = databaseContext.Instances.Where(x => x.Online.Value)
+ var dbInstances = databaseContext
+ .Instances
+ .AsQueryable()
+ .Where(x => x.Online.Value)
.Include(x => x.RepositorySettings)
.Include(x => x.ChatSettings)
.ThenInclude(x => x.Channels)
diff --git a/src/Tgstation.Server.Host/Components/Interop/DMApiConstants.cs b/src/Tgstation.Server.Host/Components/Interop/DMApiConstants.cs
index 125938305b..d91d5bdbf7 100644
--- a/src/Tgstation.Server.Host/Components/Interop/DMApiConstants.cs
+++ b/src/Tgstation.Server.Host/Components/Interop/DMApiConstants.cs
@@ -33,7 +33,7 @@ namespace Tgstation.Server.Host.Components.Interop
///
/// The DMAPI being used.
///
- public static readonly Version Version = new Version(5, 2, 1);
+ public static readonly Version Version = new Version(5, 2, 2);
///
/// for use when communicating with the DMAPI.
diff --git a/src/Tgstation.Server.Host/Components/Session/ReattachInfoHandler.cs b/src/Tgstation.Server.Host/Components/Session/ReattachInfoHandler.cs
index c194aad5b0..001f0a1087 100644
--- a/src/Tgstation.Server.Host/Components/Session/ReattachInfoHandler.cs
+++ b/src/Tgstation.Server.Host/Components/Session/ReattachInfoHandler.cs
@@ -56,7 +56,11 @@ namespace Tgstation.Server.Host.Components.Session
logger.LogDebug("Saving reattach information: {0}...", reattachInformation);
- var deleteTask = db.WatchdogReattachInformations.Where(x => x.InstanceId == metadata.Id).DeleteAsync(cancellationToken);
+ var deleteTask = db
+ .WatchdogReattachInformations
+ .AsQueryable()
+ .Where(x => x.InstanceId == metadata.Id)
+ .DeleteAsync(cancellationToken);
Models.ReattachInformation ConvertReattachInfo(ReattachInformation wdInfo)
{
@@ -93,7 +97,9 @@ namespace Tgstation.Server.Host.Components.Session
Models.DualReattachInformation result = null;
await databaseContextFactory.UseContext(async (db) =>
{
- var instance = await db.Instances.Where(x => x.Id == metadata.Id)
+ var instance = await db.Instances
+ .AsQueryable()
+ .Where(x => x.Id == metadata.Id)
.Include(x => x.WatchdogReattachInformation).ThenInclude(x => x.Alpha).ThenInclude(x => x.CompileJob)
.Include(x => x.WatchdogReattachInformation).ThenInclude(x => x.Bravo).ThenInclude(x => x.CompileJob)
.FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
diff --git a/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs
index 13c96f350a..e3e8968fd8 100644
--- a/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs
+++ b/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs
@@ -192,7 +192,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
};
///
- protected override void DisposeAndNullControllers()
+ protected override void DisposeAndNullControllersImpl()
{
Server?.Dispose();
Server = null;
diff --git a/src/Tgstation.Server.Host/Components/Watchdog/ExperimentalWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/ExperimentalWatchdog.cs
index 02daf7d157..6547ef6d62 100644
--- a/src/Tgstation.Server.Host/Components/Watchdog/ExperimentalWatchdog.cs
+++ b/src/Tgstation.Server.Host/Components/Watchdog/ExperimentalWatchdog.cs
@@ -392,10 +392,8 @@ namespace Tgstation.Server.Host.Components.Watchdog
}
#pragma warning restore CA1502
- ///
- /// Call on and and set them to
- ///
- protected override void DisposeAndNullControllers()
+ ///
+ protected override void DisposeAndNullControllersImpl()
{
alphaServer?.Dispose();
alphaServer = null;
diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs
index 74b3e06371..d6eafdef32 100644
--- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs
+++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs
@@ -112,6 +112,11 @@ namespace Tgstation.Server.Host.Components.Watchdog
///
readonly IRestartRegistration restartRegistration;
+ ///
+ /// used for .
+ ///
+ readonly object controllerDisposeLock;
+
///
/// If the should in
///
@@ -201,6 +206,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
ActiveLaunchParameters = initialLaunchParameters;
releaseServers = false;
ActiveParametersUpdated = new TaskCompletionSource
public const string Section = "General";
+ ///
+ /// The default value of .
+ ///
+ public const ushort DefaultApiPort = 5000;
+
///
/// The default value for .
///
@@ -37,7 +42,12 @@ namespace Tgstation.Server.Host.Configuration
///
/// The default value for
///
- const int DefaultRestartTimeout = 10000;
+ const int DefaultRestartTimeout = 60000;
+
+ ///
+ /// The port the TGS API listens on.
+ ///
+ public ushort ApiPort { get; set; }
///
/// A GitHub personal access token to use for bypassing rate limits on requests. Requires no scopes
diff --git a/src/Tgstation.Server.Host/Controllers/ApiController.cs b/src/Tgstation.Server.Host/Controllers/ApiController.cs
index c021746687..a60949643f 100644
--- a/src/Tgstation.Server.Host/Controllers/ApiController.cs
+++ b/src/Tgstation.Server.Host/Controllers/ApiController.cs
@@ -163,7 +163,7 @@ namespace Tgstation.Server.Host.Controllers
if (ApiHeaders != null)
Logger.LogDebug(
- "Request made by User ID {0}. Api version: {1}. User-Agent: {2}. Type: {3}. Route {4}{5} to Instance {6}",
+ "Request details: User ID {0}. Api version: {1}. User-Agent: {2}. Type: {3}. Route {4}{5} to Instance {6}",
AuthenticationContext?.User.Id.Value.ToString(CultureInfo.InvariantCulture),
ApiHeaders.ApiVersion.Semver(),
ApiHeaders.RawUserAgent,
diff --git a/src/Tgstation.Server.Host/Controllers/ChatController.cs b/src/Tgstation.Server.Host/Controllers/ChatController.cs
index 321a004b71..153406d210 100644
--- a/src/Tgstation.Server.Host/Controllers/ChatController.cs
+++ b/src/Tgstation.Server.Host/Controllers/ChatController.cs
@@ -80,6 +80,7 @@ namespace Tgstation.Server.Host.Controllers
var countOfExistingBotsInInstance = await DatabaseContext
.ChatBots
+ .AsQueryable()
.Where(x => x.InstanceId == Instance.Id)
.CountAsync(cancellationToken)
.ConfigureAwait(false);
@@ -140,7 +141,14 @@ namespace Tgstation.Server.Host.Controllers
public async Task Delete(long id, CancellationToken cancellationToken)
{
var instance = instanceManager.GetInstance(Instance);
- await Task.WhenAll(instance.Chat.DeleteConnection(id, cancellationToken), DatabaseContext.ChatBots.Where(x => x.Id == id).DeleteAsync(cancellationToken)).ConfigureAwait(false);
+ await Task.WhenAll(
+ instance.Chat.DeleteConnection(id, cancellationToken),
+ DatabaseContext
+ .ChatBots
+ .AsQueryable()
+ .Where(x => x.Id == id)
+ .DeleteAsync(cancellationToken))
+ .ConfigureAwait(false);
return Ok();
}
@@ -156,7 +164,11 @@ namespace Tgstation.Server.Host.Controllers
[ProducesResponseType(typeof(IEnumerable), 200)]
public async Task List(CancellationToken cancellationToken)
{
- var query = DatabaseContext.ChatBots.Where(x => x.InstanceId == Instance.Id).Include(x => x.Channels);
+ var query = DatabaseContext
+ .ChatBots
+ .AsQueryable()
+ .Where(x => x.InstanceId == Instance.Id)
+ .Include(x => x.Channels);
var results = await query.ToListAsync(cancellationToken).ConfigureAwait(false);
@@ -183,7 +195,10 @@ namespace Tgstation.Server.Host.Controllers
[ProducesResponseType(410)]
public async Task GetId(long id, CancellationToken cancellationToken)
{
- var query = DatabaseContext.ChatBots.Where(x => x.Id == id).Include(x => x.Channels);
+ var query = DatabaseContext.ChatBots
+ .AsQueryable()
+ .Where(x => x.Id == id)
+ .Include(x => x.Channels);
var results = await query.FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
if (results == default)
@@ -221,7 +236,11 @@ namespace Tgstation.Server.Host.Controllers
if (earlyOut != null)
return earlyOut;
- var query = DatabaseContext.ChatBots.Where(x => x.InstanceId == Instance.Id && x.Id == model.Id).Include(x => x.Channels);
+ var query = DatabaseContext
+ .ChatBots
+ .AsQueryable()
+ .Where(x => x.InstanceId == Instance.Id && x.Id == model.Id)
+ .Include(x => x.Channels);
var current = await query.FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs
index bae044577b..f2ed58b9d8 100644
--- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs
+++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs
@@ -109,7 +109,13 @@ namespace Tgstation.Server.Host.Controllers
if (settings == null)
{
- settings = await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).Select(x => x.DreamDaemonSettings).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
+ settings = await DatabaseContext
+ .Instances
+ .AsQueryable()
+ .Where(x => x.Id == Instance.Id)
+ .Select(x => x.DreamDaemonSettings)
+ .FirstOrDefaultAsync(cancellationToken)
+ .ConfigureAwait(false);
if (settings == default)
return StatusCode((int)HttpStatusCode.Gone);
}
@@ -191,7 +197,13 @@ namespace Tgstation.Server.Host.Controllers
return BadRequest(new ErrorMessage(ErrorCode.DreamDaemonDoubleSoft));
// alias for changing DD settings
- var current = await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).Select(x => x.DreamDaemonSettings).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
+ var current = await DatabaseContext
+ .Instances
+ .AsQueryable()
+ .Where(x => x.Id == Instance.Id)
+ .Select(x => x.DreamDaemonSettings)
+ .FirstOrDefaultAsync(cancellationToken)
+ .ConfigureAwait(false);
if (current == default)
return StatusCode((int)HttpStatusCode.Gone);
diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs
index 3f011a9cea..bf2354fc98 100644
--- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs
+++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs
@@ -61,7 +61,12 @@ namespace Tgstation.Server.Host.Controllers
public async Task Read(CancellationToken cancellationToken)
{
var instance = instanceManager.GetInstance(Instance);
- var dreamMakerSettings = await DatabaseContext.DreamMakerSettings.Where(x => x.InstanceId == Instance.Id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
+ var dreamMakerSettings = await DatabaseContext
+ .DreamMakerSettings
+ .AsQueryable()
+ .Where(x => x.InstanceId == Instance.Id)
+ .FirstOrDefaultAsync(cancellationToken)
+ .ConfigureAwait(false);
return Json(dreamMakerSettings.ToApi());
}
@@ -79,7 +84,9 @@ namespace Tgstation.Server.Host.Controllers
[ProducesResponseType(404)]
public async Task GetId(long id, CancellationToken cancellationToken)
{
- var compileJob = await DatabaseContext.CompileJobs
+ var compileJob = await DatabaseContext
+ .CompileJobs
+ .AsQueryable()
.Where(x => x.Id == id && x.Job.Instance.Id == Instance.Id)
.Include(x => x.Job).ThenInclude(x => x.StartedBy)
.Include(x => x.RevisionInformation).ThenInclude(x => x.PrimaryTestMerge).ThenInclude(x => x.MergedBy)
@@ -101,10 +108,17 @@ namespace Tgstation.Server.Host.Controllers
[ProducesResponseType(typeof(List), 200)]
public async Task List(CancellationToken cancellationToken)
{
- var compileJobs = await DatabaseContext.CompileJobs.Where(x => x.Job.Instance.Id == Instance.Id).OrderByDescending(x => x.Job.StoppedAt).Select(x => new EntityId
- {
- Id = x.Id
- }).ToListAsync(cancellationToken).ConfigureAwait(false);
+ var compileJobs = await DatabaseContext
+ .CompileJobs
+ .AsQueryable()
+ .Where(x => x.Job.Instance.Id == Instance.Id)
+ .OrderByDescending(x => x.Job.StoppedAt)
+ .Select(x => new EntityId
+ {
+ Id = x.Id
+ })
+ .ToListAsync(cancellationToken)
+ .ConfigureAwait(false);
return Json(compileJobs);
}
@@ -159,7 +173,12 @@ namespace Tgstation.Server.Host.Controllers
if (model.ApiValidationPort == 0)
throw new InvalidOperationException("ApiValidationPort cannot be 0!");
- var hostModel = await DatabaseContext.DreamMakerSettings.Where(x => x.InstanceId == Instance.Id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
+ var hostModel = await DatabaseContext
+ .DreamMakerSettings
+ .AsQueryable()
+ .Where(x => x.InstanceId == Instance.Id)
+ .FirstOrDefaultAsync(cancellationToken)
+ .ConfigureAwait(false);
if (hostModel == null)
return StatusCode((int)HttpStatusCode.Gone);
diff --git a/src/Tgstation.Server.Host/Controllers/HomeController.cs b/src/Tgstation.Server.Host/Controllers/HomeController.cs
index eff10e36d1..b10307a232 100644
--- a/src/Tgstation.Server.Host/Controllers/HomeController.cs
+++ b/src/Tgstation.Server.Host/Controllers/HomeController.cs
@@ -196,12 +196,12 @@ namespace Tgstation.Server.Host.Controllers
using (systemIdentity)
{
// Get the user from the database
- IQueryable query;
+ IQueryable query = DatabaseContext.Users.AsQueryable();
string canonicalName = Models.User.CanonicalizeName(ApiHeaders.Username);
if (systemIdentity == null)
- query = DatabaseContext.Users.Where(x => x.CanonicalName == canonicalName);
+ query = query.Where(x => x.CanonicalName == canonicalName);
else
- query = DatabaseContext.Users.Where(x => x.CanonicalName == canonicalName || x.SystemIdentifier == systemIdentity.Uid);
+ query = query.Where(x => x.CanonicalName == canonicalName || x.SystemIdentifier == systemIdentity.Uid);
var users = await query.Select(x => new User
{
Id = x.Id,
diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs
index ed76728410..fe2dbb1233 100644
--- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs
+++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs
@@ -174,18 +174,25 @@ namespace Tgstation.Server.Host.Controllers
var newCancellationToken = cts.Token;
try
{
- await DatabaseContext.Instances.ForEachAsync(
- otherInstance =>
+ await DatabaseContext
+ .Instances
+ .AsQueryable()
+ .Select(x => new Models.Instance
{
- if (++countOfOtherInstances >= generalConfiguration.InstanceLimit)
- earlyOut ??= Conflict(new ErrorMessage(ErrorCode.InstanceLimitReached));
- else if (InstanceIsChildOf(otherInstance.Path))
- earlyOut ??= Conflict(new ErrorMessage(ErrorCode.InstanceAtConflictingPath));
+ Path = x.Path
+ })
+ .ForEachAsync(
+ otherInstance =>
+ {
+ if (++countOfOtherInstances >= generalConfiguration.InstanceLimit)
+ earlyOut ??= Conflict(new ErrorMessage(ErrorCode.InstanceLimitReached));
+ else if (InstanceIsChildOf(otherInstance.Path))
+ earlyOut ??= Conflict(new ErrorMessage(ErrorCode.InstanceAtConflictingPath));
- if (earlyOut != null && !newCancellationToken.IsCancellationRequested)
- cts.Cancel();
- },
- newCancellationToken)
+ if (earlyOut != null && !newCancellationToken.IsCancellationRequested)
+ cts.Cancel();
+ },
+ newCancellationToken)
.ConfigureAwait(false);
}
catch (OperationCanceledException)
@@ -312,7 +319,10 @@ namespace Tgstation.Server.Host.Controllers
[ProducesResponseType(410)]
public async Task Delete(long id, CancellationToken cancellationToken)
{
- var originalModel = await DatabaseContext.Instances.Where(x => x.Id == id)
+ var originalModel = await DatabaseContext
+ .Instances
+ .AsQueryable()
+ .Where(x => x.Id == id)
.Include(x => x.WatchdogReattachInformation)
.Include(x => x.WatchdogReattachInformation.Alpha)
.Include(x => x.WatchdogReattachInformation.Bravo)
@@ -358,7 +368,10 @@ namespace Tgstation.Server.Host.Controllers
if (model == null)
throw new ArgumentNullException(nameof(model));
- IQueryable InstanceQuery() => DatabaseContext.Instances.Where(x => x.Id == model.Id);
+ IQueryable InstanceQuery() => DatabaseContext
+ .Instances
+ .AsQueryable()
+ .Where(x => x.Id == model.Id);
var moveJob = await InstanceQuery()
.SelectMany(x => x.Jobs).
@@ -435,6 +448,7 @@ namespace Tgstation.Server.Host.Controllers
{
var countOfExistingChatBots = await DatabaseContext
.ChatBots
+ .AsQueryable()
.Where(x => x.InstanceId == originalModel.Id)
.CountAsync(cancellationToken)
.ConfigureAwait(false);
@@ -582,7 +596,10 @@ namespace Tgstation.Server.Host.Controllers
var cantList = !AuthenticationContext.User.InstanceManagerRights.Value.HasFlag(InstanceManagerRights.List);
IQueryable QueryForUser()
{
- var query = DatabaseContext.Instances.Where(x => x.Id == id);
+ var query = DatabaseContext
+ .Instances
+ .AsQueryable()
+ .Where(x => x.Id == id);
if (cantList)
query = query.Include(x => x.InstanceUsers);
diff --git a/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs b/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs
index 6b0e2237cd..edd642fab8 100644
--- a/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs
+++ b/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs
@@ -101,7 +101,14 @@ namespace Tgstation.Server.Host.Controllers
if (earlyOut != null)
return earlyOut;
- var originalUser = await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).SelectMany(x => x.InstanceUsers).Where(x => x.UserId == model.UserId).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
+ var originalUser = await DatabaseContext
+ .Instances
+ .AsQueryable()
+ .Where(x => x.Id == Instance.Id)
+ .SelectMany(x => x.InstanceUsers)
+ .Where(x => x.UserId == model.UserId)
+ .FirstOrDefaultAsync(cancellationToken)
+ .ConfigureAwait(false);
if (originalUser == null)
return StatusCode((int)HttpStatusCode.Gone);
@@ -141,7 +148,13 @@ namespace Tgstation.Server.Host.Controllers
[ProducesResponseType(typeof(IEnumerable), 200)]
public async Task List(CancellationToken cancellationToken)
{
- var users = await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).SelectMany(x => x.InstanceUsers).ToListAsync(cancellationToken).ConfigureAwait(false);
+ var users = await DatabaseContext
+ .Instances
+ .AsQueryable()
+ .Where(x => x.Id == Instance.Id)
+ .SelectMany(x => x.InstanceUsers)
+ .ToListAsync(cancellationToken)
+ .ConfigureAwait(false);
return Json(users.Select(x => x.ToApi()));
}
@@ -160,7 +173,14 @@ namespace Tgstation.Server.Host.Controllers
public async Task GetId(long id, CancellationToken cancellationToken)
{
// this functions as userId
- var user = await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).SelectMany(x => x.InstanceUsers).Where(x => x.UserId == id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
+ var user = await DatabaseContext
+ .Instances
+ .AsQueryable()
+ .Where(x => x.Id == Instance.Id)
+ .SelectMany(x => x.InstanceUsers)
+ .Where(x => x.UserId == id)
+ .FirstOrDefaultAsync(cancellationToken)
+ .ConfigureAwait(false);
if (user == default)
return StatusCode((int)HttpStatusCode.Gone);
return Json(user.ToApi());
@@ -178,7 +198,14 @@ namespace Tgstation.Server.Host.Controllers
[ProducesResponseType(204)]
public async Task Delete(long id, CancellationToken cancellationToken)
{
- await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).SelectMany(x => x.InstanceUsers).Where(x => x.UserId == id).DeleteAsync(cancellationToken).ConfigureAwait(false);
+ await DatabaseContext
+ .Instances
+ .AsQueryable()
+ .Where(x => x.Id == Instance.Id)
+ .SelectMany(x => x.InstanceUsers)
+ .Where(x => x.UserId == id)
+ .DeleteAsync(cancellationToken)
+ .ConfigureAwait(false);
return NoContent();
}
}
diff --git a/src/Tgstation.Server.Host/Controllers/JobController.cs b/src/Tgstation.Server.Host/Controllers/JobController.cs
index f912483f85..483d9ed476 100644
--- a/src/Tgstation.Server.Host/Controllers/JobController.cs
+++ b/src/Tgstation.Server.Host/Controllers/JobController.cs
@@ -49,7 +49,13 @@ namespace Tgstation.Server.Host.Controllers
[ProducesResponseType(typeof(IEnumerable), 200)]
public async Task Read(CancellationToken cancellationToken)
{
- var result = await DatabaseContext.Jobs.Where(x => x.Instance.Id == Instance.Id && !x.StoppedAt.HasValue).OrderByDescending(x => x.StartedAt).ToListAsync(cancellationToken).ConfigureAwait(false);
+ var result = await DatabaseContext
+ .Jobs
+ .AsQueryable()
+ .Where(x => x.Instance.Id == Instance.Id && !x.StoppedAt.HasValue)
+ .OrderByDescending(x => x.StartedAt)
+ .ToListAsync(cancellationToken)
+ .ConfigureAwait(false);
return Json(result.Select(x => x.ToApi()));
}
@@ -65,10 +71,17 @@ namespace Tgstation.Server.Host.Controllers
public async Task List(CancellationToken cancellationToken)
{
// you KNOW this will need pagination eventually right?
- var jobs = await DatabaseContext.Jobs.Where(x => x.Instance.Id == Instance.Id).OrderByDescending(x => x.StartedAt).Select(x => new Api.Models.EntityId
- {
- Id = x.Id
- }).ToListAsync(cancellationToken).ConfigureAwait(false);
+ var jobs = await DatabaseContext
+ .Jobs
+ .AsQueryable()
+ .Where(x => x.Instance.Id == Instance.Id)
+ .OrderByDescending(x => x.StartedAt)
+ .Select(x => new Api.Models.EntityId
+ {
+ Id = x.Id
+ })
+ .ToListAsync(cancellationToken)
+ .ConfigureAwait(false);
return Json(jobs);
}
@@ -89,7 +102,12 @@ namespace Tgstation.Server.Host.Controllers
public async Task Delete(long id, CancellationToken cancellationToken)
{
// don't care if an instance post or not at this point
- var job = await DatabaseContext.Jobs.Where(x => x.Id == id && x.Instance.Id == Instance.Id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
+ var job = await DatabaseContext
+ .Jobs
+ .AsQueryable()
+ .Where(x => x.Id == id && x.Instance.Id == Instance.Id)
+ .FirstOrDefaultAsync(cancellationToken)
+ .ConfigureAwait(false);
if (job == default(Job))
return NotFound();
@@ -117,7 +135,13 @@ namespace Tgstation.Server.Host.Controllers
[ProducesResponseType(404)]
public async Task GetId(long id, CancellationToken cancellationToken)
{
- var job = await DatabaseContext.Jobs.Where(x => x.Id == id && x.Instance.Id == Instance.Id).Include(x => x.StartedBy).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
+ var job = await DatabaseContext
+ .Jobs
+ .AsQueryable()
+ .Where(x => x.Id == id && x.Instance.Id == Instance.Id)
+ .Include(x => x.StartedBy)
+ .FirstOrDefaultAsync(cancellationToken)
+ .ConfigureAwait(false);
if (job == default)
return NotFound();
var api = job.ToApi();
diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs
index 08bc40afbd..287a9bc26a 100644
--- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs
+++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs
@@ -156,7 +156,12 @@ namespace Tgstation.Server.Host.Controllers
if (model.AccessUser == null ^ model.AccessToken == null)
return BadRequest(ErrorCode.RepoMismatchUserAndAccessToken);
- var currentModel = await DatabaseContext.RepositorySettings.Where(x => x.InstanceId == Instance.Id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
+ var currentModel = await DatabaseContext
+ .RepositorySettings
+ .AsQueryable()
+ .Where(x => x.InstanceId == Instance.Id)
+ .FirstOrDefaultAsync(cancellationToken)
+ .ConfigureAwait(false);
if (currentModel == default)
return StatusCode((int)HttpStatusCode.Gone);
@@ -236,7 +241,12 @@ namespace Tgstation.Server.Host.Controllers
[ProducesResponseType(410)]
public async Task Delete(CancellationToken cancellationToken)
{
- var currentModel = await DatabaseContext.RepositorySettings.Where(x => x.InstanceId == Instance.Id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
+ var currentModel = await DatabaseContext
+ .RepositorySettings
+ .AsQueryable()
+ .Where(x => x.InstanceId == Instance.Id)
+ .FirstOrDefaultAsync(cancellationToken)
+ .ConfigureAwait(false);
if (currentModel == default)
return StatusCode((int)HttpStatusCode.Gone);
@@ -275,7 +285,12 @@ namespace Tgstation.Server.Host.Controllers
[ProducesResponseType(410)]
public async Task Read(CancellationToken cancellationToken)
{
- var currentModel = await DatabaseContext.RepositorySettings.Where(x => x.InstanceId == Instance.Id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
+ var currentModel = await DatabaseContext
+ .RepositorySettings
+ .AsQueryable()
+ .Where(x => x.InstanceId == Instance.Id)
+ .FirstOrDefaultAsync(cancellationToken)
+ .ConfigureAwait(false);
if (currentModel == default)
return StatusCode((int)HttpStatusCode.Gone, new ErrorMessage(ErrorCode.RepoMissing));
@@ -345,6 +360,7 @@ namespace Tgstation.Server.Host.Controllers
var currentModel = await DatabaseContext
.RepositorySettings
+ .AsQueryable()
.Where(x => x.InstanceId == Instance.Id)
.FirstOrDefaultAsync(cancellationToken)
.ConfigureAwait(false);
@@ -653,6 +669,7 @@ namespace Tgstation.Server.Host.Controllers
await databaseContextFactory.UseContext(
async databaseContext =>
dbPull = await databaseContext.RevisionInformations
+ .AsQueryable()
.Where(x => x.Instance.Id == Instance.Id
&& x.OriginCommitSha == lastRevisionInfo.OriginCommitSha
&& x.ActiveTestMerges.Count <= model.NewTestMerges.Count
diff --git a/src/Tgstation.Server.Host/Controllers/UserController.cs b/src/Tgstation.Server.Host/Controllers/UserController.cs
index 820f043ac3..6f0a1d1c0c 100644
--- a/src/Tgstation.Server.Host/Controllers/UserController.cs
+++ b/src/Tgstation.Server.Host/Controllers/UserController.cs
@@ -192,7 +192,10 @@ namespace Tgstation.Server.Host.Controllers
var originalUser = passwordEditOnly
? AuthenticationContext.User
- : await DatabaseContext.Users.Where(x => x.Id == model.Id)
+ : await DatabaseContext
+ .Users
+ .AsQueryable()
+ .Where(x => x.Id == model.Id)
.Include(x => x.CreatedBy)
.FirstOrDefaultAsync(cancellationToken)
.ConfigureAwait(false);
@@ -301,6 +304,7 @@ namespace Tgstation.Server.Host.Controllers
return Forbid();
var user = await DatabaseContext.Users
+ .AsQueryable()
.Where(x => x.Id == id)
.Include(x => x.CreatedBy)
.FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs
index 7117a95628..c7ed253c9c 100644
--- a/src/Tgstation.Server.Host/Core/Application.cs
+++ b/src/Tgstation.Server.Host/Core/Application.cs
@@ -202,7 +202,7 @@ namespace Tgstation.Server.Host.Core
// CORS conditionally enabled later
services.AddCors();
- void AddTypedContext() where TContext : DatabaseContext
+ void AddTypedContext() where TContext : DatabaseContext
{
services.AddDbContext(builder =>
{
@@ -226,6 +226,9 @@ namespace Tgstation.Server.Host.Core
case DatabaseType.Sqlite:
AddTypedContext();
break;
+ case DatabaseType.PostgresSql:
+ AddTypedContext();
+ break;
default:
throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid {0}: {1}!", nameof(DatabaseType), dbType));
}
@@ -354,6 +357,8 @@ namespace Tgstation.Server.Host.Core
// Final point where we wrap exceptions in a 500 (ErrorMessage) response
applicationBuilder.UseServerErrorHandling();
+ applicationBuilder.UseRequestCounting();
+
// 503 requests made while the application is starting
applicationBuilder.UseAsyncInitialization(async (cancellationToken) =>
{
diff --git a/src/Tgstation.Server.Host/Core/ServerPortProivder.cs b/src/Tgstation.Server.Host/Core/ServerPortProivder.cs
index a71dc72a29..04c009949d 100644
--- a/src/Tgstation.Server.Host/Core/ServerPortProivder.cs
+++ b/src/Tgstation.Server.Host/Core/ServerPortProivder.cs
@@ -1,6 +1,9 @@
using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
using System;
using System.Linq;
+using Tgstation.Server.Host.Configuration;
namespace Tgstation.Server.Host.Core
{
@@ -8,14 +11,25 @@ namespace Tgstation.Server.Host.Core
sealed class ServerPortProivder : IServerPortProvider
{
///
- public ushort HttpApiPort { get; }
+ public ushort HttpApiPort => generalConfiguration.ApiPort;
+
+ ///
+ /// The for the .
+ ///
+ readonly GeneralConfiguration generalConfiguration;
///
/// Initializes a new instance of the .
///
+ /// The containing the value of .
/// The to use.
- public ServerPortProivder(IConfiguration configuration)
+ /// The to use.
+ public ServerPortProivder(
+ IOptions generalConfigurationOptions,
+ IConfiguration configuration,
+ ILogger logger)
{
+ generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
if (configuration == null)
throw new ArgumentNullException(nameof(configuration));
@@ -26,8 +40,13 @@ namespace Tgstation.Server.Host.Core
.GetSection("Url")
.Value;
- if (httpEndpoint == null)
- throw new InvalidOperationException("Missing required configuration option Kestrel:EndPoints:Http:Url!");
+ if (generalConfiguration.ApiPort == default && httpEndpoint == null)
+ throw new InvalidOperationException("Missing required configuration option General:ApiPort!");
+
+ if (generalConfiguration.ApiPort != default)
+ return;
+
+ logger.LogWarning("The \"Kestrel\" configuration section is deprecated! Please set your API port using the \"General:ApiPort\" configuration option!");
var splits = httpEndpoint.Split(":", StringSplitOptions.RemoveEmptyEntries);
var portString = splits.Last();
@@ -36,7 +55,7 @@ namespace Tgstation.Server.Host.Core
if (!UInt16.TryParse(portString, out var result))
throw new InvalidOperationException($"Failed to parse HTTP EndPoint port: {httpEndpoint}");
- HttpApiPort = result;
+ generalConfiguration.ApiPort = result;
}
}
}
diff --git a/src/Tgstation.Server.Host/Core/SwaggerConfiguration.cs b/src/Tgstation.Server.Host/Core/SwaggerConfiguration.cs
index cadd4c3e5f..fa4c191023 100644
--- a/src/Tgstation.Server.Host/Core/SwaggerConfiguration.cs
+++ b/src/Tgstation.Server.Host/Core/SwaggerConfiguration.cs
@@ -133,6 +133,14 @@ namespace Tgstation.Server.Host.Core
swaggerGenOptions.DocumentFilter();
swaggerGenOptions.SchemaFilter();
+ swaggerGenOptions.CustomSchemaIds(type =>
+ {
+ if (type == typeof(Api.Models.Internal.User))
+ return "ShallowUser";
+
+ return type.Name;
+ });
+
swaggerGenOptions.AddSecurityDefinition(PasswordSecuritySchemeId, new OpenApiSecurityScheme
{
In = ParameterLocation.Header,
diff --git a/src/Tgstation.Server.Host/Database/DatabaseCollection.cs b/src/Tgstation.Server.Host/Database/DatabaseCollection.cs
index 16652b38f3..e2ebf973e8 100644
--- a/src/Tgstation.Server.Host/Database/DatabaseCollection.cs
+++ b/src/Tgstation.Server.Host/Database/DatabaseCollection.cs
@@ -5,7 +5,6 @@ using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
-using System.Threading.Tasks;
namespace Tgstation.Server.Host.Database
{
@@ -48,9 +47,7 @@ namespace Tgstation.Server.Host.Database
public void Attach(TModel model) => dbSet.Attach(model);
///
- public Task ForEachAsync(Action action, CancellationToken cancellationToken) => dbSet
- .AsAsyncEnumerable()
- .ForEachAsync(action, cancellationToken);
+ public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) => dbSet.AsAsyncEnumerable().GetAsyncEnumerator();
///
public IEnumerator GetEnumerator() => dbSet.AsQueryable().GetEnumerator();
@@ -61,9 +58,6 @@ namespace Tgstation.Server.Host.Database
///
public void RemoveRange(IEnumerable models) => dbSet.RemoveRange(models);
- ///
- public Task> ToListAsync(CancellationToken cancellationToken) => dbSet.AsQueryable().ToListAsync(cancellationToken);
-
///
IEnumerator IEnumerable.GetEnumerator() => dbSet.AsQueryable().GetEnumerator();
}
diff --git a/src/Tgstation.Server.Host/Database/DatabaseConnectionFactory.cs b/src/Tgstation.Server.Host/Database/DatabaseConnectionFactory.cs
index bc0f3f6963..037418fbdc 100644
--- a/src/Tgstation.Server.Host/Database/DatabaseConnectionFactory.cs
+++ b/src/Tgstation.Server.Host/Database/DatabaseConnectionFactory.cs
@@ -3,6 +3,7 @@ using System.Data.Common;
using System.Data.SqlClient;
using Microsoft.Data.Sqlite;
using MySql.Data.MySqlClient;
+using Npgsql;
using Tgstation.Server.Host.Configuration;
namespace Tgstation.Server.Host.Database
@@ -34,6 +35,11 @@ namespace Tgstation.Server.Host.Database
{
ConnectionString = connectionString
};
+ case DatabaseType.PostgresSql:
+ return new NpgsqlConnection
+ {
+ ConnectionString = connectionString
+ };
default:
throw new ArgumentOutOfRangeException(nameof(databaseType), databaseType, "Invalid DatabaseType!");
}
diff --git a/src/Tgstation.Server.Host/Database/DatabaseContext.cs b/src/Tgstation.Server.Host/Database/DatabaseContext.cs
index fd86c5ca35..10dddb039d 100644
--- a/src/Tgstation.Server.Host/Database/DatabaseContext.cs
+++ b/src/Tgstation.Server.Host/Database/DatabaseContext.cs
@@ -18,95 +18,94 @@ namespace Tgstation.Server.Host.Database
///
/// Backend abstract implementation of
///
- /// The child used to implement a backend.
#pragma warning disable CA1506 // TODO: Decomplexify
- abstract class DatabaseContext : DbContext, IDatabaseContext where TParentContext : DbContext
+ abstract class DatabaseContext : DbContext, IDatabaseContext
{
///
public DatabaseType DatabaseType => DatabaseConfiguration.DatabaseType;
///
- /// The s in the .
+ /// The s in the .
///
public DbSet Users { get; set; }
///
- /// The s in the .
+ /// The s in the .
///
public DbSet Instances { get; set; }
///
- /// The s in the .
+ /// The s in the .
///
public DbSet CompileJobs { get; set; }
///
- /// The s in the .
+ /// The s in the .
///
public DbSet RevisionInformations { get; set; }
///
- /// The in the .
+ /// The in the .
///
public DbSet DreamMakerSettings { get; set; }
///
- /// The s in the .
+ /// The s in the .
///
public DbSet ChatBots { get; set; }
///
- /// The in the .
+ /// The in the .
///
public DbSet DreamDaemonSettings { get; set; }
///
- /// The in the .
+ /// The in the .
///
public DbSet RepositorySettings { get; set; }
///
- /// The s in the .
+ /// The s in the .
///
public DbSet InstanceUsers { get; set; }
///
- /// The s in the .
+ /// The s in the .
///
public DbSet ChatChannels { get; set; }
///
- /// The s in the .
+ /// The s in the .
///
public DbSet Jobs { get; set; }
///
- /// The s in the .
+ /// The s in the .
///
public DbSet ReattachInformations { get; set; }
///
- /// The s in the .
+ /// The s in the .
///
public DbSet WatchdogReattachInformations { get; set; }
///
- /// The s in the
+ /// The s in the
///
public DbSet TestMerges { get; set; }
///
- /// The s om the
+ /// The s om the
///
public DbSet RevInfoTestMerges { get; set; }
///
- /// The for the
+ /// The for the
///
protected ILogger Logger { get; }
///
- /// The for the
+ /// The for the
///
protected DatabaseConfiguration DatabaseConfiguration { get; }
@@ -150,7 +149,7 @@ namespace Tgstation.Server.Host.Database
IDatabaseCollection IDatabaseContext.WatchdogReattachInformations => watchdogReattachInformationsCollection;
///
- /// The for the
+ /// The for the
///
readonly IDatabaseSeeder databaseSeeder;
@@ -220,13 +219,13 @@ namespace Tgstation.Server.Host.Database
readonly IDatabaseCollection watchdogReattachInformationsCollection;
///
- /// Construct a
+ /// Construct a
///
- /// The for the
+ /// The for the .
/// The containing the value of
/// The value of
/// The value of
- public DatabaseContext(DbContextOptions dbContextOptions, IOptions databaseConfigurationOptions, IDatabaseSeeder databaseSeeder, ILogger logger) : base(dbContextOptions)
+ public DatabaseContext(DbContextOptions dbContextOptions, IOptions databaseConfigurationOptions, IDatabaseSeeder databaseSeeder, ILogger logger) : base(dbContextOptions)
{
DatabaseConfiguration = databaseConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(databaseConfigurationOptions));
this.databaseSeeder = databaseSeeder ?? throw new ArgumentNullException(nameof(databaseSeeder));
@@ -354,9 +353,14 @@ namespace Tgstation.Server.Host.Database
// Update this with new migrations as they are made
string targetMigration = null;
+
+ if (DatabaseType == DatabaseType.PostgresSql && version < new Version(4, 3, 0))
+ throw new NotSupportedException("Cannot migrate below version 4.3.0 with PostgresSql!");
+
if (version < new Version(4, 1, 0))
throw new NotSupportedException("Cannot migrate below version 4.1.0!");
- else if (version < new Version(4, 2, 0))
+
+ if (version < new Version(4, 2, 0))
targetMigration = DatabaseType == DatabaseType.Sqlite ? nameof(SLRebuild) : nameof(MSFixCascadingDelete);
if (targetMigration == null)
@@ -379,6 +383,9 @@ namespace Tgstation.Server.Host.Database
case DatabaseType.Sqlite:
migrationSubstitution = "SL{0}";
break;
+ case DatabaseType.PostgresSql:
+ migrationSubstitution = "PG{0}";
+ break;
default:
throw new InvalidOperationException($"Invalid DatabaseType: {DatabaseType}");
}
@@ -402,7 +409,7 @@ namespace Tgstation.Server.Host.Database
}
///
- /// Ensure the is correct for the .
+ /// Ensure the is correct for the .
///
protected abstract void ValidateDatabaseType();
}
diff --git a/src/Tgstation.Server.Host/Database/DatabaseSeeder.cs b/src/Tgstation.Server.Host/Database/DatabaseSeeder.cs
index ec15fb3e3c..e7173b0067 100644
--- a/src/Tgstation.Server.Host/Database/DatabaseSeeder.cs
+++ b/src/Tgstation.Server.Host/Database/DatabaseSeeder.cs
@@ -14,12 +14,12 @@ namespace Tgstation.Server.Host.Database
sealed class DatabaseSeeder : IDatabaseSeeder
{
///
- /// The for the
+ /// The for the
///
readonly ICryptographySuite cryptographySuite;
///
- /// The for the .
+ /// The for the .
///
readonly IPlatformIdentifier platformIdentifier;
@@ -76,7 +76,11 @@ namespace Tgstation.Server.Host.Database
if (platformIdentifier.IsWindows)
{
// normalize backslashes to forward slashes
- var allInstances = await databaseContext.Instances.ToListAsync(cancellationToken).ConfigureAwait(false);
+ var allInstances = await databaseContext
+ .Instances
+ .AsQueryable()
+ .ToListAsync(cancellationToken)
+ .ConfigureAwait(false);
foreach (var instance in allInstances)
instance.Path = instance.Path.Replace('\\', '/');
}
@@ -107,6 +111,7 @@ namespace Tgstation.Server.Host.Database
{
var admin = await databaseContext
.Users
+ .AsQueryable()
.Where(x => x.CanonicalName == User.CanonicalizeName(Api.Models.User.AdminName))
.FirstOrDefaultAsync(cancellationToken)
.ConfigureAwait(false);
diff --git a/src/Tgstation.Server.Host/Database/Design/DesignTimeDbContextFactoryHelpers.cs b/src/Tgstation.Server.Host/Database/Design/DesignTimeDbContextFactoryHelpers.cs
index 9699c86a16..98f71aeaaf 100644
--- a/src/Tgstation.Server.Host/Database/Design/DesignTimeDbContextFactoryHelpers.cs
+++ b/src/Tgstation.Server.Host/Database/Design/DesignTimeDbContextFactoryHelpers.cs
@@ -4,7 +4,7 @@ using Tgstation.Server.Host.Configuration;
namespace Tgstation.Server.Host.Database.Design
{
///
- /// Contains helpers for creating design time s
+ /// Contains helpers for creating design time s
///
static class DesignTimeDbContextFactoryHelpers
{
@@ -14,7 +14,7 @@ namespace Tgstation.Server.Host.Database.Design
/// The .
/// The .
/// The for the
- public static IOptions GetDbContextOptions(DatabaseType databaseType, string connectionString)
+ public static IOptions GetDatabaseConfiguration(DatabaseType databaseType, string connectionString)
{
var dbConfig = new DatabaseConfiguration
{
diff --git a/src/Tgstation.Server.Host/Database/Design/MySqlDesignTimeDbContextFactory.cs b/src/Tgstation.Server.Host/Database/Design/MySqlDesignTimeDbContextFactory.cs
index 61f2cfd8df..5b39d9e63d 100644
--- a/src/Tgstation.Server.Host/Database/Design/MySqlDesignTimeDbContextFactory.cs
+++ b/src/Tgstation.Server.Host/Database/Design/MySqlDesignTimeDbContextFactory.cs
@@ -20,7 +20,7 @@ namespace Tgstation.Server.Host.Database.Design
using var loggerFactory = new LoggerFactory();
return new MySqlDatabaseContext(
new DbContextOptions(),
- DesignTimeDbContextFactoryHelpers.GetDbContextOptions(
+ DesignTimeDbContextFactoryHelpers.GetDatabaseConfiguration(
DatabaseType.MariaDB,
"Server=127.0.0.1;User Id=root;Password=fake;Database=TGS_Design"),
new DatabaseSeeder(
diff --git a/src/Tgstation.Server.Host/Database/Design/PostgresSqlDesignTimeDbContextFactory.cs b/src/Tgstation.Server.Host/Database/Design/PostgresSqlDesignTimeDbContextFactory.cs
new file mode 100644
index 0000000000..2af7d7c939
--- /dev/null
+++ b/src/Tgstation.Server.Host/Database/Design/PostgresSqlDesignTimeDbContextFactory.cs
@@ -0,0 +1,31 @@
+using Microsoft.AspNetCore.Identity;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Design;
+using Microsoft.Extensions.Logging;
+using Tgstation.Server.Host.Configuration;
+using Tgstation.Server.Host.Models;
+using Tgstation.Server.Host.Security;
+using Tgstation.Server.Host.System;
+
+namespace Tgstation.Server.Host.Database.Design
+{
+ ///
+ sealed class PostgresSqlDesignTimeDbContextFactory : IDesignTimeDbContextFactory
+ {
+ ///
+ public PostgresSqlDatabaseContext CreateDbContext(string[] args)
+ {
+ using var loggerFactory = new LoggerFactory();
+ return new PostgresSqlDatabaseContext(
+ new DbContextOptions(),
+ DesignTimeDbContextFactoryHelpers.GetDatabaseConfiguration(
+ DatabaseType.PostgresSql,
+ "Application Name=tgstation-server;Host=127.0.0.1;Password=qCkWimNgLfWwpr7TnUHs;Username=postgres;Database=TGS_Design"),
+ new DatabaseSeeder(
+ new CryptographySuite(
+ new PasswordHasher()),
+ new PlatformIdentifier()),
+ loggerFactory.CreateLogger());
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Host/Database/Design/SqlServerDesignTimeDbContextFactory.cs b/src/Tgstation.Server.Host/Database/Design/SqlServerDesignTimeDbContextFactory.cs
index 68cc43ed2f..427d84ba8a 100644
--- a/src/Tgstation.Server.Host/Database/Design/SqlServerDesignTimeDbContextFactory.cs
+++ b/src/Tgstation.Server.Host/Database/Design/SqlServerDesignTimeDbContextFactory.cs
@@ -20,7 +20,7 @@ namespace Tgstation.Server.Host.Database.Design
using var loggerFactory = new LoggerFactory();
return new SqlServerDatabaseContext(
new DbContextOptions(),
- DesignTimeDbContextFactoryHelpers.GetDbContextOptions(
+ DesignTimeDbContextFactoryHelpers.GetDatabaseConfiguration(
DatabaseType.SqlServer,
"Data Source=fake;Initial Catalog=TGS_Design;Integrated Security=True;Application Name=tgstation-server"),
new DatabaseSeeder(
diff --git a/src/Tgstation.Server.Host/Database/Design/SqliteDesignTimeDbContextFactory.cs b/src/Tgstation.Server.Host/Database/Design/SqliteDesignTimeDbContextFactory.cs
index ba5690ec75..91de1284f2 100644
--- a/src/Tgstation.Server.Host/Database/Design/SqliteDesignTimeDbContextFactory.cs
+++ b/src/Tgstation.Server.Host/Database/Design/SqliteDesignTimeDbContextFactory.cs
@@ -20,7 +20,7 @@ namespace Tgstation.Server.Host.Database.Design
using var loggerFactory = new LoggerFactory();
return new SqliteDatabaseContext(
new DbContextOptions(),
- DesignTimeDbContextFactoryHelpers.GetDbContextOptions(
+ DesignTimeDbContextFactoryHelpers.GetDatabaseConfiguration(
DatabaseType.Sqlite,
"Data Source=tgs_design.sqlite3;Mode=ReadWriteCreate"),
new DatabaseSeeder(
diff --git a/src/Tgstation.Server.Host/Database/IDatabaseCollection.cs b/src/Tgstation.Server.Host/Database/IDatabaseCollection.cs
index 9c3318e35d..a9e1fe0e03 100644
--- a/src/Tgstation.Server.Host/Database/IDatabaseCollection.cs
+++ b/src/Tgstation.Server.Host/Database/IDatabaseCollection.cs
@@ -1,8 +1,5 @@
-using System;
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
namespace Tgstation.Server.Host.Database
{
@@ -10,7 +7,7 @@ namespace Tgstation.Server.Host.Database
/// Represents a database table.
///
/// The type of model.
- public interface IDatabaseCollection : IQueryable
+ public interface IDatabaseCollection : IQueryable, IAsyncEnumerable
{
///
/// An of s prioritizing in the working set.
@@ -46,20 +43,5 @@ namespace Tgstation.Server.Host.Database
///
/// An of s to remove.
void RemoveRange(IEnumerable models);
-
- ///
- /// Asyncronously run a given on the .
- ///
- /// The to run.
- /// The for the operation.
- /// A representing the running operation.
- Task ForEachAsync(Action action, CancellationToken cancellationToken);
-
- ///
- /// Retrieve all the s in the table.
- ///
- /// The for the operation.
- /// A resulting in a of all in the table.
- Task> ToListAsync(CancellationToken cancellationToken);
}
}
diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200516111712_PGCreate.Designer.cs b/src/Tgstation.Server.Host/Database/Migrations/20200516111712_PGCreate.Designer.cs
new file mode 100644
index 0000000000..36d63b62c3
--- /dev/null
+++ b/src/Tgstation.Server.Host/Database/Migrations/20200516111712_PGCreate.Designer.cs
@@ -0,0 +1,816 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+namespace Tgstation.Server.Host.Database.Migrations
+{
+ [DbContext(typeof(PostgresSqlDatabaseContext))]
+ [Migration("20200516111712_PGCreate")]
+ partial class PGCreate
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn)
+ .HasAnnotation("ProductVersion", "3.1.4")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
+
+ b.Property("ChannelLimit")
+ .HasColumnType("integer");
+
+ b.Property("ConnectionString")
+ .IsRequired()
+ .HasColumnType("character varying(10000)")
+ .HasMaxLength(10000);
+
+ b.Property("Enabled")
+ .HasColumnType("boolean");
+
+ b.Property("InstanceId")
+ .HasColumnType("bigint");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("character varying(100)")
+ .HasMaxLength(100);
+
+ b.Property("Provider")
+ .HasColumnType("integer");
+
+ b.Property("ReconnectionInterval")
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("InstanceId", "Name")
+ .IsUnique();
+
+ b.ToTable("ChatBots");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
+
+ b.Property("ChatSettingsId")
+ .HasColumnType("bigint");
+
+ b.Property("DiscordChannelId")
+ .HasColumnType("numeric(20,0)");
+
+ b.Property("IrcChannel")
+ .HasColumnType("character varying(100)")
+ .HasMaxLength(100);
+
+ b.Property("IsAdminChannel")
+ .IsRequired()
+ .HasColumnType("boolean");
+
+ b.Property("IsUpdatesChannel")
+ .IsRequired()
+ .HasColumnType("boolean");
+
+ b.Property("IsWatchdogChannel")
+ .IsRequired()
+ .HasColumnType("boolean");
+
+ b.Property("Tag")
+ .HasColumnType("character varying(10000)")
+ .HasMaxLength(10000);
+
+ b.HasKey("Id");
+
+ b.HasIndex("ChatSettingsId", "DiscordChannelId")
+ .IsUnique();
+
+ b.HasIndex("ChatSettingsId", "IrcChannel")
+ .IsUnique();
+
+ b.ToTable("ChatChannels");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
+
+ b.Property("ByondVersion")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("DMApiMajorVersion")
+ .HasColumnType("integer");
+
+ b.Property("DMApiMinorVersion")
+ .HasColumnType("integer");
+
+ b.Property("DMApiPatchVersion")
+ .HasColumnType("integer");
+
+ b.Property("DirectoryName")
+ .IsRequired()
+ .HasColumnType("uuid");
+
+ b.Property("DmeName")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("JobId")
+ .HasColumnType("bigint");
+
+ b.Property("MinimumSecurityLevel")
+ .HasColumnType("integer");
+
+ b.Property("Output")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("RevisionInformationId")
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DirectoryName");
+
+ b.HasIndex("JobId")
+ .IsUnique();
+
+ b.HasIndex("RevisionInformationId");
+
+ b.ToTable("CompileJobs");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
+
+ b.Property("AllowWebClient")
+ .IsRequired()
+ .HasColumnType("boolean");
+
+ b.Property("AutoStart")
+ .IsRequired()
+ .HasColumnType("boolean");
+
+ b.Property("HeartbeatSeconds")
+ .HasColumnType("bigint");
+
+ b.Property("InstanceId")
+ .HasColumnType("bigint");
+
+ b.Property("PrimaryPort")
+ .HasColumnType("integer");
+
+ b.Property("SecondaryPort")
+ .HasColumnType("integer");
+
+ b.Property("SecurityLevel")
+ .HasColumnType("integer");
+
+ b.Property("StartupTimeout")
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("InstanceId")
+ .IsUnique();
+
+ b.ToTable("DreamDaemonSettings");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
+
+ b.Property("ApiValidationPort")
+ .HasColumnType("integer");
+
+ b.Property("ApiValidationSecurityLevel")
+ .HasColumnType("integer");
+
+ b.Property("InstanceId")
+ .HasColumnType("bigint");
+
+ b.Property("ProjectName")
+ .HasColumnType("character varying(10000)")
+ .HasMaxLength(10000);
+
+ b.HasKey("Id");
+
+ b.HasIndex("InstanceId")
+ .IsUnique();
+
+ b.ToTable("DreamMakerSettings");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.DualReattachInformation", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
+
+ b.Property("AlphaId")
+ .HasColumnType("bigint");
+
+ b.Property("AlphaIsActive")
+ .HasColumnType("boolean");
+
+ b.Property("BravoId")
+ .HasColumnType("bigint");
+
+ b.Property("InstanceId")
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AlphaId");
+
+ b.HasIndex("BravoId");
+
+ b.HasIndex("InstanceId")
+ .IsUnique();
+
+ b.ToTable("WatchdogReattachInformations");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
+
+ b.Property("AutoUpdateInterval")
+ .HasColumnType("bigint");
+
+ b.Property("ChatBotLimit")
+ .HasColumnType("integer");
+
+ b.Property("ConfigurationType")
+ .HasColumnType("integer");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("character varying(10000)")
+ .HasMaxLength(10000);
+
+ b.Property("Online")
+ .IsRequired()
+ .HasColumnType("boolean");
+
+ b.Property("Path")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Path")
+ .IsUnique();
+
+ b.ToTable("Instances");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
+
+ b.Property("ByondRights")
+ .HasColumnType("numeric(20,0)");
+
+ b.Property("ChatBotRights")
+ .HasColumnType("numeric(20,0)");
+
+ b.Property("ConfigurationRights")
+ .HasColumnType("numeric(20,0)");
+
+ b.Property("DreamDaemonRights")
+ .HasColumnType("numeric(20,0)");
+
+ b.Property("DreamMakerRights")
+ .HasColumnType("numeric(20,0)");
+
+ b.Property("InstanceId")
+ .HasColumnType("bigint");
+
+ b.Property("InstanceUserRights")
+ .HasColumnType("numeric(20,0)");
+
+ b.Property("RepositoryRights")
+ .HasColumnType("numeric(20,0)");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("InstanceId");
+
+ b.HasIndex("UserId", "InstanceId")
+ .IsUnique();
+
+ b.ToTable("InstanceUsers");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
+
+ b.Property("CancelRight")
+ .HasColumnType("numeric(20,0)");
+
+ b.Property("CancelRightsType")
+ .HasColumnType("numeric(20,0)");
+
+ b.Property("Cancelled")
+ .IsRequired()
+ .HasColumnType("boolean");
+
+ b.Property("CancelledById")
+ .HasColumnType("bigint");
+
+ b.Property("Description")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("ErrorCode")
+ .HasColumnType("bigint");
+
+ b.Property("ExceptionDetails")
+ .HasColumnType("text");
+
+ b.Property("InstanceId")
+ .HasColumnType("bigint");
+
+ b.Property("StartedAt")
+ .IsRequired()
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("StartedById")
+ .HasColumnType("bigint");
+
+ b.Property("StoppedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CancelledById");
+
+ b.HasIndex("InstanceId");
+
+ b.HasIndex("StartedById");
+
+ b.ToTable("Jobs");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
+
+ b.Property("AccessIdentifier")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("CompileJobId")
+ .HasColumnType("bigint");
+
+ b.Property("IsPrimary")
+ .HasColumnType("boolean");
+
+ b.Property("LaunchSecurityLevel")
+ .HasColumnType("integer");
+
+ b.Property("Port")
+ .HasColumnType("integer");
+
+ b.Property("ProcessId")
+ .HasColumnType("integer");
+
+ b.Property("RebootState")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CompileJobId");
+
+ b.ToTable("ReattachInformations");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
+
+ b.Property("AccessToken")
+ .HasColumnType("character varying(10000)")
+ .HasMaxLength(10000);
+
+ b.Property("AccessUser")
+ .HasColumnType("character varying(10000)")
+ .HasMaxLength(10000);
+
+ b.Property("AutoUpdatesKeepTestMerges")
+ .IsRequired()
+ .HasColumnType("boolean");
+
+ b.Property("AutoUpdatesSynchronize")
+ .IsRequired()
+ .HasColumnType("boolean");
+
+ b.Property("CommitterEmail")
+ .IsRequired()
+ .HasColumnType("character varying(10000)")
+ .HasMaxLength(10000);
+
+ b.Property("CommitterName")
+ .IsRequired()
+ .HasColumnType("character varying(10000)")
+ .HasMaxLength(10000);
+
+ b.Property("InstanceId")
+ .HasColumnType("bigint");
+
+ b.Property("PostTestMergeComment")
+ .IsRequired()
+ .HasColumnType("boolean");
+
+ b.Property("PushTestMergeCommits")
+ .IsRequired()
+ .HasColumnType("boolean");
+
+ b.Property("ShowTestMergeCommitters")
+ .IsRequired()
+ .HasColumnType("boolean");
+
+ b.HasKey("Id");
+
+ b.HasIndex("InstanceId")
+ .IsUnique();
+
+ b.ToTable("RepositorySettings");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
+
+ b.Property("RevisionInformationId")
+ .HasColumnType("bigint");
+
+ b.Property("TestMergeId")
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("RevisionInformationId");
+
+ b.HasIndex("TestMergeId");
+
+ b.ToTable("RevInfoTestMerges");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
+
+ b.Property("CommitSha")
+ .IsRequired()
+ .HasColumnType("character varying(40)")
+ .HasMaxLength(40);
+
+ b.Property("InstanceId")
+ .HasColumnType("bigint");
+
+ b.Property("OriginCommitSha")
+ .IsRequired()
+ .HasColumnType("character varying(40)")
+ .HasMaxLength(40);
+
+ b.HasKey("Id");
+
+ b.HasIndex("InstanceId", "CommitSha")
+ .IsUnique();
+
+ b.ToTable("RevisionInformations");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
+
+ b.Property("Author")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("BodyAtMerge")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Comment")
+ .HasColumnType("character varying(10000)")
+ .HasMaxLength(10000);
+
+ b.Property("MergedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("MergedById")
+ .HasColumnType("bigint");
+
+ b.Property("Number")
+ .HasColumnType("integer");
+
+ b.Property("PrimaryRevisionInformationId")
+ .IsRequired()
+ .HasColumnType("bigint");
+
+ b.Property("PullRequestRevision")
+ .IsRequired()
+ .HasColumnType("character varying(40)")
+ .HasMaxLength(40);
+
+ b.Property("TitleAtMerge")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Url")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("MergedById");
+
+ b.HasIndex("PrimaryRevisionInformationId")
+ .IsUnique();
+
+ b.ToTable("TestMerges");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.User", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
+
+ b.Property("AdministrationRights")
+ .HasColumnType("numeric(20,0)");
+
+ b.Property("CanonicalName")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("CreatedAt")
+ .IsRequired()
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("CreatedById")
+ .HasColumnType("bigint");
+
+ b.Property("Enabled")
+ .IsRequired()
+ .HasColumnType("boolean");
+
+ b.Property("InstanceManagerRights")
+ .HasColumnType("numeric(20,0)");
+
+ b.Property("LastPasswordUpdate")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("character varying(10000)")
+ .HasMaxLength(10000);
+
+ b.Property("PasswordHash")
+ .HasColumnType("text");
+
+ b.Property("SystemIdentifier")
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CanonicalName")
+ .IsUnique();
+
+ b.HasIndex("CreatedById");
+
+ b.HasIndex("SystemIdentifier")
+ .IsUnique();
+
+ b.ToTable("Users");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
+ {
+ b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
+ .WithMany("ChatSettings")
+ .HasForeignKey("InstanceId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b =>
+ {
+ b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings")
+ .WithMany("Channels")
+ .HasForeignKey("ChatSettingsId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b =>
+ {
+ b.HasOne("Tgstation.Server.Host.Models.Job", "Job")
+ .WithOne()
+ .HasForeignKey("Tgstation.Server.Host.Models.CompileJob", "JobId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation")
+ .WithMany("CompileJobs")
+ .HasForeignKey("RevisionInformationId")
+ .OnDelete(DeleteBehavior.ClientNoAction)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b =>
+ {
+ b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
+ .WithOne("DreamDaemonSettings")
+ .HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b =>
+ {
+ b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
+ .WithOne("DreamMakerSettings")
+ .HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.DualReattachInformation", b =>
+ {
+ b.HasOne("Tgstation.Server.Host.Models.ReattachInformation", "Alpha")
+ .WithMany()
+ .HasForeignKey("AlphaId");
+
+ b.HasOne("Tgstation.Server.Host.Models.ReattachInformation", "Bravo")
+ .WithMany()
+ .HasForeignKey("BravoId");
+
+ b.HasOne("Tgstation.Server.Host.Models.Instance", null)
+ .WithOne("WatchdogReattachInformation")
+ .HasForeignKey("Tgstation.Server.Host.Models.DualReattachInformation", "InstanceId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b =>
+ {
+ b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
+ .WithMany("InstanceUsers")
+ .HasForeignKey("InstanceId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Tgstation.Server.Host.Models.User", null)
+ .WithMany("InstanceUsers")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b =>
+ {
+ b.HasOne("Tgstation.Server.Host.Models.User", "CancelledBy")
+ .WithMany()
+ .HasForeignKey("CancelledById");
+
+ b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
+ .WithMany("Jobs")
+ .HasForeignKey("InstanceId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy")
+ .WithMany()
+ .HasForeignKey("StartedById")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b =>
+ {
+ b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob")
+ .WithMany()
+ .HasForeignKey("CompileJobId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b =>
+ {
+ b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
+ .WithOne("RepositorySettings")
+ .HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b =>
+ {
+ b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation")
+ .WithMany("ActiveTestMerges")
+ .HasForeignKey("RevisionInformationId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge")
+ .WithMany("RevisonInformations")
+ .HasForeignKey("TestMergeId")
+ .OnDelete(DeleteBehavior.ClientNoAction)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b =>
+ {
+ b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
+ .WithMany("RevisionInformations")
+ .HasForeignKey("InstanceId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b =>
+ {
+ b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy")
+ .WithMany("TestMerges")
+ .HasForeignKey("MergedById")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+
+ b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation")
+ .WithOne("PrimaryTestMerge")
+ .HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.User", b =>
+ {
+ b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy")
+ .WithMany("CreatedUsers")
+ .HasForeignKey("CreatedById");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200516111712_PGCreate.cs b/src/Tgstation.Server.Host/Database/Migrations/20200516111712_PGCreate.cs
new file mode 100644
index 0000000000..af77ee5d60
--- /dev/null
+++ b/src/Tgstation.Server.Host/Database/Migrations/20200516111712_PGCreate.cs
@@ -0,0 +1,638 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+namespace Tgstation.Server.Host.Database.Migrations
+{
+ ///
+ /// Create initial schema for PostgreSQL.
+ ///
+ public partial class PGCreate : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ if (migrationBuilder == null)
+ throw new ArgumentNullException(nameof(migrationBuilder));
+
+ migrationBuilder.CreateTable(
+ name: "Instances",
+ columns: table => new
+ {
+ Id = table.Column(nullable: false)
+ .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
+ Name = table.Column(maxLength: 10000, nullable: false),
+ Path = table.Column(nullable: false),
+ Online = table.Column(nullable: false),
+ ConfigurationType = table.Column(nullable: false),
+ AutoUpdateInterval = table.Column(nullable: false),
+ ChatBotLimit = table.Column(nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Instances", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "Users",
+ columns: table => new
+ {
+ Id = table.Column(nullable: false)
+ .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
+ Enabled = table.Column(nullable: false),
+ CreatedAt = table.Column(nullable: false),
+ SystemIdentifier = table.Column(nullable: true),
+ Name = table.Column(maxLength: 10000, nullable: false),
+ AdministrationRights = table.Column(nullable: false),
+ InstanceManagerRights = table.Column(nullable: false),
+ PasswordHash = table.Column(nullable: true),
+ CreatedById = table.Column(nullable: true),
+ CanonicalName = table.Column(nullable: false),
+ LastPasswordUpdate = table.Column(nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Users", x => x.Id);
+ table.ForeignKey(
+ name: "FK_Users_Users_CreatedById",
+ column: x => x.CreatedById,
+ principalTable: "Users",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Restrict);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "ChatBots",
+ columns: table => new
+ {
+ Id = table.Column(nullable: false)
+ .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
+ Name = table.Column(maxLength: 100, nullable: false),
+ Enabled = table.Column(nullable: true),
+ ReconnectionInterval = table.Column(nullable: false),
+ ChannelLimit = table.Column(nullable: false),
+ Provider = table.Column(nullable: false),
+ ConnectionString = table.Column(maxLength: 10000, nullable: false),
+ InstanceId = table.Column(nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_ChatBots", x => x.Id);
+ table.ForeignKey(
+ name: "FK_ChatBots_Instances_InstanceId",
+ column: x => x.InstanceId,
+ principalTable: "Instances",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "DreamDaemonSettings",
+ columns: table => new
+ {
+ Id = table.Column(nullable: false)
+ .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
+ AllowWebClient = table.Column(nullable: false),
+ SecurityLevel = table.Column(nullable: false),
+ PrimaryPort = table.Column(nullable: false),
+ SecondaryPort = table.Column(nullable: false),
+ StartupTimeout = table.Column(nullable: false),
+ HeartbeatSeconds = table.Column(nullable: false),
+ AutoStart = table.Column(nullable: false),
+ InstanceId = table.Column(nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_DreamDaemonSettings", x => x.Id);
+ table.ForeignKey(
+ name: "FK_DreamDaemonSettings_Instances_InstanceId",
+ column: x => x.InstanceId,
+ principalTable: "Instances",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "DreamMakerSettings",
+ columns: table => new
+ {
+ Id = table.Column(nullable: false)
+ .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
+ ProjectName = table.Column(maxLength: 10000, nullable: true),
+ ApiValidationPort = table.Column(nullable: false),
+ ApiValidationSecurityLevel = table.Column(nullable: false),
+ InstanceId = table.Column(nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_DreamMakerSettings", x => x.Id);
+ table.ForeignKey(
+ name: "FK_DreamMakerSettings_Instances_InstanceId",
+ column: x => x.InstanceId,
+ principalTable: "Instances",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "RepositorySettings",
+ columns: table => new
+ {
+ Id = table.Column(nullable: false)
+ .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
+ CommitterName = table.Column(maxLength: 10000, nullable: false),
+ CommitterEmail = table.Column(maxLength: 10000, nullable: false),
+ AccessUser = table.Column(maxLength: 10000, nullable: true),
+ AccessToken = table.Column(maxLength: 10000, nullable: true),
+ PushTestMergeCommits = table.Column(nullable: false),
+ ShowTestMergeCommitters = table.Column(nullable: false),
+ AutoUpdatesKeepTestMerges = table.Column(nullable: false),
+ AutoUpdatesSynchronize = table.Column(nullable: false),
+ PostTestMergeComment = table.Column(nullable: false),
+ InstanceId = table.Column(nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_RepositorySettings", x => x.Id);
+ table.ForeignKey(
+ name: "FK_RepositorySettings_Instances_InstanceId",
+ column: x => x.InstanceId,
+ principalTable: "Instances",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "RevisionInformations",
+ columns: table => new
+ {
+ Id = table.Column(nullable: false)
+ .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
+ CommitSha = table.Column(maxLength: 40, nullable: false),
+ OriginCommitSha = table.Column(maxLength: 40, nullable: false),
+ InstanceId = table.Column(nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_RevisionInformations", x => x.Id);
+ table.ForeignKey(
+ name: "FK_RevisionInformations_Instances_InstanceId",
+ column: x => x.InstanceId,
+ principalTable: "Instances",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "InstanceUsers",
+ columns: table => new
+ {
+ Id = table.Column(nullable: false)
+ .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
+ UserId = table.Column(nullable: false),
+ InstanceUserRights = table.Column(nullable: false),
+ ByondRights = table.Column(nullable: false),
+ DreamDaemonRights = table.Column(nullable: false),
+ DreamMakerRights = table.Column(nullable: false),
+ RepositoryRights = table.Column(nullable: false),
+ ChatBotRights = table.Column(nullable: false),
+ ConfigurationRights = table.Column(nullable: false),
+ InstanceId = table.Column(nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_InstanceUsers", x => x.Id);
+ table.ForeignKey(
+ name: "FK_InstanceUsers_Instances_InstanceId",
+ column: x => x.InstanceId,
+ principalTable: "Instances",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ table.ForeignKey(
+ name: "FK_InstanceUsers_Users_UserId",
+ column: x => x.UserId,
+ principalTable: "Users",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "Jobs",
+ columns: table => new
+ {
+ Id = table.Column(nullable: false)
+ .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
+ Description = table.Column(nullable: false),
+ ErrorCode = table.Column(nullable: true),
+ ExceptionDetails = table.Column(nullable: true),
+ StartedAt = table.Column(nullable: false),
+ StoppedAt = table.Column(nullable: true),
+ Cancelled = table.Column(nullable: false),
+ CancelRightsType = table.Column(nullable: true),
+ CancelRight = table.Column(nullable: true),
+ StartedById = table.Column(nullable: false),
+ CancelledById = table.Column(nullable: true),
+ InstanceId = table.Column(nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Jobs", x => x.Id);
+ table.ForeignKey(
+ name: "FK_Jobs_Users_CancelledById",
+ column: x => x.CancelledById,
+ principalTable: "Users",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Restrict);
+ table.ForeignKey(
+ name: "FK_Jobs_Instances_InstanceId",
+ column: x => x.InstanceId,
+ principalTable: "Instances",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ table.ForeignKey(
+ name: "FK_Jobs_Users_StartedById",
+ column: x => x.StartedById,
+ principalTable: "Users",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "ChatChannels",
+ columns: table => new
+ {
+ Id = table.Column(nullable: false)
+ .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
+ IrcChannel = table.Column(maxLength: 100, nullable: true),
+ DiscordChannelId = table.Column(nullable: true),
+ IsAdminChannel = table.Column(nullable: false),
+ IsWatchdogChannel = table.Column(nullable: false),
+ IsUpdatesChannel = table.Column(nullable: false),
+ Tag = table.Column(maxLength: 10000, nullable: true),
+ ChatSettingsId = table.Column(nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_ChatChannels", x => x.Id);
+ table.ForeignKey(
+ name: "FK_ChatChannels_ChatBots_ChatSettingsId",
+ column: x => x.ChatSettingsId,
+ principalTable: "ChatBots",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "TestMerges",
+ columns: table => new
+ {
+ Id = table.Column(nullable: false)
+ .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
+ Number = table.Column(nullable: false),
+ PullRequestRevision = table.Column(maxLength: 40, nullable: false),
+ Comment = table.Column(maxLength: 10000, nullable: true),
+ TitleAtMerge = table.Column(nullable: false),
+ BodyAtMerge = table.Column(nullable: false),
+ Url = table.Column(nullable: false),
+ Author = table.Column(nullable: false),
+ MergedAt = table.Column(nullable: false),
+ MergedById = table.Column(nullable: false),
+ PrimaryRevisionInformationId = table.Column(nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_TestMerges", x => x.Id);
+ table.ForeignKey(
+ name: "FK_TestMerges_Users_MergedById",
+ column: x => x.MergedById,
+ principalTable: "Users",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Restrict);
+ table.ForeignKey(
+ name: "FK_TestMerges_RevisionInformations_PrimaryRevisionInformationId",
+ column: x => x.PrimaryRevisionInformationId,
+ principalTable: "RevisionInformations",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "CompileJobs",
+ columns: table => new
+ {
+ Id = table.Column(nullable: false)
+ .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
+ DmeName = table.Column(nullable: false),
+ Output = table.Column(nullable: false),
+ DirectoryName = table.Column(nullable: false),
+ MinimumSecurityLevel = table.Column(nullable: false),
+ JobId = table.Column(nullable: false),
+ RevisionInformationId = table.Column(nullable: false),
+ ByondVersion = table.Column(nullable: false),
+ DMApiMajorVersion = table.Column(nullable: true),
+ DMApiMinorVersion = table.Column(nullable: true),
+ DMApiPatchVersion = table.Column(nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_CompileJobs", x => x.Id);
+ table.ForeignKey(
+ name: "FK_CompileJobs_Jobs_JobId",
+ column: x => x.JobId,
+ principalTable: "Jobs",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ table.ForeignKey(
+ name: "FK_CompileJobs_RevisionInformations_RevisionInformationId",
+ column: x => x.RevisionInformationId,
+ principalTable: "RevisionInformations",
+ principalColumn: "Id");
+ });
+
+ migrationBuilder.CreateTable(
+ name: "RevInfoTestMerges",
+ columns: table => new
+ {
+ Id = table.Column(nullable: false)
+ .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
+ TestMergeId = table.Column(nullable: false),
+ RevisionInformationId = table.Column(nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_RevInfoTestMerges", x => x.Id);
+ table.ForeignKey(
+ name: "FK_RevInfoTestMerges_RevisionInformations_RevisionInformationId",
+ column: x => x.RevisionInformationId,
+ principalTable: "RevisionInformations",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ table.ForeignKey(
+ name: "FK_RevInfoTestMerges_TestMerges_TestMergeId",
+ column: x => x.TestMergeId,
+ principalTable: "TestMerges",
+ principalColumn: "Id");
+ });
+
+ migrationBuilder.CreateTable(
+ name: "ReattachInformations",
+ columns: table => new
+ {
+ Id = table.Column(nullable: false)
+ .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
+ AccessIdentifier = table.Column(nullable: false),
+ ProcessId = table.Column(nullable: false),
+ IsPrimary = table.Column(nullable: false),
+ Port = table.Column