Merge pull request #1442 from tgstation/FileOutput [APIDeploy][DMDeploy]

Add support for reading DreamDaemon output to file + other stuff
This commit is contained in:
Jordan Dominion
2023-04-01 17:48:31 -04:00
committed by GitHub
42 changed files with 6205 additions and 773 deletions
+5 -5
View File
@@ -3,12 +3,12 @@
<!-- Integration tests will ensure they match across the board -->
<Import Project="ControlPanelVersion.props" />
<PropertyGroup>
<TgsCoreVersion>5.6.0</TgsCoreVersion>
<TgsCoreVersion>5.7.0</TgsCoreVersion>
<TgsConfigVersion>4.4.0</TgsConfigVersion>
<TgsApiVersion>9.8.1</TgsApiVersion>
<TgsApiLibraryVersion>10.2.0</TgsApiLibraryVersion>
<TgsClientVersion>11.2.1</TgsClientVersion>
<TgsDmapiVersion>6.1.0</TgsDmapiVersion>
<TgsApiVersion>9.9.0</TgsApiVersion>
<TgsApiLibraryVersion>10.3.0</TgsApiLibraryVersion>
<TgsClientVersion>11.3.0</TgsClientVersion>
<TgsDmapiVersion>6.2.0</TgsDmapiVersion>
<TgsInteropVersion>5.4.0</TgsInteropVersion>
<TgsHostWatchdogVersion>1.2.1</TgsHostWatchdogVersion>
<TgsContainerScriptVersion>1.2.1</TgsContainerScriptVersion>
+3 -1
View File
@@ -1,6 +1,6 @@
// tgstation-server DMAPI
#define TGS_DMAPI_VERSION "6.1.0"
#define TGS_DMAPI_VERSION "6.2.0"
// All functions and datums outside this document are subject to change with any version and should not be relied on.
@@ -258,6 +258,8 @@
var/help_text = ""
/// If this command should be available to game administrators only
var/admin_only = FALSE
/// A subtype of [/datum/tgs_chat_command] that is ignored when enumerating available commands. Use this to create shared base /datums for commands.
var/ignore_type
/**
* Process command activation. Should return a [/datum/tgs_message_content] to respond to the issuer with.
+4 -1
View File
@@ -10,9 +10,12 @@
var/warned_about_the_dangers_of_robutussin = !warnings_only
for(var/I in typesof(/datum/tgs_chat_command) - /datum/tgs_chat_command)
if(!warned_about_the_dangers_of_robutussin)
TGS_ERROR_LOG("Custom chat commands in [ApiVersion()] lacks the /datum/tgs_chat_user/sender.channel field!")
TGS_WARNING_LOG("Custom chat commands in [ApiVersion()] lacks the /datum/tgs_chat_user/sender.channel field!")
warned_about_the_dangers_of_robutussin = TRUE
var/datum/tgs_chat_command/stc = I
if(stc.ignore_type == I)
continue
var/command_name = initial(stc.name)
if(!command_name || findtext(command_name, " ") || findtext(command_name, "'") || findtext(command_name, "\""))
if(warnings_only && !warned_command_names[command_name])
+3
View File
@@ -3,6 +3,9 @@
custom_commands = list()
for(var/I in typesof(/datum/tgs_chat_command) - /datum/tgs_chat_command)
var/datum/tgs_chat_command/stc = new I
if(stc.ignore_type == I)
continue
var/command_name = stc.name
if(!command_name || findtext(command_name, " ") || findtext(command_name, "'") || findtext(command_name, "\""))
TGS_ERROR_LOG("Custom command [command_name] ([I]) can't be used as it is empty or contains illegal characters!")
+5 -2
View File
@@ -3,14 +3,17 @@
custom_commands = list()
for(var/I in typesof(/datum/tgs_chat_command) - /datum/tgs_chat_command)
var/datum/tgs_chat_command/stc = new I
if(stc.ignore_type == I)
continue
var/command_name = stc.name
if(!command_name || findtext(command_name, " ") || findtext(command_name, "'") || findtext(command_name, "\""))
TGS_WARNING_LOG("Custom command [command_name] ([I]) can't be used as it is empty or contains illegal characters!")
TGS_ERROR_LOG("Custom command [command_name] ([I]) can't be used as it is empty or contains illegal characters!")
continue
if(results[command_name])
var/datum/other = custom_commands[command_name]
TGS_WARNING_LOG("Custom commands [other.type] and [I] have the same name (\"[command_name]\"), only [other.type] will be available!")
TGS_ERROR_LOG("Custom commands [other.type] and [I] have the same name (\"[command_name]\"), only [other.type] will be available!")
continue
results += list(list(DMAPI5_CUSTOM_CHAT_COMMAND_NAME = command_name, DMAPI5_CUSTOM_CHAT_COMMAND_HELP_TEXT = stc.help_text, DMAPI5_CUSTOM_CHAT_COMMAND_ADMIN_ONLY = stc.admin_only))
custom_commands[command_name] = stc
@@ -84,6 +84,13 @@ namespace Tgstation.Server.Api.Models.Internal
[StringLength(Limits.MaximumStringLength)]
public string? AdditionalParameters { get; set; }
/// <summary>
/// If process output/error text should be logged.
/// </summary>
[Required]
[ResponseOptions]
public bool? LogOutput { get; set; }
/// <summary>
/// Check if we match a given set of <paramref name="otherParameters"/>. <see cref="StartupTimeout"/> is excluded.
/// </summary>
@@ -100,7 +107,8 @@ namespace Tgstation.Server.Api.Models.Internal
&& Port == otherParameters.Port
&& TopicRequestTimeout == otherParameters.TopicRequestTimeout
&& AdditionalParameters == otherParameters.AdditionalParameters
&& StartProfiler == otherParameters.StartProfiler; // We intentionally don't check StartupTimeout, heartbeat seconds, or heartbeat dump as they don't matter in terms of the watchdog
&& StartProfiler == otherParameters.StartProfiler
&& LogOutput == otherParameters.LogOutput; // We intentionally don't check StartupTimeout, heartbeat seconds, or heartbeat dump as they don't matter in terms of the watchdog
}
}
}
@@ -102,5 +102,10 @@ namespace Tgstation.Server.Api.Rights
/// User can change <see cref="Models.Internal.DreamDaemonLaunchParameters.StartProfiler"/>
/// </summary>
SetProfiler = 131072,
/// <summary>
/// User can change <see cref="Models.Internal.DreamDaemonLaunchParameters.LogOutput"/>
/// </summary>
SetLogOutput = 262144,
}
}
@@ -187,7 +187,7 @@ namespace Tgstation.Server.Host.Components.Byond
try
{
// noShellExecute because we aren't doing runas shennanigans
using var directXInstaller = processExecutor.LaunchProcess(
await using var directXInstaller = await processExecutor.LaunchProcess(
IOManager.ConcatPath(rbdx, "DXSETUP.exe"),
rbdx,
"/silent",
@@ -228,13 +228,12 @@ namespace Tgstation.Server.Host.Components.Byond
Logger.LogInformation("Adding Windows Firewall exception for {path}...", dreamDaemonPath);
try
{
using var netshProcess = processExecutor.LaunchProcess(
await using var netshProcess = await processExecutor.LaunchProcess(
"netsh.exe",
IOManager.ResolvePath(),
$"advfirewall firewall add rule name=\"TGS DreamDaemon\" program=\"{dreamDaemonPath}\" protocol=tcp dir=in enable=yes action=allow",
true,
true,
true);
readStandardHandles: true,
noShellExecute: true);
int exitCode;
using (cancellationToken.Register(() => netshProcess.Terminate()))
@@ -235,6 +235,7 @@ namespace Tgstation.Server.Host.Components.Deployment
.Select(x => new Models.DreamDaemonSettings
{
StartupTimeout = x.StartupTimeout,
LogOutput = x.LogOutput,
})
.FirstOrDefaultAsync(cancellationToken)
;
@@ -325,7 +326,7 @@ namespace Tgstation.Server.Host.Components.Deployment
compileJob = await Compile(
revInfo,
dreamMakerSettings,
ddSettings.StartupTimeout.Value,
ddSettings,
repo,
remoteDeploymentManager,
progressReporter,
@@ -462,7 +463,7 @@ namespace Tgstation.Server.Host.Components.Deployment
/// </summary>
/// <param name="revisionInformation">The <see cref="RevisionInformation"/>.</param>
/// <param name="dreamMakerSettings">The <see cref="Api.Models.Internal.DreamMakerSettings"/>.</param>
/// <param name="apiValidateTimeout">The API validation timeout.</param>
/// <param name="launchParameters">The <see cref="DreamDaemonLaunchParameters"/>.</param>
/// <param name="repository">The <see cref="IRepository"/>.</param>
/// <param name="remoteDeploymentManager">The <see cref="IRemoteDeploymentManager"/>.</param>
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> to report progress of the operation.</param>
@@ -473,7 +474,7 @@ namespace Tgstation.Server.Host.Components.Deployment
async Task<Models.CompileJob> Compile(
Models.RevisionInformation revisionInformation,
Api.Models.Internal.DreamMakerSettings dreamMakerSettings,
uint apiValidateTimeout,
DreamDaemonLaunchParameters launchParameters,
IRepository repository,
IRemoteDeploymentManager remoteDeploymentManager,
JobProgressReporter progressReporter,
@@ -525,10 +526,10 @@ namespace Tgstation.Server.Host.Components.Deployment
await RunCompileJob(
job,
dreamMakerSettings,
launchParameters,
byondLock,
repository,
remoteDeploymentManager,
apiValidateTimeout,
combinedTokenSource.Token)
;
}
@@ -559,19 +560,19 @@ namespace Tgstation.Server.Host.Components.Deployment
/// </summary>
/// <param name="job">The <see cref="CompileJob"/> to run and populate.</param>
/// <param name="dreamMakerSettings">The <see cref="Api.Models.Internal.DreamMakerSettings"/> to use.</param>
/// <param name="launchParameters">The <see cref="DreamDaemonLaunchParameters"/> to use.</param>
/// <param name="byondLock">The <see cref="IByondExecutableLock"/> to use.</param>
/// <param name="repository">The <see cref="IRepository"/> to use.</param>
/// <param name="remoteDeploymentManager">The <see cref="IRemoteDeploymentManager"/> to use.</param>
/// <param name="apiValidateTimeout">The timeout for validating the DMAPI.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
async Task RunCompileJob(
Models.CompileJob job,
Api.Models.Internal.DreamMakerSettings dreamMakerSettings,
DreamDaemonLaunchParameters launchParameters,
IByondExecutableLock byondLock,
IRepository repository,
IRemoteDeploymentManager remoteDeploymentManager,
uint apiValidateTimeout,
CancellationToken cancellationToken)
{
var outputDirectory = job.DirectoryName.ToString();
@@ -655,14 +656,14 @@ namespace Tgstation.Server.Host.Components.Deployment
currentStage = "Validating DMAPI";
await VerifyApi(
apiValidateTimeout,
launchParameters.StartupTimeout.Value,
dreamMakerSettings.ApiValidationSecurityLevel.Value,
job,
byondLock,
dreamMakerSettings.ApiValidationPort.Value,
dreamMakerSettings.RequireDMApiValidation.Value,
cancellationToken)
;
launchParameters.LogOutput.Value,
cancellationToken);
}
catch (JobException)
{
@@ -717,9 +718,8 @@ namespace Tgstation.Server.Host.Components.Deployment
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
async Task ProgressTask(JobProgressReporter progressReporter, TimeSpan? estimatedDuration, CancellationToken cancellationToken)
{
var noEstimate = !estimatedDuration.HasValue;
progressReporter.StageName = currentStage;
double? lastReport = noEstimate ? null : 0;
double? lastReport = estimatedDuration.HasValue ? 0 : null;
progressReporter.ReportProgress(lastReport);
var minimumSleepInterval = TimeSpan.FromMilliseconds(250);
@@ -738,25 +738,31 @@ namespace Tgstation.Server.Host.Components.Deployment
{
for (var iteration = 0; iteration < (estimatedDuration.HasValue ? 99 : Int32.MaxValue); ++iteration)
{
var nextInterval = DateTimeOffset.UtcNow + sleepInterval;
do
if (estimatedDuration.HasValue)
{
var remainingSleepThisInterval = nextInterval - DateTimeOffset.UtcNow;
var nextSleepSpan = remainingSleepThisInterval < minimumSleepInterval ? remainingSleepThisInterval : minimumSleepInterval;
var nextInterval = DateTimeOffset.UtcNow + sleepInterval;
do
{
var remainingSleepThisInterval = nextInterval - DateTimeOffset.UtcNow;
var nextSleepSpan = remainingSleepThisInterval < minimumSleepInterval ? remainingSleepThisInterval : minimumSleepInterval;
await Task.Delay(nextSleepSpan, cancellationToken);
progressReporter.StageName = currentStage;
progressReporter.ReportProgress(lastReport);
await Task.Delay(nextSleepSpan, cancellationToken);
progressReporter.StageName = currentStage;
progressReporter.ReportProgress(lastReport);
}
while (DateTimeOffset.UtcNow < nextInterval);
}
while (DateTimeOffset.UtcNow < nextInterval);
else
await Task.Delay(minimumSleepInterval, cancellationToken);
progressReporter.StageName = currentStage;
lastReport = noEstimate ? null : sleepInterval * (iteration + 1) / estimatedDuration.Value;
lastReport = estimatedDuration.HasValue ? sleepInterval * (iteration + 1) / estimatedDuration.Value : null;
progressReporter.ReportProgress(lastReport);
}
}
catch (OperationCanceledException)
catch (OperationCanceledException ex)
{
logger.LogTrace(ex, "ProgressTask aborted.");
}
}
@@ -769,6 +775,7 @@ namespace Tgstation.Server.Host.Components.Deployment
/// <param name="byondLock">The current <see cref="IByondExecutableLock"/>.</param>
/// <param name="portToUse">The port to use for API validation.</param>
/// <param name="requireValidate">If the API validation is required to complete the deployment.</param>
/// <param name="logOutput">If output should be logged to the DreamDaemon Diagnostics folder.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
async Task VerifyApi(
@@ -778,6 +785,7 @@ namespace Tgstation.Server.Host.Components.Deployment
IByondExecutableLock byondLock,
ushort portToUse,
bool requireValidate,
bool logOutput,
CancellationToken cancellationToken)
{
logger.LogTrace("Verifying {0}DMAPI...", requireValidate ? "required " : String.Empty);
@@ -791,6 +799,7 @@ namespace Tgstation.Server.Host.Components.Deployment
TopicRequestTimeout = 0, // not used
HeartbeatSeconds = 0, // not used
StartProfiler = false,
LogOutput = logOutput,
};
job.MinimumSecurityLevel = securityLevel; // needed for the TempDmbProvider
@@ -854,14 +863,13 @@ namespace Tgstation.Server.Host.Components.Deployment
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
async Task<int> RunDreamMaker(string dreamMakerPath, Models.CompileJob job, CancellationToken cancellationToken)
{
using var dm = processExecutor.LaunchProcess(
await using var dm = await processExecutor.LaunchProcess(
dreamMakerPath,
ioManager.ResolvePath(
job.DirectoryName.ToString()),
$"-clean {job.DmeName}.{DmeExtension}",
true,
true,
true);
readStandardHandles: true,
noShellExecute: true);
if (sessionConfiguration.LowPriorityDeploymentProcesses)
dm.AdjustPriority(false);
@@ -271,6 +271,7 @@ namespace Tgstation.Server.Host.Components
cryptographySuite,
assemblyInformationProvider,
gameIoManager,
diagnosticsIOManager,
chatManager,
networkPromptReaper,
platformIdentifier,
@@ -308,10 +309,11 @@ namespace Tgstation.Server.Host.Components
remoteDeploymentManagerFactory,
metadata,
metadata.DreamDaemonSettings);
eventConsumer.SetWatchdog(watchdog);
commandFactory.SetWatchdog(watchdog);
try
{
eventConsumer.SetWatchdog(watchdog);
commandFactory.SetWatchdog(watchdog);
Instance instance = null;
var dreamMaker = new DreamMaker(
byond,
@@ -275,7 +275,7 @@ namespace Tgstation.Server.Host.Components.Session
byondLock.Dispose();
}
process.Dispose();
await process.DisposeAsync();
bridgeRegistration?.Dispose();
ReattachInformation.Dmb?.Dispose(); // will be null when released
chatTrackingContext.Dispose();
@@ -656,8 +656,8 @@ namespace Tgstation.Server.Host.Components.Session
var result = new LaunchResult
{
ExitCode = process.Lifetime.IsCompleted ? (int?)await process.Lifetime : null,
StartupTime = startupTask.IsCompleted ? (TimeSpan?)(DateTimeOffset.UtcNow - startTime) : null,
ExitCode = process.Lifetime.IsCompleted ? await process.Lifetime : null,
StartupTime = startupTask.IsCompleted ? (DateTimeOffset.UtcNow - startTime) : null,
};
logger.LogTrace("Launch result: {0}", result);
@@ -6,6 +6,8 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Byond.TopicSender;
using Microsoft.Extensions.Logging;
using Tgstation.Server.Api;
@@ -30,6 +32,11 @@ namespace Tgstation.Server.Host.Components.Session
/// <inheritdoc />
sealed class SessionControllerFactory : ISessionControllerFactory
{
/// <summary>
/// Path in Diagnostics folder to DreamDaemon logs.
/// </summary>
const string DreamDaemonLogsPath = "DreamDaemonLogs";
/// <summary>
/// The <see cref="IProcessExecutor"/> for the <see cref="SessionControllerFactory"/>.
/// </summary>
@@ -56,9 +63,14 @@ namespace Tgstation.Server.Host.Components.Session
readonly IAssemblyInformationProvider assemblyInformationProvider;
/// <summary>
/// The <see cref="IIOManager"/> for the <see cref="SessionControllerFactory"/>.
/// The <see cref="IIOManager"/> for the Game directory.
/// </summary>
readonly IIOManager ioManager;
readonly IIOManager gameIOManager;
/// <summary>
/// The <see cref="IIOManager"/> for the Diagnostics directory.
/// </summary>
readonly IIOManager diagnosticsIOManager;
/// <summary>
/// The <see cref="IChatManager"/> for the <see cref="SessionControllerFactory"/>.
@@ -168,7 +180,8 @@ namespace Tgstation.Server.Host.Components.Session
/// <param name="cryptographySuite">The value of <see cref="cryptographySuite"/>.</param>
/// <param name="assemblyInformationProvider">The value of <see cref="assemblyInformationProvider"/>.</param>
/// <param name="instance">The value of <see cref="instance"/>.</param>
/// <param name="ioManager">The value of <see cref="ioManager"/>.</param>
/// <param name="gameIOManager">The value of <see cref="gameIOManager"/>.</param>
/// <param name="diagnosticsIOManager">The value of <see cref="diagnosticsIOManager"/>.</param>
/// <param name="chat">The value of <see cref="chat"/>.</param>
/// <param name="networkPromptReaper">The value of <see cref="networkPromptReaper"/>.</param>
/// <param name="platformIdentifier">The value of <see cref="platformIdentifier"/>.</param>
@@ -184,7 +197,8 @@ namespace Tgstation.Server.Host.Components.Session
ITopicClientFactory topicClientFactory,
ICryptographySuite cryptographySuite,
IAssemblyInformationProvider assemblyInformationProvider,
IIOManager ioManager,
IIOManager gameIOManager,
IIOManager diagnosticsIOManager,
IChatManager chat,
INetworkPromptReaper networkPromptReaper,
IPlatformIdentifier platformIdentifier,
@@ -201,7 +215,8 @@ namespace Tgstation.Server.Host.Components.Session
this.topicClientFactory = topicClientFactory ?? throw new ArgumentNullException(nameof(topicClientFactory));
this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite));
this.assemblyInformationProvider = assemblyInformationProvider ?? throw new ArgumentNullException(nameof(assemblyInformationProvider));
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
this.gameIOManager = gameIOManager ?? throw new ArgumentNullException(nameof(gameIOManager));
this.diagnosticsIOManager = diagnosticsIOManager ?? throw new ArgumentNullException(nameof(diagnosticsIOManager));
this.chat = chat ?? throw new ArgumentNullException(nameof(chat));
this.networkPromptReaper = networkPromptReaper ?? throw new ArgumentNullException(nameof(networkPromptReaper));
this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier));
@@ -215,7 +230,6 @@ namespace Tgstation.Server.Host.Components.Session
}
/// <inheritdoc />
#pragma warning disable CA1506 // TODO: Decomplexify
public async Task<ISessionController> LaunchNew(
IDmbProvider dmbProvider,
IByondExecutableLock currentByondLock,
@@ -248,125 +262,65 @@ namespace Tgstation.Server.Host.Components.Session
throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid DreamDaemonSecurity value: {0}", dmbProvider.CompileJob.MinimumSecurityLevel));
}
var chatTrackingContext = chat.CreateTrackingContext();
// get the byond lock
var byondLock = currentByondLock ?? await byond.UseExecutables(Version.Parse(dmbProvider.CompileJob.ByondVersion), cancellationToken);
try
{
// get the byond lock
var byondLock = currentByondLock ?? await byond.UseExecutables(Version.Parse(dmbProvider.CompileJob.ByondVersion), cancellationToken);
logger.LogDebug(
"Launching session with CompileJob {compileJobId}...",
dmbProvider.CompileJob.Id);
if (launchParameters.SecurityLevel == DreamDaemonSecurity.Trusted)
await byondLock.TrustDmbPath(gameIOManager.ConcatPath(dmbProvider.Directory, dmbProvider.DmbName), cancellationToken);
PortBindTest(launchParameters.Port.Value);
await CheckPagerIsNotRunning(cancellationToken);
string outputFilePath = null;
if (launchParameters.LogOutput.Value)
{
await diagnosticsIOManager.CreateDirectory(DreamDaemonLogsPath, cancellationToken);
var now = DateTimeOffset.UtcNow;
outputFilePath = diagnosticsIOManager.ResolvePath(
diagnosticsIOManager.ConcatPath(
DreamDaemonLogsPath,
$"dd-utc-{DateTimeOffset.UtcNow.ToString("yyyy-MM-dd-HH-mm-ss", CultureInfo.InvariantCulture)}{(apiValidate ? "-dmapi" : String.Empty)}.log"));
logger.LogInformation("Logging DreamDaemon output to {path}...", outputFilePath);
}
else if (!byondLock.SupportsCli)
outputFilePath = gameIOManager.ConcatPath(dmbProvider.Directory, $"{Guid.NewGuid()}.dd.log");
var accessIdentifier = cryptographySuite.GetSecureString();
var byondTopicSender = topicClientFactory.CreateTopicClient(
TimeSpan.FromMilliseconds(
launchParameters.TopicRequestTimeout.Value));
if (!apiValidate && dmbProvider.CompileJob.DMApiVersion == null)
logger.LogDebug("Session will have no DMAPI support!");
// launch dd
var process = await CreateDreamDaemonProcess(
dmbProvider,
byondTopicSender,
byondLock,
launchParameters,
accessIdentifier,
outputFilePath,
apiValidate,
cancellationToken);
try
{
logger.LogDebug(
"Launching session with CompileJob {0}...",
dmbProvider.CompileJob.Id);
if (launchParameters.SecurityLevel == DreamDaemonSecurity.Trusted)
await byondLock.TrustDmbPath(ioManager.ConcatPath(dmbProvider.Directory, dmbProvider.DmbName), cancellationToken);
PortBindTest(launchParameters.Port.Value);
await CheckPagerIsNotRunning(cancellationToken);
var accessIdentifier = cryptographySuite.GetSecureString();
var byondTopicSender = topicClientFactory.CreateTopicClient(
TimeSpan.FromMilliseconds(
launchParameters.TopicRequestTimeout.Value));
// set command line options
// more sanitization here cause it uses the same scheme
var parameters = $"{DMApiConstants.ParamApiVersion}={byondTopicSender.SanitizeString(DMApiConstants.InteropVersion.Semver().ToString())}&{byondTopicSender.SanitizeString(DMApiConstants.ParamServerPort)}={serverPortProvider.HttpApiPort}&{byondTopicSender.SanitizeString(DMApiConstants.ParamAccessIdentifier)}={byondTopicSender.SanitizeString(accessIdentifier)}";
if (!String.IsNullOrEmpty(launchParameters.AdditionalParameters))
parameters = $"{parameters}&{launchParameters.AdditionalParameters}";
// important to run on all ports to allow port changing
Guid? logFileGuid = null;
var arguments = String.Format(
CultureInfo.InvariantCulture,
"{0} -port {1} -ports 1-65535 {2}-close -verbose -{3} -{4}{5}{6} -params \"{7}\"",
dmbProvider.DmbName,
launchParameters.Port.Value,
launchParameters.AllowWebClient.Value ? "-webclient " : String.Empty,
SecurityWord(launchParameters.SecurityLevel.Value),
VisibilityWord(launchParameters.Visibility.Value),
!byondLock.SupportsCli
? $" -logself -log {logFileGuid = Guid.NewGuid()}"
: !platformIdentifier.IsWindows // Just use stdout on if CLI is supported
? " -logself"
: String.Empty, // Windows doesn't output anything to dd.exe if -logself is set?
launchParameters.StartProfiler.Value
? " -profile"
: String.Empty,
parameters);
if (!apiValidate && dmbProvider.CompileJob.DMApiVersion == null)
logger.LogDebug("Session will have no DMAPI support!");
// launch dd
var process = processExecutor.LaunchProcess(
byondLock.DreamDaemonPath,
dmbProvider.Directory,
arguments,
byondLock.SupportsCli,
byondLock.SupportsCli,
byondLock.SupportsCli);
var cliSupported = byondLock.SupportsCli;
async Task<string> GetDDOutput()
{
// DCT x2: None available
if (cliSupported)
return await process.GetCombinedOutput(default);
var logFilePath = ioManager.ConcatPath(dmbProvider.Directory, logFileGuid.ToString());
try
{
var dreamDaemonLogBytes = await ioManager.ReadAllBytes(
logFilePath,
default)
;
return Encoding.UTF8.GetString(dreamDaemonLogBytes);
}
finally
{
try
{
// DCT: No token available
await ioManager.DeleteFile(logFilePath, default);
}
catch (Exception ex)
{
logger.LogWarning(ex, "Failed to delete DreamDaemon log file {0}!", logFilePath);
}
}
}
// Log DD output
async Task PostLifetime()
{
try
{
var ddOutput = await GetDDOutput();
logger.LogTrace(
"DreamDaemon Output:{0}{1}",
Environment.NewLine,
ddOutput);
}
catch (Exception ex)
{
logger.LogWarning(ex, "Error reading DreamDaemon output!");
}
}
var chatTrackingContext = chat.CreateTrackingContext();
try
{
networkPromptReaper.RegisterProcess(process);
var runtimeInformation = CreateRuntimeInformation(
dmbProvider,
chatTrackingContext,
launchParameters.SecurityLevel.Value,
launchParameters.Visibility.Value,
launchParameters,
apiValidate);
var reattachInformation = new ReattachInformation(
@@ -387,56 +341,38 @@ namespace Tgstation.Server.Host.Components.Session
chat,
assemblyInformationProvider,
loggerFactory.CreateLogger<SessionController>(),
PostLifetime,
() => !launchParameters.LogOutput.Value
? LogDDOutput(process, outputFilePath, byondLock.SupportsCli, default) // DCT: None available
: Task.CompletedTask,
launchParameters.StartupTimeout,
false,
apiValidate);
if (apiValidate)
{
if (sessionConfiguration.HighPriorityLiveDreamDaemon)
process.AdjustPriority(true);
}
else if (sessionConfiguration.LowPriorityDeploymentProcesses)
process.AdjustPriority(false);
// If this isnt a staging DD (From a Deployment), fire off an event
if (!apiValidate)
await eventConsumer.HandleEvent(
EventType.DreamDaemonLaunch,
new List<string>
{
process.Id.ToString(CultureInfo.InvariantCulture),
},
cancellationToken)
;
return sessionController;
}
catch
{
using (process)
{
process.Terminate();
await process.Lifetime;
throw;
}
chatTrackingContext.Dispose();
throw;
}
}
catch
{
if (currentByondLock == null)
byondLock.Dispose();
throw;
await using (process)
{
process.Terminate();
await process.Lifetime;
throw;
}
}
}
catch
{
chatTrackingContext.Dispose();
if (currentByondLock == null)
byondLock.Dispose();
throw;
}
}
#pragma warning restore CA1506
/// <inheritdoc />
public async Task<ISessionController> Reattach(
@@ -448,30 +384,30 @@ namespace Tgstation.Server.Host.Components.Session
logger.LogTrace("Begin session reattach...");
var byondTopicSender = topicClientFactory.CreateTopicClient(reattachInformation.TopicRequestTimeout);
var chatTrackingContext = chat.CreateTrackingContext();
var byondLock = await byond.UseExecutables(Version.Parse(reattachInformation.Dmb.CompileJob.ByondVersion), cancellationToken);
try
{
var byondLock = await byond.UseExecutables(Version.Parse(reattachInformation.Dmb.CompileJob.ByondVersion), cancellationToken);
logger.LogDebug(
"Attaching to session PID: {0}, CompileJob: {1}...",
reattachInformation.ProcessId,
reattachInformation.Dmb.CompileJob.Id);
var process = processExecutor.GetProcess(reattachInformation.ProcessId);
if (process == null)
return null;
try
{
logger.LogDebug(
"Attaching to session PID: {0}, CompileJob: {1}...",
reattachInformation.ProcessId,
reattachInformation.Dmb.CompileJob.Id);
var process = processExecutor.GetProcess(reattachInformation.ProcessId);
if (process == null)
return null;
networkPromptReaper.RegisterProcess(process);
var chatTrackingContext = chat.CreateTrackingContext();
try
{
networkPromptReaper.RegisterProcess(process);
var runtimeInformation = CreateRuntimeInformation(
reattachInformation.Dmb,
chatTrackingContext,
null,
null,
false);
reattachInformation.SetRuntimeInformation(runtimeInformation);
@@ -497,19 +433,160 @@ namespace Tgstation.Server.Host.Components.Session
return controller;
}
finally
catch
{
process?.Dispose();
chatTrackingContext.Dispose();
throw;
}
}
finally
catch
{
byondLock?.Dispose();
await process.DisposeAsync();
throw;
}
}
finally
catch
{
chatTrackingContext?.Dispose();
byondLock.Dispose();
throw;
}
}
/// <summary>
/// Creates the DreamDaemon <see cref="IProcess"/>.
/// </summary>
/// <param name="dmbProvider">The <see cref="IDmbProvider"/>.</param>
/// <param name="byondTopicSender">The <see cref="ITopicClient"/> to use for sanitization.</param>
/// <param name="byondLock">The <see cref="IByondExecutableLock"/>.</param>
/// <param name="launchParameters">The <see cref="DreamDaemonLaunchParameters"/>.</param>
/// <param name="accessIdentifier">The secure string to use for the session.</param>
/// <param name="logFilePath">The path to log DreamDaemon output to.</param>
/// <param name="apiValidate">If we are only validating the DMAPI then exiting.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in the DreamDaemon <see cref="IProcess"/>.</returns>
async Task<IProcess> CreateDreamDaemonProcess(
IDmbProvider dmbProvider,
ITopicClient byondTopicSender,
IByondExecutableLock byondLock,
DreamDaemonLaunchParameters launchParameters,
string accessIdentifier,
string logFilePath,
bool apiValidate,
CancellationToken cancellationToken)
{
// set command line options
// more sanitization here cause it uses the same scheme
var parameters = $"{DMApiConstants.ParamApiVersion}={byondTopicSender.SanitizeString(DMApiConstants.InteropVersion.Semver().ToString())}&{byondTopicSender.SanitizeString(DMApiConstants.ParamServerPort)}={serverPortProvider.HttpApiPort}&{byondTopicSender.SanitizeString(DMApiConstants.ParamAccessIdentifier)}={byondTopicSender.SanitizeString(accessIdentifier)}";
if (!String.IsNullOrEmpty(launchParameters.AdditionalParameters))
parameters = $"{parameters}&{launchParameters.AdditionalParameters}";
// important to run on all ports to allow port changing
var arguments = String.Format(
CultureInfo.InvariantCulture,
"{0} -port {1} -ports 1-65535 {2}-close -verbose -{3} -{4}{5}{6} -params \"{7}\"",
dmbProvider.DmbName,
launchParameters.Port.Value,
launchParameters.AllowWebClient.Value ? "-webclient " : String.Empty,
SecurityWord(launchParameters.SecurityLevel.Value),
VisibilityWord(launchParameters.Visibility.Value),
!byondLock.SupportsCli
? $" -logself -log {logFilePath}"
: !platformIdentifier.IsWindows // Just use stdout on if CLI is supported
? " -logself"
: String.Empty, // Windows doesn't output anything to dd.exe if -logself is set?
launchParameters.StartProfiler.Value
? " -profile"
: String.Empty,
parameters);
var process = await processExecutor.LaunchProcess(
byondLock.DreamDaemonPath,
dmbProvider.Directory,
arguments,
logFilePath,
byondLock.SupportsCli,
true);
try
{
if (apiValidate)
{
if (sessionConfiguration.HighPriorityLiveDreamDaemon)
process.AdjustPriority(true);
}
else if (sessionConfiguration.LowPriorityDeploymentProcesses)
process.AdjustPriority(false);
networkPromptReaper.RegisterProcess(process);
// If this isnt a staging DD (From a Deployment), fire off an event
if (!apiValidate)
await eventConsumer.HandleEvent(
EventType.DreamDaemonLaunch,
new List<string>
{
process.Id.ToString(CultureInfo.InvariantCulture),
},
cancellationToken);
return process;
}
catch
{
await using (process)
{
process.Terminate();
await process.Lifetime;
throw;
}
}
}
/// <summary>
/// Attempts to log DreamDaemon output.
/// </summary>
/// <param name="process">The DreamDaemon <see cref="IProcess"/>.</param>
/// <param name="outputFilePath">The path to the DreamDaemon log file. Will be deleted.</param>
/// <param name="cliSupported">If DreamDaemon was launched with CLI capabilities.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
async Task LogDDOutput(IProcess process, string outputFilePath, bool cliSupported, CancellationToken cancellationToken)
{
try
{
string ddOutput;
if (cliSupported)
ddOutput = await process.GetCombinedOutput(cancellationToken);
else
try
{
var dreamDaemonLogBytes = await gameIOManager.ReadAllBytes(
outputFilePath,
cancellationToken);
ddOutput = Encoding.UTF8.GetString(dreamDaemonLogBytes);
}
finally
{
try
{
await gameIOManager.DeleteFile(outputFilePath, cancellationToken);
}
catch (Exception ex)
{
logger.LogWarning(ex, "Failed to delete DreamDaemon log file {outputFilePath}!", outputFilePath);
}
}
logger.LogTrace(
"DreamDaemon Output:{newLine}{output}",
Environment.NewLine,
ddOutput);
}
catch (Exception ex)
{
logger.LogWarning(ex, "Error reading DreamDaemon output!");
}
}
@@ -518,23 +595,21 @@ namespace Tgstation.Server.Host.Components.Session
/// </summary>
/// <param name="dmbProvider">The <see cref="IDmbProvider"/>.</param>
/// <param name="chatTrackingContext">The <see cref="IChatTrackingContext"/>.</param>
/// <param name="securityLevel">The <see cref="DreamDaemonSecurity"/> level if any.</param>
/// <param name="visibility">The <see cref="DreamDaemonVisibility"/> if any.</param>
/// <param name="launchParameters">The <see cref="DreamDaemonLaunchParameters"/> if any.</param>
/// <param name="apiValidateOnly">The value of <see cref="RuntimeInformation.ApiValidateOnly"/>.</param>
/// <returns>A new <see cref="RuntimeInformation"/> class.</returns>
RuntimeInformation CreateRuntimeInformation(
IDmbProvider dmbProvider,
IChatTrackingContext chatTrackingContext,
DreamDaemonSecurity? securityLevel,
DreamDaemonVisibility? visibility,
DreamDaemonLaunchParameters launchParameters,
bool apiValidateOnly)
=> new RuntimeInformation(
chatTrackingContext,
dmbProvider,
assemblyInformationProvider.Version,
instance.Name,
securityLevel,
visibility,
launchParameters?.SecurityLevel,
launchParameters?.Visibility,
serverPortProvider.HttpApiPort,
apiValidateOnly);
@@ -548,12 +623,12 @@ namespace Tgstation.Server.Host.Components.Session
if (!platformIdentifier.IsWindows)
return;
using var otherProcess = processExecutor.GetProcessByName("byond");
await using var otherProcess = processExecutor.GetProcessByName("byond");
if (otherProcess == null)
return;
var otherUsernameTask = otherProcess.GetExecutingUsername(cancellationToken);
using var ourProcess = processExecutor.GetCurrentProcess();
await using var ourProcess = processExecutor.GetCurrentProcess();
var ourUserName = await ourProcess.GetExecutingUsername(cancellationToken);
var otherUserName = await otherUsernameTask;
@@ -99,7 +99,7 @@ namespace Tgstation.Server.Host.Components.Session
{
try
{
using var process = processExecutor.GetProcess(reattachInfo.ProcessId);
await using var process = processExecutor.GetProcess(reattachInfo.ProcessId);
if (process != null)
{
if (reattachInfo == result)
@@ -562,7 +562,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
foreach (var scriptFile in scriptFiles)
{
logger.LogTrace("Running event script {scriptFile}...", scriptFile);
using (var script = processExecutor.LaunchProcess(
await using (var script = await processExecutor.LaunchProcess(
ioManager.ConcatPath(resolvedScriptsDir, scriptFile),
resolvedScriptsDir,
String.Join(
@@ -576,9 +576,8 @@ namespace Tgstation.Server.Host.Components.StaticFiles
return $"\"{arg}\"";
})),
true,
true,
true))
readStandardHandles: true,
noShellExecute: true))
using (cancellationToken.Register(() => script.Terminate()))
{
var exitCode = await script.Lifetime;
@@ -150,7 +150,8 @@ namespace Tgstation.Server.Host.Controllers
| DreamDaemonRights.SetTopicTimeout
| DreamDaemonRights.SetAdditionalParameters
| DreamDaemonRights.SetVisibility
| DreamDaemonRights.SetProfiler)]
| DreamDaemonRights.SetProfiler
| DreamDaemonRights.SetLogOutput)]
[ProducesResponseType(typeof(DreamDaemonResponse), 200)]
[ProducesResponseType(typeof(ErrorMessageResponse), 410)]
#pragma warning disable CA1502 // TODO: Decomplexify
@@ -224,7 +225,8 @@ namespace Tgstation.Server.Host.Controllers
|| CheckModified(x => x.DumpOnHeartbeatRestart, DreamDaemonRights.CreateDump)
|| CheckModified(x => x.TopicRequestTimeout, DreamDaemonRights.SetTopicTimeout)
|| CheckModified(x => x.AdditionalParameters, DreamDaemonRights.SetAdditionalParameters)
|| CheckModified(x => x.StartProfiler, DreamDaemonRights.SetProfiler))
|| CheckModified(x => x.StartProfiler, DreamDaemonRights.SetProfiler)
|| CheckModified(x => x.LogOutput, DreamDaemonRights.SetLogOutput))
return Forbid();
await DatabaseContext.Save(cancellationToken);
@@ -365,6 +367,7 @@ namespace Tgstation.Server.Host.Controllers
result.TopicRequestTimeout = settings.TopicRequestTimeout.Value;
result.AdditionalParameters = settings.AdditionalParameters;
result.StartProfiler = settings.StartProfiler;
result.LogOutput = settings.LogOutput;
}
if (revision)
@@ -725,6 +725,7 @@ namespace Tgstation.Server.Host.Controllers
TopicRequestTimeout = generalConfiguration.ByondTopicTimeout,
AdditionalParameters = String.Empty,
StartProfiler = false,
LogOutput = false,
},
DreamMakerSettings = new DreamMakerSettings
{
@@ -379,22 +379,22 @@ namespace Tgstation.Server.Host.Database
/// <summary>
/// Used by unit tests to remind us to setup the correct MSSQL migration downgrades.
/// </summary>
internal static readonly Type MSLatestMigration = typeof(MSAddProfiler);
internal static readonly Type MSLatestMigration = typeof(MSAddDreamDaemonLogOutput);
/// <summary>
/// Used by unit tests to remind us to setup the correct MYSQL migration downgrades.
/// </summary>
internal static readonly Type MYLatestMigration = typeof(MYAddProfiler);
internal static readonly Type MYLatestMigration = typeof(MYAddDreamDaemonLogOutput);
/// <summary>
/// Used by unit tests to remind us to setup the correct PostgresSQL migration downgrades.
/// </summary>
internal static readonly Type PGLatestMigration = typeof(PGAddProfiler);
internal static readonly Type PGLatestMigration = typeof(PGAddDreamDaemonLogOutput);
/// <summary>
/// Used by unit tests to remind us to setup the correct SQLite migration downgrades.
/// </summary>
internal static readonly Type SLLatestMigration = typeof(SLAddProfiler);
internal static readonly Type SLLatestMigration = typeof(SLAddDreamDaemonLogOutput);
/// <inheritdoc />
#pragma warning disable CA1502 // Cyclomatic complexity
@@ -425,6 +425,15 @@ namespace Tgstation.Server.Host.Database
string BadDatabaseType() => throw new ArgumentException($"Invalid DatabaseType: {currentDatabaseType}", nameof(currentDatabaseType));
if (targetVersion < new Version(5, 7, 0))
targetMigration = currentDatabaseType switch
{
DatabaseType.MySql => nameof(MYAddProfiler),
DatabaseType.PostgresSql => nameof(PGAddProfiler),
DatabaseType.SqlServer => nameof(MSAddProfiler),
DatabaseType.Sqlite => nameof(SLAddProfiler),
_ => BadDatabaseType(),
};
if (targetVersion < new Version(4, 19, 0))
targetMigration = currentDatabaseType switch
{
@@ -0,0 +1,39 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Tgstation.Server.Host.Database.Migrations
{
/// <summary>
/// Adds the DreamDaemon LogOutput column for MSSQL.
/// </summary>
public partial class MSAddDreamDaemonLogOutput : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
if (migrationBuilder == null)
throw new ArgumentNullException(nameof(migrationBuilder));
migrationBuilder.AddColumn<bool>(
name: "LogOutput",
table: "DreamDaemonSettings",
type: "bit",
nullable: false,
defaultValue: false);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
if (migrationBuilder == null)
throw new ArgumentNullException(nameof(migrationBuilder));
migrationBuilder.DropColumn(
name: "LogOutput",
table: "DreamDaemonSettings");
}
}
}
@@ -0,0 +1,39 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Tgstation.Server.Host.Database.Migrations
{
/// <summary>
/// Adds the DreamDaemon LogOutput column for PostgresSQL.
/// </summary>
public partial class PGAddDreamDaemonLogOutput : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
if (migrationBuilder == null)
throw new ArgumentNullException(nameof(migrationBuilder));
migrationBuilder.AddColumn<bool>(
name: "LogOutput",
table: "DreamDaemonSettings",
type: "boolean",
nullable: false,
defaultValue: false);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
if (migrationBuilder == null)
throw new ArgumentNullException(nameof(migrationBuilder));
migrationBuilder.DropColumn(
name: "LogOutput",
table: "DreamDaemonSettings");
}
}
}
@@ -0,0 +1,39 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Tgstation.Server.Host.Database.Migrations
{
/// <summary>
/// Adds the DreamDaemon LogOutput column for SQLite.
/// </summary>
public partial class SLAddDreamDaemonLogOutput : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
if (migrationBuilder == null)
throw new ArgumentNullException(nameof(migrationBuilder));
migrationBuilder.AddColumn<bool>(
name: "LogOutput",
table: "DreamDaemonSettings",
type: "INTEGER",
nullable: false,
defaultValue: false);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
if (migrationBuilder == null)
throw new ArgumentNullException(nameof(migrationBuilder));
migrationBuilder.DropColumn(
name: "LogOutput",
table: "DreamDaemonSettings");
}
}
}
@@ -0,0 +1,276 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Tgstation.Server.Host.Database.Migrations
{
/// <summary>
/// Adds the DreamDaemon LogOutput column for MYSQL.
/// </summary>
/// <remarks>Note the version upgrade of Pomelo caused some freakyness. The down migrations should be harmless here.</remarks>
public partial class MYAddDreamDaemonLogOutput : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
if (migrationBuilder == null)
throw new ArgumentNullException(nameof(migrationBuilder));
migrationBuilder.AddColumn<bool>(
name: "LogOutput",
table: "DreamDaemonSettings",
type: "tinyint(1)",
nullable: false,
defaultValue: false);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
if (migrationBuilder == null)
throw new ArgumentNullException(nameof(migrationBuilder));
migrationBuilder.DropColumn(
name: "LogOutput",
table: "DreamDaemonSettings");
migrationBuilder.AlterColumn<string>(
name: "PasswordHash",
table: "Users",
type: "longtext CHARACTER SET utf8mb4",
nullable: true,
oldClrType: typeof(string),
oldType: "longtext",
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "Url",
table: "TestMerges",
type: "longtext CHARACTER SET utf8mb4",
nullable: false,
oldClrType: typeof(string),
oldType: "longtext")
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "TitleAtMerge",
table: "TestMerges",
type: "longtext CHARACTER SET utf8mb4",
nullable: false,
oldClrType: typeof(string),
oldType: "longtext")
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "Comment",
table: "TestMerges",
type: "longtext CHARACTER SET utf8mb4",
maxLength: 10000,
nullable: true,
oldClrType: typeof(string),
oldType: "longtext",
oldMaxLength: 10000,
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "BodyAtMerge",
table: "TestMerges",
type: "longtext CHARACTER SET utf8mb4",
nullable: false,
oldClrType: typeof(string),
oldType: "longtext")
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "Author",
table: "TestMerges",
type: "longtext CHARACTER SET utf8mb4",
nullable: false,
oldClrType: typeof(string),
oldType: "longtext")
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "CommitterName",
table: "RepositorySettings",
type: "longtext CHARACTER SET utf8mb4",
maxLength: 10000,
nullable: false,
oldClrType: typeof(string),
oldType: "longtext",
oldMaxLength: 10000)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "CommitterEmail",
table: "RepositorySettings",
type: "longtext CHARACTER SET utf8mb4",
maxLength: 10000,
nullable: false,
oldClrType: typeof(string),
oldType: "longtext",
oldMaxLength: 10000)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "AccessUser",
table: "RepositorySettings",
type: "longtext CHARACTER SET utf8mb4",
maxLength: 10000,
nullable: true,
oldClrType: typeof(string),
oldType: "longtext",
oldMaxLength: 10000,
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "AccessToken",
table: "RepositorySettings",
type: "longtext CHARACTER SET utf8mb4",
maxLength: 10000,
nullable: true,
oldClrType: typeof(string),
oldType: "longtext",
oldMaxLength: 10000,
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "AccessIdentifier",
table: "ReattachInformations",
type: "longtext CHARACTER SET utf8mb4",
nullable: false,
oldClrType: typeof(string),
oldType: "longtext")
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "ExceptionDetails",
table: "Jobs",
type: "longtext CHARACTER SET utf8mb4",
nullable: true,
oldClrType: typeof(string),
oldType: "longtext",
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "Description",
table: "Jobs",
type: "longtext CHARACTER SET utf8mb4",
nullable: false,
oldClrType: typeof(string),
oldType: "longtext")
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "ProjectName",
table: "DreamMakerSettings",
type: "longtext CHARACTER SET utf8mb4",
maxLength: 10000,
nullable: true,
oldClrType: typeof(string),
oldType: "longtext",
oldMaxLength: 10000,
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "AdditionalParameters",
table: "DreamDaemonSettings",
type: "longtext CHARACTER SET utf8mb4",
maxLength: 10000,
nullable: false,
oldClrType: typeof(string),
oldType: "longtext",
oldMaxLength: 10000)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "RepositoryOrigin",
table: "CompileJobs",
type: "longtext CHARACTER SET utf8mb4",
nullable: true,
oldClrType: typeof(string),
oldType: "longtext",
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "Output",
table: "CompileJobs",
type: "longtext CHARACTER SET utf8mb4",
nullable: false,
oldClrType: typeof(string),
oldType: "longtext")
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "DmeName",
table: "CompileJobs",
type: "longtext CHARACTER SET utf8mb4",
nullable: false,
oldClrType: typeof(string),
oldType: "longtext")
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "ByondVersion",
table: "CompileJobs",
type: "longtext CHARACTER SET utf8mb4",
nullable: false,
oldClrType: typeof(string),
oldType: "longtext")
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "Tag",
table: "ChatChannels",
type: "longtext CHARACTER SET utf8mb4",
maxLength: 10000,
nullable: true,
oldClrType: typeof(string),
oldType: "longtext",
oldMaxLength: 10000,
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "ConnectionString",
table: "ChatBots",
type: "longtext CHARACTER SET utf8mb4",
maxLength: 10000,
nullable: false,
oldClrType: typeof(string),
oldType: "longtext",
oldMaxLength: 10000)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
}
}
}
@@ -4,16 +4,19 @@ using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
#nullable disable
namespace Tgstation.Server.Host.Database.Migrations
{
[DbContext(typeof(MySqlDatabaseContext))]
partial class MySqlDatabaseContextModelSnapshot : ModelSnapshot
{
/// <inheritdoc />
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.1.20")
.HasAnnotation("ProductVersion", "6.0.15")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
@@ -28,8 +31,10 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("ConnectionString")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("longtext");
MySqlPropertyBuilderExtensions.HasCharSet(b.Property<string>("ConnectionString"), "utf8mb4");
b.Property<bool?>("Enabled")
.HasColumnType("tinyint(1)");
@@ -39,8 +44,8 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("Name")
.IsRequired()
.HasColumnType("varchar(100) CHARACTER SET utf8mb4")
.HasMaxLength(100);
.HasMaxLength(100)
.HasColumnType("varchar(100)");
b.Property<int>("Provider")
.HasColumnType("int");
@@ -70,8 +75,10 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasColumnType("bigint unsigned");
b.Property<string>("IrcChannel")
.HasColumnType("varchar(100) CHARACTER SET utf8mb4")
.HasMaxLength(100);
.HasMaxLength(100)
.HasColumnType("varchar(100)");
MySqlPropertyBuilderExtensions.HasCharSet(b.Property<string>("IrcChannel"), "utf8mb4");
b.Property<bool?>("IsAdminChannel")
.IsRequired()
@@ -86,8 +93,10 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasColumnType("tinyint(1)");
b.Property<string>("Tag")
.HasColumnType("longtext CHARACTER SET utf8mb4")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("longtext");
MySqlPropertyBuilderExtensions.HasCharSet(b.Property<string>("Tag"), "utf8mb4");
b.HasKey("Id");
@@ -108,7 +117,9 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("ByondVersion")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4");
.HasColumnType("longtext");
MySqlPropertyBuilderExtensions.HasCharSet(b.Property<string>("ByondVersion"), "utf8mb4");
b.Property<int?>("DMApiMajorVersion")
.HasColumnType("int");
@@ -125,7 +136,9 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("DmeName")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4");
.HasColumnType("longtext");
MySqlPropertyBuilderExtensions.HasCharSet(b.Property<string>("DmeName"), "utf8mb4");
b.Property<int?>("GitHubDeploymentId")
.HasColumnType("int");
@@ -141,10 +154,14 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("Output")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4");
.HasColumnType("longtext");
MySqlPropertyBuilderExtensions.HasCharSet(b.Property<string>("Output"), "utf8mb4");
b.Property<string>("RepositoryOrigin")
.HasColumnType("longtext CHARACTER SET utf8mb4");
.HasColumnType("longtext");
MySqlPropertyBuilderExtensions.HasCharSet(b.Property<string>("RepositoryOrigin"), "utf8mb4");
b.Property<long>("RevisionInformationId")
.HasColumnType("bigint");
@@ -169,8 +186,10 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("AdditionalParameters")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("longtext");
MySqlPropertyBuilderExtensions.HasCharSet(b.Property<string>("AdditionalParameters"), "utf8mb4");
b.Property<bool?>("AllowWebClient")
.IsRequired()
@@ -191,6 +210,10 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<bool?>("LogOutput")
.IsRequired()
.HasColumnType("tinyint(1)");
b.Property<ushort?>("Port")
.IsRequired()
.HasColumnType("smallint unsigned");
@@ -238,8 +261,10 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasColumnType("bigint");
b.Property<string>("ProjectName")
.HasColumnType("longtext CHARACTER SET utf8mb4")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("longtext");
MySqlPropertyBuilderExtensions.HasCharSet(b.Property<string>("ProjectName"), "utf8mb4");
b.Property<bool?>("RequireDMApiValidation")
.IsRequired()
@@ -276,8 +301,10 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("Name")
.IsRequired()
.HasColumnType("varchar(100) CHARACTER SET utf8mb4")
.HasMaxLength(100);
.HasMaxLength(100)
.HasColumnType("varchar(100)");
MySqlPropertyBuilderExtensions.HasCharSet(b.Property<string>("Name"), "utf8mb4");
b.Property<bool?>("Online")
.IsRequired()
@@ -285,10 +312,14 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("Path")
.IsRequired()
.HasColumnType("varchar(255) CHARACTER SET utf8mb4");
.HasColumnType("varchar(255)");
MySqlPropertyBuilderExtensions.HasCharSet(b.Property<string>("Path"), "utf8mb4");
b.Property<string>("SwarmIdentifer")
.HasColumnType("varchar(255) CHARACTER SET utf8mb4");
.HasColumnType("varchar(255)");
MySqlPropertyBuilderExtensions.HasCharSet(b.Property<string>("SwarmIdentifer"), "utf8mb4");
b.HasKey("Id");
@@ -362,13 +393,17 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("Description")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4");
.HasColumnType("longtext");
MySqlPropertyBuilderExtensions.HasCharSet(b.Property<string>("Description"), "utf8mb4");
b.Property<uint?>("ErrorCode")
.HasColumnType("int unsigned");
b.Property<string>("ExceptionDetails")
.HasColumnType("longtext CHARACTER SET utf8mb4");
.HasColumnType("longtext");
MySqlPropertyBuilderExtensions.HasCharSet(b.Property<string>("ExceptionDetails"), "utf8mb4");
b.Property<long>("InstanceId")
.HasColumnType("bigint");
@@ -402,8 +437,10 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("ExternalUserId")
.IsRequired()
.HasColumnType("varchar(100) CHARACTER SET utf8mb4")
.HasMaxLength(100);
.HasMaxLength(100)
.HasColumnType("varchar(100)");
MySqlPropertyBuilderExtensions.HasCharSet(b.Property<string>("ExternalUserId"), "utf8mb4");
b.Property<int>("Provider")
.HasColumnType("int");
@@ -458,7 +495,9 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("AccessIdentifier")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4");
.HasColumnType("longtext");
MySqlPropertyBuilderExtensions.HasCharSet(b.Property<string>("AccessIdentifier"), "utf8mb4");
b.Property<long>("CompileJobId")
.HasColumnType("bigint");
@@ -492,12 +531,16 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasColumnType("bigint");
b.Property<string>("AccessToken")
.HasColumnType("longtext CHARACTER SET utf8mb4")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("longtext");
MySqlPropertyBuilderExtensions.HasCharSet(b.Property<string>("AccessToken"), "utf8mb4");
b.Property<string>("AccessUser")
.HasColumnType("longtext CHARACTER SET utf8mb4")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("longtext");
MySqlPropertyBuilderExtensions.HasCharSet(b.Property<string>("AccessUser"), "utf8mb4");
b.Property<bool?>("AutoUpdatesKeepTestMerges")
.IsRequired()
@@ -509,13 +552,17 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("CommitterEmail")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("longtext");
MySqlPropertyBuilderExtensions.HasCharSet(b.Property<string>("CommitterEmail"), "utf8mb4");
b.Property<string>("CommitterName")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("longtext");
MySqlPropertyBuilderExtensions.HasCharSet(b.Property<string>("CommitterName"), "utf8mb4");
b.Property<bool?>("CreateGitHubDeployments")
.IsRequired()
@@ -577,16 +624,20 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("CommitSha")
.IsRequired()
.HasColumnType("varchar(40) CHARACTER SET utf8mb4")
.HasMaxLength(40);
.HasMaxLength(40)
.HasColumnType("varchar(40)");
MySqlPropertyBuilderExtensions.HasCharSet(b.Property<string>("CommitSha"), "utf8mb4");
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<string>("OriginCommitSha")
.IsRequired()
.HasColumnType("varchar(40) CHARACTER SET utf8mb4")
.HasMaxLength(40);
.HasMaxLength(40)
.HasColumnType("varchar(40)");
MySqlPropertyBuilderExtensions.HasCharSet(b.Property<string>("OriginCommitSha"), "utf8mb4");
b.Property<DateTimeOffset>("Timestamp")
.HasColumnType("datetime(6)");
@@ -607,15 +658,21 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("Author")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4");
.HasColumnType("longtext");
MySqlPropertyBuilderExtensions.HasCharSet(b.Property<string>("Author"), "utf8mb4");
b.Property<string>("BodyAtMerge")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4");
.HasColumnType("longtext");
MySqlPropertyBuilderExtensions.HasCharSet(b.Property<string>("BodyAtMerge"), "utf8mb4");
b.Property<string>("Comment")
.HasColumnType("longtext CHARACTER SET utf8mb4")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("longtext");
MySqlPropertyBuilderExtensions.HasCharSet(b.Property<string>("Comment"), "utf8mb4");
b.Property<DateTimeOffset>("MergedAt")
.HasColumnType("datetime(6)");
@@ -632,16 +689,22 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("TargetCommitSha")
.IsRequired()
.HasColumnType("varchar(40) CHARACTER SET utf8mb4")
.HasMaxLength(40);
.HasMaxLength(40)
.HasColumnType("varchar(40)");
MySqlPropertyBuilderExtensions.HasCharSet(b.Property<string>("TargetCommitSha"), "utf8mb4");
b.Property<string>("TitleAtMerge")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4");
.HasColumnType("longtext");
MySqlPropertyBuilderExtensions.HasCharSet(b.Property<string>("TitleAtMerge"), "utf8mb4");
b.Property<string>("Url")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4");
.HasColumnType("longtext");
MySqlPropertyBuilderExtensions.HasCharSet(b.Property<string>("Url"), "utf8mb4");
b.HasKey("Id");
@@ -661,8 +724,10 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("CanonicalName")
.IsRequired()
.HasColumnType("varchar(100) CHARACTER SET utf8mb4")
.HasMaxLength(100);
.HasMaxLength(100)
.HasColumnType("varchar(100)");
MySqlPropertyBuilderExtensions.HasCharSet(b.Property<string>("CanonicalName"), "utf8mb4");
b.Property<DateTimeOffset?>("CreatedAt")
.IsRequired()
@@ -683,15 +748,21 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("Name")
.IsRequired()
.HasColumnType("varchar(100) CHARACTER SET utf8mb4")
.HasMaxLength(100);
.HasMaxLength(100)
.HasColumnType("varchar(100)");
MySqlPropertyBuilderExtensions.HasCharSet(b.Property<string>("Name"), "utf8mb4");
b.Property<string>("PasswordHash")
.HasColumnType("longtext CHARACTER SET utf8mb4");
.HasColumnType("longtext");
MySqlPropertyBuilderExtensions.HasCharSet(b.Property<string>("PasswordHash"), "utf8mb4");
b.Property<string>("SystemIdentifier")
.HasColumnType("varchar(100) CHARACTER SET utf8mb4")
.HasMaxLength(100);
.HasMaxLength(100)
.HasColumnType("varchar(100)");
MySqlPropertyBuilderExtensions.HasCharSet(b.Property<string>("SystemIdentifier"), "utf8mb4");
b.HasKey("Id");
@@ -716,8 +787,10 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("Name")
.IsRequired()
.HasColumnType("varchar(100) CHARACTER SET utf8mb4")
.HasMaxLength(100);
.HasMaxLength(100)
.HasColumnType("varchar(100)");
MySqlPropertyBuilderExtensions.HasCharSet(b.Property<string>("Name"), "utf8mb4");
b.HasKey("Id");
@@ -734,6 +807,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Instance");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b =>
@@ -743,6 +818,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("ChatSettingsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("ChatSettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b =>
@@ -758,6 +835,10 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("RevisionInformationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Job");
b.Navigation("RevisionInformation");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b =>
@@ -767,6 +848,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Instance");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b =>
@@ -776,6 +859,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Instance");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.InstancePermissionSet", b =>
@@ -791,6 +876,10 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("PermissionSetId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Instance");
b.Navigation("PermissionSet");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b =>
@@ -810,6 +899,12 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("StartedById")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("CancelledBy");
b.Navigation("Instance");
b.Navigation("StartedBy");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b =>
@@ -818,6 +913,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.WithMany("OAuthConnections")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
b.Navigation("User");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b =>
@@ -831,6 +928,10 @@ namespace Tgstation.Server.Host.Database.Migrations
.WithOne("PermissionSet")
.HasForeignKey("Tgstation.Server.Host.Models.PermissionSet", "UserId")
.OnDelete(DeleteBehavior.Cascade);
b.Navigation("Group");
b.Navigation("User");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b =>
@@ -840,6 +941,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("CompileJobId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("CompileJob");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b =>
@@ -849,6 +952,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Instance");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b =>
@@ -864,6 +969,10 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("TestMergeId")
.OnDelete(DeleteBehavior.ClientNoAction)
.IsRequired();
b.Navigation("RevisionInformation");
b.Navigation("TestMerge");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b =>
@@ -873,6 +982,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Instance");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b =>
@@ -888,6 +999,10 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("MergedBy");
b.Navigation("PrimaryRevisionInformation");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.User", b =>
@@ -899,6 +1014,70 @@ namespace Tgstation.Server.Host.Database.Migrations
b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group")
.WithMany("Users")
.HasForeignKey("GroupId");
b.Navigation("CreatedBy");
b.Navigation("Group");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
{
b.Navigation("Channels");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b =>
{
b.Navigation("ChatSettings");
b.Navigation("DreamDaemonSettings");
b.Navigation("DreamMakerSettings");
b.Navigation("InstancePermissionSets");
b.Navigation("Jobs");
b.Navigation("RepositorySettings");
b.Navigation("RevisionInformations");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b =>
{
b.Navigation("InstancePermissionSets");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b =>
{
b.Navigation("ActiveTestMerges");
b.Navigation("CompileJobs");
b.Navigation("PrimaryTestMerge");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b =>
{
b.Navigation("RevisonInformations");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.User", b =>
{
b.Navigation("CreatedUsers");
b.Navigation("OAuthConnections");
b.Navigation("PermissionSet");
b.Navigation("TestMerges");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.UserGroup", b =>
{
b.Navigation("PermissionSet")
.IsRequired();
b.Navigation("Users");
});
#pragma warning restore 612, 618
}
@@ -3,35 +3,39 @@ using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Tgstation.Server.Host.Database.Migrations
{
[DbContext(typeof(PostgresSqlDatabaseContext))]
partial class PostgresSqlDatabaseContextModelSnapshot : ModelSnapshot
{
/// <inheritdoc />
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn)
.HasAnnotation("ProductVersion", "3.1.20")
.HasAnnotation("ProductVersion", "6.0.15")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
{
b.Property<long?>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long?>("Id"));
b.Property<int>("ChannelLimit")
.HasColumnType("integer");
b.Property<string>("ConnectionString")
.IsRequired()
.HasColumnType("character varying(10000)")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("character varying(10000)");
b.Property<bool?>("Enabled")
.HasColumnType("boolean");
@@ -41,8 +45,8 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("Name")
.IsRequired()
.HasColumnType("character varying(100)")
.HasMaxLength(100);
.HasMaxLength(100)
.HasColumnType("character varying(100)");
b.Property<int>("Provider")
.HasColumnType("integer");
@@ -62,8 +66,9 @@ namespace Tgstation.Server.Host.Database.Migrations
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<long>("ChatSettingsId")
.HasColumnType("bigint");
@@ -72,8 +77,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasColumnType("numeric(20,0)");
b.Property<string>("IrcChannel")
.HasColumnType("character varying(100)")
.HasMaxLength(100);
.HasMaxLength(100)
.HasColumnType("character varying(100)");
b.Property<bool?>("IsAdminChannel")
.IsRequired()
@@ -88,8 +93,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasColumnType("boolean");
b.Property<string>("Tag")
.HasColumnType("character varying(10000)")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("character varying(10000)");
b.HasKey("Id");
@@ -106,8 +111,9 @@ namespace Tgstation.Server.Host.Database.Migrations
{
b.Property<long?>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long?>("Id"));
b.Property<string>("ByondVersion")
.IsRequired()
@@ -168,13 +174,14 @@ namespace Tgstation.Server.Host.Database.Migrations
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<string>("AdditionalParameters")
.IsRequired()
.HasColumnType("character varying(10000)")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("character varying(10000)");
b.Property<bool?>("AllowWebClient")
.IsRequired()
@@ -194,6 +201,10 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<bool?>("LogOutput")
.IsRequired()
.HasColumnType("boolean");
b.Property<int>("Port")
.HasColumnType("integer");
@@ -225,8 +236,9 @@ namespace Tgstation.Server.Host.Database.Migrations
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<int>("ApiValidationPort")
.HasColumnType("integer");
@@ -238,8 +250,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasColumnType("bigint");
b.Property<string>("ProjectName")
.HasColumnType("character varying(10000)")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("character varying(10000)");
b.Property<bool?>("RequireDMApiValidation")
.IsRequired()
@@ -261,8 +273,9 @@ namespace Tgstation.Server.Host.Database.Migrations
{
b.Property<long?>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long?>("Id"));
b.Property<long>("AutoUpdateInterval")
.HasColumnType("bigint");
@@ -275,8 +288,8 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("Name")
.IsRequired()
.HasColumnType("character varying(100)")
.HasMaxLength(100);
.HasMaxLength(100)
.HasColumnType("character varying(100)");
b.Property<bool?>("Online")
.IsRequired()
@@ -301,8 +314,9 @@ namespace Tgstation.Server.Host.Database.Migrations
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<decimal>("ByondRights")
.HasColumnType("numeric(20,0)");
@@ -345,8 +359,9 @@ namespace Tgstation.Server.Host.Database.Migrations
{
b.Property<long?>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long?>("Id"));
b.Property<decimal?>("CancelRight")
.HasColumnType("numeric(20,0)");
@@ -399,13 +414,14 @@ namespace Tgstation.Server.Host.Database.Migrations
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<string>("ExternalUserId")
.IsRequired()
.HasColumnType("character varying(100)")
.HasMaxLength(100);
.HasMaxLength(100)
.HasColumnType("character varying(100)");
b.Property<int>("Provider")
.HasColumnType("integer");
@@ -427,8 +443,9 @@ namespace Tgstation.Server.Host.Database.Migrations
{
b.Property<long?>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long?>("Id"));
b.Property<decimal>("AdministrationRights")
.HasColumnType("numeric(20,0)");
@@ -457,8 +474,9 @@ namespace Tgstation.Server.Host.Database.Migrations
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<string>("AccessIdentifier")
.IsRequired()
@@ -493,16 +511,17 @@ namespace Tgstation.Server.Host.Database.Migrations
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<string>("AccessToken")
.HasColumnType("character varying(10000)")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("character varying(10000)");
b.Property<string>("AccessUser")
.HasColumnType("character varying(10000)")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("character varying(10000)");
b.Property<bool?>("AutoUpdatesKeepTestMerges")
.IsRequired()
@@ -514,13 +533,13 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("CommitterEmail")
.IsRequired()
.HasColumnType("character varying(10000)")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("character varying(10000)");
b.Property<string>("CommitterName")
.IsRequired()
.HasColumnType("character varying(10000)")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("character varying(10000)");
b.Property<bool?>("CreateGitHubDeployments")
.IsRequired()
@@ -557,8 +576,9 @@ namespace Tgstation.Server.Host.Database.Migrations
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<long>("RevisionInformationId")
.HasColumnType("bigint");
@@ -579,21 +599,22 @@ namespace Tgstation.Server.Host.Database.Migrations
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<string>("CommitSha")
.IsRequired()
.HasColumnType("character varying(40)")
.HasMaxLength(40);
.HasMaxLength(40)
.HasColumnType("character varying(40)");
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<string>("OriginCommitSha")
.IsRequired()
.HasColumnType("character varying(40)")
.HasMaxLength(40);
.HasMaxLength(40)
.HasColumnType("character varying(40)");
b.Property<DateTimeOffset>("Timestamp")
.HasColumnType("timestamp with time zone");
@@ -610,8 +631,9 @@ namespace Tgstation.Server.Host.Database.Migrations
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<string>("Author")
.IsRequired()
@@ -622,8 +644,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasColumnType("text");
b.Property<string>("Comment")
.HasColumnType("character varying(10000)")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("character varying(10000)");
b.Property<DateTimeOffset>("MergedAt")
.HasColumnType("timestamp with time zone");
@@ -640,8 +662,8 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("TargetCommitSha")
.IsRequired()
.HasColumnType("character varying(40)")
.HasMaxLength(40);
.HasMaxLength(40)
.HasColumnType("character varying(40)");
b.Property<string>("TitleAtMerge")
.IsRequired()
@@ -665,13 +687,14 @@ namespace Tgstation.Server.Host.Database.Migrations
{
b.Property<long?>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long?>("Id"));
b.Property<string>("CanonicalName")
.IsRequired()
.HasColumnType("character varying(100)")
.HasMaxLength(100);
.HasMaxLength(100)
.HasColumnType("character varying(100)");
b.Property<DateTimeOffset?>("CreatedAt")
.IsRequired()
@@ -692,15 +715,15 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("Name")
.IsRequired()
.HasColumnType("character varying(100)")
.HasMaxLength(100);
.HasMaxLength(100)
.HasColumnType("character varying(100)");
b.Property<string>("PasswordHash")
.HasColumnType("text");
b.Property<string>("SystemIdentifier")
.HasColumnType("character varying(100)")
.HasMaxLength(100);
.HasMaxLength(100)
.HasColumnType("character varying(100)");
b.HasKey("Id");
@@ -721,13 +744,14 @@ namespace Tgstation.Server.Host.Database.Migrations
{
b.Property<long?>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long?>("Id"));
b.Property<string>("Name")
.IsRequired()
.HasColumnType("character varying(100)")
.HasMaxLength(100);
.HasMaxLength(100)
.HasColumnType("character varying(100)");
b.HasKey("Id");
@@ -744,6 +768,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Instance");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b =>
@@ -753,6 +779,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("ChatSettingsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("ChatSettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b =>
@@ -768,6 +796,10 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("RevisionInformationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Job");
b.Navigation("RevisionInformation");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b =>
@@ -777,6 +809,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Instance");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b =>
@@ -786,6 +820,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Instance");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.InstancePermissionSet", b =>
@@ -801,6 +837,10 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("PermissionSetId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Instance");
b.Navigation("PermissionSet");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b =>
@@ -820,6 +860,12 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("StartedById")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("CancelledBy");
b.Navigation("Instance");
b.Navigation("StartedBy");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b =>
@@ -828,6 +874,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.WithMany("OAuthConnections")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
b.Navigation("User");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b =>
@@ -841,6 +889,10 @@ namespace Tgstation.Server.Host.Database.Migrations
.WithOne("PermissionSet")
.HasForeignKey("Tgstation.Server.Host.Models.PermissionSet", "UserId")
.OnDelete(DeleteBehavior.Cascade);
b.Navigation("Group");
b.Navigation("User");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b =>
@@ -850,6 +902,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("CompileJobId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("CompileJob");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b =>
@@ -859,6 +913,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Instance");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b =>
@@ -874,6 +930,10 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("TestMergeId")
.OnDelete(DeleteBehavior.ClientNoAction)
.IsRequired();
b.Navigation("RevisionInformation");
b.Navigation("TestMerge");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b =>
@@ -883,6 +943,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Instance");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b =>
@@ -898,6 +960,10 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("MergedBy");
b.Navigation("PrimaryRevisionInformation");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.User", b =>
@@ -909,6 +975,70 @@ namespace Tgstation.Server.Host.Database.Migrations
b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group")
.WithMany("Users")
.HasForeignKey("GroupId");
b.Navigation("CreatedBy");
b.Navigation("Group");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
{
b.Navigation("Channels");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b =>
{
b.Navigation("ChatSettings");
b.Navigation("DreamDaemonSettings");
b.Navigation("DreamMakerSettings");
b.Navigation("InstancePermissionSets");
b.Navigation("Jobs");
b.Navigation("RepositorySettings");
b.Navigation("RevisionInformations");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b =>
{
b.Navigation("InstancePermissionSets");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b =>
{
b.Navigation("ActiveTestMerges");
b.Navigation("CompileJobs");
b.Navigation("PrimaryTestMerge");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b =>
{
b.Navigation("RevisonInformations");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.User", b =>
{
b.Navigation("CreatedUsers");
b.Navigation("OAuthConnections");
b.Navigation("PermissionSet");
b.Navigation("TestMerges");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.UserGroup", b =>
{
b.Navigation("PermissionSet")
.IsRequired();
b.Navigation("Users");
});
#pragma warning restore 612, 618
}
@@ -3,35 +3,39 @@ using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
#nullable disable
namespace Tgstation.Server.Host.Database.Migrations
{
[DbContext(typeof(SqlServerDatabaseContext))]
partial class SqlServerDatabaseContextModelSnapshot : ModelSnapshot
{
/// <inheritdoc />
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.1.20")
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
.HasAnnotation("ProductVersion", "6.0.15")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1);
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
{
b.Property<long?>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
.HasColumnType("bigint");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long?>("Id"), 1L, 1);
b.Property<int>("ChannelLimit")
.HasColumnType("int");
b.Property<string>("ConnectionString")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("nvarchar(max)");
b.Property<bool?>("Enabled")
.HasColumnType("bit");
@@ -41,8 +45,8 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(100)")
.HasMaxLength(100);
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<int>("Provider")
.HasColumnType("int");
@@ -62,8 +66,9 @@ namespace Tgstation.Server.Host.Database.Migrations
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
.HasColumnType("bigint");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"), 1L, 1);
b.Property<long>("ChatSettingsId")
.HasColumnType("bigint");
@@ -72,8 +77,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasColumnType("decimal(20,0)");
b.Property<string>("IrcChannel")
.HasColumnType("nvarchar(100)")
.HasMaxLength(100);
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<bool?>("IsAdminChannel")
.IsRequired()
@@ -88,8 +93,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasColumnType("bit");
b.Property<string>("Tag")
.HasColumnType("nvarchar(max)")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
@@ -108,8 +113,9 @@ namespace Tgstation.Server.Host.Database.Migrations
{
b.Property<long?>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
.HasColumnType("bigint");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long?>("Id"), 1L, 1);
b.Property<string>("ByondVersion")
.IsRequired()
@@ -170,13 +176,14 @@ namespace Tgstation.Server.Host.Database.Migrations
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
.HasColumnType("bigint");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"), 1L, 1);
b.Property<string>("AdditionalParameters")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("nvarchar(max)");
b.Property<bool?>("AllowWebClient")
.IsRequired()
@@ -196,6 +203,10 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<bool?>("LogOutput")
.IsRequired()
.HasColumnType("bit");
b.Property<int>("Port")
.HasColumnType("int");
@@ -227,8 +238,9 @@ namespace Tgstation.Server.Host.Database.Migrations
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
.HasColumnType("bigint");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"), 1L, 1);
b.Property<int>("ApiValidationPort")
.HasColumnType("int");
@@ -240,8 +252,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasColumnType("bigint");
b.Property<string>("ProjectName")
.HasColumnType("nvarchar(max)")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("nvarchar(max)");
b.Property<bool?>("RequireDMApiValidation")
.IsRequired()
@@ -263,8 +275,9 @@ namespace Tgstation.Server.Host.Database.Migrations
{
b.Property<long?>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
.HasColumnType("bigint");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long?>("Id"), 1L, 1);
b.Property<long>("AutoUpdateInterval")
.HasColumnType("bigint");
@@ -277,8 +290,8 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(100)")
.HasMaxLength(100);
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<bool?>("Online")
.IsRequired()
@@ -304,8 +317,9 @@ namespace Tgstation.Server.Host.Database.Migrations
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
.HasColumnType("bigint");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"), 1L, 1);
b.Property<decimal>("ByondRights")
.HasColumnType("decimal(20,0)");
@@ -348,8 +362,9 @@ namespace Tgstation.Server.Host.Database.Migrations
{
b.Property<long?>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
.HasColumnType("bigint");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long?>("Id"), 1L, 1);
b.Property<decimal?>("CancelRight")
.HasColumnType("decimal(20,0)");
@@ -402,13 +417,14 @@ namespace Tgstation.Server.Host.Database.Migrations
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
.HasColumnType("bigint");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"), 1L, 1);
b.Property<string>("ExternalUserId")
.IsRequired()
.HasColumnType("nvarchar(100)")
.HasMaxLength(100);
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<int>("Provider")
.HasColumnType("int");
@@ -430,8 +446,9 @@ namespace Tgstation.Server.Host.Database.Migrations
{
b.Property<long?>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
.HasColumnType("bigint");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long?>("Id"), 1L, 1);
b.Property<decimal>("AdministrationRights")
.HasColumnType("decimal(20,0)");
@@ -462,8 +479,9 @@ namespace Tgstation.Server.Host.Database.Migrations
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
.HasColumnType("bigint");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"), 1L, 1);
b.Property<string>("AccessIdentifier")
.IsRequired()
@@ -498,16 +516,17 @@ namespace Tgstation.Server.Host.Database.Migrations
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
.HasColumnType("bigint");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"), 1L, 1);
b.Property<string>("AccessToken")
.HasColumnType("nvarchar(max)")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("nvarchar(max)");
b.Property<string>("AccessUser")
.HasColumnType("nvarchar(max)")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("nvarchar(max)");
b.Property<bool?>("AutoUpdatesKeepTestMerges")
.IsRequired()
@@ -519,13 +538,13 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("CommitterEmail")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("nvarchar(max)");
b.Property<string>("CommitterName")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("nvarchar(max)");
b.Property<bool?>("CreateGitHubDeployments")
.IsRequired()
@@ -562,8 +581,9 @@ namespace Tgstation.Server.Host.Database.Migrations
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
.HasColumnType("bigint");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"), 1L, 1);
b.Property<long>("RevisionInformationId")
.HasColumnType("bigint");
@@ -584,21 +604,22 @@ namespace Tgstation.Server.Host.Database.Migrations
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
.HasColumnType("bigint");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"), 1L, 1);
b.Property<string>("CommitSha")
.IsRequired()
.HasColumnType("nvarchar(40)")
.HasMaxLength(40);
.HasMaxLength(40)
.HasColumnType("nvarchar(40)");
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<string>("OriginCommitSha")
.IsRequired()
.HasColumnType("nvarchar(40)")
.HasMaxLength(40);
.HasMaxLength(40)
.HasColumnType("nvarchar(40)");
b.Property<DateTimeOffset>("Timestamp")
.HasColumnType("datetimeoffset");
@@ -615,8 +636,9 @@ namespace Tgstation.Server.Host.Database.Migrations
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
.HasColumnType("bigint");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"), 1L, 1);
b.Property<string>("Author")
.IsRequired()
@@ -627,8 +649,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasColumnType("nvarchar(max)");
b.Property<string>("Comment")
.HasColumnType("nvarchar(max)")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("nvarchar(max)");
b.Property<DateTimeOffset>("MergedAt")
.HasColumnType("datetimeoffset");
@@ -645,8 +667,8 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("TargetCommitSha")
.IsRequired()
.HasColumnType("nvarchar(40)")
.HasMaxLength(40);
.HasMaxLength(40)
.HasColumnType("nvarchar(40)");
b.Property<string>("TitleAtMerge")
.IsRequired()
@@ -670,13 +692,14 @@ namespace Tgstation.Server.Host.Database.Migrations
{
b.Property<long?>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
.HasColumnType("bigint");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long?>("Id"), 1L, 1);
b.Property<string>("CanonicalName")
.IsRequired()
.HasColumnType("nvarchar(100)")
.HasMaxLength(100);
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<DateTimeOffset?>("CreatedAt")
.IsRequired()
@@ -697,15 +720,15 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(100)")
.HasMaxLength(100);
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<string>("PasswordHash")
.HasColumnType("nvarchar(max)");
b.Property<string>("SystemIdentifier")
.HasColumnType("nvarchar(100)")
.HasMaxLength(100);
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.HasKey("Id");
@@ -727,13 +750,14 @@ namespace Tgstation.Server.Host.Database.Migrations
{
b.Property<long?>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
.HasColumnType("bigint");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long?>("Id"), 1L, 1);
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(100)")
.HasMaxLength(100);
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.HasKey("Id");
@@ -750,6 +774,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Instance");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b =>
@@ -759,6 +785,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("ChatSettingsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("ChatSettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b =>
@@ -774,6 +802,10 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("RevisionInformationId")
.OnDelete(DeleteBehavior.ClientNoAction)
.IsRequired();
b.Navigation("Job");
b.Navigation("RevisionInformation");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b =>
@@ -783,6 +815,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Instance");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b =>
@@ -792,6 +826,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Instance");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.InstancePermissionSet", b =>
@@ -807,6 +843,10 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("PermissionSetId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Instance");
b.Navigation("PermissionSet");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b =>
@@ -826,6 +866,12 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("StartedById")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("CancelledBy");
b.Navigation("Instance");
b.Navigation("StartedBy");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b =>
@@ -834,6 +880,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.WithMany("OAuthConnections")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
b.Navigation("User");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b =>
@@ -847,6 +895,10 @@ namespace Tgstation.Server.Host.Database.Migrations
.WithOne("PermissionSet")
.HasForeignKey("Tgstation.Server.Host.Models.PermissionSet", "UserId")
.OnDelete(DeleteBehavior.Cascade);
b.Navigation("Group");
b.Navigation("User");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b =>
@@ -856,6 +908,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("CompileJobId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("CompileJob");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b =>
@@ -865,6 +919,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Instance");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b =>
@@ -880,6 +936,10 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("TestMergeId")
.OnDelete(DeleteBehavior.ClientNoAction)
.IsRequired();
b.Navigation("RevisionInformation");
b.Navigation("TestMerge");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b =>
@@ -889,6 +949,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Instance");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b =>
@@ -904,6 +966,10 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("MergedBy");
b.Navigation("PrimaryRevisionInformation");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.User", b =>
@@ -915,6 +981,70 @@ namespace Tgstation.Server.Host.Database.Migrations
b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group")
.WithMany("Users")
.HasForeignKey("GroupId");
b.Navigation("CreatedBy");
b.Navigation("Group");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
{
b.Navigation("Channels");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b =>
{
b.Navigation("ChatSettings");
b.Navigation("DreamDaemonSettings");
b.Navigation("DreamMakerSettings");
b.Navigation("InstancePermissionSets");
b.Navigation("Jobs");
b.Navigation("RepositorySettings");
b.Navigation("RevisionInformations");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b =>
{
b.Navigation("InstancePermissionSets");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b =>
{
b.Navigation("ActiveTestMerges");
b.Navigation("CompileJobs");
b.Navigation("PrimaryTestMerge");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b =>
{
b.Navigation("RevisonInformations");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.User", b =>
{
b.Navigation("CreatedUsers");
b.Navigation("OAuthConnections");
b.Navigation("PermissionSet");
b.Navigation("TestMerges");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.UserGroup", b =>
{
b.Navigation("PermissionSet")
.IsRequired();
b.Navigation("Users");
});
#pragma warning restore 612, 618
}
@@ -4,16 +4,18 @@ using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
#nullable disable
namespace Tgstation.Server.Host.Database.Migrations
{
[DbContext(typeof(SqliteDatabaseContext))]
partial class SqliteDatabaseContextModelSnapshot : ModelSnapshot
{
/// <inheritdoc />
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.1.20");
modelBuilder.HasAnnotation("ProductVersion", "6.0.15");
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
{
@@ -27,8 +29,8 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("ConnectionString")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("TEXT");
b.Property<bool?>("Enabled")
.HasColumnType("INTEGER");
@@ -38,8 +40,8 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(100);
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<int>("Provider")
.HasColumnType("INTEGER");
@@ -69,8 +71,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasColumnType("INTEGER");
b.Property<string>("IrcChannel")
.HasColumnType("TEXT")
.HasMaxLength(100);
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<bool?>("IsAdminChannel")
.IsRequired()
@@ -85,8 +87,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasColumnType("INTEGER");
b.Property<string>("Tag")
.HasColumnType("TEXT")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("TEXT");
b.HasKey("Id");
@@ -168,8 +170,8 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("AdditionalParameters")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("TEXT");
b.Property<bool?>("AllowWebClient")
.IsRequired()
@@ -190,6 +192,10 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<long>("InstanceId")
.HasColumnType("INTEGER");
b.Property<bool?>("LogOutput")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<ushort?>("Port")
.IsRequired()
.HasColumnType("INTEGER");
@@ -237,8 +243,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasColumnType("INTEGER");
b.Property<string>("ProjectName")
.HasColumnType("TEXT")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("TEXT");
b.Property<bool?>("RequireDMApiValidation")
.IsRequired()
@@ -275,8 +281,8 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(100);
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<bool?>("Online")
.IsRequired()
@@ -401,8 +407,8 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("ExternalUserId")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(100);
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<int>("Provider")
.HasColumnType("INTEGER");
@@ -491,12 +497,12 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasColumnType("INTEGER");
b.Property<string>("AccessToken")
.HasColumnType("TEXT")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("TEXT");
b.Property<string>("AccessUser")
.HasColumnType("TEXT")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("TEXT");
b.Property<bool?>("AutoUpdatesKeepTestMerges")
.IsRequired()
@@ -508,13 +514,13 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("CommitterEmail")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("TEXT");
b.Property<string>("CommitterName")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("TEXT");
b.Property<bool?>("CreateGitHubDeployments")
.IsRequired()
@@ -576,16 +582,16 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("CommitSha")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(40);
.HasMaxLength(40)
.HasColumnType("TEXT");
b.Property<long>("InstanceId")
.HasColumnType("INTEGER");
b.Property<string>("OriginCommitSha")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(40);
.HasMaxLength(40)
.HasColumnType("TEXT");
b.Property<DateTimeOffset>("Timestamp")
.HasColumnType("TEXT");
@@ -613,8 +619,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasColumnType("TEXT");
b.Property<string>("Comment")
.HasColumnType("TEXT")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("TEXT");
b.Property<DateTimeOffset>("MergedAt")
.HasColumnType("TEXT");
@@ -631,8 +637,8 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("TargetCommitSha")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(40);
.HasMaxLength(40)
.HasColumnType("TEXT");
b.Property<string>("TitleAtMerge")
.IsRequired()
@@ -660,8 +666,8 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("CanonicalName")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(100);
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<DateTimeOffset?>("CreatedAt")
.IsRequired()
@@ -682,15 +688,15 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(100);
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<string>("PasswordHash")
.HasColumnType("TEXT");
b.Property<string>("SystemIdentifier")
.HasColumnType("TEXT")
.HasMaxLength(100);
.HasMaxLength(100)
.HasColumnType("TEXT");
b.HasKey("Id");
@@ -715,8 +721,8 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(100);
.HasMaxLength(100)
.HasColumnType("TEXT");
b.HasKey("Id");
@@ -733,6 +739,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Instance");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b =>
@@ -742,6 +750,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("ChatSettingsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("ChatSettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b =>
@@ -757,6 +767,10 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("RevisionInformationId")
.OnDelete(DeleteBehavior.ClientNoAction)
.IsRequired();
b.Navigation("Job");
b.Navigation("RevisionInformation");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b =>
@@ -766,6 +780,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Instance");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b =>
@@ -775,6 +791,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Instance");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.InstancePermissionSet", b =>
@@ -790,6 +808,10 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("PermissionSetId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Instance");
b.Navigation("PermissionSet");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b =>
@@ -809,6 +831,12 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("StartedById")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("CancelledBy");
b.Navigation("Instance");
b.Navigation("StartedBy");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b =>
@@ -817,6 +845,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.WithMany("OAuthConnections")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
b.Navigation("User");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b =>
@@ -830,6 +860,10 @@ namespace Tgstation.Server.Host.Database.Migrations
.WithOne("PermissionSet")
.HasForeignKey("Tgstation.Server.Host.Models.PermissionSet", "UserId")
.OnDelete(DeleteBehavior.Cascade);
b.Navigation("Group");
b.Navigation("User");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b =>
@@ -839,6 +873,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("CompileJobId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("CompileJob");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b =>
@@ -848,6 +884,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Instance");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b =>
@@ -863,6 +901,10 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("TestMergeId")
.OnDelete(DeleteBehavior.ClientNoAction)
.IsRequired();
b.Navigation("RevisionInformation");
b.Navigation("TestMerge");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b =>
@@ -872,6 +914,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Instance");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b =>
@@ -887,6 +931,10 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("MergedBy");
b.Navigation("PrimaryRevisionInformation");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.User", b =>
@@ -898,6 +946,70 @@ namespace Tgstation.Server.Host.Database.Migrations
b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group")
.WithMany("Users")
.HasForeignKey("GroupId");
b.Navigation("CreatedBy");
b.Navigation("Group");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
{
b.Navigation("Channels");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b =>
{
b.Navigation("ChatSettings");
b.Navigation("DreamDaemonSettings");
b.Navigation("DreamMakerSettings");
b.Navigation("InstancePermissionSets");
b.Navigation("Jobs");
b.Navigation("RepositorySettings");
b.Navigation("RevisionInformations");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b =>
{
b.Navigation("InstancePermissionSets");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b =>
{
b.Navigation("ActiveTestMerges");
b.Navigation("CompileJobs");
b.Navigation("PrimaryTestMerge");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b =>
{
b.Navigation("RevisonInformations");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.User", b =>
{
b.Navigation("CreatedUsers");
b.Navigation("OAuthConnections");
b.Navigation("PermissionSet");
b.Navigation("TestMerges");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.UserGroup", b =>
{
b.Navigation("PermissionSet")
.IsRequired();
b.Navigation("Users");
});
#pragma warning restore 612, 618
}
@@ -5,6 +5,8 @@ using Microsoft.EntityFrameworkCore;
using Pomelo.EntityFrameworkCore.MySql.Infrastructure;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.Extensions;
using Tgstation.Server.Host.Models;
namespace Tgstation.Server.Host.Database
{
@@ -60,5 +62,48 @@ namespace Tgstation.Server.Host.Database
mySqlOptions.UseQuerySplittingBehavior(QuerySplittingBehavior.SingleQuery);
});
}
/// <inheritdoc />
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Added to prevent a column type change after upgrading Pomelo.Mysql
// Related: https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql/issues/1606
modelBuilder
.MapMySqlTextField<ChatBot>(x => x.ConnectionString)
.MapMySqlTextField<ChatChannel>(x => x.Tag)
.MapMySqlTextField<ChatChannel>(x => x.IrcChannel)
.MapMySqlTextField<CompileJob>(x => x.ByondVersion)
.MapMySqlTextField<CompileJob>(x => x.DmeName)
.MapMySqlTextField<CompileJob>(x => x.Output)
.MapMySqlTextField<CompileJob>(x => x.RepositoryOrigin)
.MapMySqlTextField<DreamDaemonSettings>(x => x.AdditionalParameters)
.MapMySqlTextField<DreamMakerSettings>(x => x.ProjectName)
.MapMySqlTextField<Instance>(x => x.Name)
.MapMySqlTextField<Instance>(x => x.Path)
.MapMySqlTextField<Instance>(x => x.SwarmIdentifer)
.MapMySqlTextField<Job>(x => x.Description)
.MapMySqlTextField<Job>(x => x.ExceptionDetails)
.MapMySqlTextField<OAuthConnection>(x => x.ExternalUserId)
.MapMySqlTextField<ReattachInformation>(x => x.AccessIdentifier)
.MapMySqlTextField<RepositorySettings>(x => x.AccessToken)
.MapMySqlTextField<RepositorySettings>(x => x.AccessUser)
.MapMySqlTextField<RepositorySettings>(x => x.CommitterEmail)
.MapMySqlTextField<RepositorySettings>(x => x.CommitterName)
.MapMySqlTextField<RevisionInformation>(x => x.CommitSha)
.MapMySqlTextField<RevisionInformation>(x => x.OriginCommitSha)
.MapMySqlTextField<TestMerge>(x => x.Author)
.MapMySqlTextField<TestMerge>(x => x.BodyAtMerge)
.MapMySqlTextField<TestMerge>(x => x.Comment)
.MapMySqlTextField<TestMerge>(x => x.TargetCommitSha)
.MapMySqlTextField<TestMerge>(x => x.TitleAtMerge)
.MapMySqlTextField<TestMerge>(x => x.Url)
.MapMySqlTextField<User>(x => x.CanonicalName)
.MapMySqlTextField<User>(x => x.Name)
.MapMySqlTextField<User>(x => x.PasswordHash)
.MapMySqlTextField<User>(x => x.SystemIdentifier)
.MapMySqlTextField<UserGroup>(x => x.Name);
}
}
}
@@ -0,0 +1,68 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.EntityFrameworkCore;
using Tgstation.Server.Api.Models;
namespace Tgstation.Server.Host.Extensions
{
/// <summary>
/// Extension methods for the <see cref="ModelBuilder"/> <see langword="class"/>.
/// </summary>
static class ModelBuilderExtensions
{
/// <summary>
/// Set a given <typeparamref name="TEntity"/>'s property's column charset to "utf8mb4". Only for use with the MySQL/MariaDB provider.
/// </summary>
/// <typeparam name="TEntity">The entity.</typeparam>
/// <param name="modelBuilder">The <see cref="ModelBuilder"/>.</param>
/// <param name="expression">The <see cref="Expression"/> accessing the relevant property.</param>
/// <returns><paramref name="modelBuilder"/>.</returns>
public static ModelBuilder MapMySqlTextField<TEntity>(
this ModelBuilder modelBuilder,
Expression<Func<TEntity, string>> expression)
where TEntity : class
{
var property = modelBuilder
.Entity<TEntity>()
.Property(expression);
property
.HasCharSet("utf8mb4");
var propertyInfo = GetPropertyFromExpression(expression);
var stringLengthAttribute = propertyInfo.GetCustomAttribute<StringLengthAttribute>();
if (stringLengthAttribute?.MaximumLength == Limits.MaximumStringLength)
property.HasColumnType("longtext");
return modelBuilder;
}
/// <summary>
/// Get the <see cref="PropertyInfo"/> pointed to by an <paramref name="expression"/>.
/// </summary>
/// <typeparam name="TEntity">The entity.</typeparam>
/// <param name="expression">The <see cref="Expression"/> accessing the relevant property.</param>
/// <returns>The <see cref="PropertyInfo"/> pointed to by <paramref name="expression"/>.</returns>
static PropertyInfo GetPropertyFromExpression<TEntity>(Expression<Func<TEntity, string>> expression)
{
MemberExpression memberExpression;
// this line is necessary, because sometimes the expression comes in as Convert(originalexpression)
if (expression.Body is UnaryExpression unaryExpression)
if (unaryExpression.Operand is MemberExpression unaryAsMember)
memberExpression = unaryAsMember;
else
throw new ArgumentException("Cannot get property from expression!", nameof(expression));
else if (expression.Body is MemberExpression)
memberExpression = (MemberExpression)expression.Body;
else
throw new ArgumentException("Cannot get property from expression!", nameof(expression));
return (PropertyInfo)memberExpression.Member;
}
}
}
@@ -44,8 +44,6 @@ namespace Tgstation.Server.Host.IO
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
static async Task NormalizeAndDelete(DirectoryInfo dir, CancellationToken cancellationToken)
{
var tasks = new List<Task>();
// check if we are a symbolic link
if (!dir.Attributes.HasFlag(FileAttributes.Directory) || dir.Attributes.HasFlag(FileAttributes.ReparsePoint))
{
@@ -53,6 +51,9 @@ namespace Tgstation.Server.Host.IO
return;
}
await Task.Yield();
var tasks = new List<Task>();
foreach (var subDir in dir.EnumerateDirectories())
{
cancellationToken.ThrowIfCancellationRequested();
@@ -102,7 +103,14 @@ namespace Tgstation.Server.Host.IO
src = ResolvePath(src);
dest = ResolvePath(dest);
await Task.WhenAll(CopyDirectoryImpl(src, dest, ignore, postCopyCallback, cancellationToken));
var allTasks = CopyDirectoryImpl(src, dest, ignore, postCopyCallback, cancellationToken);
// Special tactics, increase the size of the ThreadPool until we have a 10-1 file-thread ratio.
var allFileTasks = allTasks.Skip(1);
var unityTask = Task.WhenAll(allFileTasks);
await unityTask.ConfigureAwait(false);
}
/// <inheritdoc />
@@ -115,23 +123,25 @@ namespace Tgstation.Server.Host.IO
throw new ArgumentNullException(nameof(src));
if (dest == null)
throw new ArgumentNullException(nameof(dest));
// 0 size buffers prevents unnecessary buffering, async mode just uses the copy buffers See https://github.com/dotnet/runtime/blob/ad8031c813bae48d529ed6d265a2441c4b41fe7b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Windows.cs#L163-L169
using var srcStream = new FileStream(
ResolvePath(src),
FileMode.Open,
FileAccess.Read,
FileShare.Read | FileShare.Delete,
DefaultBufferSize,
0,
FileOptions.Asynchronous | FileOptions.SequentialScan);
using var destStream = new FileStream(
ResolvePath(dest),
FileMode.Create,
FileAccess.Write,
FileShare.ReadWrite | FileShare.Delete,
DefaultBufferSize,
FileShare.Read | FileShare.Delete,
0,
FileOptions.Asynchronous | FileOptions.SequentialScan);
// value taken from documentation
await srcStream.CopyToAsync(destStream, 81920, cancellationToken);
await srcStream.CopyToAsync(destStream, DefaultBufferSize, cancellationToken);
}
/// <inheritdoc />
@@ -246,16 +256,22 @@ namespace Tgstation.Server.Host.IO
/// <inheritdoc />
public async Task WriteAllBytes(string path, byte[] contents, CancellationToken cancellationToken)
{
using var file = CreateAsyncWriteStream(path);
await file.WriteAsync(contents, cancellationToken);
}
/// <inheritdoc />
public FileStream CreateAsyncWriteStream(string path)
{
path = ResolvePath(path);
using var file = new FileStream(
return new FileStream(
path,
FileMode.Create,
FileAccess.Write,
FileShare.ReadWrite,
FileShare.Read | FileShare.Delete,
DefaultBufferSize,
FileOptions.Asynchronous | FileOptions.SequentialScan);
await file.WriteAsync(contents, cancellationToken);
}
/// <inheritdoc />
@@ -374,7 +390,7 @@ namespace Tgstation.Server.Host.IO
/// <param name="ignore">Files and folders to ignore at the root level.</param>
/// <param name="postCopyCallback">The optional callback called for each source/dest file pair post copy.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="IEnumerable{T}"/> of <see cref="Task"/>s representing the running operation.</returns>
/// <returns>A <see cref="IEnumerable{T}"/> of <see cref="Task"/>s representing the running operations. The first <see cref="Task"/> returned is always the necessary call to <see cref="CreateDirectory(string, CancellationToken)"/>.</returns>
IEnumerable<Task> CopyDirectoryImpl(
string src,
string dest,
@@ -383,43 +399,53 @@ namespace Tgstation.Server.Host.IO
CancellationToken cancellationToken)
{
var dir = new DirectoryInfo(src);
Task subdirCreationTask = null;
foreach (var subDirectory in dir.EnumerateDirectories())
{
if (ignore != null && ignore.Contains(subDirectory.Name))
continue;
var checkingSubdirCreationTask = true;
foreach (var copyTask in CopyDirectoryImpl(subDirectory.FullName, Path.Combine(dest, subDirectory.Name), null, postCopyCallback, cancellationToken))
yield return copyTask;
{
if (subdirCreationTask == null)
{
subdirCreationTask = copyTask;
yield return subdirCreationTask;
}
else if (!checkingSubdirCreationTask)
yield return copyTask;
checkingSubdirCreationTask = false;
}
}
async Task CopyThisDirectory()
foreach (var fileInfo in dir.EnumerateFiles())
{
await CreateDirectory(dest, cancellationToken);
var fileCopyTasks = new List<Task>();
foreach (var fileInfo in dir.EnumerateFiles())
if (subdirCreationTask == null)
{
if (ignore != null && ignore.Contains(fileInfo.Name))
return;
var sourceFile = fileInfo.FullName;
var destFile = Path.Combine(dest, fileInfo.Name);
async Task CopyThisFile()
{
// Grab all tasks before firing
await Task.Yield();
await CopyFile(sourceFile, destFile, cancellationToken);
if (postCopyCallback != null)
await postCopyCallback(sourceFile, destFile);
}
fileCopyTasks.Add(CopyThisFile());
subdirCreationTask = CreateDirectory(dest, cancellationToken);
yield return subdirCreationTask;
}
await Task.WhenAll(fileCopyTasks);
}
if (ignore != null && ignore.Contains(fileInfo.Name))
continue;
yield return CopyThisDirectory();
var sourceFile = fileInfo.FullName;
var destFile = ConcatPath(dest, fileInfo.Name);
async Task CopyThisFile()
{
// Grab all tasks before firing
await subdirCreationTask;
await Task.Yield();
await CopyFile(sourceFile, destFile, cancellationToken);
if (postCopyCallback != null)
await postCopyCallback(sourceFile, destFile);
}
yield return CopyThisFile();
}
}
}
}
@@ -101,6 +101,13 @@ namespace Tgstation.Server.Host.IO
/// <returns>A <see cref="Task{TResult}"/> resulting in the files in <paramref name="path"/>.</returns>
Task<IReadOnlyList<string>> GetFiles(string path, CancellationToken cancellationToken);
/// <summary>
/// Creates a <see cref="FileStream"/> for writing.
/// </summary>
/// <param name="path">The path of the file to write, will be truncated.</param>
/// <returns>The open <see cref="FileStream"/>.</returns>
FileStream CreateAsyncWriteStream(string path);
/// <summary>
/// Writes some <paramref name="contents"/> to a file at <paramref name="path"/> overwriting previous content.
/// </summary>
+2 -4
View File
@@ -112,8 +112,7 @@ namespace Tgstation.Server.Host
{
if (b.FullPath == updatePath && File.Exists(b.FullPath))
{
if (logger != null)
logger.LogInformation("Host watchdog appears to be requesting server termination!");
logger?.LogInformation("Host watchdog appears to be requesting server termination!");
cancellationTokenSource.Cancel();
}
};
@@ -134,8 +133,7 @@ namespace Tgstation.Server.Host
}
catch (OperationCanceledException ex)
{
if (logger != null)
logger.LogDebug(ex, "Server run cancelled!");
logger?.LogDebug(ex, "Server run cancelled!");
}
catch (Exception ex)
{
+1 -15
View File
@@ -7,7 +7,7 @@ namespace Tgstation.Server.Host.System
/// <summary>
/// Abstraction over a <see cref="global::System.Diagnostics.Process"/>.
/// </summary>
interface IProcess : IProcessBase, IDisposable
interface IProcess : IProcessBase, IAsyncDisposable
{
/// <summary>
/// The <see cref="IProcess"/>' ID.
@@ -19,20 +19,6 @@ namespace Tgstation.Server.Host.System
/// </summary>
Task Startup { get; }
/// <summary>
/// Get the stderr output of the <see cref="IProcess"/>.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in the stderr output of the <see cref="IProcess"/>.</returns>
Task<string> GetErrorOutput(CancellationToken cancellationToken);
/// <summary>
/// Get the stdout output of the <see cref="IProcess"/>.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in the stdout output of the <see cref="IProcess"/>.</returns>
Task<string> GetStandardOutput(CancellationToken cancellationToken);
/// <summary>
/// Get the stderr and stdout output of the <see cref="IProcess"/>.
/// </summary>
@@ -1,4 +1,6 @@
namespace Tgstation.Server.Host.System
using System.Threading.Tasks;
namespace Tgstation.Server.Host.System
{
/// <summary>
/// For launching <see cref="IProcess"/>'.
@@ -11,16 +13,16 @@
/// <param name="fileName">The full path to the executable file.</param>
/// <param name="workingDirectory">The working directory for the <see cref="IProcess"/>.</param>
/// <param name="arguments">The arguments for the <see cref="IProcess"/>.</param>
/// <param name="readOutput">If standard output should be read.</param>
/// <param name="readError">If standard error should be read.</param>
/// <param name="noShellExecute">If shell execute should not be used. Must be set if <paramref name="readError"/> or <paramref name="readOutput"/> are set.</param>
/// <returns>A new <see cref="IProcess"/>.</returns>
IProcess LaunchProcess(
/// <param name="fileRedirect">File to write process output and error streams to. Requires <paramref name="readStandardHandles"/> to be <see langword="true"/>.</param>
/// <param name="readStandardHandles">If the process output and error streams should be read.</param>
/// <param name="noShellExecute">If shell execute should not be used. Must be set if <paramref name="readStandardHandles"/> is set.</param>
/// <returns>A <see cref="Task"/> resulting in the new <see cref="IProcess"/>.</returns>
Task<IProcess> LaunchProcess(
string fileName,
string workingDirectory,
string arguments = null,
bool readOutput = false,
bool readError = false,
string fileRedirect = null,
bool readStandardHandles = false,
bool noShellExecute = false);
/// <summary>
@@ -90,13 +90,12 @@ namespace Tgstation.Server.Host.System
string output;
int exitCode;
using (var gcoreProc = lazyLoadedProcessExecutor.Value.LaunchProcess(
await using (var gcoreProc = await lazyLoadedProcessExecutor.Value.LaunchProcess(
GCorePath,
Environment.CurrentDirectory,
$"-o {outputFile} {process.Id}",
true,
true,
true))
readStandardHandles: true,
noShellExecute: true))
{
using (cancellationToken.Register(() => gcoreProc.Terminate()))
exitCode = await gcoreProc.Lifetime;
+23 -51
View File
@@ -1,13 +1,11 @@
using System;
using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Win32.SafeHandles;
using Tgstation.Server.Host.Extensions;
using Tgstation.Server.Host.IO;
namespace Tgstation.Server.Host.System
@@ -39,6 +37,11 @@ namespace Tgstation.Server.Host.System
/// </summary>
readonly global::System.Diagnostics.Process handle;
/// <summary>
/// The <see cref="CancellationTokenSource"/> used to shutdown the <see cref="readTask"/>.
/// </summary>
readonly CancellationTokenSource readerCts;
/// <summary>
/// The <see cref="global::System.Diagnostics.Process.SafeHandle"/>.
/// </summary>
@@ -46,38 +49,26 @@ namespace Tgstation.Server.Host.System
readonly SafeProcessHandle safeHandle;
/// <summary>
/// The <see cref="Task{TResult}"/> resulting in the process' standard output text.
/// The <see cref="Task{TResult}"/> resulting in the process' standard output/error text.
/// </summary>
readonly Task<string> standardOutputTask;
/// <summary>
/// The <see cref="Task{TResult}"/> resulting in the process' standard error text.
/// </summary>
readonly Task<string> standardErrorTask;
/// <summary>
/// The <see cref="Task{TResult}"/> resulting in the process' unified standard output and standard error text.
/// </summary>
readonly StringBuilder combinedStringBuilder;
readonly Task<string> readTask;
/// <summary>
/// Initializes a new instance of the <see cref="Process"/> class.
/// </summary>
/// <param name="processFeatures">The value of <see cref="processFeatures"/>.</param>
/// <param name="handle">The value of <see cref="handle"/>.</param>
/// <param name="readerCts">The value of <see cref="readerCts"/>.</param>
/// <param name="lifetime">The value of <see cref="Lifetime"/>.</param>
/// <param name="standardOutputTask">The value of <see cref="standardOutputTask"/>.</param>
/// <param name="standardErrorTask">The value of <see cref="standardErrorTask"/>.</param>
/// <param name="combinedStringBuilder">The value of <see cref="combinedStringBuilder"/>.</param>
/// <param name="readTask">The value of <see cref="readTask"/>.</param>
/// <param name="logger">The value of <see cref="logger"/>.</param>
/// <param name="preExisting">If <paramref name="handle"/> was NOT just created.</param>
public Process(
IProcessFeatures processFeatures,
global::System.Diagnostics.Process handle,
CancellationTokenSource readerCts,
Task<int> lifetime,
Task<string> standardOutputTask,
Task<string> standardErrorTask,
StringBuilder combinedStringBuilder,
Task<string> readTask,
ILogger<Process> logger,
bool preExisting)
{
@@ -87,11 +78,11 @@ namespace Tgstation.Server.Host.System
safeHandle = handle.SafeHandle;
Id = handle.Id;
this.readerCts = readerCts;
this.processFeatures = processFeatures ?? throw new ArgumentNullException(nameof(processFeatures));
this.standardOutputTask = standardOutputTask;
this.standardErrorTask = standardErrorTask;
this.combinedStringBuilder = combinedStringBuilder;
this.readTask = readTask;
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
@@ -123,42 +114,23 @@ namespace Tgstation.Server.Host.System
}
/// <inheritdoc />
public void Dispose()
public async ValueTask DisposeAsync()
{
readerCts?.Cancel();
readerCts?.Dispose();
if (readTask != null)
await readTask;
safeHandle.Dispose();
handle.Dispose();
}
/// <inheritdoc />
public async Task<string> GetCombinedOutput(CancellationToken cancellationToken)
public Task<string> GetCombinedOutput(CancellationToken cancellationToken)
{
if (combinedStringBuilder == null)
if (readTask == null)
throw new InvalidOperationException("Output/Error stream reading was not enabled!");
await Task.WhenAll(
GetStandardOutput(cancellationToken),
GetErrorOutput(cancellationToken))
;
return combinedStringBuilder.ToString().TrimStart(Environment.NewLine.ToCharArray());
}
/// <inheritdoc />
public async Task<string> GetErrorOutput(CancellationToken cancellationToken)
{
if (standardErrorTask == null)
throw new InvalidOperationException("Error stream reading was not enabled!");
if (!standardErrorTask.IsCompleted)
logger.LogTrace("Waiting for PID {0} to close error stream...", Id);
return await standardErrorTask.WithToken(cancellationToken);
}
/// <inheritdoc />
public async Task<string> GetStandardOutput(CancellationToken cancellationToken)
{
if (standardOutputTask == null)
throw new InvalidOperationException("Output stream reading was not enabled!");
if (!standardOutputTask.IsCompleted)
logger.LogTrace("Waiting for PID {0} to close output stream...", Id);
return await standardOutputTask.WithToken(cancellationToken);
return readTask;
}
/// <inheritdoc />
@@ -1,10 +1,14 @@
using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Tgstation.Server.Host.Extensions;
using Tgstation.Server.Host.IO;
namespace Tgstation.Server.Host.System
{
/// <inheritdoc />
@@ -15,6 +19,11 @@ namespace Tgstation.Server.Host.System
/// </summary>
readonly IProcessFeatures processFeatures;
/// <summary>
/// The <see cref="IIOManager"/> for the <see cref="ProcessExecutor"/>.
/// </summary>
readonly IIOManager ioManager;
/// <summary>
/// The <see cref="ILogger"/> for the <see cref="ProcessExecutor"/>.
/// </summary>
@@ -25,6 +34,150 @@ namespace Tgstation.Server.Host.System
/// </summary>
readonly ILoggerFactory loggerFactory;
/// <summary>
/// Initializes a new instance of the <see cref="ProcessExecutor"/> class.
/// </summary>
/// <param name="processFeatures">The value of <see cref="processFeatures"/>.</param>
/// <param name="ioManager">The value of <see cref="ioManager"/>.</param>
/// <param name="logger">The value of <see cref="logger"/>.</param>
/// <param name="loggerFactory">The value of <see cref="loggerFactory"/>.</param>
public ProcessExecutor(
IProcessFeatures processFeatures,
IIOManager ioManager,
ILogger<ProcessExecutor> logger,
ILoggerFactory loggerFactory)
{
this.processFeatures = processFeatures ?? throw new ArgumentNullException(nameof(processFeatures));
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
}
/// <inheritdoc />
public IProcess GetProcess(int id)
{
logger.LogDebug("Attaching to process {0}...", id);
global::System.Diagnostics.Process handle;
try
{
handle = global::System.Diagnostics.Process.GetProcessById(id);
}
catch (Exception e)
{
logger.LogDebug(e, "Unable to get process {0}!", id);
return null;
}
return CreateFromExistingHandle(handle);
}
/// <inheritdoc />
public IProcess GetCurrentProcess()
{
logger.LogTrace("Getting current process...");
var handle = global::System.Diagnostics.Process.GetCurrentProcess();
return CreateFromExistingHandle(handle);
}
/// <inheritdoc />
public async Task<IProcess> LaunchProcess(
string fileName,
string workingDirectory,
string arguments,
string fileRedirect,
bool readStandardHandles,
bool noShellExecute)
{
if (fileName == null)
throw new ArgumentNullException(nameof(fileName));
if (workingDirectory == null)
throw new ArgumentNullException(nameof(workingDirectory));
if (arguments == null)
throw new ArgumentNullException(nameof(arguments));
if (!noShellExecute && readStandardHandles)
throw new InvalidOperationException("Requesting output/error reading requires noShellExecute to be true!");
logger.LogDebug(
"{0}aunching process in {1}: {2} {3}",
noShellExecute ? "L" : "Shell l",
workingDirectory,
fileName,
arguments);
var handle = new global::System.Diagnostics.Process();
try
{
handle.StartInfo.FileName = fileName;
handle.StartInfo.Arguments = arguments;
handle.StartInfo.WorkingDirectory = workingDirectory;
handle.StartInfo.UseShellExecute = !noShellExecute;
var processStartTcs = new TaskCompletionSource();
var lifetimeTaskTask = AttachExitHandlerBeforeLaunch(handle, processStartTcs.Task);
Task<string> readTask = null;
CancellationTokenSource disposeCts = null;
if (readStandardHandles)
{
handle.StartInfo.RedirectStandardOutput = true;
handle.StartInfo.RedirectStandardError = true;
disposeCts = new CancellationTokenSource();
readTask = ConsumeReaders(handle, processStartTcs.Task, fileRedirect, disposeCts.Token);
}
try
{
handle.Start();
processStartTcs.SetResult();
}
catch (Exception ex)
{
processStartTcs.SetException(ex);
throw;
}
var process = new Process(
processFeatures,
handle,
disposeCts,
await lifetimeTaskTask, // won't block
readTask,
loggerFactory.CreateLogger<Process>(),
false);
return process;
}
catch
{
handle.Dispose();
throw;
}
}
/// <inheritdoc />
public IProcess GetProcessByName(string name)
{
logger.LogTrace("GetProcessByName: {processName}...", name ?? throw new ArgumentNullException(nameof(name)));
var procs = global::System.Diagnostics.Process.GetProcessesByName(name);
global::System.Diagnostics.Process handle = null;
foreach (var proc in procs)
if (handle == null)
handle = proc;
else
{
logger.LogTrace("Disposing extra found PID: {pid}...", proc.Id);
proc.Dispose();
}
if (handle == null)
return null;
return CreateFromExistingHandle(handle);
}
/// <summary>
/// Wrapper for <see cref="AttachExitHandler(global::System.Diagnostics.Process, Func{int})"/> to safely provide the process ID.
/// </summary>
@@ -92,180 +245,81 @@ namespace Tgstation.Server.Host.System
}
/// <summary>
/// Initializes a new instance of the <see cref="ProcessExecutor"/> class.
/// Consume the stdout/stderr streams into a <see cref="Task"/>.
/// </summary>
/// <param name="processFeatures">The value of <see cref="processFeatures"/>.</param>
/// <param name="logger">The value of <see cref="logger"/>.</param>
/// <param name="loggerFactory">The value of <see cref="loggerFactory"/>.</param>
public ProcessExecutor(
IProcessFeatures processFeatures,
ILogger<ProcessExecutor> logger,
ILoggerFactory loggerFactory)
/// <param name="handle">The <see cref="global::System.Diagnostics.Process"/>.</param>
/// <param name="startTask">The <see cref="Task"/> that completes when <paramref name="handle"/> starts.</param>
/// <param name="fileRedirect">The optional path to redirect the streams to.</param>
/// <param name="disposeToken">The <see cref="CancellationToken"/> that triggers when the <see cref="Process"/> is disposed.</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in the program's output/error text if <paramref name="fileRedirect"/> is <see langword="null"/>, <see langword="null"/> otherwise.</returns>
async Task<string> ConsumeReaders(global::System.Diagnostics.Process handle, Task startTask, string fileRedirect, CancellationToken disposeToken)
{
this.processFeatures = processFeatures ?? throw new ArgumentNullException(nameof(processFeatures));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
}
await startTask;
/// <inheritdoc />
public IProcess GetProcess(int id)
{
logger.LogDebug("Attaching to process {0}...", id);
global::System.Diagnostics.Process handle;
try
var pid = handle.Id;
logger.LogTrace("Starting read for PID {pid}...", pid);
var stdOutHandle = handle.StandardOutput;
var stdErrHandle = handle.StandardError;
Task<string> outputReadTask = null, errorReadTask = null;
bool outputOpen = true, errorOpen = true;
async Task<string> GetNextLine()
{
handle = global::System.Diagnostics.Process.GetProcessById(id);
}
catch (Exception e)
{
logger.LogDebug(e, "Unable to get process {0}!", id);
return null;
}
if (outputOpen && outputReadTask == null)
outputReadTask = stdOutHandle.ReadLineAsync();
return CreateFromExistingHandle(handle);
}
if (errorOpen && errorReadTask == null)
errorReadTask = stdErrHandle.ReadLineAsync();
/// <inheritdoc />
public IProcess GetCurrentProcess()
{
logger.LogTrace("Getting current process...");
var handle = global::System.Diagnostics.Process.GetCurrentProcess();
return CreateFromExistingHandle(handle);
}
/// <inheritdoc />
public IProcess LaunchProcess(
string fileName,
string workingDirectory,
string arguments,
bool readOutput,
bool readError,
bool noShellExecute)
{
if (fileName == null)
throw new ArgumentNullException(nameof(fileName));
if (workingDirectory == null)
throw new ArgumentNullException(nameof(workingDirectory));
if (arguments == null)
throw new ArgumentNullException(nameof(arguments));
if (!noShellExecute && (readOutput || readError))
throw new InvalidOperationException("Requesting output/error reading requires noShellExecute to be true!");
logger.LogDebug(
"{0}aunching process in {1}: {2} {3}",
noShellExecute ? "L" : "Shell l",
workingDirectory,
fileName,
arguments);
var handle = new global::System.Diagnostics.Process();
try
{
handle.StartInfo.FileName = fileName;
handle.StartInfo.Arguments = arguments;
handle.StartInfo.WorkingDirectory = workingDirectory;
handle.StartInfo.UseShellExecute = !noShellExecute;
StringBuilder combinedStringBuilder = null;
Task<string> outputTask = null;
Task<string> errorTask = null;
var processStartTcs = new TaskCompletionSource<object>();
if (readOutput || readError)
var completedTask = await Task.WhenAny(outputReadTask ?? errorReadTask, errorReadTask ?? outputReadTask).WithToken(disposeToken);
var line = await completedTask;
if (completedTask == outputReadTask)
{
combinedStringBuilder = new StringBuilder();
async Task<string> ConsumeReader(Func<TextReader> readerFunc, bool isOutputStream)
{
var stringBuilder = new StringBuilder();
string text;
await processStartTcs.Task;
var pid = handle.Id;
var streamType = isOutputStream ? "out" : "err";
logger.LogTrace("Starting std{0} read for PID {1}...", streamType, pid);
var reader = readerFunc();
while ((text = await reader.ReadLineAsync()) != null)
{
combinedStringBuilder.AppendLine();
combinedStringBuilder.Append(text);
stringBuilder.AppendLine();
stringBuilder.Append(text);
}
logger.LogTrace("Finished std{0} read for PID {1}", streamType, pid);
return stringBuilder.ToString();
}
if (readOutput)
{
outputTask = ConsumeReader(() => handle.StandardOutput, true);
handle.StartInfo.RedirectStandardOutput = true;
}
if (readError)
{
errorTask = ConsumeReader(() => handle.StandardError, false);
handle.StartInfo.RedirectStandardError = true;
}
outputReadTask = null;
if (line == null)
outputOpen = false;
}
var lifetimeTaskTask = AttachExitHandlerBeforeLaunch(handle, processStartTcs.Task);
try
{
handle.Start();
processStartTcs.SetResult(null);
}
catch (Exception ex)
{
processStartTcs.SetException(ex);
throw;
}
var process = new Process(
processFeatures,
handle,
lifetimeTaskTask.GetAwaiter().GetResult(), // won't block
outputTask,
errorTask,
combinedStringBuilder,
loggerFactory.CreateLogger<Process>(),
false);
return process;
}
catch
{
handle.Dispose();
throw;
}
}
/// <inheritdoc />
public IProcess GetProcessByName(string name)
{
logger.LogTrace("GetProcessByName: {processName}...", name ?? throw new ArgumentNullException(nameof(name)));
var procs = global::System.Diagnostics.Process.GetProcessesByName(name);
global::System.Diagnostics.Process handle = null;
foreach (var proc in procs)
if (handle == null)
handle = proc;
else
{
logger.LogTrace("Disposing extra found PID: {pid}...", proc.Id);
proc.Dispose();
errorReadTask = null;
if (line == null)
errorOpen = false;
}
if (handle == null)
return null;
if (line == null && (errorOpen || outputOpen))
return await GetNextLine();
return CreateFromExistingHandle(handle);
return line;
}
using var fileStream = fileRedirect != null ? ioManager.CreateAsyncWriteStream(fileRedirect) : null;
using var writer = fileStream != null ? new StreamWriter(fileStream) : null;
string text;
var stringBuilder = fileStream == null ? new StringBuilder() : null;
try
{
while ((text = await GetNextLine()) != null)
{
if (fileStream != null)
{
await writer.WriteLineAsync(text);
await writer.FlushAsync();
}
else
stringBuilder.AppendLine(text);
}
logger.LogTrace("Finished read for PID {pid}", pid);
}
catch (OperationCanceledException ex)
{
logger.LogWarning(ex, "PID {pid} stream reading interrupted!", pid);
if (fileStream != null)
await writer.WriteLineAsync("-- Process detached, log truncated. This is likely due a to TGS restart --");
}
return stringBuilder?.ToString();
}
/// <summary>
@@ -273,7 +327,7 @@ namespace Tgstation.Server.Host.System
/// </summary>
/// <param name="handle">The <see cref="global::System.Diagnostics.Process"/> to create a <see cref="IProcess"/> from.</param>
/// <returns>The <see cref="IProcess"/> based on <paramref name="handle"/>.</returns>
private IProcess CreateFromExistingHandle(global::System.Diagnostics.Process handle)
IProcess CreateFromExistingHandle(global::System.Diagnostics.Process handle)
{
try
{
@@ -281,10 +335,9 @@ namespace Tgstation.Server.Host.System
return new Process(
processFeatures,
handle,
null,
AttachExitHandler(handle, () => pid),
null,
null,
null,
loggerFactory.CreateLogger<Process>(),
true);
}
@@ -59,14 +59,15 @@ namespace Tgstation.Server.Host.System.Tests
new Lazy<IProcessExecutor>(() => processExecutor),
new DefaultIOManager(new AssemblyInformationProvider()),
loggerFactory.CreateLogger<PosixProcessFeatures>()),
Mock.Of<IIOManager>(),
loggerFactory.CreateLogger<ProcessExecutor>(),
loggerFactory);
using var subProc = processExecutor
await using var subProc = await processExecutor
.LaunchProcess(
"dotnet",
pathToSignalTestApp,
$"run -c {CurrentConfig} --no-build",
true,
null,
true,
true);
@@ -230,11 +230,12 @@ namespace Tgstation.Server.Tests.Instance
IProcessExecutor executor = null;
executor = new ProcessExecutor(
RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? (IProcessFeatures)new WindowsProcessFeatures(Mock.Of<ILogger<WindowsProcessFeatures>>())
? new WindowsProcessFeatures(Mock.Of<ILogger<WindowsProcessFeatures>>())
: new PosixProcessFeatures(new Lazy<IProcessExecutor>(() => executor), Mock.Of<IIOManager>(), Mock.Of<ILogger<PosixProcessFeatures>>()),
Mock.Of<IIOManager>(),
Mock.Of<ILogger<ProcessExecutor>>(),
LoggerFactory.Create(x => { }));
using var ourProcessHandler = executor
await using var ourProcessHandler = executor
.GetProcess(ddProc.Id);
// Ensure it's responding to heartbeats
@@ -32,6 +32,7 @@ using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.Database;
using Tgstation.Server.Host.Database.Migrations;
using Tgstation.Server.Host.Extensions;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.Jobs;
using Tgstation.Server.Host.System;
using Tgstation.Server.Tests.Instance;
@@ -627,7 +628,7 @@ namespace Tgstation.Server.Tests
{
try
{
Console.WriteLine($"TEST: CreateAdminClient attempt {I}...");
System.Console.WriteLine($"TEST: CreateAdminClient attempt {I}...");
return await clientFactory.CreateFromLogin(
url,
DefaultCredentials.AdminUserName,
@@ -753,6 +754,7 @@ namespace Tgstation.Server.Tests
TopicRequestTimeout = 1000,
AdditionalParameters = String.Empty,
StartProfiler = false,
LogOutput = true,
},
DreamMakerSettings = new Host.Models.DreamMakerSettings
{
@@ -856,7 +858,7 @@ namespace Tgstation.Server.Tests
}
catch (Exception ex)
{
Console.WriteLine($"[{DateTimeOffset.UtcNow}] TEST ERROR: {ex}");
System.Console.WriteLine($"[{DateTimeOffset.UtcNow}] TEST ERROR: {ex}");
serverCts.Cancel();
throw;
}
@@ -991,12 +993,12 @@ namespace Tgstation.Server.Tests
}
catch (ApiException ex)
{
Console.WriteLine($"[{DateTimeOffset.UtcNow}] TEST ERROR: {ex.ErrorCode}: {ex.Message}\n{ex.AdditionalServerData}");
System.Console.WriteLine($"[{DateTimeOffset.UtcNow}] TEST ERROR: {ex.ErrorCode}: {ex.Message}\n{ex.AdditionalServerData}");
throw;
}
catch (Exception ex)
{
Console.WriteLine($"[{DateTimeOffset.UtcNow}] TEST ERROR: {ex}");
System.Console.WriteLine($"[{DateTimeOffset.UtcNow}] TEST ERROR: {ex}");
throw;
}
finally
@@ -1048,17 +1050,17 @@ namespace Tgstation.Server.Tests
var platformIdentifier = new PlatformIdentifier();
var processExecutor = new ProcessExecutor(
Mock.Of<IProcessFeatures>(),
Mock.Of<IIOManager>(),
Mock.Of<ILogger<ProcessExecutor>>(),
LoggerFactory.Create(x => { }));
using var process = processExecutor.LaunchProcess("test." + platformIdentifier.ScriptFileExtension, ".", String.Empty, true, true, true);
await using var process = await processExecutor.LaunchProcess("test." + platformIdentifier.ScriptFileExtension, ".", String.Empty, null, true, true);
using var cts = new CancellationTokenSource();
cts.CancelAfter(3000);
var exitCode = await process.Lifetime.WithToken(cts.Token);
Assert.AreEqual(0, exitCode);
Assert.AreEqual(String.Empty, (await process.GetErrorOutput(default)).Trim());
Assert.AreEqual("Hello World!", (await process.GetStandardOutput(default)).Trim());
Assert.AreEqual("Hello World!", (await process.GetCombinedOutput(default)).Trim());
}
[TestMethod]