Add support for reading DD output to file

This commit is contained in:
Dominion
2023-03-31 20:38:10 -04:00
parent 177a3ece57
commit d929ed6fec
36 changed files with 5985 additions and 723 deletions
+4 -4
View File
@@ -3,11 +3,11 @@
<!-- 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>
<TgsApiVersion>9.9.0</TgsApiVersion>
<TgsApiLibraryVersion>10.3.0</TgsApiLibraryVersion>
<TgsClientVersion>11.3.0</TgsClientVersion>
<TgsDmapiVersion>6.1.0</TgsDmapiVersion>
<TgsInteropVersion>5.4.0</TgsInteropVersion>
<TgsHostWatchdogVersion>1.2.1</TgsHostWatchdogVersion>
@@ -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)
{
@@ -769,6 +770,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 +780,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 +794,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 +858,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,388 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Tgstation.Server.Host.Database.Migrations
{
/// <summary>
/// Adds the DreamDaemon LogOutput column for MYSQL. Also corrects string column annotations.
/// </summary>
public partial class MYAddDreamDaemonLogOutput : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
if (migrationBuilder == null)
throw new ArgumentNullException(nameof(migrationBuilder));
migrationBuilder.AlterColumn<string>(
name: "Comment",
table: "TestMerges",
type: "varchar(10000)",
maxLength: 10000,
nullable: true,
oldClrType: typeof(string),
oldType: "longtext CHARACTER SET utf8mb4",
oldMaxLength: 10000,
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "CommitterName",
table: "RepositorySettings",
type: "varchar(10000)",
maxLength: 10000,
nullable: false,
oldClrType: typeof(string),
oldType: "longtext CHARACTER SET utf8mb4",
oldMaxLength: 10000)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "CommitterEmail",
table: "RepositorySettings",
type: "varchar(10000)",
maxLength: 10000,
nullable: false,
oldClrType: typeof(string),
oldType: "longtext CHARACTER SET utf8mb4",
oldMaxLength: 10000)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "AccessUser",
table: "RepositorySettings",
type: "varchar(10000)",
maxLength: 10000,
nullable: true,
oldClrType: typeof(string),
oldType: "longtext CHARACTER SET utf8mb4",
oldMaxLength: 10000,
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "AccessToken",
table: "RepositorySettings",
type: "varchar(10000)",
maxLength: 10000,
nullable: true,
oldClrType: typeof(string),
oldType: "longtext CHARACTER SET utf8mb4",
oldMaxLength: 10000,
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "ProjectName",
table: "DreamMakerSettings",
type: "varchar(10000)",
maxLength: 10000,
nullable: true,
oldClrType: typeof(string),
oldType: "longtext CHARACTER SET utf8mb4",
oldMaxLength: 10000,
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "AdditionalParameters",
table: "DreamDaemonSettings",
type: "varchar(10000)",
maxLength: 10000,
nullable: false,
oldClrType: typeof(string),
oldType: "longtext CHARACTER SET utf8mb4",
oldMaxLength: 10000)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AddColumn<bool>(
name: "LogOutput",
table: "DreamDaemonSettings",
type: "tinyint(1)",
nullable: false,
defaultValue: false);
migrationBuilder.AlterColumn<string>(
name: "Tag",
table: "ChatChannels",
type: "varchar(10000)",
maxLength: 10000,
nullable: true,
oldClrType: typeof(string),
oldType: "longtext CHARACTER SET utf8mb4",
oldMaxLength: 10000,
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "ConnectionString",
table: "ChatBots",
type: "varchar(10000)",
maxLength: 10000,
nullable: false,
oldClrType: typeof(string),
oldType: "longtext CHARACTER SET utf8mb4",
oldMaxLength: 10000)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
}
/// <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: "varchar(10000)",
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: "varchar(10000)",
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: "varchar(10000)",
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: "varchar(10000)",
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: "varchar(10000)",
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: "varchar(10000)",
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: "varchar(10000)",
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: "varchar(10000)",
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: "varchar(10000)",
oldMaxLength: 10000)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
}
}
}
@@ -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");
}
}
}
@@ -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,8 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("ConnectionString")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("varchar(10000)");
b.Property<bool?>("Enabled")
.HasColumnType("tinyint(1)");
@@ -39,8 +42,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 +73,8 @@ 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)");
b.Property<bool?>("IsAdminChannel")
.IsRequired()
@@ -86,8 +89,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasColumnType("tinyint(1)");
b.Property<string>("Tag")
.HasColumnType("longtext CHARACTER SET utf8mb4")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("varchar(10000)");
b.HasKey("Id");
@@ -108,7 +111,7 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("ByondVersion")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4");
.HasColumnType("longtext");
b.Property<int?>("DMApiMajorVersion")
.HasColumnType("int");
@@ -125,7 +128,7 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("DmeName")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4");
.HasColumnType("longtext");
b.Property<int?>("GitHubDeploymentId")
.HasColumnType("int");
@@ -141,10 +144,10 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("Output")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4");
.HasColumnType("longtext");
b.Property<string>("RepositoryOrigin")
.HasColumnType("longtext CHARACTER SET utf8mb4");
.HasColumnType("longtext");
b.Property<long>("RevisionInformationId")
.HasColumnType("bigint");
@@ -169,8 +172,8 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("AdditionalParameters")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("varchar(10000)");
b.Property<bool?>("AllowWebClient")
.IsRequired()
@@ -191,6 +194,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 +245,8 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasColumnType("bigint");
b.Property<string>("ProjectName")
.HasColumnType("longtext CHARACTER SET utf8mb4")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("varchar(10000)");
b.Property<bool?>("RequireDMApiValidation")
.IsRequired()
@@ -276,8 +283,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<bool?>("Online")
.IsRequired()
@@ -285,10 +292,10 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("Path")
.IsRequired()
.HasColumnType("varchar(255) CHARACTER SET utf8mb4");
.HasColumnType("varchar(255)");
b.Property<string>("SwarmIdentifer")
.HasColumnType("varchar(255) CHARACTER SET utf8mb4");
.HasColumnType("varchar(255)");
b.HasKey("Id");
@@ -362,13 +369,13 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("Description")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4");
.HasColumnType("longtext");
b.Property<uint?>("ErrorCode")
.HasColumnType("int unsigned");
b.Property<string>("ExceptionDetails")
.HasColumnType("longtext CHARACTER SET utf8mb4");
.HasColumnType("longtext");
b.Property<long>("InstanceId")
.HasColumnType("bigint");
@@ -402,8 +409,8 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("ExternalUserId")
.IsRequired()
.HasColumnType("varchar(100) CHARACTER SET utf8mb4")
.HasMaxLength(100);
.HasMaxLength(100)
.HasColumnType("varchar(100)");
b.Property<int>("Provider")
.HasColumnType("int");
@@ -458,7 +465,7 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("AccessIdentifier")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4");
.HasColumnType("longtext");
b.Property<long>("CompileJobId")
.HasColumnType("bigint");
@@ -492,12 +499,12 @@ namespace Tgstation.Server.Host.Database.Migrations
.HasColumnType("bigint");
b.Property<string>("AccessToken")
.HasColumnType("longtext CHARACTER SET utf8mb4")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("varchar(10000)");
b.Property<string>("AccessUser")
.HasColumnType("longtext CHARACTER SET utf8mb4")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("varchar(10000)");
b.Property<bool?>("AutoUpdatesKeepTestMerges")
.IsRequired()
@@ -509,13 +516,13 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("CommitterEmail")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("varchar(10000)");
b.Property<string>("CommitterName")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("varchar(10000)");
b.Property<bool?>("CreateGitHubDeployments")
.IsRequired()
@@ -577,16 +584,16 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("CommitSha")
.IsRequired()
.HasColumnType("varchar(40) CHARACTER SET utf8mb4")
.HasMaxLength(40);
.HasMaxLength(40)
.HasColumnType("varchar(40)");
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<string>("OriginCommitSha")
.IsRequired()
.HasColumnType("varchar(40) CHARACTER SET utf8mb4")
.HasMaxLength(40);
.HasMaxLength(40)
.HasColumnType("varchar(40)");
b.Property<DateTimeOffset>("Timestamp")
.HasColumnType("datetime(6)");
@@ -607,15 +614,15 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("Author")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4");
.HasColumnType("longtext");
b.Property<string>("BodyAtMerge")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4");
.HasColumnType("longtext");
b.Property<string>("Comment")
.HasColumnType("longtext CHARACTER SET utf8mb4")
.HasMaxLength(10000);
.HasMaxLength(10000)
.HasColumnType("varchar(10000)");
b.Property<DateTimeOffset>("MergedAt")
.HasColumnType("datetime(6)");
@@ -632,16 +639,16 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("TargetCommitSha")
.IsRequired()
.HasColumnType("varchar(40) CHARACTER SET utf8mb4")
.HasMaxLength(40);
.HasMaxLength(40)
.HasColumnType("varchar(40)");
b.Property<string>("TitleAtMerge")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4");
.HasColumnType("longtext");
b.Property<string>("Url")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4");
.HasColumnType("longtext");
b.HasKey("Id");
@@ -661,8 +668,8 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<string>("CanonicalName")
.IsRequired()
.HasColumnType("varchar(100) CHARACTER SET utf8mb4")
.HasMaxLength(100);
.HasMaxLength(100)
.HasColumnType("varchar(100)");
b.Property<DateTimeOffset?>("CreatedAt")
.IsRequired()
@@ -683,15 +690,15 @@ 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<string>("PasswordHash")
.HasColumnType("longtext CHARACTER SET utf8mb4");
.HasColumnType("longtext");
b.Property<string>("SystemIdentifier")
.HasColumnType("varchar(100) CHARACTER SET utf8mb4")
.HasMaxLength(100);
.HasMaxLength(100)
.HasColumnType("varchar(100)");
b.HasKey("Id");
@@ -716,8 +723,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.HasKey("Id");
@@ -734,6 +741,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 +752,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 +769,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 +782,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 +793,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 +810,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 +833,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 +847,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 +862,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 +875,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 +886,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 +903,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 +916,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 +933,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 +948,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
}
@@ -246,16 +246,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,
DefaultBufferSize,
FileOptions.Asynchronous | FileOptions.SequentialScan);
await file.WriteAsync(contents, cancellationToken);
}
/// <inheritdoc />
@@ -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,
@@ -856,7 +857,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 +992,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 +1049,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]