diff --git a/src/Tgstation.Server.Api/Models/ErrorCode.cs b/src/Tgstation.Server.Api/Models/ErrorCode.cs
index b3b6167889..ecb96a8c53 100644
--- a/src/Tgstation.Server.Api/Models/ErrorCode.cs
+++ b/src/Tgstation.Server.Api/Models/ErrorCode.cs
@@ -234,7 +234,7 @@ namespace Tgstation.Server.Api.Models
///
/// contained duplicate s.
///
- [Description("The same pull request was present more than once in the test merge requests!")]
+ [Description("The same pull request was present more than once in the test merge requests or is already merged!")]
RepoDuplicateTestMerge,
///
@@ -303,5 +303,161 @@ namespace Tgstation.Server.Api.Models
///
[Description("Set amount of chatChannels exceeds the configured channelLimit!")]
ChatBotMaxChannels,
+
+ ///
+ /// Failed to install DirectX with BYOND.
+ ///
+ [Description("Unable to start DirectX installer process! Is the server running with admin privileges?")]
+ ByondDirectXInstallFail,
+
+ ///
+ /// Failed to download a given BYOND version.
+ ///
+ [Description("Error downloading specified BYOND version!")]
+ ByondDownloadFail,
+
+ ///
+ /// Failed to lock BYOND executables.
+ ///
+ [Description("Could not acquire lock on BYOND installation as none exist!")]
+ ByondNoVersionsInstalled,
+
+ ///
+ /// The DMAPI never validated itself
+ ///
+ [Description("DreamDaemon exited without validating the DMAPI@")]
+ DreamMakerNeverValidated,
+
+ ///
+ /// The DMAPI sent an invalid validation request.
+ ///
+ [Description("The DMAPI sent an invalid validation request!")]
+ DreamMakerInvalidValidation,
+
+ ///
+ /// DMAPI validation timeout.
+ ///
+ [Description("The DreamDaemon startup timeout was hit before the DMAPI validated!")]
+ DreamMakerValidationTimeout,
+
+ ///
+ /// No .dme could be found for deployment.
+ ///
+ [Description("No .dme configured and could not automatically detect one!")]
+ DreamMakerNoDme,
+
+ ///
+ /// The configured .dme could not be found.
+ ///
+ [Description("Could not load configured .dme!")]
+ DreamMakerMissingDme,
+
+ ///
+ /// DreamMaker failed to compile.
+ ///
+ [Description("DreamMaker exited with a non-zero exit code!")]
+ DreamMakerExitCode,
+
+ ///
+ /// Deployment already in progress
+ ///
+ [Description("There is already a deployment operation in progress!")]
+ DreamMakerCompileJobInProgress,
+
+ ///
+ /// Missing settings in database.
+ ///
+ [Description("Could not retrieve DreamDaemon settings from the database!")]
+ InstanceMissingDreamDaemonSettings,
+
+ ///
+ /// Missing settings in database.
+ ///
+ [Description("Could not retrieve DreamMaker settings from the database!")]
+ InstanceMissingDreamMakerSettings,
+
+ ///
+ /// Missing settings in database.
+ ///
+ [Description("Could not retrieve Repository settings from the database!")]
+ InstanceMissingRepositorySettings,
+
+ ///
+ /// Performing an automatic update with the flag resulted in merge conflicts.
+ ///
+ [Description("Performing this automatic update as a merge would result in conficts. Aborting!")]
+ InstanceUpdateTestMergeConflict,
+
+ ///
+ /// and are required for this operation.
+ ///
+ [Description("Git credentials are required for this operation!")]
+ RepoCredentialsRequired,
+
+ ///
+ /// The remote returned an invalid authentication request.
+ ///
+ [Description("The remote is requesting authentication, but is not allowing credentials to be received!")]
+ RepoCannotAuthenticate,
+
+ ///
+ /// Cannot perform operation while not on a .
+ ///
+ [Description("This git operation requires the repository HEAD to currently be on a tracked reference!")]
+ RepoReferenceRequired,
+
+ ///
+ /// Attempted to start the watchdog when it was already running.
+ ///
+ [Description("The watchdog is already running!")]
+ WatchdogRunning,
+
+ ///
+ /// Attempted to start the watchdog with a corrupted .
+ ///
+ [Description("Cannot launch with active compile job as it is corrupted!")]
+ WatchdogCompileJobCorrupted,
+
+ ///
+ /// DreamDaemon exited before it finished starting.
+ ///
+ [Description("DreamDaemon failed to start!")]
+ WatchdogStartupFailed,
+
+ ///
+ /// DreamDaemon timed-out before it finished starting.
+ ///
+ [Description("DreamDaemon failed to start within the configured timeout!")]
+ WatchdogStartupTimeout,
+
+ ///
+ /// Attempted to test merge with an unsupported remote.
+ ///
+ [Description("Test merging with the current remote is not supported!")]
+ RepoUnsupportedTestMergeRemote,
+
+ ///
+ /// Either or was in one when it should have been the other.
+ ///
+ [Description("The value set for checkoutSha or reference should be in the other field!")]
+ RepoSwappedShaOrReference,
+
+ ///
+ /// A merge conflict occurred during a git operation.
+ ///
+ [Description("A merge conflict occurred while performing the operation!")]
+ RepoMergeConflict,
+
+ ///
+ /// The current does not track a remote reference.
+ ///
+ [Description("The repository's current reference is unsuitable for this operation as it does not track a remote reference!")]
+ RepoReferenceNotTracking,
+
+ ///
+ /// Encounted merge conflicts while test merging.
+ ///
+ [Description("Encountered merge conflicts while test merging one or more pull requests!")]
+ RepoTestMergeConflict,
}
}
\ No newline at end of file
diff --git a/src/Tgstation.Server.Api/Models/Internal/Job.cs b/src/Tgstation.Server.Api/Models/Internal/Job.cs
index 945d0d6aaa..3e5656e56e 100644
--- a/src/Tgstation.Server.Api/Models/Internal/Job.cs
+++ b/src/Tgstation.Server.Api/Models/Internal/Job.cs
@@ -15,6 +15,11 @@ namespace Tgstation.Server.Api.Models.Internal
[Required]
public string Description { get; set; }
+ ///
+ /// The associated with the if any.
+ ///
+ public ErrorCode? ErrorCode { get; set; }
+
///
/// Details of any exceptions caught during the
///
diff --git a/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs b/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs
index 38c57a09d0..1af10cf0e2 100644
--- a/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs
+++ b/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs
@@ -1,13 +1,13 @@
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
-using System.Globalization;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Api;
+using Tgstation.Server.Api.Models;
using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.Jobs;
@@ -162,7 +162,7 @@ namespace Tgstation.Server.Host.Components.Byond
catch (WebException e)
{
// since the user can easily provide non-exitent version numbers, we'll turn this into a JobException
- throw new JobException(String.Format(CultureInfo.InvariantCulture, "Error downloading BYOND version: {0}", e.Message));
+ throw new JobException(ErrorCode.ByondDownloadFail, e);
}
catch (OperationCanceledException)
{
@@ -207,7 +207,7 @@ namespace Tgstation.Server.Host.Components.Byond
{
var versionToUse = requiredVersion ?? ActiveVersion;
if (versionToUse == null)
- throw new JobException("No BYOND versions installed!");
+ throw new JobException(ErrorCode.ByondNoVersionsInstalled);
await InstallVersion(versionToUse, cancellationToken).ConfigureAwait(false);
var versionKey = VersionKey(versionToUse);
diff --git a/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs b/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs
index 7f26bfac61..a2ea8589dc 100644
--- a/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs
+++ b/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs
@@ -1,9 +1,9 @@
using Microsoft.Extensions.Logging;
using System;
-using System.Globalization;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using Tgstation.Server.Api.Models;
using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.Jobs;
@@ -114,7 +114,7 @@ namespace Tgstation.Server.Host.Components.Byond
}
catch (Exception e)
{
- throw new JobException("Unable to start DirectX installer process! Is the server running with admin privileges?", e);
+ throw new JobException(ErrorCode.ByondDirectXInstallFail, e);
}
using (directXInstaller)
@@ -125,7 +125,7 @@ namespace Tgstation.Server.Host.Components.Byond
cancellationToken.ThrowIfCancellationRequested();
if (exitCode != 0)
- throw new JobException(String.Format(CultureInfo.InvariantCulture, "Failed to install included DirectX! Exit code: {0}", exitCode));
+ throw new JobException(ErrorCode.ByondDirectXInstallFail, new JobException($"Invalid exit code: {exitCode}"));
installedDirectX = true;
}
}
diff --git a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs
index ae05724d6b..0a7a26153a 100644
--- a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs
+++ b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs
@@ -193,34 +193,31 @@ namespace Tgstation.Server.Host.Components.Deployment
{
var validationStatus = controller.ApiValidationStatus;
logger.LogTrace("API validation status: {0}", validationStatus);
+
+ job.DMApiVersion = controller.DMApiVersion;
switch (validationStatus)
{
case ApiValidationStatus.RequiresUltrasafe:
job.MinimumSecurityLevel = DreamDaemonSecurity.Ultrasafe;
return;
case ApiValidationStatus.RequiresSafe:
- if (securityLevel == DreamDaemonSecurity.Ultrasafe)
- throw new JobException("This game must be run with at least the 'Safe' DreamDaemon security level!");
job.MinimumSecurityLevel = DreamDaemonSecurity.Safe;
return;
case ApiValidationStatus.RequiresTrusted:
- if (securityLevel != DreamDaemonSecurity.Trusted)
- throw new JobException("This game must be run with at least the 'Trusted' DreamDaemon security level!");
job.MinimumSecurityLevel = DreamDaemonSecurity.Trusted;
return;
case ApiValidationStatus.NeverValidated:
- break;
+ throw new JobException(ErrorCode.DreamMakerNeverValidated);
case ApiValidationStatus.BadValidationRequest:
- throw new JobException("Recieved an unrecognized API validation request from DreamDaemon!");
+ throw new JobException(ErrorCode.DreamMakerInvalidValidation);
case ApiValidationStatus.UnaskedValidationRequest:
default:
- throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Session controller returned unexpected ApiValidationStatus: {0}", validationStatus));
+ throw new InvalidOperationException(
+ $"Session controller returned unexpected ApiValidationStatus: {validationStatus}");
}
-
- job.DMApiVersion = controller.DMApiVersion;
}
- throw new JobException("DMAPI validation timed out! Is the security level too high?");
+ throw new JobException(ErrorCode.DreamMakerValidationTimeout);
}
}
@@ -395,7 +392,7 @@ namespace Tgstation.Server.Host.Components.Deployment
var foundPaths = await ioManager.GetFilesWithExtension(dirA, DmeExtension, cancellationToken).ConfigureAwait(false);
var foundPath = foundPaths.FirstOrDefault();
if (foundPath == default)
- throw new JobException("Unable to find any .dme!");
+ throw new JobException(ErrorCode.DreamMakerNoDme);
var dmeWithExtension = ioManager.GetFileName(foundPath);
job.DmeName = dmeWithExtension.Substring(0, dmeWithExtension.Length - DmeExtension.Length - 1);
}
@@ -404,7 +401,7 @@ namespace Tgstation.Server.Host.Components.Deployment
var targetDme = ioManager.ConcatPath(dirA, String.Join('.', job.DmeName, DmeExtension));
var targetDmeExists = await ioManager.FileExists(targetDme, cancellationToken).ConfigureAwait(false);
if (!targetDmeExists)
- throw new JobException("Unable to locate specified .dme!");
+ throw new JobException(ErrorCode.DreamMakerMissingDme);
}
logger.LogDebug("Selected {0}.dme for compilation!", job.DmeName);
@@ -418,7 +415,9 @@ namespace Tgstation.Server.Host.Components.Deployment
try
{
if (exitCode != 0)
- throw new JobException(String.Format(CultureInfo.InvariantCulture, "DM exited with a non-zero code: {0}{1}{2}", exitCode, Environment.NewLine, job.Output));
+ throw new JobException(
+ ErrorCode.DreamMakerExitCode,
+ new JobException($"Exit code: {exitCode}{Environment.NewLine}{Environment.NewLine}{job.Output}"));
await VerifyApi(apiValidateTimeout, dreamMakerSettings.ApiValidationSecurityLevel.Value, job, byondLock, dreamMakerSettings.ApiValidationPort.Value, cancellationToken).ConfigureAwait(false);
}
@@ -476,7 +475,7 @@ namespace Tgstation.Server.Host.Components.Deployment
lock (this)
{
if (compiling)
- throw new JobException("There is already a compile job in progress!");
+ throw new JobException(ErrorCode.DreamMakerCompileJobInProgress);
compiling = true;
}
diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs
index 6dba85ae04..34d582a381 100644
--- a/src/Tgstation.Server.Host/Components/Instance.cs
+++ b/src/Tgstation.Server.Host/Components/Instance.cs
@@ -169,13 +169,13 @@ namespace Tgstation.Server.Host.Components
throw new ArgumentNullException(nameof(progressReporter));
var ddSettings = await databaseContext.DreamDaemonSettings.Where(x => x.InstanceId == metadata.Id).Select(x => new DreamDaemonSettings
- {
- StartupTimeout = x.StartupTimeout,
- })
+ {
+ StartupTimeout = x.StartupTimeout,
+ })
.FirstOrDefaultAsync(cancellationToken)
.ConfigureAwait(false);
if (ddSettings == default)
- throw new JobException("Missing DreamDaemonSettings in DB!");
+ throw new JobException(Api.Models.ErrorCode.InstanceMissingDreamDaemonSettings);
var previousCompileJobs = await databaseContext.CompileJobs
.Where(x => x.Job.Instance.Id == metadata.Id)
@@ -191,18 +191,17 @@ namespace Tgstation.Server.Host.Components
var dreamMakerSettings = await databaseContext.DreamMakerSettings.Where(x => x.InstanceId == metadata.Id).FirstAsync(cancellationToken).ConfigureAwait(false);
if (dreamMakerSettings == default)
- throw new JobException("Missing DreamMakerSettings in DB!");
+ throw new JobException(Api.Models.ErrorCode.InstanceMissingDreamMakerSettings);
RepositorySettings repositorySettings = null;
string repoOwner = null;
string repoName = null;
CompileJob compileJob;
RevisionInformation revInfo;
- bool loadedRepositorySettings;
using (var repo = await RepositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false))
{
if (repo == null)
- throw new JobException("Missing Repository!");
+ throw new JobException(Api.Models.ErrorCode.RepoMissing);
if (repo.IsGitHubRepository)
{
@@ -219,10 +218,9 @@ namespace Tgstation.Server.Host.Components
})
.FirstOrDefaultAsync(cancellationToken)
.ConfigureAwait(false);
- loadedRepositorySettings = true;
+ if (repositorySettings == default)
+ throw new JobException(Api.Models.ErrorCode.InstanceMissingRepositorySettings);
}
- else
- loadedRepositorySettings = false;
var repoSha = repo.Head;
revInfo = await databaseContext.RevisionInformations.Where(x => x.CommitSha == repoSha && x.Instance.Id == metadata.Id).Include(x => x.ActiveTestMerges).ThenInclude(x => x.TestMerge).ThenInclude(x => x.MergedBy).FirstOrDefaultAsync().ConfigureAwait(false);
@@ -243,7 +241,7 @@ namespace Tgstation.Server.Host.Components
}
TimeSpan? averageSpan = null;
- if(previousCompileJobs.Count != 0)
+ if (previousCompileJobs.Count != 0)
{
var totalSpan = TimeSpan.Zero;
foreach (var I in previousCompileJobs)
@@ -260,80 +258,74 @@ namespace Tgstation.Server.Host.Components
job.PostComplete = ct => compileJobConsumer.LoadCompileJob(compileJob, ct);
- if (loadedRepositorySettings)
+ if (repositorySettings?.AccessToken != null)
{
- if (repositorySettings == default)
- throw new JobException("Missing repository settings!");
+ // potential for commenting on a test merge change
+ var outgoingCompileJob = LatestCompileJob();
- if (repositorySettings.AccessToken != null)
+ if (outgoingCompileJob != null && outgoingCompileJob.RevisionInformation.CommitSha != compileJob.RevisionInformation.CommitSha && repositorySettings.PostTestMergeComment.Value)
{
- // potential for commenting on a test merge change
- var outgoingCompileJob = LatestCompileJob();
+ var gitHubClient = gitHubClientFactory.CreateClient(repositorySettings.AccessToken);
- if(outgoingCompileJob != null && outgoingCompileJob.RevisionInformation.CommitSha != compileJob.RevisionInformation.CommitSha && repositorySettings.PostTestMergeComment.Value)
+ async Task CommentOnPR(int prNumber, string comment)
{
- var gitHubClient = gitHubClientFactory.CreateClient(repositorySettings.AccessToken);
-
- async Task CommentOnPR(int prNumber, string comment)
+ try
{
- try
- {
- await gitHubClient.Issue.Comment.Create(repoOwner, repoName, prNumber, comment).ConfigureAwait(false);
- }
- catch (ApiException e)
- {
- logger.LogWarning("Error posting GitHub comment! Exception: {0}", e);
- }
+ await gitHubClient.Issue.Comment.Create(repoOwner, repoName, prNumber, comment).ConfigureAwait(false);
+ }
+ catch (ApiException e)
+ {
+ logger.LogWarning("Error posting GitHub comment! Exception: {0}", e);
}
-
- var tasks = new List();
-
- string FormatTestMerge(TestMerge testMerge, bool updated) => String.Format(CultureInfo.InvariantCulture, "#### Test Merge {4}{0}{0}##### Server Instance{0}{5}{1}{0}{0}##### Revision{0}Origin: {6}{0}Pull Request: {2}{0}Server: {7}{3}",
- Environment.NewLine,
- repositorySettings.ShowTestMergeCommitters.Value ? String.Format(CultureInfo.InvariantCulture, "{0}{0}##### Merged By{0}{1}", Environment.NewLine, testMerge.MergedBy.Name) : String.Empty,
- testMerge.PullRequestRevision,
- testMerge.Comment != null ? String.Format(CultureInfo.InvariantCulture, "{0}{0}##### Comment{0}{1}", Environment.NewLine, testMerge.Comment) : String.Empty,
- updated ? "Updated" : "Deployed",
- metadata.Name,
- compileJob.RevisionInformation.OriginCommitSha,
- compileJob.RevisionInformation.CommitSha);
-
- // added prs
- foreach (var I in compileJob
- .RevisionInformation
- .ActiveTestMerges
- .Select(x => x.TestMerge)
- .Where(x => !outgoingCompileJob
- .RevisionInformation
- .ActiveTestMerges
- .Any(y => y.TestMerge.Number == x.Number)))
- tasks.Add(CommentOnPR(I.Number, FormatTestMerge(I, false)));
-
- // removed prs
- foreach (var I in outgoingCompileJob
- .RevisionInformation
- .ActiveTestMerges
- .Select(x => x.TestMerge)
- .Where(x => !compileJob
- .RevisionInformation
- .ActiveTestMerges
- .Any(y => y.TestMerge.Number == x.Number)))
- tasks.Add(CommentOnPR(I.Number, "#### Test Merge Removed"));
-
- // updated prs
- foreach(var I in compileJob
- .RevisionInformation
- .ActiveTestMerges
- .Select(x => x.TestMerge)
- .Where(x => outgoingCompileJob
- .RevisionInformation
- .ActiveTestMerges
- .Any(y => y.TestMerge.Number == x.Number)))
- tasks.Add(CommentOnPR(I.Number, FormatTestMerge(I, true)));
-
- if (tasks.Any())
- await Task.WhenAll(tasks).ConfigureAwait(false);
}
+
+ var tasks = new List();
+
+ string FormatTestMerge(TestMerge testMerge, bool updated) => String.Format(CultureInfo.InvariantCulture, "#### Test Merge {4}{0}{0}##### Server Instance{0}{5}{1}{0}{0}##### Revision{0}Origin: {6}{0}Pull Request: {2}{0}Server: {7}{3}",
+ Environment.NewLine,
+ repositorySettings.ShowTestMergeCommitters.Value ? String.Format(CultureInfo.InvariantCulture, "{0}{0}##### Merged By{0}{1}", Environment.NewLine, testMerge.MergedBy.Name) : String.Empty,
+ testMerge.PullRequestRevision,
+ testMerge.Comment != null ? String.Format(CultureInfo.InvariantCulture, "{0}{0}##### Comment{0}{1}", Environment.NewLine, testMerge.Comment) : String.Empty,
+ updated ? "Updated" : "Deployed",
+ metadata.Name,
+ compileJob.RevisionInformation.OriginCommitSha,
+ compileJob.RevisionInformation.CommitSha);
+
+ // added prs
+ foreach (var I in compileJob
+ .RevisionInformation
+ .ActiveTestMerges
+ .Select(x => x.TestMerge)
+ .Where(x => !outgoingCompileJob
+ .RevisionInformation
+ .ActiveTestMerges
+ .Any(y => y.TestMerge.Number == x.Number)))
+ tasks.Add(CommentOnPR(I.Number, FormatTestMerge(I, false)));
+
+ // removed prs
+ foreach (var I in outgoingCompileJob
+ .RevisionInformation
+ .ActiveTestMerges
+ .Select(x => x.TestMerge)
+ .Where(x => !compileJob
+ .RevisionInformation
+ .ActiveTestMerges
+ .Any(y => y.TestMerge.Number == x.Number)))
+ tasks.Add(CommentOnPR(I.Number, "#### Test Merge Removed"));
+
+ // updated prs
+ foreach (var I in compileJob
+ .RevisionInformation
+ .ActiveTestMerges
+ .Select(x => x.TestMerge)
+ .Where(x => outgoingCompileJob
+ .RevisionInformation
+ .ActiveTestMerges
+ .Any(y => y.TestMerge.Number == x.Number)))
+ tasks.Add(CommentOnPR(I.Number, FormatTestMerge(I, true)));
+
+ if (tasks.Any())
+ await Task.WhenAll(tasks).ConfigureAwait(false);
}
}
}
@@ -464,7 +456,7 @@ namespace Tgstation.Server.Host.Components
var result = await repo.MergeOrigin(repositorySettings.CommitterName, repositorySettings.CommitterEmail, NextProgressReporter(), jobCancellationToken).ConfigureAwait(false);
if (!result.HasValue)
- throw new JobException("Merge conflict while preserving test merges!");
+ throw new JobException(Api.Models.ErrorCode.InstanceUpdateTestMergeConflict);
currentRevInfo = await currentRevInfoTask.ConfigureAwait(false);
diff --git a/src/Tgstation.Server.Host/Components/Repository/LibGit2RepositoryFactory.cs b/src/Tgstation.Server.Host/Components/Repository/LibGit2RepositoryFactory.cs
index 3d4663dde3..69059f93cc 100644
--- a/src/Tgstation.Server.Host/Components/Repository/LibGit2RepositoryFactory.cs
+++ b/src/Tgstation.Server.Host/Components/Repository/LibGit2RepositoryFactory.cs
@@ -4,6 +4,7 @@ using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;
+using Tgstation.Server.Api.Models;
using Tgstation.Server.Host.Jobs;
namespace Tgstation.Server.Host.Components.Repository
@@ -93,9 +94,9 @@ namespace Tgstation.Server.Host.Components.Repository
return new DefaultCredentials();
if (supportsUserPass)
- throw new JobException("Remote does not support anonymous authentication!");
+ throw new JobException(ErrorCode.RepoCredentialsRequired);
- throw new JobException("Server does not support anonymous or username/password authentication!");
+ throw new JobException(ErrorCode.RepoCannotAuthenticate);
};
}
}
diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs
index 6a1c0abf2d..947e82cb77 100644
--- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs
+++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs
@@ -465,7 +465,7 @@ namespace Tgstation.Server.Host.Components.Repository
if (progressReporter == null)
throw new ArgumentNullException(nameof(progressReporter));
if (!Tracking)
- throw new JobException("Cannot reset to origin while not on a tracked reference!");
+ throw new JobException(ErrorCode.RepoReferenceRequired);
logger.LogTrace("Reset to origin...");
var trackedBranch = libGitRepo.Head.TrackedBranch;
await eventConsumer.HandleEvent(EventType.RepoResetOrigin, new List { trackedBranch.FriendlyName, trackedBranch.Tip.Sha }, cancellationToken).ConfigureAwait(false);
@@ -521,7 +521,7 @@ namespace Tgstation.Server.Host.Components.Repository
await Task.Factory.StartNew(() =>
{
if (!Tracking)
- throw new JobException("Cannot reset to origin while not on a tracked reference!");
+ throw new JobException(ErrorCode.RepoReferenceRequired);
libGitRepo.RemoveUntrackedFiles();
diff --git a/src/Tgstation.Server.Host/Components/Watchdog/ExperimentalWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/ExperimentalWatchdog.cs
index d83712d943..333582ed73 100644
--- a/src/Tgstation.Server.Host/Components/Watchdog/ExperimentalWatchdog.cs
+++ b/src/Tgstation.Server.Host/Components/Watchdog/ExperimentalWatchdog.cs
@@ -185,7 +185,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
var dmbBackup = await DmbFactory.FromCompileJob(monitorState.ActiveServer.Dmb.CompileJob, cancellationToken).ConfigureAwait(false);
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
+ throw new InvalidOperationException("Watchdog double crit-fail!"); // 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(
dmbBackup,
diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs
index 2b61522029..b3877611ec 100644
--- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs
+++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs
@@ -4,10 +4,10 @@ using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Diagnostics;
-using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Internal;
using Tgstation.Server.Api.Rights;
using Tgstation.Server.Host.Components.Chat;
@@ -253,10 +253,10 @@ namespace Tgstation.Server.Host.Components.Watchdog
Logger.LogTrace("Begin LaunchNoLock");
if (Running)
- throw new JobException("Watchdog already running!");
+ throw new JobException(ErrorCode.WatchdogRunning);
if (!DmbFactory.DmbAvailable)
- throw new JobException("Corrupted compilation, please redeploy!");
+ throw new JobException(ErrorCode.WatchdogCompileJobCorrupted);
// 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;
@@ -371,9 +371,13 @@ namespace Tgstation.Server.Host.Components.Watchdog
// Dead sessions won't trigger this
if (launchResult.ExitCode.HasValue) // you killed us ray...
- throw new JobException(String.Format(CultureInfo.InvariantCulture, "{0} failed to start: {1}", serverName, launchResult));
+ throw new JobException(
+ ErrorCode.WatchdogStartupFailed,
+ new JobException($"{serverName} failed to start: {launchResult}"));
if (!launchResult.StartupTime.HasValue)
- throw new JobException(String.Format(CultureInfo.InvariantCulture, "{0} timed out on startup: {1}s", serverName, ActiveLaunchParameters.StartupTimeout.Value));
+ throw new JobException(
+ ErrorCode.WatchdogStartupTimeout,
+ new JobException($"{serverName} timed out on startup: {ActiveLaunchParameters.StartupTimeout.Value}s"));
}
///
diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs
index f2006ed345..3e9fa3fedc 100644
--- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs
+++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs
@@ -198,7 +198,7 @@ namespace Tgstation.Server.Host.Controllers
using (var repos = await repoManager.CloneRepository(new Uri(origin), cloneBranch, currentModel.AccessUser, currentModel.AccessToken, progressReporter, ct).ConfigureAwait(false))
{
if (repos == null)
- throw new JobException("Filesystem conflict while cloning repository!");
+ throw new JobException(ErrorCode.RepoExists);
var instance = new Models.Instance
{
Id = Instance.Id
@@ -447,7 +447,7 @@ namespace Tgstation.Server.Host.Controllers
using (var repo = await repoManager.LoadRepository(ct).ConfigureAwait(false))
{
if (repo == null)
- throw new JobException("Repository could not be loaded!");
+ throw new JobException(ErrorCode.RepoMissing);
var modelHasShaOrReference = model.CheckoutSha != null || model.Reference != null;
@@ -456,7 +456,7 @@ namespace Tgstation.Server.Host.Controllers
string postUpdateSha = null;
if (newTestMerges && !repo.IsGitHubRepository)
- throw new JobException("Cannot test merge on a non GitHub based repository!");
+ throw new JobException(ErrorCode.RepoUnsupportedTestMergeRemote);
var committerName = currentModel.ShowTestMergeCommitters.Value
? AuthenticationContext.User.Name
@@ -501,14 +501,14 @@ namespace Tgstation.Server.Host.Controllers
if (model.UpdateFromOrigin == true)
{
if (!repo.Tracking)
- throw new JobException("Not on an updatable reference!");
+ throw new JobException(ErrorCode.RepoReferenceRequired);
await repo.FetchOrigin(currentModel.AccessUser, currentModel.AccessToken, NextProgressReporter(), ct).ConfigureAwait(false);
doneSteps = 1;
if (!modelHasShaOrReference)
{
var fastForward = await repo.MergeOrigin(committerName, currentModel.CommitterEmail, NextProgressReporter(), ct).ConfigureAwait(false);
if (!fastForward.HasValue)
- throw new JobException("Merge conflict occurred during origin update!");
+ throw new JobException(ErrorCode.RepoMergeConflict);
await UpdateRevInfo().ConfigureAwait(false);
if (fastForward.Value)
{
@@ -531,7 +531,7 @@ namespace Tgstation.Server.Host.Controllers
var isSha = await repo.IsSha(committish, cancellationToken).ConfigureAwait(false);
if ((isSha && model.Reference != null) || (!isSha && model.CheckoutSha != null))
- throw new JobException("Attempted to checkout a SHA or reference that was actually the opposite!");
+ throw new JobException(ErrorCode.RepoSwappedShaOrReference);
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
@@ -542,7 +542,7 @@ namespace Tgstation.Server.Host.Controllers
if (hardResettingToOriginReference)
{
if (!repo.Tracking)
- throw new JobException("Checked out reference does not track a remote object!");
+ throw new JobException(ErrorCode.RepoReferenceNotTracking);
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);
@@ -692,29 +692,36 @@ namespace Tgstation.Server.Host.Controllers
string errorMessage = null;
if (lastRevisionInfo.ActiveTestMerges.Any(x => x.TestMerge.Number == I.Number))
- throw new JobException("Cannot test merge the same PR twice in one HEAD!");
+ throw new JobException(ErrorCode.RepoDuplicateTestMerge);
+ Exception exception = null;
try
{
// load from cache if possible
if (prMap == null || !prMap.TryGetValue(I.Number, out pr))
pr = await gitHubClient.PullRequest.Get(repoOwner, repoName, I.Number).ConfigureAwait(false);
}
- catch (Octokit.RateLimitExceededException)
+ catch (Octokit.RateLimitExceededException ex)
{
// you look at your anonymous access and sigh
- errorMessage = "P.R.E. RATE LIMITED";
+ errorMessage = "REMOTE API ERROR: RATE LIMITED";
+ exception = ex;
}
- catch (Octokit.AuthorizationException)
+ catch (Octokit.AuthorizationException ex)
{
- errorMessage = "P.R.E. BAD CREDENTIALS";
+ errorMessage = "REMOTE API ERROR: BAD CREDENTIALS";
+ exception = ex;
}
- catch (Octokit.NotFoundException)
+ catch (Octokit.NotFoundException ex)
{
// you look at your shithub and sigh
- errorMessage = "P.R.E. NOT FOUND";
+ errorMessage = "REMOTE API ERROR: PULL REQUEST NOT FOUND";
+ exception = ex;
}
+ if (exception != null)
+ Logger.LogWarning("Error retrieving pull request metadata: {0}", exception);
+
// 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,7 +736,10 @@ namespace Tgstation.Server.Host.Controllers
ct).ConfigureAwait(false);
if (!mergeResult.HasValue)
- throw new JobException(String.Format(CultureInfo.InvariantCulture, "Merge of PR #{0} at {1} conflicted!", I.Number, I.PullRequestRevision.Substring(0, 7)));
+ throw new JobException(
+ ErrorCode.RepoTestMergeConflict,
+ new JobException(
+ $"Merge of PR #{I.Number} at {I.PullRequestRevision.Substring(0, 7)} conflicted!"));
++doneSteps;
diff --git a/src/Tgstation.Server.Host/Jobs/JobException.cs b/src/Tgstation.Server.Host/Jobs/JobException.cs
index 40469a0e59..55f2bb97d5 100644
--- a/src/Tgstation.Server.Host/Jobs/JobException.cs
+++ b/src/Tgstation.Server.Host/Jobs/JobException.cs
@@ -1,4 +1,5 @@
using System;
+using Tgstation.Server.Api.Models;
namespace Tgstation.Server.Host.Jobs
{
@@ -7,6 +8,11 @@ namespace Tgstation.Server.Host.Jobs
///
public sealed class JobException : Exception
{
+ ///
+ /// The associated with the .
+ ///
+ public ErrorCode? ErrorCode { get; set; }
+
///
/// Construct a
///
@@ -30,5 +36,22 @@ namespace Tgstation.Server.Host.Jobs
public JobException(string message, Exception innerException) : base(message, innerException)
{
}
+
+ ///
+ /// Construct a with a .
+ ///
+ /// The associated .
+ public JobException(ErrorCode errorCode) : base(errorCode.Describe())
+ {
+ }
+
+ ///
+ /// Construct a with a and .
+ ///
+ /// The associated .
+ /// The inner for the nase
+ public JobException(ErrorCode errorCode, Exception innerException) : base(errorCode.Describe(), innerException)
+ {
+ }
}
}
diff --git a/src/Tgstation.Server.Host/Jobs/JobManager.cs b/src/Tgstation.Server.Host/Jobs/JobManager.cs
index 677cb36238..055748b4a0 100644
--- a/src/Tgstation.Server.Host/Jobs/JobManager.cs
+++ b/src/Tgstation.Server.Host/Jobs/JobManager.cs
@@ -89,10 +89,16 @@ namespace Tgstation.Server.Host.Jobs
}
catch (JobException e)
{
+ job.ErrorCode = e.ErrorCode;
job.ExceptionDetails = e.Message;
LogRegularException();
if (e.InnerException != null)
- logger.LogDebug("Inner exception for job {0}: {1}", job.Id, e.InnerException);
+ logger.LogDebug(
+ "Inner exception for job {0}: {1}",
+ job.Id,
+ e.InnerException is JobException
+ ? e.InnerException.Message
+ : e.InnerException.ToString());
}
catch (Exception e)
{