Address all Host stylecop issues asside from complexity/maintainibilty

This commit is contained in:
Jordan Brown
2018-11-28 16:05:27 -05:00
parent d4bbe7533c
commit 46eea5403a
62 changed files with 9058 additions and 376 deletions
File diff suppressed because it is too large Load Diff
@@ -33,7 +33,7 @@ namespace Tgstation.Server.Host.Components.Byond
/// <param name="path">The path to the BYOND installation</param>
/// <param name="version">The <see cref="Version"/> of BYOND being installed</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <returns></returns>
/// <returns>A <see cref="Task"/> representing the running operation</returns>
Task InstallByond(string path, Version version, CancellationToken cancellationToken);
/// <summary>
@@ -26,6 +26,7 @@ namespace Tgstation.Server.Host.Components.Byond
/// </summary>
/// <param name="version">The new <see cref="Version"/></param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <returns>A <see cref="Task"/> representing the running operation</returns>
Task ChangeVersion(Version version, CancellationToken cancellationToken);
/// <summary>
@@ -27,7 +27,7 @@ namespace Tgstation.Server.Host.Components.Chat.Commands
/// <summary>
/// Construct a <see cref="VersionCommand"/>
/// </summary>
/// <param name="application"></param>
/// <param name="application">The value of <see cref="application"/></param>
public VersionCommand(IApplication application)
{
this.application = application ?? throw new ArgumentNullException(nameof(application));
@@ -38,6 +38,15 @@ namespace Tgstation.Server.Host.Components.Chat
bool active;
/// <summary>
/// Construct a <see cref="JsonTrackingContext"/>
/// </summary>
/// <param name="ioManager">The value of <see cref="ioManager"/></param>
/// <param name="customCommandHandler">The value of <see cref="customCommandHandler"/></param>
/// <param name="logger">The value of <see cref="logger"/></param>
/// <param name="onDispose">The value of <see cref="onDispose"/></param>
/// <param name="commandsPath">The value of <see cref="commandsPath"/></param>
/// <param name="channelsPath">The value of <see cref="channelsPath"/></param>
public JsonTrackingContext(IIOManager ioManager, ICustomCommandHandler customCommandHandler, ILogger<JsonTrackingContext> logger, Action onDispose, string commandsPath, string channelsPath)
{
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
@@ -256,6 +256,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
client.WriteLine("CAP REQ :sasl", Priority.Critical); // needs to be put in the buffer before anything else
cancellationToken.ThrowIfCancellationRequested();
}
client.Login(nickname, nickname, 0, nickname);
}
@@ -317,6 +318,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
if (client.GetIrcUser(nickname) == null)
client.RfcNick(nickname);
}
client.Listen();
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
}
@@ -329,6 +331,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
logger.LogWarning("Unable to connect to IRC: {0}", e);
return false;
}
return true;
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
@@ -397,6 +400,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
id = channelIdCounter++;
channelIdMap.Add(id.Value, x.IrcChannel);
}
return new Channel
{
RealId = id.Value,
@@ -105,7 +105,7 @@ namespace Tgstation.Server.Host.Components.Compiler
var deleteJob = ioManager.DeleteDirectory(job.DirectoryName.ToString(), cleanupCts.Token);
Task otherTask;
// lock (this) //already locked below
// lock (this) //already locked below
otherTask = cleanupTask;
await Task.WhenAll(otherTask, deleteJob).ConfigureAwait(false);
}
@@ -16,9 +16,6 @@ namespace Tgstation.Server.Host.Components.Compiler
/// <inheritdoc />
public string SecondaryDirectory => ioManager.ResolvePath(ioManager.ConcatPath(CompileJob.DirectoryName.ToString(), DreamMaker.BDirectoryName));
/// <inheritdoc />
public RevisionInformation RevisionInformation => CompileJob.RevisionInformation;
/// <summary>
/// The <see cref="CompileJob"/> for the <see cref="DmbProvider"/>
/// </summary>
@@ -43,6 +43,5 @@ namespace Tgstation.Server.Host.Components.Compiler
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <returns>A <see cref="Task"/> representing the running operation</returns>
Task CleanUnusedCompileJobs(CompileJob exceptThisOne, CancellationToken cancellationToken);
}
}
@@ -36,6 +36,7 @@ namespace Tgstation.Server.Host.Components.Compiler
/// <inheritdoc />
public void Dispose() { }
/// <inheritdoc />
public void KeepAlive() => throw new NotSupportedException();
}
}
@@ -16,7 +16,7 @@ namespace Tgstation.Server.Host.Components
readonly IConfiguration configuration;
/// <summary>
/// The <see cref="IWatchdog"/> for the
/// The <see cref="IWatchdog"/> for the <see cref="EventConsumer"/>
/// </summary>
IWatchdog watchdog;
@@ -46,8 +46,10 @@ namespace Tgstation.Server.Host.Components
/// <param name="watchdog">The value of <see cref="watchdog"/></param>
public void SetWatchdog(IWatchdog watchdog)
{
#pragma warning disable IDE0016 // Use 'throw' expression
if (watchdog == null)
throw new ArgumentNullException(nameof(watchdog));
#pragma warning restore IDE0016 // Use 'throw' expression
if (this.watchdog != null)
throw new InvalidOperationException("watchdog already set!");
this.watchdog = watchdog;
@@ -9,18 +9,22 @@
/// Parameters: Reference name, commit sha
/// </summary>
RepoResetOrigin = 0,
/// <summary>
/// Parameters: Checkout target
/// </summary>
RepoCheckout = 1,
/// <summary>
/// No parameters
/// </summary>
RepoFetch = 2,
/// <summary>
/// Parameters: Pull request number, pull request sha, merger message
/// </summary>
RepoMergePullRequest = 3,
/// <summary>
/// Parameters: Absolute path to repository root
/// </summary>
@@ -30,26 +34,32 @@
/// Parameters: Version being installed
/// </summary>
ByondInstallStart = 5,
/// <summary>
/// Parameters: Error string
/// </summary>
ByondInstallFail = 6,
/// <summary>
/// Parameters: Old active version, new active version
/// </summary>
ByondActiveVersionChange = 7,
/// <summary>
/// Parameters: Game directory path, origin commit sha
/// </summary>
CompileStart = 8,
/// <summary>
/// No parameters
/// </summary>
CompileCancelled = 9,
/// <summary>
/// Parameters: Game directory path, "1" if compile succeeded and api validation failed, "0" otherwise
/// </summary>
CompileFailure = 10,
/// <summary>
/// Parameters: Game directory path
/// </summary>
@@ -25,7 +25,7 @@ namespace Tgstation.Server.Host.Components
/// The <see cref="IByondManager"/> for the <see cref="IInstance"/>
/// </summary>
IByondManager ByondManager { get; }
/// <summary>
/// The <see cref="IWatchdog"/> for the <see cref="IInstance"/>
/// </summary>
@@ -37,7 +37,7 @@ namespace Tgstation.Server.Host.Components
IChat Chat { get; }
/// <summary>
/// The <see cref="StaticFiles.IConfiguration"/> for the <see cref="IInstance"/>
/// The <see cref="IConfiguration"/> for the <see cref="IInstance"/>
/// </summary>
IConfiguration Configuration { get; }
@@ -27,9 +27,6 @@ namespace Tgstation.Server.Host.Components
/// <inheritdoc />
public IByondManager ByondManager { get; }
/// <inheritdoc />
public IDreamMaker DreamMaker { get; }
/// <inheritdoc />
public IWatchdog Watchdog { get; }
@@ -39,8 +36,15 @@ namespace Tgstation.Server.Host.Components
/// <inheritdoc />
public StaticFiles.IConfiguration Configuration { get; }
/// <inheritdoc />
public ICompileJobConsumer CompileJobConsumer { get; }
/// <summary>
/// The <see cref="IDreamMaker"/> for the <see cref="Instance"/>
/// </summary>
readonly IDreamMaker dreamMaker;
/// <summary>
/// The <see cref="ICompileJobConsumer"/> for the <see cref="Instance"/>
/// </summary>
readonly ICompileJobConsumer compileJobConsumer;
/// <summary>
/// The <see cref="IDatabaseContextFactory"/> for the <see cref="Instance"/>
@@ -81,6 +85,7 @@ namespace Tgstation.Server.Host.Components
/// The auto update <see cref="Task"/>
/// </summary>
Task timerTask;
/// <summary>
/// <see cref="CancellationTokenSource"/> for <see cref="timerTask"/>
/// </summary>
@@ -92,11 +97,11 @@ namespace Tgstation.Server.Host.Components
/// <param name="metadata">The value of <see cref="metadata"/></param>
/// <param name="repositoryManager">The value of <see cref="RepositoryManager"/></param>
/// <param name="byondManager">The value of <see cref="ByondManager"/></param>
/// <param name="dreamMaker">The value of <see cref="DreamMaker"/></param>
/// <param name="dreamMaker">The value of <see cref="dreamMaker"/></param>
/// <param name="watchdog">The value of <see cref="Watchdog"/></param>
/// <param name="chat">The value of <see cref="Chat"/></param>
/// <param name="configuration">The value of <see cref="Configuration"/></param>
/// <param name="compileJobConsumer">The value of <see cref="CompileJobConsumer"/></param>
/// <param name="compileJobConsumer">The value of <see cref="compileJobConsumer"/></param>
/// <param name="databaseContextFactory">The value of <see cref="databaseContextFactory"/></param>
/// <param name="dmbFactory">The value of <see cref="dmbFactory"/></param>
/// <param name="jobManager">The value of <see cref="jobManager"/></param>
@@ -108,11 +113,11 @@ namespace Tgstation.Server.Host.Components
this.metadata = metadata ?? throw new ArgumentNullException(nameof(metadata));
RepositoryManager = repositoryManager ?? throw new ArgumentNullException(nameof(repositoryManager));
ByondManager = byondManager ?? throw new ArgumentNullException(nameof(byondManager));
DreamMaker = dreamMaker ?? throw new ArgumentNullException(nameof(dreamMaker));
this.dreamMaker = dreamMaker ?? throw new ArgumentNullException(nameof(dreamMaker));
Watchdog = watchdog ?? throw new ArgumentNullException(nameof(watchdog));
Chat = chat ?? throw new ArgumentNullException(nameof(chat));
Configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
CompileJobConsumer = compileJobConsumer ?? throw new ArgumentNullException(nameof(compileJobConsumer));
this.compileJobConsumer = compileJobConsumer ?? throw new ArgumentNullException(nameof(compileJobConsumer));
this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory));
this.dmbFactory = dmbFactory ?? throw new ArgumentNullException(nameof(dmbFactory));
this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
@@ -125,7 +130,7 @@ namespace Tgstation.Server.Host.Components
public void Dispose()
{
timerCts?.Dispose();
CompileJobConsumer.Dispose();
compileJobConsumer.Dispose();
Configuration.Dispose();
Chat.Dispose();
Watchdog.Dispose();
@@ -135,9 +140,10 @@ namespace Tgstation.Server.Host.Components
/// <inheritdoc />
public async Task CompileProcess(Job job, IDatabaseContext databaseContext, Action<int> progressReporter, CancellationToken cancellationToken)
{
//DO NOT FOLLOW THE SUGGESTION FOR A THROW EXPRESSION HERE
#pragma warning disable IDE0016 // Use 'throw' expression
if (job == null)
throw new ArgumentNullException(nameof(job));
#pragma warning restore IDE0016 // Use 'throw' expression
if (databaseContext == null)
throw new ArgumentNullException(nameof(databaseContext));
if (progressReporter == null)
@@ -151,8 +157,6 @@ namespace Tgstation.Server.Host.Components
var compileJobsTask = databaseContext.CompileJobs
.Where(x => x.Job.Instance.Id == metadata.Id)
.OrderByDescending(x => x.Job.StoppedAt)
//TODO: Replace with this select when the issues linked in https://github.com/tgstation/tgstation-server/issues/737 are fixed
//.Select(x => x.Job.StoppedAt.Value - x.Job.StartedAt.Value)
.Select(x => new Job
{
StoppedAt = x.Job.StoppedAt,
@@ -206,7 +210,7 @@ namespace Tgstation.Server.Host.Components
logger.LogWarning(Repository.Repository.OriginTrackingErrorTemplate, repoSha);
databaseContext.Instances.Attach(revInfo.Instance);
}
TimeSpan? averageSpan = null;
var previousCompileJobs = await compileJobsTask.ConfigureAwait(false);
if(previousCompileJobs.Count != 0)
@@ -217,14 +221,14 @@ namespace Tgstation.Server.Host.Components
averageSpan = totalSpan / previousCompileJobs.Count;
}
compileJob = await DreamMaker.Compile(revInfo, dreamMakerSettings, ddSettings.StartupTimeout.Value, repo, progressReporter, averageSpan, cancellationToken).ConfigureAwait(false);
compileJob = await dreamMaker.Compile(revInfo, dreamMakerSettings, ddSettings.StartupTimeout.Value, repo, progressReporter, averageSpan, cancellationToken).ConfigureAwait(false);
}
compileJob.Job = job;
databaseContext.CompileJobs.Add(compileJob); //will be saved by job context
job.PostComplete = ct => CompileJobConsumer.LoadCompileJob(compileJob, ct);
databaseContext.CompileJobs.Add(compileJob); // will be saved by job context
job.PostComplete = ct => compileJobConsumer.LoadCompileJob(compileJob, ct);
if (repositorySettingsTask != null)
{
@@ -234,9 +238,9 @@ namespace Tgstation.Server.Host.Components
if (repositorySettings.AccessToken != null)
{
//potential for commenting on a test merge change
// potential for commenting on a test merge change
var outgoingCompileJob = LatestCompileJob();
if(outgoingCompileJob != null && outgoingCompileJob.RevisionInformation.CommitSha != compileJob.RevisionInformation.CommitSha)
{
var gitHubClient = gitHubClientFactory.CreateClient(repositorySettings.AccessToken);
@@ -263,10 +267,9 @@ namespace Tgstation.Server.Host.Components
updated ? "Updated" : "Deployed",
metadata.Name,
compileJob.RevisionInformation.OriginCommitSha,
compileJob.RevisionInformation.CommitSha
);
compileJob.RevisionInformation.CommitSha);
//added prs
// added prs
foreach (var I in compileJob
.RevisionInformation
.ActiveTestMerges
@@ -277,7 +280,7 @@ namespace Tgstation.Server.Host.Components
.Any(y => y.TestMerge.Number == x.Number)))
tasks.Add(CommentOnPR(I.Number.Value, FormatTestMerge(I, false)));
//removed prs
// removed prs
foreach (var I in outgoingCompileJob
.RevisionInformation
.ActiveTestMerges
@@ -288,7 +291,7 @@ namespace Tgstation.Server.Host.Components
.Any(y => y.TestMerge.Number == x.Number)))
tasks.Add(CommentOnPR(I.Number.Value, "#### Test Merge Removed"));
//updated prs
// updated prs
foreach(var I in compileJob
.RevisionInformation
.ActiveTestMerges
@@ -341,11 +344,10 @@ namespace Tgstation.Server.Host.Components
{
var repositorySettingsTask = databaseContext.RepositorySettings.Where(x => x.InstanceId == metadata.Id).FirstAsync(jobCancellationToken);
//assume 5 steps with synchronize
// assume 5 steps with synchronize
const int ProgressSections = 7;
const int ProgressStep = 100 / ProgressSections;
const int NumSteps = 3;
var doneSteps = 0;
@@ -353,8 +355,8 @@ namespace Tgstation.Server.Host.Components
{
var tmpDoneSteps = doneSteps;
++doneSteps;
return progress => progressReporter((progress + 100 * tmpDoneSteps) / NumSteps);
};
return progress => progressReporter((progress + (100 * tmpDoneSteps)) / NumSteps);
}
using (var repo = await RepositoryManager.LoadRepository(jobCancellationToken).ConfigureAwait(false))
{
@@ -374,7 +376,7 @@ namespace Tgstation.Server.Host.Components
var repositorySettings = await repositorySettingsTask.ConfigureAwait(false);
//the main point of auto update is to pull the remote
// the main point of auto update is to pull the remote
await repo.FetchOrigin(repositorySettings.AccessUser, repositorySettings.AccessToken, NextProgressReporter(), jobCancellationToken).ConfigureAwait(false);
RevisionInformation currentRevInfo = null;
@@ -415,7 +417,7 @@ namespace Tgstation.Server.Host.Components
hasDbChanges = true;
}
//take appropriate auto update actions
// take appropriate auto update actions
bool shouldSyncTracked;
if (repositorySettings.AutoUpdatesKeepTestMerges.Value)
{
@@ -459,7 +461,7 @@ namespace Tgstation.Server.Host.Components
shouldSyncTracked = true;
}
//synch if necessary
// synch if necessary
if (repositorySettings.AutoUpdatesSynchronize.Value && startSha != repo.Head)
{
var pushedOrigin = await repo.Sychronize(repositorySettings.AccessUser, repositorySettings.AccessToken, repositorySettings.CommitterName, repositorySettings.CommitterEmail, NextProgressReporter(), shouldSyncTracked, jobCancellationToken).ConfigureAwait(false);
@@ -498,7 +500,7 @@ namespace Tgstation.Server.Host.Components
continue;
}
//finally set up the job
// finally set up the job
var compileProcessJob = new Job
{
StartedBy = user,
@@ -527,9 +529,10 @@ namespace Tgstation.Server.Host.Components
{
break;
}
logger.LogTrace("Leaving auto update loop...");
}
/// <inheritdoc />
public void Rename(string newName)
{
@@ -541,9 +544,9 @@ namespace Tgstation.Server.Host.Components
/// <inheritdoc />
public async Task StartAsync(CancellationToken cancellationToken)
{
await Task.WhenAll(SetAutoUpdateInterval(metadata.AutoUpdateInterval.Value), Configuration.StartAsync(cancellationToken), ByondManager.StartAsync(cancellationToken), Chat.StartAsync(cancellationToken), CompileJobConsumer.StartAsync(cancellationToken)).ConfigureAwait(false);
await Task.WhenAll(SetAutoUpdateInterval(metadata.AutoUpdateInterval.Value), Configuration.StartAsync(cancellationToken), ByondManager.StartAsync(cancellationToken), Chat.StartAsync(cancellationToken), compileJobConsumer.StartAsync(cancellationToken)).ConfigureAwait(false);
//dependent on so many things, its just safer this way
// dependent on so many things, its just safer this way
await Watchdog.StartAsync(cancellationToken).ConfigureAwait(false);
CompileJob latestCompileJob = null;
@@ -555,7 +558,7 @@ namespace Tgstation.Server.Host.Components
}
/// <inheritdoc />
public Task StopAsync(CancellationToken cancellationToken) => Task.WhenAll(SetAutoUpdateInterval(0), Configuration.StopAsync(cancellationToken), ByondManager.StopAsync(cancellationToken), Watchdog.StopAsync(cancellationToken), Chat.StopAsync(cancellationToken), CompileJobConsumer.StopAsync(cancellationToken));
public Task StopAsync(CancellationToken cancellationToken) => Task.WhenAll(SetAutoUpdateInterval(0), Configuration.StopAsync(cancellationToken), ByondManager.StopAsync(cancellationToken), Watchdog.StopAsync(cancellationToken), Chat.StopAsync(cancellationToken), compileJobConsumer.StopAsync(cancellationToken));
/// <inheritdoc />
public async Task SetAutoUpdateInterval(uint newInterval)
@@ -571,12 +574,13 @@ namespace Tgstation.Server.Host.Components
else
toWait = Task.CompletedTask;
}
await toWait.ConfigureAwait(false);
if (newInterval == 0)
return;
lock (this)
{
//race condition, just quit
// race condition, just quit
if (timerTask != null)
return;
timerCts?.Dispose();
@@ -154,10 +154,10 @@ namespace Tgstation.Server.Host.Components
/// <inheritdoc />
public IInstance CreateInstance(Models.Instance metadata)
{
//Create the ioManager for the instance
// Create the ioManager for the instance
var instanceIoManager = new ResolvingIOManager(ioManager, metadata.Path);
//various other ioManagers
// various other ioManagers
var repoIoManager = new ResolvingIOManager(instanceIoManager, "Repository");
var byondIOManager = new ResolvingIOManager(instanceIoManager, "Byond");
var gameIoManager = new ResolvingIOManager(instanceIoManager, "Game");
@@ -94,6 +94,7 @@ namespace Tgstation.Server.Host.Components
return;
disposed = true;
}
foreach (var I in instances)
I.Value.Dispose();
}
@@ -146,9 +147,10 @@ namespace Tgstation.Server.Host.Components
throw new InvalidOperationException("Instance not online!");
instances.Remove(metadata.Id);
}
try
{
//we are the one responsible for cancelling his jobs
// we are the one responsible for cancelling his jobs
var tasks = new List<Task>();
await databaseContextFactory.UseContext(async db =>
{
@@ -194,6 +196,7 @@ namespace Tgstation.Server.Host.Components
instance.Dispose();
throw;
}
await instance.StartAsync(cancellationToken).ConfigureAwait(false);
}
@@ -236,7 +239,7 @@ namespace Tgstation.Server.Host.Components
await Task.WhenAll(instances.Select(x => x.Value.StopAsync(cancellationToken))).ConfigureAwait(false);
await instanceFactory.StopAsync(cancellationToken).ConfigureAwait(false);
//downgrade the db if necessary
// downgrade the db if necessary
if (downgradeVersion != null)
await databaseContextFactory.UseContext(db => db.SchemaDowngradeForServerVersion(downgradeVersion, cancellationToken)).ConfigureAwait(false);
}
@@ -2,10 +2,24 @@
namespace Tgstation.Server.Host.Components.Interop
{
/// <summary>
/// Represents a chat command to be handled by DD
/// </summary>
sealed class ChatCommand
{
/// <summary>
/// The command name
/// </summary>
public string Command { get; set; }
/// <summary>
/// The command params
/// </summary>
public string Params { get; set; }
/// <summary>
/// The <see cref="Chat.User"/> that sent the command
/// </summary>
public User User { get; set; }
}
}
@@ -95,7 +95,7 @@ namespace Tgstation.Server.Host.Components.Interop
/// </summary>
/// <param name="sender">The sender of the event</param>
/// <param name="e">The <see cref="FileSystemEventArgs"/></param>
async void HandleWrite(object sender, FileSystemEventArgs e) //this is what async void was made for
async void HandleWrite(object sender, FileSystemEventArgs e) // this is what async void was made for
{
try
{
@@ -115,7 +115,7 @@ namespace Tgstation.Server.Host.Components.Interop
}
catch (JsonException ex)
{
//file not fully written yet
// file not fully written yet
logger.LogDebug("Suppressing json convert exception for command file write: {0}", ex);
return;
}
@@ -1,31 +1,93 @@
namespace Tgstation.Server.Host.Components.Interop
{
/// <summary>
/// Constants used for communication with the DMAPI
/// </summary>
static class Constants
{
//interop values, match them up with the appropriate api.dm
//api version 4.0.0.0
/// <summary>
/// Identifies a TGS execution. The server version
/// </summary>
public const string DMParamHostVersion = "server_service_version";
/// <summary>
/// Path to the <see cref="JsonFile"/>
/// </summary>
public const string DMParamInfoJson = "tgs_json";
/// <summary>
/// The <see cref="JsonFile.AccessIdentifier"/>
/// </summary>
public const string DMInteropAccessIdentifier = "tgs_tok";
/// <summary>
/// Generic OK response
/// </summary>
public const string DMResponseSuccess = "tgs_succ";
/// <summary>
/// Change port
/// </summary>
public const string DMTopicChangePort = "tgs_port";
/// <summary>
/// Change reboot mode
/// </summary>
public const string DMTopicChangeReboot = "tgs_rmode";
/// <summary>
/// Chat command
/// </summary>
public const string DMTopicChatCommand = "tgs_chat_comm";
/// <summary>
/// Notify of an <see cref="EventType"/>
/// </summary>
public const string DMTopicEvent = "tgs_event";
/// <summary>
/// Response to an interop export from DM
/// </summary>
public const string DMTopicInteropResponse = "tgs_interop";
/// <summary>
/// Set port command
/// </summary>
public const string DMCommandNewPort = "tgs_new_port";
/// <summary>
/// API validation command
/// </summary>
public const string DMCommandApiValidate = "tgs_validate";
/// <summary>
/// Server primed command
/// </summary>
public const string DMCommandServerPrimed = "tgs_prime";
/// <summary>
/// World reboot command
/// </summary>
public const string DMCommandWorldReboot = "tgs_reboot";
/// <summary>
/// Terminate process command
/// </summary>
public const string DMCommandEndProcess = "tgs_kill";
/// <summary>
/// Chat send command
/// </summary>
public const string DMCommandChat = "tgs_chat_send";
/// <summary>
/// Topic command parameter
/// </summary>
public const string DMParameterCommand = "tgs_com";
/// <summary>
/// Command data
/// </summary>
public const string DMParameterData = "tgs_data";
}
}
@@ -70,8 +70,8 @@ namespace Tgstation.Server.Host.Components.Repository
/// <param name="committerEmail">The e-mail of the merge committer</param>
/// <param name="username">The username to fetch from the origin repository</param>
/// <param name="password">The password to fetch from the origin repository</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <param name="progressReporter"><see cref="Action{T1}"/> to report 0-100 <see cref="int"/> progress of the operation</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in a <see cref="Nullable{T}"/> <see cref="bool"/> representing the merge result that is <see langword="true"/> after a fast forward or up to date, <see langword="false"/> on a non-fast-forward, <see langword="null"/> on a conflict</returns>
Task<bool?> AddTestMerge(TestMergeParameters testMergeParameters, string committerName, string committerEmail, string username, string password, Action<int> progressReporter, CancellationToken cancellationToken);
@@ -119,8 +119,8 @@ namespace Tgstation.Server.Host.Components.Repository
/// <param name="password">The password to fetch from the origin repository</param>
/// <param name="committerName">The name of the potential committer</param>
/// <param name="committerEmail">The e-mail of the potential committer</param>
/// <param name="synchronizeTrackedBranch">If the synchronizations should be made to the tracked reference as opposed to a temporary branch</param>
/// <param name="progressReporter"><see cref="Action{T1}"/> to report 0-100 <see cref="int"/> progress of the operation</param>
/// <param name="synchronizeTrackedBranch">If the synchronizations should be made to the tracked reference as opposed to a temporary branch</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in <see langword="true"/> if commits were pushed to the tracked origin reference, <see langword="false"/> otherwise</returns>
Task<bool> Sychronize(string username, string password, string committerName, string committerEmail, Action<int> progressReporter, bool synchronizeTrackedBranch, CancellationToken cancellationToken);
@@ -84,27 +84,12 @@ namespace Tgstation.Server.Host.Components.Repository
/// </summary>
readonly Action onDispose;
void GetRepositoryOwnerName(string remote, out string owner, out string name)
{
//Assume standard gh format: [(git)|(https)]://github.com/owner/repo(.git)[0-1]
//Yes use .git twice in case it was weird
var toRemove = new string[] { ".git", "/", ".git" };
foreach (string item in toRemove)
if (remote.EndsWith(item, StringComparison.OrdinalIgnoreCase))
remote = remote.Substring(0, remote.LastIndexOf(item, StringComparison.OrdinalIgnoreCase));
var splits = remote.Split('/');
name = splits[splits.Length - 1];
owner = splits[splits.Length - 2].Split('.')[0];
logger.LogTrace("GetRepositoryOwnerName({0}) => {1} / {2}", remote, owner, name);
}
/// <summary>
/// Converts a given <paramref name="progressReporter"/> to a <see cref="LibGit2Sharp.Handlers.CheckoutProgressHandler"/>
/// </summary>
/// <param name="progressReporter"><see cref="Action{T1}"/> to report 0-100 <see cref="int"/> progress of the operation</param>
/// <returns>A <see cref="LibGit2Sharp.Handlers.CheckoutProgressHandler"/> based on <paramref name="progressReporter"/></returns>
static CheckoutProgressHandler CheckoutProgressHandler(Action<int> progressReporter) => (a, completedSteps, totalSteps) => progressReporter((int)((((float)completedSteps) / totalSteps) * 100));
static CheckoutProgressHandler CheckoutProgressHandler(Action<int> progressReporter) => (a, completedSteps, totalSteps) => progressReporter((int)(((float)completedSteps) / totalSteps * 100));
/// <summary>
/// Construct a <see cref="Repository"/>
@@ -140,6 +125,21 @@ namespace Tgstation.Server.Host.Components.Repository
onDispose.Invoke();
}
void GetRepositoryOwnerName(string remote, out string owner, out string name)
{
// Assume standard gh format: [(git)|(https)]://github.com/owner/repo(.git)[0-1]
// Yes use .git twice in case it was weird
var toRemove = new string[] { ".git", "/", ".git" };
foreach (string item in toRemove)
if (remote.EndsWith(item, StringComparison.OrdinalIgnoreCase))
remote = remote.Substring(0, remote.LastIndexOf(item, StringComparison.OrdinalIgnoreCase));
var splits = remote.Split('/');
name = splits[splits.Length - 1];
owner = splits[splits.Length - 2].Split('.')[0];
logger.LogTrace("GetRepositoryOwnerName({0}) => {1} / {2}", remote, owner, name);
}
/// <summary>
/// Generate a standard set of <see cref="PushOptions"/>
/// </summary>
@@ -264,7 +264,7 @@ namespace Tgstation.Server.Host.Components.Repository
FailOnConflict = true,
FastForwardStrategy = FastForwardStrategy.NoFastForward,
SkipReuc = true,
OnCheckoutProgress = (a, completedSteps, totalSteps) => progressReporter(50 + ((int)((((float)completedSteps) / totalSteps) * 50)))
OnCheckoutProgress = (a, completedSteps, totalSteps) => progressReporter(50 + ((int)(((float)completedSteps) / totalSteps * 50)))
});
}
finally
@@ -373,7 +373,7 @@ namespace Tgstation.Server.Host.Components.Repository
try
{
var forcePushString = String.Format(CultureInfo.InvariantCulture, "+{0}:{0}", branch.CanonicalName);
repository.Network.Push(remote,forcePushString, GeneratePushOptions(progress => progressReporter((int)(0.9f * progress)), username, password, cancellationToken));
repository.Network.Push(remote, forcePushString, GeneratePushOptions(progress => progressReporter((int)(0.9f * progress)), username, password, cancellationToken));
var removalString = String.Format(CultureInfo.InvariantCulture, ":{0}", branch.CanonicalName);
repository.Network.Push(remote, removalString, GeneratePushOptions(progress => progressReporter(90 + (int)(0.1f * progress)), username, password, cancellationToken));
}
@@ -510,6 +510,7 @@ namespace Tgstation.Server.Host.Components.Repository
logger.LogTrace("Not synchronizing due to lack of credentials!");
return false;
}
logger.LogTrace("Begin Synchronize...");
if (username == null)
@@ -596,16 +597,18 @@ namespace Tgstation.Server.Host.Components.Repository
/// <inheritdoc />
public Task<bool> IsSha(string committish, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
{
//check if it's a tag
// check if it's a tag
var gitObject = repository.Lookup(committish, ObjectType.Tag);
if (gitObject != null)
return false;
cancellationToken.ThrowIfCancellationRequested();
//check if it's a branch
// check if it's a branch
if (repository.Branches[committish] != null)
return false;
cancellationToken.ThrowIfCancellationRequested();
//err on the side of references, if we can't look it up, assume its a reference
// err on the side of references, if we can't look it up, assume its a reference
if (repository.Lookup<Commit>(committish) != null)
return true;
return false;
@@ -95,6 +95,7 @@ namespace Tgstation.Server.Host.Components.Repository
throw new InvalidOperationException("The repository is already being cloned!");
CloneInProgress = true;
}
try
{
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
@@ -139,6 +140,7 @@ namespace Tgstation.Server.Host.Components.Repository
{
logger.LogDebug("Error deleting partially cloned repository! Exception: {0}", e);
}
throw;
}
else
@@ -147,12 +149,14 @@ namespace Tgstation.Server.Host.Components.Repository
return null;
}
}
logger.LogInformation("Clone complete!");
}
finally
{
CloneInProgress = false;
}
return await LoadRepository(cancellationToken).ConfigureAwait(false);
}
@@ -188,6 +192,7 @@ namespace Tgstation.Server.Host.Components.Repository
semaphore.Release();
return null;
}
return new Repository(repo, ioManager, eventConsumer, credentialsProvider, repositoryLogger, () =>
{
logger.LogTrace("Releasing semaphore due to Repository disposal...");
@@ -4,7 +4,6 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
@@ -131,12 +130,11 @@ namespace Tgstation.Server.Host.Components.StaticFiles
/// <inheritdoc />
public async Task<ServerSideModifications> CopyDMFilesTo(string dmeFile, string destination, CancellationToken cancellationToken)
{
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
{
await EnsureDirectories(cancellationToken).ConfigureAwait(false);
//just assume no other fs race conditions here
// just assume no other fs race conditions here
var dmeExistsTask = ioManager.FileExists(ioManager.ConcatPath(CodeModificationsSubdirectory, dmeFile), cancellationToken);
var headFileExistsTask = ioManager.FileExists(ioManager.ConcatPath(CodeModificationsSubdirectory, CodeModificationsHeadFile), cancellationToken);
var tailFileExistsTask = ioManager.FileExists(ioManager.ConcatPath(CodeModificationsSubdirectory, CodeModificationsTailFile), cancellationToken);
@@ -154,7 +152,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
return null;
string IncludeLine(string filePath) => String.Format(CultureInfo.InvariantCulture, "#include \"{0}\"", filePath);
return new ServerSideModifications(headFileExistsTask.Result ? IncludeLine(CodeModificationsHeadFile) : null, tailFileExistsTask.Result ? IncludeLine(CodeModificationsTailFile) : null, false);
}
}
@@ -168,7 +166,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
configurationRelativePath = '.' + configurationRelativePath;
var resolved = ioManager.ResolvePath(configurationRelativePath);
var local = !nullOrEmptyCheck ? ioManager.ResolvePath(".") : null;
if (!nullOrEmptyCheck && resolved.Length < local.Length) //.. fuccbois
if (!nullOrEmptyCheck && resolved.Length < local.Length) // .. fuccbois
throw new InvalidOperationException("Attempted to access file outside of configuration manager!");
return resolved;
}
@@ -201,6 +199,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
result = null;
return;
}
enumerator = synchronousIOManager.GetFiles(path, cancellationToken);
result.AddRange(enumerator.Select(x => new ConfigurationFile
{
@@ -236,7 +235,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
#pragma warning disable CA5350 // Do not use insecure cryptographic algorithm SHA1.
using (var sha1 = new SHA1Managed())
#pragma warning restore CA5350 // Do not use insecure cryptographic algorithm SHA1.
sha1String = String.Join("", sha1.ComputeHash(content).Select(b => b.ToString("x2", CultureInfo.InvariantCulture)));
sha1String = String.Join(String.Empty, sha1.ComputeHash(content).Select(b => b.ToString("x2", CultureInfo.InvariantCulture)));
result = new ConfigurationFile
{
Content = content,
@@ -248,7 +247,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
}
catch (UnauthorizedAccessException)
{
//this happens on windows, dunno about linux
// this happens on windows, dunno about linux
bool isDirectory;
try
{
@@ -289,7 +288,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
var results = new List<string> { StaticIgnoreFile };
//we don't want to lose trailing whitespace on linux
// we don't want to lose trailing whitespace on linux
using (var reader = new StringReader(ignoreFileText))
{
cancellationToken.ThrowIfCancellationRequested();
@@ -299,7 +298,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
}
return results;
};
}
IReadOnlyList<string> ignoreFiles;
@@ -316,9 +315,9 @@ namespace Tgstation.Server.Host.Components.StaticFiles
{
var fileName = ioManager.GetFileName(x);
// need to normalize
bool ignored;
if (platformIdentifier.IsWindows)
//need to normalize
ignored = ignoreFiles.Any(y => fileName.ToUpperInvariant() == y.ToUpperInvariant());
else
ignored = ignoreFiles.Any(y => fileName == y);
@@ -367,7 +366,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
if (!success)
return;
if (data != null)
postWriteHandler.HandleWrite(path);
postWriteHandler.HandleWrite(path);
result = new ConfigurationFile
{
Content = data,
@@ -379,7 +378,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
}
catch (UnauthorizedAccessException)
{
//this happens on windows, dunno about linux
// this happens on windows, dunno about linux
bool isDirectory;
try
{
@@ -442,7 +441,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
if (!EventTypeScriptFileNameMap.TryGetValue(eventType, out var scriptName))
return true;
//always execute in serial
// always execute in serial
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
{
var files = await ioManager.GetFilesWithExtension(EventScriptsSubdirectory, platformIdentifier.ScriptFileExtension, cancellationToken).ConfigureAwait(false);
@@ -458,6 +457,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
return false;
}
}
return true;
}
@@ -466,7 +466,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
{
await EnsureDirectories(cancellationToken).ConfigureAwait(false);
var path = ValidateConfigRelativePath(configurationRelativePath);
var result = false;
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
{
@@ -477,6 +477,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
else
CheckDeleteImpl();
}
return result;
}
}
@@ -9,22 +9,27 @@
/// The DMAPI never contacted the server for validation
/// </summary>
NeverValidated,
/// <summary>
/// The server was contacted for validation but it was never requested
/// </summary>
UnaskedValidationRequest,
/// <summary>
/// The validation request was malformed
/// </summary>
BadValidationRequest,
/// <summary>
/// Valid API. The game must be run with a minimum security level of <see cref="Api.Models.DreamDaemonSecurity.Safe"/>
/// </summary>
RequiresSafe,
/// <summary>
/// Valid API. The game must be run with a security level of <see cref="Api.Models.DreamDaemonSecurity.Trusted"/>
/// </summary>
RequiresTrusted,
/// <summary>
/// Valid API. The game must be run with a minimum security level of <see cref="Api.Models.DreamDaemonSecurity.Ultrasafe"/>
/// </summary>
@@ -72,6 +72,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
return;
disposed = true;
}
Dmb.Dispose();
}
@@ -9,18 +9,22 @@
/// The monitor should continue as normal
/// </summary>
Continue,
/// <summary>
/// Skips the next call to HandleMonitorWakeup action
/// Skips the next call to HandleMonitorWakeup action
/// </summary>
Skip,
/// <summary>
/// The monitor should kill and restart both servers
/// </summary>
Restart,
/// <summary>
/// The monitor should stop checking actions for this iteration and continue its loop
/// </summary>
Break,
/// <summary>
/// The monitor should exit. Does not kill servers
/// </summary>
@@ -9,26 +9,32 @@
/// The active server crashed or exited
/// </summary>
ActiveServerCrashed,
/// <summary>
/// The inactive server crashed or exited
/// </summary>
InactiveServerCrashed,
/// <summary>
/// The active server called /world/Reboot()
/// </summary>
ActiveServerRebooted,
/// <summary>
/// The inactive server called /world/Reboot()
/// </summary>
InactiveServerRebooted,
/// <summary>
/// The inactive server is past that point where DD hangs when you press "Go"
/// </summary>
InactiveServerStartupComplete,
/// <summary>
/// A new .dmb was deployed
/// </summary>
NewDmbAvailable,
/// <summary>
/// Server launch parameters were changed
/// </summary>
@@ -2,11 +2,13 @@
namespace Tgstation.Server.Host.Components.Watchdog
{
//POSIX BYOND doesn't prompt you when you change the port
/// <inheritdoc />
sealed class PosixNetworkPromptReaper : INetworkPromptReaper
{
/// <inheritdoc />
public void RegisterProcess(IProcess process) { }
public void RegisterProcess(IProcess process)
{
// POSIX BYOND doesn't prompt you when you change the port
}
}
}
@@ -9,10 +9,12 @@
/// Run DreamDaemon's normal reboot process
/// </summary>
Normal = 0,
/// <summary>
/// Shutdown DreamDaemon
/// </summary>
Shutdown = 1,
/// <summary>
/// Restart the DreamDaemon process
/// </summary>
@@ -136,6 +136,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
/// The <see cref="TaskCompletionSource{TResult}"/> <see cref="SetPort(ushort, CancellationToken)"/> waits on when DreamDaemon currently has it's ports closed
/// </summary>
TaskCompletionSource<bool> portAssignmentTcs;
/// <summary>
/// The port to assign DreamDaemon when it queries for it
/// </summary>
@@ -181,7 +182,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
/// <param name="startupTimeout">The optional time to wait before failing the <see cref="LaunchResult"/></param>
public SessionController(ReattachInformation reattachInformation, IProcess process, IByondExecutableLock byondLock, IByondTopicSender byondTopicSender, IJsonTrackingContext chatJsonTrackingContext, ICommContext interopContext, IChat chat, ILogger<SessionController> logger, DreamDaemonSecurity? launchSecurityLevel, uint? startupTimeout)
{
this.chatJsonTrackingContext = chatJsonTrackingContext; //null valid
this.chatJsonTrackingContext = chatJsonTrackingContext; // null valid
this.reattachInformation = reattachInformation ?? throw new ArgumentNullException(nameof(reattachInformation));
this.byondTopicSender = byondTopicSender ?? throw new ArgumentNullException(nameof(byondTopicSender));
this.process = process ?? throw new ArgumentNullException(nameof(process));
@@ -219,17 +220,18 @@ namespace Tgstation.Server.Host.Components.Watchdog
StartupTime = process.Startup.IsCompleted ? (TimeSpan?)(DateTimeOffset.Now - startTime) : null
};
return result;
};
}
LaunchResult = GetLaunchResult();
logger.LogDebug("Created session controller. Primary: {0}, CommsKey: {1}, Port: {2}", IsPrimary, reattachInformation.AccessIdentifier, Port);
}
/// <summary>
/// Finalize the <see cref="SessionController"/>
/// Finalizes an instance of the <see cref="SessionController"/> class.
/// </summary>
/// <remarks>The finalizer dispose pattern is necessary so we don't accidentally leak the executable</remarks>
#pragma warning disable CA1821 // Remove empty Finalizers //TODO remove this when https://github.com/dotnet/roslyn-analyzers/issues/1241 is fixed
#pragma warning disable CA1821 // Remove empty Finalizers TODO: remove this when https://github.com/dotnet/roslyn-analyzers/issues/1241 is fixed
~SessionController() => Dispose(false);
#pragma warning restore CA1821 // Remove empty Finalizers
@@ -240,7 +242,10 @@ namespace Tgstation.Server.Host.Components.Watchdog
GC.SuppressFinalize(this);
}
/// <inheritdoc />
/// <summary>
/// Implements the <see cref="IDisposable"/> pattern
/// </summary>
/// <param name="disposing">If this function was NOT called by the finalizer</param>
void Dispose(bool disposing)
{
lock (this)
@@ -254,9 +259,10 @@ namespace Tgstation.Server.Host.Components.Watchdog
process.Terminate();
byondLock.Dispose();
}
process.Dispose();
interopContext.Dispose();
Dmb?.Dispose(); //will be null when released
Dmb?.Dispose(); // will be null when released
chatJsonTrackingContext.Dispose();
disposed = true;
}
@@ -307,9 +313,10 @@ namespace Tgstation.Server.Host.Components.Watchdog
logger.LogDebug("Exception while decoding chat message! Exception: {0}", e);
goto default;
}
break;
case Constants.DMCommandServerPrimed:
//currently unused, maybe in the future
// currently unused, maybe in the future
break;
case Constants.DMCommandEndProcess:
TerminationWasRequested = true;
@@ -327,18 +334,17 @@ namespace Tgstation.Server.Host.Components.Watchdog
}
if (!nextPort.HasValue)
//not ready yet, so what we'll do is accept the random port DD opened on for now and change it later when we decide to
reattachInformation.Port = currentPort;
reattachInformation.Port = currentPort; // not ready yet, so what we'll do is accept the random port DD opened on for now and change it later when we decide to
else
{
//nextPort is ready, tell DD to switch to that
//if it fails it'll kill itself
// nextPort is ready, tell DD to switch to that
// if it fails it'll kill itself
content = new Dictionary<string, ushort> { { Constants.DMParameterData, nextPort.Value } };
reattachInformation.Port = nextPort.Value;
overrideResponsePort = currentPort;
nextPort = null;
//we'll also get here from SetPort so complete that task
// we'll also get here from SetPort so complete that task
var tmpTcs = portAssignmentTcs;
portAssignmentTcs = null;
if (tmpTcs != null)
@@ -347,6 +353,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
portClosedForReboot = false;
}
break;
case Constants.DMCommandApiValidate:
if (!launchSecurityLevel.HasValue)
@@ -356,6 +363,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
content = new ErrorMessage { Message = "Invalid API validation request!" };
break;
}
if (!query.TryGetValue(Constants.DMParameterData, out var stringMinimumSecurityLevelObject) || !Enum.TryParse<DreamDaemonSecurity>(stringMinimumSecurityLevelObject as string, out var minimumSecurityLevel))
apiValidationStatus = ApiValidationStatus.BadValidationRequest;
else
@@ -373,6 +381,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
default:
throw new InvalidOperationException("Enum.TryParse failed to validate the DreamDaemonSecurity range!");
}
break;
case Constants.DMCommandWorldReboot:
if (ClosePortOnReboot)
@@ -381,6 +390,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
content = new Dictionary<string, int> { { Constants.DMParameterData, 0 } };
portClosedForReboot = true;
}
var oldTcs = rebootTcs;
rebootTcs = new TaskCompletionSource<object>();
postRespond = () => oldTcs.SetResult(null);
@@ -418,7 +428,8 @@ namespace Tgstation.Server.Host.Components.Watchdog
public ReattachInformation Release()
{
CheckDisposed();
//we still don't want to dispose the dmb yet, even though we're keeping it alive
// we still don't want to dispose the dmb yet, even though we're keeping it alive
var tmpProvider = reattachInformation.Dmb;
reattachInformation.Dmb = null;
released = true;
@@ -441,8 +452,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
byondTopicSender.SanitizeString(Constants.DMInteropAccessIdentifier),
byondTopicSender.SanitizeString(reattachInformation.AccessIdentifier),
byondTopicSender.SanitizeString(Constants.DMParameterCommand),
//intentionally don't sanitize command, that's up to the caller
command);
command); // intentionally don't sanitize command, that's up to the caller
var targetPort = overridePort ?? reattachInformation.Port;
logger.LogTrace("Export to :{0}. Query: {1}", targetPort, commandString);
@@ -5,7 +5,6 @@ using Newtonsoft.Json.Serialization;
using System;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -138,12 +137,12 @@ namespace Tgstation.Server.Host.Components.Watchdog
const string JsonPostfix = "tgs.json";
var basePath = primaryDirectory ? dmbProvider.PrimaryDirectory : dmbProvider.SecondaryDirectory;
//delete all previous tgs json files
var files = await ioManager.GetFilesWithExtension(basePath, JsonPostfix, cancellationToken).ConfigureAwait(false);
// delete all previous tgs json files
var files = await ioManager.GetFilesWithExtension(basePath, JsonPostfix, cancellationToken).ConfigureAwait(false);
await Task.WhenAll(files.Select(x => ioManager.DeleteFile(x, cancellationToken))).ConfigureAwait(false);
//i changed this back from guids, hopefully i don't regret that
// i changed this back from guids, hopefully i don't regret that
string JsonFile(string name) => String.Format(CultureInfo.InvariantCulture, "{0}.{1}", name, JsonPostfix);
var securityLevelToUse = launchParameters.SecurityLevel.Value;
@@ -162,7 +161,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid DreamDaemonSecurity value: {0}", dmbProvider.CompileJob.MinimumSecurityLevel));
}
//setup interop files
// setup interop files
var interopInfo = new JsonFile
{
AccessIdentifier = accessIdentifier,
@@ -197,19 +196,19 @@ namespace Tgstation.Server.Host.Components.Watchdog
var chatJsonTrackingContext = await chatJsonTrackingTask.ConfigureAwait(false);
try
{
//get the byond lock
// get the byond lock
var byondLock = currentByondLock ?? await byond.UseExecutables(Version.Parse(dmbProvider.CompileJob.ByondVersion), cancellationToken).ConfigureAwait(false);
try
{
//create interop context
// create interop context
var context = new CommContext(ioManager, loggerFactory.CreateLogger<CommContext>(), basePath, interopInfo.ServerCommandsJson);
try
{
//set command line options
//more sanitization here cause it uses the same scheme
// set command line options
// more sanitization here cause it uses the same scheme
var parameters = String.Format(CultureInfo.InvariantCulture, "{2}={0}&{3}={1}", byondTopicSender.SanitizeString(application.Version.ToString()), byondTopicSender.SanitizeString(interopJsonFile), byondTopicSender.SanitizeString(Constants.DMParamHostVersion), byondTopicSender.SanitizeString(Constants.DMParamInfoJson));
//important to run on all ports to allow port changing
// important to run on all ports to allow port changing
var arguments = String.Format(CultureInfo.InvariantCulture, "{0} -port {1} -ports 1-65535 {2}-close -{3} -verbose -public -params \"{4}\"",
dmbProvider.DmbName,
primaryPort ? launchParameters.PrimaryPort : launchParameters.SecondaryPort,
@@ -217,15 +216,16 @@ namespace Tgstation.Server.Host.Components.Watchdog
SecurityWord(securityLevelToUse),
parameters);
//See #719
// See https://github.com/tgstation/tgstation-server/issues/719
var noShellExecute = !platformIdentifier.IsWindows;
//launch dd
// launch dd
var process = processExecutor.LaunchProcess(byondLock.DreamDaemonPath, basePath, arguments, noShellExecute: noShellExecute);
try
{
networkPromptReaper.RegisterProcess(process);
//return the session controller for it
// return the session controller for it
var result = new SessionController(new ReattachInformation
{
AccessIdentifier = accessIdentifier,
@@ -238,7 +238,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
ServerCommandsJson = interopInfo.ServerCommandsJson,
}, process, byondLock, byondTopicSender, chatJsonTrackingContext, context, chat, loggerFactory.CreateLogger<SessionController>(), launchParameters.SecurityLevel, launchParameters.StartupTimeout);
//writeback launch parameter's fixed security level
// writeback launch parameter's fixed security level
launchParameters.SecurityLevel = securityLevelToUse;
return result;
@@ -317,6 +317,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
if (result == null)
chatJsonTrackingContext.Dispose();
}
return result;
}
@@ -5,6 +5,7 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Text;
@@ -134,6 +135,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
/// Server designation alpha
/// </summary>
ISessionController alphaServer;
/// <summary>
/// Server designation bravo
/// </summary>
@@ -179,7 +181,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
if (serverControl == null)
throw new ArgumentNullException(nameof(serverControl));
chat.RegisterCommandHandler(this);
AlphaIsActive = true;
@@ -239,7 +241,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
return;
}
//merely set the reboot state
// merely set the reboot state
var toKill = AlphaIsActive ? alphaServer : bravoServer;
var other = AlphaIsActive ? bravoServer : alphaServer;
if (toKill != null)
@@ -257,26 +259,27 @@ namespace Tgstation.Server.Host.Components.Watchdog
{
logger.LogDebug("Monitor activation. Reason: {0}", activationReason);
//this is where the bulk of the watchdog handling code lives and is fraught with lambdas, sorry not sorry
//I'll do my best to walk you through it
// this is where the bulk of the watchdog handling code lives and is fraught with lambdas, sorry not sorry
// I'll do my best to walk you through it
//returns true if the inactive server can't be used immediately
//also sets monitor to restart if the above holds
// returns true if the inactive server can't be used immediately
// also sets monitor to restart if the above holds
bool FullRestartDeadInactive()
{
if (monitorState.RebootingInactiveServer || monitorState.InactiveServerCritFail)
{
logger.LogInformation("Inactive server is {0}! Restarting monitor...", monitorState.InactiveServerCritFail ? "critically failed" : "still rebooting");
monitorState.NextAction = MonitorAction.Restart; //will dispose server
monitorState.NextAction = MonitorAction.Restart; // will dispose server
return true;
}
return false;
};
//trys to set inactive server's port to the public game port
//doesn't handle closing active server's port
//returns true on success and swaps inactiveserver and activeserver also sets LastLaunchParameters to ActiveLaunchParameters
//on failure, sets monitor to restart
return false;
}
// trys to set inactive server's port to the public game port
// doesn't handle closing active server's port
// returns true on success and swaps inactiveserver and activeserver also sets LastLaunchParameters to ActiveLaunchParameters
// on failure, sets monitor to restart
async Task<bool> MakeInactiveActive()
{
logger.LogDebug("Setting inactive server to port {0}...", ActiveLaunchParameters.PrimaryPort.Value);
@@ -285,11 +288,11 @@ namespace Tgstation.Server.Host.Components.Watchdog
if (!result)
{
logger.LogWarning("Failed to activate inactive server! Restarting monitor...");
monitorState.NextAction = MonitorAction.Restart; //will dispose server
monitorState.NextAction = MonitorAction.Restart; // will dispose server
return false;
}
//inactive server should always be using active launch parameters
// inactive server should always be using active launch parameters
LastLaunchParameters = ActiveLaunchParameters;
var tmp = monitorState.ActiveServer;
@@ -307,7 +310,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
async Task UpdateAndRestartInactiveServer(bool breakAfter)
{
activeParametersUpdated = new TaskCompletionSource<object>();
monitorState.InactiveServer.Dispose(); //kill or recycle it
monitorState.InactiveServer.Dispose(); // kill or recycle it
var desiredNextAction = breakAfter ? MonitorAction.Break : MonitorAction.Continue;
monitorState.NextAction = desiredNextAction;
@@ -325,17 +328,17 @@ namespace Tgstation.Server.Host.Components.Watchdog
catch (Exception e)
{
logger.LogError("Error occurred while recreating server! Attempting backup strategy of running DMB of running server! Exception: {0}", e.ToString());
//ahh jeez, what do we do here?
//this is our fault, so it should never happen but
//idk maybe a database error while handling the newest dmb?
//either way try to start it using the active server's dmb as a backup
// ahh jeez, what do we do here?
// this is our fault, so it should never happen but
// idk maybe a database error while handling the newest dmb?
// either way try to start it using the active server's dmb as a backup
try
{
var dmbBackup = await dmbFactory.FromCompileJob(monitorState.ActiveServer.Dmb.CompileJob, cancellationToken).ConfigureAwait(false);
if (dmbBackup == null) //NANI!?
//just give up, if THAT compile job is failing then the ActiveServer is gonna crash soon too or already has
throw new JobException("Creating backup DMB provider failed!");
if (dmbBackup == null) // NANI!?
throw new JobException("Creating backup DMB provider failed!"); // just give up, if THAT compile job is failing then the ActiveServer is gonna crash soon too or already has
monitorState.InactiveServer = await sessionControllerFactory.LaunchNew(ActiveLaunchParameters, dmbBackup, null, false, !monitorState.ActiveServer.IsPrimary, false, cancellationToken).ConfigureAwait(false);
monitorState.InactiveServer.SetHighPriority();
@@ -347,7 +350,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
}
catch (Exception e2)
{
//fuuuuucckkk
// fuuuuucckkk
logger.LogError("Backup strategy failed! Monitor will restart when active server reboots! Exception: {0}", e2.ToString());
monitorState.InactiveServerCritFail = true;
await chat.SendWatchdogMessage("Attempted reboot of inactive server failed. Watchdog will reset when active server fails or exits", cancellationToken).ConfigureAwait(false);
@@ -361,13 +364,13 @@ namespace Tgstation.Server.Host.Components.Watchdog
string ExitWord(ISessionController controller) => controller.TerminationWasRequested ? "exited" : "crashed";
//reason handling
// reason handling
switch (activationReason)
{
case MonitorActivationReason.ActiveServerCrashed:
if (monitorState.ActiveServer.RebootState == Components.Watchdog.RebootState.Shutdown)
{
//the time for graceful shutdown is now
// the time for graceful shutdown is now
await chat.SendWatchdogMessage(String.Format(CultureInfo.InvariantCulture, "Active server {0}! Exiting due to graceful termination request...", ExitWord(monitorState.ActiveServer)), cancellationToken).ConfigureAwait(false);
DisposeAndNullControllers();
monitorState.NextAction = MonitorAction.Exit;
@@ -376,118 +379,122 @@ namespace Tgstation.Server.Host.Components.Watchdog
if (FullRestartDeadInactive())
{
//tell chat about it and go ahead
// tell chat about it and go ahead
await chat.SendWatchdogMessage(String.Format(CultureInfo.InvariantCulture, "Active server {0}! Inactive server unable to online!", ExitWord(monitorState.ActiveServer)), cancellationToken).ConfigureAwait(false);
//we've already been set to restart
// we've already been set to restart
break;
}
//tell chat about it
// tell chat about it
await chat.SendWatchdogMessage(String.Format(CultureInfo.InvariantCulture, "Active server {0}! Onlining inactive server...", ExitWord(monitorState.ActiveServer)), cancellationToken).ConfigureAwait(false);
//try to active the inactive server
// try to activate the inactive server
if (!await MakeInactiveActive().ConfigureAwait(false))
//failing that, we've already been set to restart
break;
break; // failing that, we've already been set to restart
//bring up another inactive server
// bring up another inactive server
await UpdateAndRestartInactiveServer(true).ConfigureAwait(false);
break;
case MonitorActivationReason.InactiveServerCrashed:
//just announce and try to bring it back
// just announce and try to bring it back
await chat.SendWatchdogMessage(String.Format(CultureInfo.InvariantCulture, "Inactive server {0}! Rebooting...", ExitWord(monitorState.InactiveServer)), cancellationToken).ConfigureAwait(false);
await UpdateAndRestartInactiveServer(false).ConfigureAwait(false);
break;
case MonitorActivationReason.ActiveServerRebooted:
//ideal goal: active server just closed its port
//tell inactive server to open it's port and that's now the active server
// ideal goal: active server just closed its port
// tell inactive server to open it's port and that's now the active server
var rebootState = monitorState.ActiveServer.RebootState;
monitorState.ActiveServer.ResetRebootState(); //the DMAPI has already done this internally
monitorState.ActiveServer.ResetRebootState(); // the DMAPI has already done this internally
if (FullRestartDeadInactive() && rebootState != Components.Watchdog.RebootState.Shutdown)
//full restart if the inactive server is being fucky
break;
break; // full restart if the inactive server is being fucky
//what matters here is the RebootState
// what matters here is the RebootState
var restartOnceSwapped = false;
switch (rebootState)
{
case Components.Watchdog.RebootState.Normal:
//life as normal
// life as normal
break;
case Components.Watchdog.RebootState.Restart:
//reboot the current active server once the inactive one activates
// reboot the current active server once the inactive one activates
restartOnceSwapped = true;
break;
case Components.Watchdog.RebootState.Shutdown:
//graceful shutdown time
// graceful shutdown time
await chat.SendWatchdogMessage("Active server rebooted! Exiting due to graceful termination request...", cancellationToken).ConfigureAwait(false);
DisposeAndNullControllers();
monitorState.NextAction = MonitorAction.Exit;
return;
default:
Trace.Assert(false, String.Format(CultureInfo.InvariantCulture, "Invalid RebootState: {0}!", rebootState));
break;
}
//are both servers now running the same CompileJob?
// are both servers now running the same CompileJob?
var sameCompileJob = monitorState.InactiveServer.Dmb.CompileJob.Id == monitorState.ActiveServer.Dmb.CompileJob.Id;
if (!sameCompileJob || ActiveLaunchParameters != LastLaunchParameters)
//need a new launch to update either settings or compile job
restartOnceSwapped = true;
restartOnceSwapped = true; // need a new launch to update either settings or compile job
if (restartOnceSwapped)
//we need to manually restart active server
//just kill it here, easier that way
/*
* we need to manually restart active server
* just kill it here, easier that way
*/
monitorState.ActiveServer.Dispose();
var activeServerStillHasPortOpen = !restartOnceSwapped && !monitorState.ActiveServer.ClosePortOnReboot;
if (activeServerStillHasPortOpen)
//we didn't want active server to swap for some reason and it still has it's port open
//just continue as normal
/* we didn't want active server to swap for some reason and it still has it's port open
* just continue as normal
*/
break;
if (!await MakeInactiveActive().ConfigureAwait(false))
//monitor will restart
break;
break; // monitor will restart
//servers now swapped
//enable this now if inactive server is not still valid
// servers now swapped
// enable this now if inactive server is not still valid
monitorState.ActiveServer.ClosePortOnReboot = restartOnceSwapped;
if (!restartOnceSwapped)
//now try to reopen it on the private port
//failing that, just reboot it
/*
* now try to reopen it on the private port
* failing that, just reboot it
*/
restartOnceSwapped = !await monitorState.InactiveServer.SetPort(ActiveLaunchParameters.SecondaryPort.Value, cancellationToken).ConfigureAwait(false);
//break either way because any issues past this point would be solved by the reboot
if (restartOnceSwapped)
//for one reason or another
//update and reboot
await UpdateAndRestartInactiveServer(true).ConfigureAwait(false);
// break either way because any issues past this point would be solved by the reboot
if (restartOnceSwapped) // for one reason or another
await UpdateAndRestartInactiveServer(true).ConfigureAwait(false); // update and reboot
else
//only skip checking inactive server rebooted, it's guaranteed InactiveServerStartup complete wouldn't fire this iteration
monitorState.NextAction = MonitorAction.Skip;
monitorState.NextAction = MonitorAction.Skip; // only skip checking inactive server rebooted, it's guaranteed InactiveServerStartup complete wouldn't fire this iteration
break;
case MonitorActivationReason.InactiveServerRebooted:
//just don't let the active server close it's port if the inactive server isn't ready
// just don't let the active server close it's port if the inactive server isn't ready
monitorState.RebootingInactiveServer = true;
monitorState.InactiveServer.ResetRebootState();
monitorState.ActiveServer.ClosePortOnReboot = false;
monitorState.NextAction = MonitorAction.Continue;
break;
case MonitorActivationReason.InactiveServerStartupComplete:
//opposite of above case
// opposite of above case
monitorState.RebootingInactiveServer = false;
monitorState.ActiveServer.ClosePortOnReboot = true;
monitorState.NextAction = MonitorAction.Continue;
break;
case MonitorActivationReason.NewDmbAvailable:
case MonitorActivationReason.ActiveLaunchParametersUpdated:
//just reload the inactive server and wait for a swap to apply the changes
// just reload the inactive server and wait for a swap to apply the changes
await UpdateAndRestartInactiveServer(true).ConfigureAwait(false);
break;
default:
Trace.Assert(false, String.Format(CultureInfo.InvariantCulture, "Invalid monitor activation reason: {0}!", activationReason));
break;
}
}
@@ -500,15 +507,14 @@ namespace Tgstation.Server.Host.Components.Watchdog
{
logger.LogTrace("Entered MonitorLifetimes");
//this function is responsible for calling HandlerMonitorWakeup when necessary and manitaining the MonitorState
// this function is responsible for calling HandlerMonitorWakeup when necessary and manitaining the MonitorState
var iteration = 1;
for (var monitorState = new MonitorState(); monitorState.NextAction != MonitorAction.Exit; ++iteration)
{
//always start out with continue
// always start out with continue
monitorState.NextAction = MonitorAction.Continue;
//dump some info to the logs
// dump some info to the logs
logger.LogDebug("Iteration {0} of monitor loop", iteration);
try
{
@@ -516,12 +522,11 @@ namespace Tgstation.Server.Host.Components.Watchdog
logger.LogDebug("Alpha is the active server");
else
logger.LogDebug("Bravo is the active server");
if (monitorState.RebootingInactiveServer)
logger.LogDebug("Inactive server is rebooting");
//update the monitor state with the inactive/active servers
// update the monitor state with the inactive/active servers
monitorState.ActiveServer = AlphaIsActive ? alphaServer : bravoServer;
monitorState.InactiveServer = AlphaIsActive ? bravoServer : alphaServer;
@@ -533,7 +538,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
logger.LogDebug("Active server Compile Job ID: {0}", monitorState.ActiveServer.Dmb.CompileJob.Id);
logger.LogDebug("Inactive server Compile Job ID: {0}", monitorState.InactiveServer.Dmb.CompileJob.Id);
//load the activation tasks into local variables
// load the activation tasks into local variables
Task activeServerLifetime = monitorState.ActiveServer.Lifetime;
Task inactiveServerLifetime = monitorState.InactiveServer.Lifetime;
var activeServerReboot = monitorState.ActiveServer.OnReboot;
@@ -542,14 +547,15 @@ namespace Tgstation.Server.Host.Components.Watchdog
Task activeLaunchParametersChanged = activeParametersUpdated.Task;
var newDmbAvailable = dmbFactory.OnNewerDmb;
//cancel waiting if requested
// cancel waiting if requested
var cancelTcs = new TaskCompletionSource<object>();
using (cancellationToken.Register(() => cancelTcs.SetCanceled()))
{
var toWaitOn = Task.WhenAny(activeServerLifetime, inactiveServerLifetime, activeServerReboot, inactiveServerReboot, newDmbAvailable, cancelTcs.Task, activeLaunchParametersChanged);
if (monitorState.RebootingInactiveServer)
toWaitOn = Task.WhenAny(toWaitOn, inactiveServerStartup);
//wait for something to happen
// wait for something to happen
await toWaitOn.ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
}
@@ -557,14 +563,13 @@ namespace Tgstation.Server.Host.Components.Watchdog
var chatTask = Task.CompletedTask;
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
{
//always run HandleMonitorWakeup from the context of the semaphore lock
//multiple things may have happened, handle them one at a time
// always run HandleMonitorWakeup from the context of the semaphore lock
// multiple things may have happened, handle them one at a time
for (var moreActivationsToProcess = true; moreActivationsToProcess && (monitorState.NextAction == MonitorAction.Continue || monitorState.NextAction == MonitorAction.Skip);)
{
MonitorActivationReason activationReason = default; //this will always be assigned before being used
//process the tasks in this order and call HandlerMonitorWakup for each
MonitorActivationReason activationReason = default; // this will always be assigned before being used
// process the tasks in this order and call HandlerMonitorWakup for each
bool CheckActivationReason(ref Task task, MonitorActivationReason testActivationReason)
{
var taskCompleted = task?.IsCompleted == true;
@@ -576,8 +581,9 @@ namespace Tgstation.Server.Host.Components.Watchdog
activationReason = testActivationReason;
return true;
}
return false;
};
return false;
}
if (CheckActivationReason(ref activeServerLifetime, MonitorActivationReason.ActiveServerCrashed)
|| CheckActivationReason(ref inactiveServerLifetime, MonitorActivationReason.InactiveServerCrashed)
@@ -591,12 +597,12 @@ namespace Tgstation.Server.Host.Components.Watchdog
moreActivationsToProcess = false;
}
//writeback alphaServer and bravoServer from monitor state in case they changesd
// writeback alphaServer and bravoServer from monitor state in case they changesd
alphaServer = AlphaIsActive ? monitorState.ActiveServer : monitorState.InactiveServer;
bravoServer = !AlphaIsActive ? monitorState.ActiveServer : monitorState.InactiveServer;
}
//full reboot required
// full reboot required
if (monitorState.NextAction == MonitorAction.Restart)
{
logger.LogDebug("Next state action is to restart");
@@ -609,12 +615,12 @@ namespace Tgstation.Server.Host.Components.Watchdog
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
try
{
//use LaunchImplNoLock without announcements or restarting the monitor
// use LaunchImplNoLock without announcements or restarting the monitor
await LaunchImplNoLock(false, false, null, cancellationToken).ConfigureAwait(false);
if (Running)
{
logger.LogDebug("Relaunch successful, resetting monitor state...");
monitorState = new MonitorState(); //clean the slate and continue
monitorState = new MonitorState(); // clean the slate and continue
}
}
catch (OperationCanceledException)
@@ -633,7 +639,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
logger.LogWarning("Failed to automatically restart the watchdog! Attempt: {0}", retryAttempts);
else
logger.LogWarning("Failed to automatically restart the watchdog! Attempt: {0}, Exception: {1}", retryAttempts, launchException);
var retryDelay = Math.Min(Math.Pow(2, retryAttempts), 3600); //max of one hour, increasing by a power of 2 each time
var retryDelay = Math.Min(Math.Pow(2, retryAttempts), 3600); // max of one hour, increasing by a power of 2 each time
chatTask = chat.SendWatchdogMessage(String.Format(CultureInfo.InvariantCulture, "Failed to restart watchdog (Attempt: {0}), retrying in {1} seconds...", retryAttempts, retryDelay), cancellationToken);
await Task.WhenAll(asyncDelayer.Delay(TimeSpan.FromSeconds(retryDelay), cancellationToken), chatTask).ConfigureAwait(false);
}
@@ -647,11 +653,12 @@ namespace Tgstation.Server.Host.Components.Watchdog
}
catch (Exception e)
{
//really, this should NEVER happen
// really, this should NEVER happen
logger.LogError("Monitor crashed! Iteration: {0}, State: {1}, Exception: {2}", iteration, JsonConvert.SerializeObject(monitorState), e);
await chat.SendWatchdogMessage(String.Format(CultureInfo.InvariantCulture, "Monitor crashed, this should NEVER happen! Please report this, full details in logs! Restarting monitor... Error: {0}", e.Message), cancellationToken).ConfigureAwait(false);
}
}
logger.LogTrace("Monitor exiting...");
}
@@ -671,7 +678,6 @@ namespace Tgstation.Server.Host.Components.Watchdog
return true;
}
/// <inheritdoc />
public async Task ChangeSettings(DreamDaemonLaunchParameters launchParameters, CancellationToken cancellationToken)
{
@@ -681,8 +687,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
return;
ActiveLaunchParameters = launchParameters;
if (Running)
//queue an update
activeParametersUpdated.TrySetResult(null);
activeParametersUpdated.TrySetResult(null); // queue an update
}
}
@@ -701,67 +706,68 @@ namespace Tgstation.Server.Host.Components.Watchdog
if (Running)
throw new JobException("Watchdog already running!");
// this is necessary, the monitor could be in it's sleep loop trying to restart, if so cancel THAT monitor and start our own with blackjack and hookers
Task chatTask;
//this is necessary, the monitor could be in it's sleep loop trying to restart, if so cancel THAT monitor and start our own with blackjack and hookers
if (startMonitor && await StopMonitor().ConfigureAwait(false))
chatTask = chat.SendWatchdogMessage("Automatic retry sequence cancelled by manual launch. Restarting...", cancellationToken);
else if (announce)
//simple announce
chatTask = chat.SendWatchdogMessage(reattachInfo == null ? "Starting..." : "Reattaching...", cancellationToken);
chatTask = chat.SendWatchdogMessage(reattachInfo == null ? "Starting..." : "Reattaching...", cancellationToken); // simple announce
else
//no announce
chatTask = Task.CompletedTask;
//since neither server is running, this is safe to do
chatTask = Task.CompletedTask; // no announce
// since neither server is running, this is safe to do
LastLaunchParameters = ActiveLaunchParameters;
//for when we call ourself and want to not catch thrown exceptions
// for when we call ourself and want to not catch thrown exceptions
var ignoreNestedException = false;
try
{
//good ole sanity, should never fucking trigger but i don't trust myself even though I should
// good ole sanity, should never fucking trigger but i don't trust myself even though I should
// TODO: Unit test this instead?
if (alphaServer != null || bravoServer != null)
throw new InvalidOperationException("Entered LaunchNoLock with one or more of the servers not being null!");
//don't need a new dmb if reattaching
// don't need a new dmb if reattaching
var doesntNeedNewDmb = reattachInfo?.Alpha != null && reattachInfo?.Bravo != null;
var dmbToUse = doesntNeedNewDmb ? null : dmbFactory.LockNextDmb(2);
//if this try catches something, both servers are killed
// if this try catches something, both servers are killed
try
{
//start the alpha server task, either by launch a new process or attaching to an existing one
//The tasks returned are mainly for writing interop files to the directories among other things and should generally never fail
//The tasks pertaining to server startup times are in the ISessionControllers
// start the alpha server task, either by launch a new process or attaching to an existing one
// The tasks returned are mainly for writing interop files to the directories among other things and should generally never fail
// The tasks pertaining to server startup times are in the ISessionControllers
Task<ISessionController> alphaServerTask;
if (!doesntNeedNewDmb)
alphaServerTask = sessionControllerFactory.LaunchNew(ActiveLaunchParameters, dmbToUse, null, true, true, false, cancellationToken);
else
alphaServerTask = sessionControllerFactory.Reattach(reattachInfo.Alpha, cancellationToken);
//retrieve the session controller
// retrieve the session controller
var startTime = DateTimeOffset.Now;
alphaServer = await alphaServerTask.ConfigureAwait(false);
//failed reattaches will return null
// failed reattaches will return null
alphaServer?.SetHighPriority();
//extra delay for total ordering
// extra delay for total ordering
var now = DateTimeOffset.Now;
var delay = now - startTime;
//definitely not if reattaching though
// definitely not if reattaching though
if (reattachInfo == null && delay.TotalSeconds < AlphaBravoStartupSeperationInterval)
await asyncDelayer.Delay(startTime.AddSeconds(AlphaBravoStartupSeperationInterval) - now, cancellationToken).ConfigureAwait(false);
//now bring bravo up
// now bring bravo up
if (!doesntNeedNewDmb)
bravoServer = await sessionControllerFactory.LaunchNew(ActiveLaunchParameters, dmbToUse, null, false, false, false, cancellationToken).ConfigureAwait(false);
else
bravoServer = await sessionControllerFactory.Reattach(reattachInfo.Bravo, cancellationToken).ConfigureAwait(false);
//failed reattaches will return null
// failed reattaches will return null
bravoServer?.SetHighPriority();
//possiblity of null servers due to failed reattaches
// possiblity of null servers due to failed reattaches
if (alphaServer == null || bravoServer == null)
{
await chatTask.ConfigureAwait(false);
@@ -770,7 +776,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|| (alphaServer == null && reattachInfo.AlphaIsActive)
|| (bravoServer == null && !reattachInfo.AlphaIsActive))
{
//we lost the active server, just restart entirely
// we lost the active server, just restart entirely
DisposeAndNullControllers();
const string FailReattachMessage = "Unable to properly reattach to active server! Restarting...";
logger.LogWarning(FailReattachMessage);
@@ -781,7 +787,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
return;
}
//we still have the active server but the other one is dead to us, hand it off to the monitor to restart
// we still have the active server but the other one is dead to us, hand it off to the monitor to restart
const string InactiveReattachFailureMessage = "Unable to reattach to inactive server. Leaving for monitor to reboot...";
chatTask = chat.SendWatchdogMessage(InactiveReattachFailureMessage, cancellationToken);
logger.LogWarning(InactiveReattachFailureMessage);
@@ -792,13 +798,12 @@ namespace Tgstation.Server.Host.Components.Watchdog
alphaServer = sessionControllerFactory.CreateDeadSession(reattachInfo.Alpha.Dmb);
}
//throws a JobException if something went wrong with a launch
//Dead sessions won't trigger this
// throws a JobException if something went wrong with a launch
// Dead sessions won't trigger this
async Task<LaunchResult> CheckLaunch(ISessionController controller, string serverName)
{
var launch = await controller.LaunchResult.ConfigureAwait(false);
if (launch.ExitCode.HasValue)
//you killed us ray...
if (launch.ExitCode.HasValue) // you killed us ray...
throw new JobException(String.Format(CultureInfo.InvariantCulture, "{1} server failed to start: {0}", launch.ToString(), serverName));
if (!launch.StartupTime.HasValue)
throw new JobException(String.Format(CultureInfo.InvariantCulture, "{1} server timed out on startup: {0}s", launch.ToString(), ActiveLaunchParameters.StartupTimeout.Value));
@@ -807,10 +812,11 @@ namespace Tgstation.Server.Host.Components.Watchdog
var alphaLrt = CheckLaunch(alphaServer, "Alpha");
var bravoLrt = CheckLaunch(bravoServer, "Bravo");
//this task completes when both serers have finished booting
// this task completes when both serers have finished booting
var allTask = Task.WhenAll(alphaLrt, bravoLrt);
//don't forget about the cancellationToken
// don't forget about the cancellationToken
var cancelTcs = new TaskCompletionSource<object>();
using (cancellationToken.Register(() => cancelTcs.SetCanceled()))
await Task.WhenAny(allTask, cancelTcs.Task).ConfigureAwait(false);
@@ -818,7 +824,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
await allTask.ConfigureAwait(false);
//both servers are now running, alpha is the active server(unless reattach), huzzah
// both servers are now running, alpha is the active server(unless reattach), huzzah
AlphaIsActive = reattachInfo?.AlphaIsActive ?? true;
var activeServer = AlphaIsActive ? alphaServer : bravoServer;
@@ -838,34 +844,32 @@ namespace Tgstation.Server.Host.Components.Watchdog
{
if (dmbToUse != null)
{
//we locked 2 dmbs
// we locked 2 dmbs
if (bravoServer == null)
{
//bravo didn't get control of his
// bravo didn't get control of his
dmbToUse.Dispose();
if (alphaServer == null)
//alpha didn't get control of his
dmbToUse.Dispose();
dmbToUse.Dispose(); // alpha didn't get control of his
}
}
else if (doesntNeedNewDmb)
//we have reattachInfo
else if (doesntNeedNewDmb) // we have reattachInfo
if (bravoServer == null)
{
//bravo didn't get control of his
// bravo didn't get control of his
reattachInfo.Bravo?.Dmb.Dispose();
if (alphaServer == null)
//alpha didn't get control of his
reattachInfo.Alpha?.Dmb.Dispose();
reattachInfo.Alpha?.Dmb.Dispose(); // alpha didn't get control of his
}
//kill the controllers
// kill the controllers
DisposeAndNullControllers();
throw;
}
}
catch (Exception e)
{
//don't try to send chat tasks or warning logs if were suppressing exceptions or cancelled
// don't try to send chat tasks or warning logs if were suppressing exceptions or cancelled
if (!ignoreNestedException && !cancellationToken.IsCancellationRequested)
{
var originalChatTask = chatTask;
@@ -874,14 +878,16 @@ namespace Tgstation.Server.Host.Components.Watchdog
await originalChatTask.ConfigureAwait(false);
await chat.SendWatchdogMessage("Startup failed!", cancellationToken).ConfigureAwait(false);
}
chatTask = ChainChatTaskWithErrorMessage();
logger.LogWarning("Failed to start watchdog: {0}", e.ToString());
}
throw;
}
finally
{
//finish the chat task that's in flight
// finish the chat task that's in flight
try
{
await chatTask.ConfigureAwait(false);
@@ -929,6 +935,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
await LaunchImplNoLock(true, !Running, null, cancellationToken).ConfigureAwait(false);
await chatTask.ConfigureAwait(false);
}
var toReboot = AlphaIsActive ? alphaServer : bravoServer;
if (toReboot != null)
{
@@ -994,6 +1001,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
reattachInformation.Bravo = bravoServer?.Release();
await reattachInfoHandler.Save(reattachInformation, cancellationToken).ConfigureAwait(false);
}
await Terminate(false, cancellationToken).ConfigureAwait(false);
}
@@ -19,7 +19,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
/// Number of times to send the button click message. Should be at least 2 or it may fail to focus the window
/// </summary>
const int SendMessageCount = 5;
/// <summary>
/// Check for prompts each time this amount of milliseconds pass
/// </summary>
@@ -41,7 +41,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
readonly CancellationTokenSource cancellationTokenSource;
/// <summary>
/// The list of <see cref="IProcess"/>s registered
/// The list of <see cref="IProcess"/>s registered
/// </summary>
readonly List<IProcess> registeredProcesses;
@@ -77,6 +77,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
{
gcChildhandlesList.Free();
}
return childHandles;
}
@@ -117,12 +118,12 @@ namespace Tgstation.Server.Host.Components.Watchdog
if (window == IntPtr.Zero)
continue;
//found a bitch
// found a bitch
var threadId = NativeMethods.GetWindowThreadProcessId(window, out processId);
if (!registeredProcesses.Any(x => x.Id == processId))
//not our bitch
continue;
continue; // not our bitch
}
logger.LogTrace("Identified \"Network Accessibility\" window in owned process {0}", processId);
var found = false;
@@ -140,13 +141,14 @@ namespace Tgstation.Server.Host.Components.Watchdog
var windowText = stringBuilder.ToString();
if (windowText == "Yes")
{
//smash_button_meme.jpg
// smash_button_meme.jpg
logger.LogTrace("Sending \"Yes\" button clicks...");
for (var J = 0; J < SendMessageCount; ++J)
{
const int BM_CLICK = 0x00F5;
var result = NativeMethods.SendMessage(I, BM_CLICK, IntPtr.Zero, IntPtr.Zero);
}
found = true;
break;
}
@@ -9,10 +9,12 @@
/// Use Microsoft SQL Server
/// </summary>
SqlServer,
/// <summary>
/// Use MySQL
/// </summary>
MySql,
/// <summary>
/// Use MariaDB
/// </summary>
@@ -28,7 +28,7 @@ namespace Tgstation.Server.Host.Configuration
/// Where log files are stored
/// </summary>
public string Directory { get; set; }
/// <summary>
/// If file logging is disabled
/// </summary>
@@ -40,7 +40,6 @@ namespace Tgstation.Server.Host.Configuration
[JsonConverter(typeof(StringEnumConverter))]
public LogLevel LogLevel { get; set; } = DefaultLogLevel;
/// <summary>
/// The minimum <see cref="Microsoft.Extensions.Logging.LogLevel"/> to display in logs for Microsoft library sources
/// </summary>
@@ -9,14 +9,17 @@
/// Run the wizard if the appsettings.{Environment}.json is not present or empty
/// </summary>
Autodetect,
/// <summary>
/// Force run the wizard
/// </summary>
Force,
/// <summary>
/// Only run the wizard and exit
/// </summary>
Only,
/// <summary>
/// Never run the wizard
/// </summary>
@@ -8,7 +8,6 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Api;
@@ -126,6 +125,7 @@ namespace Tgstation.Server.Host.Controllers
{
Logger.LogWarning("Not found exception while retrieving upstream repository info: {0}", e);
}
return Json(new Administration
{
LatestVersion = greatestVersion,
@@ -188,19 +188,22 @@ namespace Tgstation.Server.Host.Controllers
var asset = release.Assets.Where(x => x.Name == updatesConfiguration.UpdatePackageAssetName).FirstOrDefault();
if (asset == default)
continue;
if (!serverUpdater.ApplyUpdate(version, new Uri(asset.BrowserDownloadUrl), ioManager))
return Conflict(new ErrorMessage
{
Message = "An update operation is already in progress!"
});
return Accepted(); //gtfo of here before all the cancellation tokens fire
return Accepted(); // gtfo of here before all the cancellation tokens fire
}
return StatusCode((int)HttpStatusCode.Gone);
}
/// <inheritdoc />
/// <summary>
/// Attempts to restart the server
/// </summary>
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="IActionResult"/> of the request</returns>
[HttpDelete]
[TgsAuthorize(AdministrationRights.RestartHost)]
public async Task<IActionResult> Delete()
@@ -215,6 +218,7 @@ namespace Tgstation.Server.Host.Controllers
Message = RestartNotSupportedException
});
}
await serverUpdater.Restart().ConfigureAwait(false);
return Ok();
}
@@ -47,7 +47,7 @@ namespace Tgstation.Server.Host.Controllers
/// <inheritdoc />
[TgsAuthorize(ByondRights.ReadActive)]
public override Task<IActionResult> Read(CancellationToken cancellationToken) => Task.FromResult((IActionResult)
public override Task<IActionResult> Read(CancellationToken cancellationToken) => Task.FromResult<IActionResult>(
Json(new Api.Models.Byond
{
Version = instanceManager.GetInstance(Instance).ByondManager.ActiveVersion
@@ -55,7 +55,7 @@ namespace Tgstation.Server.Host.Controllers
/// <inheritdoc />
[TgsAuthorize(ByondRights.ListInstalled)]
public override Task<IActionResult> List(CancellationToken cancellationToken) => Task.FromResult((IActionResult)
public override Task<IActionResult> List(CancellationToken cancellationToken) => Task.FromResult<IActionResult>(
Json(instanceManager.GetInstance(Instance).ByondManager.InstalledVersions.Select(x => new Api.Models.Byond
{
Version = x
@@ -73,7 +73,7 @@ namespace Tgstation.Server.Host.Controllers
var byondManager = instanceManager.GetInstance(Instance).ByondManager;
//remove cruff fields
// remove cruff fields
var installingVersion = new Version(model.Version.Major, model.Version.Minor);
var result = new Api.Models.Byond();
@@ -86,7 +86,8 @@ namespace Tgstation.Server.Host.Controllers
else
{
Logger.LogInformation("User ID {0} installing BYOND version to {2} on instance ID {1}", AuthenticationContext.User.Id, Instance.Id, installingVersion);
//run the install through the job manager
// run the install through the job manager
var job = new Models.Job
{
Description = String.Format(CultureInfo.InvariantCulture, "Install BYOND version {0}", installingVersion),
@@ -98,6 +99,7 @@ namespace Tgstation.Server.Host.Controllers
await jobManager.RegisterOperation(job, (paramJob, databaseContext, progressHandler, ct) => byondManager.ChangeVersion(installingVersion, ct), cancellationToken).ConfigureAwait(false);
result.InstallJob = job.ToApi();
}
if ((AuthenticationContext.GetRight(RightsType.Byond) & (ulong)ByondRights.ReadActive) != 0)
result.Version = byondManager.ActiveVersion;
return result.InstallJob != null ? (IActionResult)Accepted(result) : Json(result);
@@ -87,13 +87,13 @@ namespace Tgstation.Server.Host.Controllers
model.Enabled = model.Enabled ?? false;
//try to update das db first
// try to update das db first
var dbModel = new Models.ChatBot
{
Name = model.Name,
ConnectionString = model.ConnectionString,
Enabled = model.Enabled,
Channels = model.Channels?.Select(x => ConvertApiChatChannel(x)).ToList() ?? new List<Models.ChatChannel>(), //important that this isn't null
Channels = model.Channels?.Select(x => ConvertApiChatChannel(x)).ToList() ?? new List<Models.ChatChannel>(), // important that this isn't null
InstanceId = Instance.Id,
Provider = model.Provider,
};
@@ -106,7 +106,7 @@ namespace Tgstation.Server.Host.Controllers
{
try
{
//try to create it
// try to create it
var instance = instanceManager.GetInstance(Instance);
await instance.Chat.ChangeSettings(dbModel, cancellationToken).ConfigureAwait(false);
@@ -115,7 +115,7 @@ namespace Tgstation.Server.Host.Controllers
}
catch
{
//undo the add
// undo the add
DatabaseContext.ChatBots.Remove(dbModel);
await DatabaseContext.Save(default).ConfigureAwait(false);
throw;
@@ -125,6 +125,7 @@ namespace Tgstation.Server.Host.Controllers
{
return BadRequest(new ErrorMessage { Message = e.Message });
}
return StatusCode((int)HttpStatusCode.Created, dbModel.ToApi());
}
@@ -208,7 +209,7 @@ namespace Tgstation.Server.Host.Controllers
property.SetValue(current, newVal);
anySettingsModified = true;
return false;
};
}
var oldProvider = current.Provider;
@@ -238,8 +239,7 @@ namespace Tgstation.Server.Host.Controllers
var chat = instanceManager.GetInstance(Instance).Chat;
if (anySettingsModified)
//have to rebuild the thing first
await chat.ChangeSettings(current, cancellationToken).ConfigureAwait(false);
await chat.ChangeSettings(current, cancellationToken).ConfigureAwait(false); // have to rebuild the thing first
if (model.Channels != null || anySettingsModified)
await chat.ChangeChannels(current.Id, current.Channels, cancellationToken).ConfigureAwait(false);
@@ -250,6 +250,7 @@ namespace Tgstation.Server.Host.Controllers
current.ConnectionString = null;
return Json(current.ToApi());
}
return Ok();
}
}
@@ -58,6 +58,7 @@ namespace Tgstation.Server.Host.Controllers
systemIdentityToUse = null;
return true;
}
systemIdentityToUse = Instance.ConfigurationType == ConfigurationType.SystemIdentityWrite ? AuthenticationContext.SystemIdentity : null;
return false;
}
@@ -29,6 +29,7 @@ namespace Tgstation.Server.Host.Controllers
/// The <see cref="IJobManager"/> for the <see cref="DreamMakerController"/>
/// </summary>
readonly IJobManager jobManager;
/// <summary>
/// The <see cref="IInstanceManager"/> for the <see cref="DreamMakerController"/>
/// </summary>
@@ -52,7 +53,7 @@ namespace Tgstation.Server.Host.Controllers
[TgsAuthorize(DreamDaemonRights.Start)]
public override async Task<IActionResult> Create([FromBody] DreamDaemon model, CancellationToken cancellationToken)
{
//alias for launching DD
// alias for launching DD
var instance = instanceManager.GetInstance(Instance);
if (instance.Watchdog.Running)
@@ -94,7 +95,7 @@ namespace Tgstation.Server.Host.Controllers
if (settings == default)
return StatusCode((int)HttpStatusCode.Gone);
}
var result = new DreamDaemon();
if (metadata)
{
@@ -113,7 +114,7 @@ namespace Tgstation.Server.Host.Controllers
result.SoftRestart = rstate == RebootState.Restart;
result.SoftShutdown = rstate == RebootState.Shutdown;
result.StartupTimeout = settings.StartupTimeout;
};
}
if (revision)
{
@@ -126,12 +127,15 @@ namespace Tgstation.Server.Host.Controllers
return Json(result);
}
/// <inheritdoc />
/// <summary>
/// Stops DreamDaemon if it's running
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="IActionResult"/> of the operation</returns>
[HttpDelete]
[TgsAuthorize(DreamDaemonRights.Shutdown)]
public async Task<IActionResult> Delete(CancellationToken cancellationToken)
{
//alias for stopping DD
var instance = instanceManager.GetInstance(Instance);
await instance.Watchdog.Terminate(false, cancellationToken).ConfigureAwait(false);
return Ok();
@@ -150,7 +154,7 @@ namespace Tgstation.Server.Host.Controllers
if (model.SecurityLevel == DreamDaemonSecurity.Ultrasafe)
return BadRequest(new ErrorMessage { Message = "This version of TGS does not support the ultrasafe DreamDaemon configuration!" });
//alias for changing DD settings
// alias for changing DD settings
var current = await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).Select(x => x.DreamDaemonSettings).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
if (current == default)
@@ -171,7 +175,7 @@ namespace Tgstation.Server.Host.Controllers
property.SetValue(current, newVal);
return false;
};
}
var oldSoftRestart = current.SoftRestart;
var oldSoftShutdown = current.SoftShutdown;
@@ -190,9 +194,10 @@ namespace Tgstation.Server.Host.Controllers
return BadRequest(new ErrorMessage { Message = "Primary port and secondary port cannot be the same!" });
var wd = instanceManager.GetInstance(Instance).Watchdog;
await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
//run this second because current may be modified by it
// run this second because current may be modified by it
await wd.ChangeSettings(current, cancellationToken).ConfigureAwait(false);
if (!oldSoftRestart.Value && current.SoftRestart.Value)
@@ -20,12 +20,13 @@ namespace Tgstation.Server.Host.Controllers
/// Controller for managing the compiler
/// </summary>
[Route(Routes.DreamMaker)]
public sealed class DreamMakerController : ModelController<Api.Models.DreamMaker>
public sealed class DreamMakerController : ModelController<DreamMaker>
{
/// <summary>
/// The <see cref="IJobManager"/> for the <see cref="DreamMakerController"/>
/// </summary>
readonly IJobManager jobManager;
/// <summary>
/// The <see cref="IInstanceManager"/> for the <see cref="DreamMakerController"/>
/// </summary>
@@ -3,7 +3,6 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System;
using System.Linq;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Api;
@@ -23,18 +22,22 @@ namespace Tgstation.Server.Host.Controllers
/// The <see cref="ITokenFactory"/> for the <see cref="HomeController"/>
/// </summary>
readonly ITokenFactory tokenFactory;
/// <summary>
/// The <see cref="ISystemIdentityFactory"/> for the <see cref="HomeController"/>
/// </summary>
readonly ISystemIdentityFactory systemIdentityFactory;
/// <summary>
/// The <see cref="ICryptographySuite"/> for the <see cref="HomeController"/>
/// </summary>
readonly ICryptographySuite cryptographySuite;
/// <summary>
/// The <see cref="IApplication"/> for the <see cref="HomeController"/>
/// </summary>
readonly IApplication application;
/// <summary>
/// The <see cref="IIdentityCache"/> for the <see cref="HomeController"/>
/// </summary>
@@ -86,13 +89,14 @@ namespace Tgstation.Server.Host.Controllers
ISystemIdentity identity;
try
{
//trust the system over the database because a user's name can change while still having the same SID
// trust the system over the database because a user's name can change while still having the same SID
identity = await systemIdentityFactory.CreateSystemIdentity(ApiHeaders.Username, ApiHeaders.Password, cancellationToken).ConfigureAwait(false);
}
catch (NotImplementedException)
{
identity = null;
}
using (identity)
{
IQueryable<User> query;
@@ -127,7 +131,8 @@ namespace Tgstation.Server.Host.Controllers
await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
}
}
//check if the name changed and updoot accordingly
// check if the name changed and updoot accordingly
else if (identity.Username != user.Name)
{
DatabaseContext.Users.Attach(user);
@@ -142,7 +147,7 @@ namespace Tgstation.Server.Host.Controllers
var token = await tokenFactory.CreateToken(user, cancellationToken).ConfigureAwait(false);
if (identity != null)
{
//expire the identity slightly after the auth token in case of lag
// expire the identity slightly after the auth token in case of lag
var identExpiry = token.ExpiresAt.Value;
identExpiry += tokenFactory.ValidationParameters.ClockSkew;
identExpiry += TimeSpan.FromSeconds(15);
@@ -87,6 +87,7 @@ namespace Tgstation.Server.Host.Controllers
absolutePath = null;
return;
}
absolutePath = ioManager.ResolvePath(model.Path);
if (platformIdentifier.IsWindows)
model.Path = absolutePath.ToUpperInvariant();
@@ -130,7 +131,6 @@ namespace Tgstation.Server.Host.Controllers
if (rawPath.StartsWith(normalizedLocalPath, StringComparison.Ordinal))
return Conflict("Instances cannot be created in the installation directory!");
var dirExistsTask = ioManager.DirectoryExists(model.Path, cancellationToken);
bool attached = false;
if (await ioManager.FileExists(model.Path, cancellationToken).ConfigureAwait(false) || await dirExistsTask.ConfigureAwait(false))
@@ -171,8 +171,7 @@ namespace Tgstation.Server.Host.Controllers
AutoUpdatesKeepTestMerges = false,
AutoUpdatesSynchronize = false
},
//give this user full privileges on the instance
InstanceUsers = new List<Models.InstanceUser>
InstanceUsers = new List<Models.InstanceUser> // give this user full privileges on the instance
{
InstanceAdminUser()
}
@@ -185,13 +184,13 @@ namespace Tgstation.Server.Host.Controllers
try
{
//actually reserve it now
// actually reserve it now
await ioManager.CreateDirectory(rawPath, cancellationToken).ConfigureAwait(false);
await ioManager.DeleteFile(ioManager.ConcatPath(rawPath, InstanceAttachFileName), cancellationToken).ConfigureAwait(false);
}
catch
{
//oh shit delete the model
// oh shit delete the model
DatabaseContext.Instances.Remove(newInstance);
await DatabaseContext.Save(default).ConfigureAwait(false);
@@ -243,7 +242,7 @@ namespace Tgstation.Server.Host.Controllers
var attachFileName = ioManager.ConcatPath(originalModel.Path, InstanceAttachFileName);
await ioManager.WriteAllBytes(attachFileName, Array.Empty<byte>(), default).ConfigureAwait(false);
await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); //cascades everything
await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); // cascades everything
return Ok();
}
@@ -264,8 +263,7 @@ namespace Tgstation.Server.Host.Controllers
}).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
if (moveJob != default)
//cancel it now
await jobManager.CancelJob(moveJob, AuthenticationContext.User, true, cancellationToken).ConfigureAwait(false);
await jobManager.CancelJob(moveJob, AuthenticationContext.User, true, cancellationToken).ConfigureAwait(false); // cancel it now
var usersInstanceUserTask = instanceQuery.SelectMany(x => x.InstanceUsers).Where(x => x.UserId == AuthenticationContext.User.Id).FirstOrDefaultAsync(cancellationToken);
@@ -273,7 +271,7 @@ namespace Tgstation.Server.Host.Controllers
.Include(x => x.RepositorySettings)
.Include(x => x.ChatSettings)
.ThenInclude(x => x.Channels)
.Include(x => x.DreamDaemonSettings) //need these for onlining
.Include(x => x.DreamDaemonSettings) // need these for onlining
.FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
if (originalModel == default(Models.Instance))
return StatusCode((int)HttpStatusCode.Gone);
@@ -292,7 +290,7 @@ namespace Tgstation.Server.Host.Controllers
property.SetValue(originalModel, newVal);
return false;
};
}
string originalModelPath = null;
string rawPath = null;
@@ -326,7 +324,7 @@ namespace Tgstation.Server.Host.Controllers
|| CheckModified(x => x.Online, InstanceManagerRights.SetOnline))
return Forbid();
//ensure the current user has write privilege on the instance
// ensure the current user has write privilege on the instance
var usersInstanceUser = await usersInstanceUserTask.ConfigureAwait(false);
if (usersInstanceUser == default)
{
@@ -349,8 +347,8 @@ namespace Tgstation.Server.Host.Controllers
await instanceManager.OfflineInstance(originalModel, AuthenticationContext.User, cancellationToken).ConfigureAwait(false);
else if (!originalOnline && model.Online == true)
{
//force autostart false here because we don't want any long running jobs right now
//remember to document this
// force autostart false here because we don't want any long running jobs right now
// remember to document this
originalModel.DreamDaemonSettings.AutoStart = false;
await instanceManager.OnlineInstance(originalModel, cancellationToken).ConfigureAwait(false);
}
@@ -412,7 +410,7 @@ namespace Tgstation.Server.Host.Controllers
var apis = instances.Select(x => x.ToApi());
var moveJobs = await moveJobTasks.ConfigureAwait(false);
foreach(var I in moveJobs)
apis.Where(x => x.Id == I.Instance.Id).First().MoveJob = I.ToApi(); //if this .First() fails i will personally murder kevinz000 because I just know he is somehow responsible
apis.Where(x => x.Id == I.Instance.Id).First().MoveJob = I.ToApi(); // if this .First() fails i will personally murder kevinz000 because I just know he is somehow responsible
return Json(apis);
}
@@ -440,7 +438,7 @@ namespace Tgstation.Server.Host.Controllers
if (cantList && !instance.InstanceUsers.Any(x => x.UserId == AuthenticationContext.User.Id && x.AnyRights))
return Forbid();
var api = instance.ToApi();
api.MoveJob = (await moveJobTask.ConfigureAwait(false))?.ToApi();
return Json(api);
@@ -27,7 +27,7 @@ namespace Tgstation.Server.Host.Controllers
/// <param name="databaseContext">The <see cref="IDatabaseContext"/> for the <see cref="ApiController"/></param>
/// <param name="authenticationContextFactory">The <see cref="IAuthenticationContextFactory"/> for the <see cref="ApiController"/></param>
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="ApiController"/></param>
public InstanceUserController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ILogger<InstanceUserController> logger) : base(databaseContext, authenticationContextFactory, logger, true) //false instance requirement, we handle this ourself
public InstanceUserController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ILogger<InstanceUserController> logger) : base(databaseContext, authenticationContextFactory, logger, true) // false instance requirement, we handle this ourself
{ }
/// <summary>
@@ -116,7 +116,7 @@ namespace Tgstation.Server.Host.Controllers
[TgsAuthorize(InstanceUserRights.ReadUsers)]
public override async Task<IActionResult> GetId(long id, CancellationToken cancellationToken)
{
//this functions as userId
// this functions as userId
var user = await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).SelectMany(x => x.InstanceUsers).Where(x => x.UserId == id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
if (user == default)
return StatusCode((int)HttpStatusCode.Gone);
@@ -48,7 +48,7 @@ namespace Tgstation.Server.Host.Controllers
[TgsAuthorize]
public override async Task<IActionResult> List(CancellationToken cancellationToken)
{
//you KNOW this will need pagination eventually right?
// you KNOW this will need pagination eventually right?
var jobs = await DatabaseContext.Jobs.Where(x => x.Instance.Id == Instance.Id).OrderByDescending(x => x.StartedAt).Select(x => new Api.Models.Job
{
Id = x.Id
@@ -60,7 +60,7 @@ namespace Tgstation.Server.Host.Controllers
[TgsAuthorize]
public override async Task<IActionResult> Delete(long id, CancellationToken cancellationToken)
{
//don't care if an instance post or not at this point
// don't care if an instance post or not at this point
var job = await DatabaseContext.Jobs.Where(x => x.Id == id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
if (job == default(Job))
return NotFound();
@@ -83,24 +83,26 @@ namespace Tgstation.Server.Host.Controllers
var needsDbUpdate = revisionInfo == default;
if (needsDbUpdate)
{
//needs insertion
// needs insertion
revisionInfo = new Models.RevisionInformation
{
Instance = instance,
CommitSha = repoSha,
CompileJobs = new List<Models.CompileJob>(),
ActiveTestMerges = new List<RevInfoTestMerge>() //non null vals for api returns
ActiveTestMerges = new List<RevInfoTestMerge>() // non null vals for api returns
};
lock (databaseContext) //cleaner this way
lock (databaseContext) // cleaner this way
databaseContext.RevisionInformations.Add(revisionInfo);
}
revisionInfo.OriginCommitSha = revisionInfo.OriginCommitSha ?? lastOriginCommitSha;
if (revisionInfo.OriginCommitSha == null)
{
revisionInfo.OriginCommitSha = repoSha;
Logger.LogWarning(Components.Repository.Repository.OriginTrackingErrorTemplate, repoSha);
}
revInfoSink?.Invoke(revisionInfo);
return needsDbUpdate;
}
@@ -112,10 +114,11 @@ namespace Tgstation.Server.Host.Controllers
model.GitHubOwner = repository.GitHubOwner;
model.GitHubName = repository.GitHubRepoName;
}
model.Origin = repository.Origin;
model.Reference = repository.Reference;
//rev info stuff
// rev info stuff
Models.RevisionInformation revisionInfo = null;
var needsDbUpdate = await LoadRevisionInformation(repository, databaseContext, instance, null, x => revisionInfo = x, cancellationToken).ConfigureAwait(false);
model.RevisionInformation = revisionInfo.ToApi();
@@ -140,7 +143,7 @@ namespace Tgstation.Server.Host.Controllers
if (currentModel == default)
return StatusCode((int)HttpStatusCode.Gone);
//normalize github urls
// normalize github urls
const string BadGitHubUrl = "://www.github.com/";
var uiOrigin = model.Origin.ToUpperInvariant();
var uiBad = BadGitHubUrl.ToUpperInvariant();
@@ -149,7 +152,7 @@ namespace Tgstation.Server.Host.Controllers
model.Origin = uiOrigin.Replace(uiBad, uiGitHub, StringComparison.Ordinal);
currentModel.AccessToken = model.AccessToken;
currentModel.AccessUser = model.AccessUser; //intentionally only these fields, user not allowed to change anything else atm
currentModel.AccessUser = model.AccessUser; // intentionally only these fields, user not allowed to change anything else atm
var cloneBranch = model.Reference;
var origin = model.Origin;
@@ -169,8 +172,8 @@ namespace Tgstation.Server.Host.Controllers
using (var repo = await repoManager.LoadRepository(cancellationToken).ConfigureAwait(false))
{
// clone conflict
if (repo != null)
//clone conflict
return Conflict(new ErrorMessage
{
Message = "The repository already exists!"
@@ -269,10 +272,11 @@ namespace Tgstation.Server.Host.Controllers
{
if (repo != null && await PopulateApi(api, repo, DatabaseContext, Instance, cancellationToken).ConfigureAwait(false))
{
//user may have fucked with the repo without telling us, do what we can
// user may have fucked with the repo without telling us, do what we can
await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
return StatusCode((int)HttpStatusCode.Created, api);
}
return Json(api);
}
}
@@ -331,7 +335,7 @@ namespace Tgstation.Server.Host.Controllers
property.SetValue(currentModel, newVal);
return false;
};
}
if (CheckModified(x => x.AccessToken, RepositoryRights.ChangeCredentials)
|| CheckModified(x => x.AccessUser, RepositoryRights.ChangeCredentials)
@@ -346,7 +350,7 @@ namespace Tgstation.Server.Host.Controllers
if (currentModel.AccessToken?.Length == 0 && currentModel.AccessUser?.Length == 0)
{
//setting an empty string clears everything
// setting an empty string clears everything
currentModel.AccessUser = null;
currentModel.AccessToken = null;
}
@@ -380,11 +384,11 @@ namespace Tgstation.Server.Host.Controllers
await PopulateApi(api, repo, DatabaseContext, Instance, cancellationToken).ConfigureAwait(false);
}
}
//this is just db stuf so stow it away
// this is just db stuf so stow it away
await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
//format the job description
// format the job description
string description = null;
if (model.UpdateFromOrigin == true)
if (model.Reference != null)
@@ -397,16 +401,15 @@ namespace Tgstation.Server.Host.Controllers
description = String.Format(CultureInfo.InvariantCulture, "Checkout repository {0} {1}", model.Reference != null ? "reference" : "SHA", model.Reference ?? model.CheckoutSha);
if (newTestMerges)
description = String.Format(CultureInfo.InvariantCulture, "{0}est merge pull request(s) {1}{2}",
description = String.Format(CultureInfo.InvariantCulture, "{0}est merge pull request(s) {1}{2}",
description != null ? String.Format(CultureInfo.InvariantCulture, "{0} and t", description) : "T",
String.Join(", ", model.NewTestMerges.Select(x =>
String.Format(CultureInfo.InvariantCulture, "#{0}{1}", x.Number,
String.Join(", ", model.NewTestMerges.Select(x =>
String.Format(CultureInfo.InvariantCulture, "#{0}{1}", x.Number,
x.PullRequestRevision != null ? String.Format(CultureInfo.InvariantCulture, " at {0}", x.PullRequestRevision.Substring(0, 7)) : String.Empty))),
description != null ? String.Empty : " in repository");
if (description == null)
//no git changes
return Json(api);
return Json(api); // no git changes
var job = new Models.Job
{
@@ -444,14 +447,14 @@ namespace Tgstation.Server.Host.Controllers
{
var tmpDoneSteps = doneSteps;
++doneSteps;
return progress => progressReporter((progress + 100 * tmpDoneSteps) / numSteps);
};
return progress => progressReporter((progress + (100 * tmpDoneSteps)) / numSteps);
}
progressReporter(0);
//get a base line for where we are
// get a base line for where we are
Models.RevisionInformation lastRevisionInfo = null;
var attachedInstance = new Models.Instance
{
Id = Instance.Id
@@ -460,17 +463,17 @@ namespace Tgstation.Server.Host.Controllers
await LoadRevisionInformation(repo, databaseContext, attachedInstance, null, x => lastRevisionInfo = x, ct).ConfigureAwait(false);
//apply new rev info, tracking applied test merges
// apply new rev info, tracking applied test merges
async Task UpdateRevInfo()
{
var last = lastRevisionInfo;
await LoadRevisionInformation(repo, databaseContext, attachedInstance, last.OriginCommitSha, x => lastRevisionInfo = x, ct).ConfigureAwait(false);
lastRevisionInfo.ActiveTestMerges.AddRange(last.ActiveTestMerges);
};
}
try
{
//fetch/pull
// fetch/pull
if (model.UpdateFromOrigin == true)
{
if (!repo.Tracking)
@@ -494,7 +497,7 @@ namespace Tgstation.Server.Host.Controllers
}
}
//checkout/hard reset
// checkout/hard reset
if (modelHasShaOrReference)
{
if ((model.CheckoutSha != null && repo.Head.ToUpperInvariant().StartsWith(model.CheckoutSha.ToUpperInvariant(), StringComparison.Ordinal))
@@ -507,7 +510,7 @@ namespace Tgstation.Server.Host.Controllers
throw new JobException("Attempted to checkout a SHA or reference that was actually the opposite!");
await repo.CheckoutObject(committish, NextProgressReporter(), ct).ConfigureAwait(false);
await LoadRevisionInformation(repo, databaseContext, attachedInstance, null, x => lastRevisionInfo = x, ct).ConfigureAwait(false); //we've either seen origin before or what we're checking out is on origin
await LoadRevisionInformation(repo, databaseContext, attachedInstance, null, x => lastRevisionInfo = x, ct).ConfigureAwait(false); // we've either seen origin before or what we're checking out is on origin
}
else
NextProgressReporter()(100);
@@ -519,35 +522,36 @@ namespace Tgstation.Server.Host.Controllers
await repo.ResetToOrigin(NextProgressReporter(), ct).ConfigureAwait(false);
await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, currentModel.CommitterName, currentModel.CommitterEmail, NextProgressReporter(), true, ct).ConfigureAwait(false);
await LoadRevisionInformation(repo, databaseContext, attachedInstance, null, x => lastRevisionInfo = x, ct).ConfigureAwait(false);
//repo head is on origin so force this
//will update the db if necessary
// repo head is on origin so force this
// will update the db if necessary
lastRevisionInfo.OriginCommitSha = repo.Head;
}
}
// test merging
Dictionary<int, Octokit.PullRequest> prMap = null;
//test merging
if (newTestMerges)
{
//bit of sanitization
// bit of sanitization
foreach (var I in model.NewTestMerges.Where(x => String.IsNullOrWhiteSpace(x.PullRequestRevision)))
I.PullRequestRevision = null;
var gitHubClient = currentModel.AccessToken != null
? gitHubClientFactory.CreateClient(currentModel.AccessToken)
var gitHubClient = currentModel.AccessToken != null
? gitHubClientFactory.CreateClient(currentModel.AccessToken)
: (String.IsNullOrEmpty(generalConfiguration.GitHubAccessToken)
? gitHubClientFactory.CreateClient()
? gitHubClientFactory.CreateClient()
: gitHubClientFactory.CreateClient(generalConfiguration.GitHubAccessToken));
var repoOwner = repo.GitHubOwner;
var repoName = repo.GitHubRepoName;
// optimization: if we've already merged these exact same commits in this fashion before, just find the rev info for it and check it out
Models.RevisionInformation revInfoWereLookingFor = null;
bool needToApplyRemainingPrs = true;
//optimization: if we've already merged these exact same commits in this fashion before, just find the rev info for it and check it out
if (lastRevisionInfo.OriginCommitSha == lastRevisionInfo.CommitSha)
{
//In order for this to work though we need the shas of all the commits
// In order for this to work though we need the shas of all the commits
if (model.NewTestMerges.Any(x => x.PullRequestRevision == null))
prMap = new Dictionary<int, Octokit.PullRequest>();
@@ -555,14 +559,13 @@ namespace Tgstation.Server.Host.Controllers
foreach (var I in model.NewTestMerges)
{
if (I.PullRequestRevision != null)
//normalize the shas to lowercase ala libgit2
#pragma warning disable CA1308 // Normalize strings to uppercase
I.PullRequestRevision = I.PullRequestRevision?.ToLowerInvariant();
I.PullRequestRevision = I.PullRequestRevision?.ToLowerInvariant(); // ala libgit2
#pragma warning restore CA1308 // Normalize strings to uppercase
else
//retrieve the latest sha
try
{
// retrieve the latest sha
var pr = await gitHubClient.PullRequest.Get(repoOwner, repoName, I.Number.Value).ConfigureAwait(false);
prMap.Add(I.Number.Value, pr);
I.PullRequestRevision = pr.Head.Sha;
@@ -585,7 +588,7 @@ namespace Tgstation.Server.Host.Controllers
.ThenInclude(x => x.TestMerge)
.ToListAsync(cancellationToken).ConfigureAwait(false);
//split here cause this bit has to be done locally
// split here cause this bit has to be done locally
revInfoWereLookingFor = dbPull
.Where(x => x.ActiveTestMerges.Count == model.NewTestMerges.Count
&& x.ActiveTestMerges.Select(y => y.TestMerge)
@@ -597,7 +600,7 @@ namespace Tgstation.Server.Host.Controllers
if (revInfoWereLookingFor == null && model.NewTestMerges.Count > 1)
{
//okay try to add at least SOME prs we've seen before
// okay try to add at least SOME prs we've seen before
var search = model.NewTestMerges.ToList();
var appliedTestMergeIds = new List<long>();
@@ -623,7 +626,8 @@ namespace Tgstation.Server.Host.Controllers
break;
}
}
} while (revInfoWereLookingFor != null && search.Count > 0);
}
while (revInfoWereLookingFor != null && search.Count > 0);
revInfoWereLookingFor = lastGoodRevInfo;
needToApplyRemainingPrs = search.Count != 0;
@@ -637,14 +641,14 @@ namespace Tgstation.Server.Host.Controllers
if (revInfoWereLookingFor != null)
{
//goteem
// goteem
await repo.ResetToSha(revInfoWereLookingFor.CommitSha, NextProgressReporter(), cancellationToken).ConfigureAwait(false);
lastRevisionInfo = revInfoWereLookingFor;
}
if (needToApplyRemainingPrs)
{
//an invocation of LoadRevisionInformation could have already loaded this user
// an invocation of LoadRevisionInformation could have already loaded this user
var contextUser = databaseContext.Users.Local.Where(x => x.Id == AuthenticationContext.User.Id).FirstOrDefault();
if (contextUser == default)
{
@@ -667,13 +671,13 @@ namespace Tgstation.Server.Host.Controllers
try
{
//load from cache if possible
// load from cache if possible
if (prMap == null || !prMap.TryGetValue(I.Number.Value, out pr))
pr = await gitHubClient.PullRequest.Get(repoOwner, repoName, I.Number.Value).ConfigureAwait(false);
}
catch (Octokit.RateLimitExceededException)
{
//you look at your anonymous access and sigh
// you look at your anonymous access and sigh
errorMessage = "P.R.E. RATE LIMITED";
}
catch (Octokit.AuthorizationException)
@@ -682,11 +686,11 @@ namespace Tgstation.Server.Host.Controllers
}
catch (Octokit.NotFoundException)
{
//you look at your shithub and sigh
// you look at your shithub and sigh
errorMessage = "P.R.E. NOT FOUND";
}
//we want to take the earliest truth possible to prevent RCEs, if this fails AddTestMerge will set it
// we want to take the earliest truth possible to prevent RCEs, if this fails AddTestMerge will set it
if (I.PullRequestRevision == null && pr != null)
I.PullRequestRevision = pr.Head.Sha;
@@ -729,13 +733,15 @@ namespace Tgstation.Server.Host.Controllers
await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, currentModel.CommitterName, currentModel.CommitterEmail, NextProgressReporter(), false, ct).ConfigureAwait(false);
await UpdateRevInfo().ConfigureAwait(false);
}
await databaseContext.Save(ct).ConfigureAwait(false);
}
catch
{
doneSteps = 0;
numSteps = 2;
//the stuff didn't make it into the db, forget what we've done and abort
// the stuff didn't make it into the db, forget what we've done and abort
await repo.CheckoutObject(startReference ?? startSha, NextProgressReporter(), default).ConfigureAwait(false);
if (startReference != null && repo.Head != startSha)
await repo.ResetToSha(startSha, NextProgressReporter(), default).ConfigureAwait(false);
@@ -33,6 +33,7 @@ using Tgstation.Server.Host.Security;
namespace Tgstation.Server.Host.Core
{
/// <inheritdoc />
#pragma warning disable CA1506
sealed class Application : IApplication
{
/// <inheritdoc />
@@ -12,8 +12,10 @@ namespace Tgstation.Server.Host.Core
public interface IJobManager : IHostedService
{
/// <summary>
/// Get the <see cref="Api.Models.Job.Progress"/> for a job
/// Get the <see cref="Api.Models.Job.Progress"/> for a <paramref name="job"/>
/// </summary>
/// <param name="job">The <see cref="Job"/> to get <see cref="Api.Models.Job.Progress"/> for</param>
/// <returns>The <see cref="Api.Models.Job.Progress"/> of <paramref name="job"/></returns>
int? JobProgress(Job job);
/// <summary>
@@ -9,8 +9,8 @@
/// Launch a <see cref="IProcess"/>
/// </summary>
/// <param name="fileName">The full path to the executable file</param>
/// <param name="arguments">The arguments for the <see cref="IProcess"/></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. Ignored if <paramref name="readError"/> or <paramref name="readOutput"/> are set</param>
@@ -11,8 +11,8 @@ namespace Tgstation.Server.Host.Core
/// <summary>
/// Run the setup wizard if necessary
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in <see langword="true"/> if the wizard ran, <see langword="false"/> otherwise</returns>
Task<bool> CheckRunWizard(CancellationToken cancellationToken);
}
}
@@ -198,6 +198,7 @@ namespace Tgstation.Server.Host.Core
job.Cancelled = true;
job.StoppedAt = DateTimeOffset.Now;
}
await databaseContext.Save(cancellationToken).ConfigureAwait(false);
}
}).ConfigureAwait(false);
+11
View File
@@ -28,6 +28,16 @@ namespace Tgstation.Server.Host.Core
/// </summary>
readonly ILogger<Process> logger;
/// <summary>
/// Construct a <see cref="Process"/>
/// </summary>
/// <param name="handle">The value of <see cref="handle"/></param>
/// <param name="lifetime">The value of <see cref="Lifetime"/></param>
/// <param name="outputStringBuilder">The value of <see cref="outputStringBuilder"/></param>
/// <param name="errorStringBuilder">The value of <see cref="errorStringBuilder"/></param>
/// <param name="combinedStringBuilder">The value of <see cref="combinedStringBuilder"/></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(System.Diagnostics.Process handle, Task<int> lifetime, StringBuilder outputStringBuilder, StringBuilder errorStringBuilder, StringBuilder combinedStringBuilder, ILogger<Process> logger, bool preExisting)
{
this.handle = handle ?? throw new ArgumentNullException(nameof(handle));
@@ -103,6 +113,7 @@ namespace Tgstation.Server.Host.Core
}
}
/// <inheritdoc />
public void SetHighPriority()
{
try
@@ -180,6 +180,7 @@ namespace Tgstation.Server.Host.IO
cancellationToken.ThrowIfCancellationRequested();
results.Add(I);
}
return results;
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
@@ -238,6 +239,7 @@ namespace Tgstation.Server.Host.IO
results.Add(I);
cancellationToken.ThrowIfCancellationRequested();
}
return (IReadOnlyList<string>)results;
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
@@ -252,6 +254,7 @@ namespace Tgstation.Server.Host.IO
results.Add(I);
cancellationToken.ThrowIfCancellationRequested();
}
return (IReadOnlyList<string>)results;
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
@@ -1,5 +1,8 @@
namespace Tgstation.Server.Host.IO
{
/// <summary>
/// Handles changing file modes/permissions after writing
/// </summary>
interface IPostWriteHandler
{
/// <summary>
@@ -7,6 +7,7 @@ namespace Tgstation.Server.Host.Models.Migrations
/// <summary>
/// The initial database migration for MSSQL
/// </summary>
#pragma warning disable CA1506
public partial class MSInitialCreate : Migration
{
/// <summary>
@@ -7,6 +7,7 @@ namespace Tgstation.Server.Host.Models.Migrations
/// <summary>
/// The initial database migration for MySQL/MariaDB
/// </summary>
#pragma warning disable CA1506
public partial class MYInitialCreate : Migration
{
/// <summary>
@@ -28,7 +28,7 @@ namespace Tgstation.Server.Host.Models
/// <returns>A new <see cref="Repository"/></returns>
public Repository ToApi() => new Repository
{
// AccessToken = AccessToken, //never show this
// AccessToken = AccessToken, // never show this
AccessUser = AccessUser,
AutoUpdatesKeepTestMerges = AutoUpdatesKeepTestMerges,
AutoUpdatesSynchronize = AutoUpdatesSynchronize,
@@ -7,6 +7,10 @@ namespace Tgstation.Server.Host
/// <summary>
/// Native methods used by the code
/// </summary>
#pragma warning disable SA1600
#pragma warning disable SA1602
#pragma warning disable SA1611
#pragma warning disable SA1615
static class NativeMethods
{
/// <summary>
@@ -63,8 +63,8 @@ namespace Tgstation.Server.Host.Security
{
if (identity != null)
{
// var newIdentity = (WindowsIdentity)identity.Clone(); //doesn't work because of https://github.com/dotnet/corefx/issues/31841
var newIdentity = new WindowsIdentity(identity.Token); // the handle is cloned internally
// var newIdentity = (WindowsIdentity)identity.Clone(); //doesn't work because of https://github.com/dotnet/corefx/issues/31841
var newIdentity = new WindowsIdentity(identity.Token); // the handle is cloned internally
return new WindowsSystemIdentity(newIdentity);
}
@@ -77,6 +77,7 @@ namespace Tgstation.Server.Host.Security
cancellationToken.ThrowIfCancellationRequested();
}
}
return principal != null;
}
@@ -16,6 +16,7 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<NoWarn>1701;1702;SA1652</NoWarn>
<DocumentationFile>D:\tgstation-server\src\Tgstation.Server.Host\.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>