Add ErrorCodes to all JobExceptions

This commit is contained in:
Jordan Brown
2020-04-24 22:43:03 -04:00
parent c7d7e14cbf
commit 08edd075e1
13 changed files with 321 additions and 125 deletions
+157 -1
View File
@@ -234,7 +234,7 @@ namespace Tgstation.Server.Api.Models
/// <summary>
/// <see cref="Repository.NewTestMerges"/> contained duplicate <see cref="TestMergeParameters.Number"/>s.
/// </summary>
[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,
/// <summary>
@@ -303,5 +303,161 @@ namespace Tgstation.Server.Api.Models
/// </summary>
[Description("Set amount of chatChannels exceeds the configured channelLimit!")]
ChatBotMaxChannels,
/// <summary>
/// Failed to install DirectX with BYOND.
/// </summary>
[Description("Unable to start DirectX installer process! Is the server running with admin privileges?")]
ByondDirectXInstallFail,
/// <summary>
/// Failed to download a given BYOND version.
/// </summary>
[Description("Error downloading specified BYOND version!")]
ByondDownloadFail,
/// <summary>
/// Failed to lock BYOND executables.
/// </summary>
[Description("Could not acquire lock on BYOND installation as none exist!")]
ByondNoVersionsInstalled,
/// <summary>
/// The DMAPI never validated itself
/// </summary>
[Description("DreamDaemon exited without validating the DMAPI@")]
DreamMakerNeverValidated,
/// <summary>
/// The DMAPI sent an invalid validation request.
/// </summary>
[Description("The DMAPI sent an invalid validation request!")]
DreamMakerInvalidValidation,
/// <summary>
/// DMAPI validation timeout.
/// </summary>
[Description("The DreamDaemon startup timeout was hit before the DMAPI validated!")]
DreamMakerValidationTimeout,
/// <summary>
/// No .dme could be found for deployment.
/// </summary>
[Description("No .dme configured and could not automatically detect one!")]
DreamMakerNoDme,
/// <summary>
/// The configured .dme could not be found.
/// </summary>
[Description("Could not load configured .dme!")]
DreamMakerMissingDme,
/// <summary>
/// DreamMaker failed to compile.
/// </summary>
[Description("DreamMaker exited with a non-zero exit code!")]
DreamMakerExitCode,
/// <summary>
/// Deployment already in progress
/// </summary>
[Description("There is already a deployment operation in progress!")]
DreamMakerCompileJobInProgress,
/// <summary>
/// Missing <see cref="DreamDaemon"/> settings in database.
/// </summary>
[Description("Could not retrieve DreamDaemon settings from the database!")]
InstanceMissingDreamDaemonSettings,
/// <summary>
/// Missing <see cref="DreamMaker"/> settings in database.
/// </summary>
[Description("Could not retrieve DreamMaker settings from the database!")]
InstanceMissingDreamMakerSettings,
/// <summary>
/// Missing <see cref="Repository"/> settings in database.
/// </summary>
[Description("Could not retrieve Repository settings from the database!")]
InstanceMissingRepositorySettings,
/// <summary>
/// Performing an automatic update with the <see cref="Internal.RepositorySettings.AutoUpdatesKeepTestMerges"/> flag resulted in merge conflicts.
/// </summary>
[Description("Performing this automatic update as a merge would result in conficts. Aborting!")]
InstanceUpdateTestMergeConflict,
/// <summary>
/// <see cref="Internal.RepositorySettings.AccessUser"/> and <see cref="Internal.RepositorySettings.AccessToken"/> are required for this operation.
/// </summary>
[Description("Git credentials are required for this operation!")]
RepoCredentialsRequired,
/// <summary>
/// The remote returned an invalid authentication request.
/// </summary>
[Description("The remote is requesting authentication, but is not allowing credentials to be received!")]
RepoCannotAuthenticate,
/// <summary>
/// Cannot perform operation while not on a <see cref="Repository.Reference"/>.
/// </summary>
[Description("This git operation requires the repository HEAD to currently be on a tracked reference!")]
RepoReferenceRequired,
/// <summary>
/// Attempted to start the watchdog when it was already running.
/// </summary>
[Description("The watchdog is already running!")]
WatchdogRunning,
/// <summary>
/// Attempted to start the watchdog with a corrupted <see cref="CompileJob"/>.
/// </summary>
[Description("Cannot launch with active compile job as it is corrupted!")]
WatchdogCompileJobCorrupted,
/// <summary>
/// DreamDaemon exited before it finished starting.
/// </summary>
[Description("DreamDaemon failed to start!")]
WatchdogStartupFailed,
/// <summary>
/// DreamDaemon timed-out before it finished starting.
/// </summary>
[Description("DreamDaemon failed to start within the configured timeout!")]
WatchdogStartupTimeout,
/// <summary>
/// Attempted to test merge with an unsupported remote.
/// </summary>
[Description("Test merging with the current remote is not supported!")]
RepoUnsupportedTestMergeRemote,
/// <summary>
/// Either <see cref="Repository.CheckoutSha"/> or <see cref="Repository.Reference"/> was in one when it should have been the other.
/// </summary>
[Description("The value set for checkoutSha or reference should be in the other field!")]
RepoSwappedShaOrReference,
/// <summary>
/// A merge conflict occurred during a git operation.
/// </summary>
[Description("A merge conflict occurred while performing the operation!")]
RepoMergeConflict,
/// <summary>
/// The current <see cref="Repository.Reference"/> does not track a remote reference.
/// </summary>
[Description("The repository's current reference is unsuitable for this operation as it does not track a remote reference!")]
RepoReferenceNotTracking,
/// <summary>
/// Encounted merge conflicts while test merging.
/// </summary>
[Description("Encountered merge conflicts while test merging one or more pull requests!")]
RepoTestMergeConflict,
}
}
@@ -15,6 +15,11 @@ namespace Tgstation.Server.Api.Models.Internal
[Required]
public string Description { get; set; }
/// <summary>
/// The <see cref="Models.ErrorCode"/> associated with the <see cref="Job"/> if any.
/// </summary>
public ErrorCode? ErrorCode { get; set; }
/// <summary>
/// Details of any exceptions caught during the <see cref="Job"/>
/// </summary>
@@ -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);
@@ -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;
}
}
@@ -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;
}
@@ -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<Task>();
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<Task>();
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);
@@ -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);
};
}
}
@@ -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<string> { 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();
@@ -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,
@@ -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"));
}
/// <summary>
@@ -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;
@@ -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
/// </summary>
public sealed class JobException : Exception
{
/// <summary>
/// The <see cref="Api.Models.ErrorCode"/> associated with the <see cref="JobException"/>.
/// </summary>
public ErrorCode? ErrorCode { get; set; }
/// <summary>
/// Construct a <see cref="JobException"/>
/// </summary>
@@ -30,5 +36,22 @@ namespace Tgstation.Server.Host.Jobs
public JobException(string message, Exception innerException) : base(message, innerException)
{
}
/// <summary>
/// Construct a <see cref="JobException"/> with a <paramref name="errorCode"/>.
/// </summary>
/// <param name="errorCode">The associated <see cref="Api.Models.ErrorCode"/>.</param>
public JobException(ErrorCode errorCode) : base(errorCode.Describe())
{
}
/// <summary>
/// Construct a <see cref="JobException"/> with a <paramref name="errorCode"/> and <paramref name="innerException"/>.
/// </summary>
/// <param name="errorCode">The associated <see cref="Api.Models.ErrorCode"/>.</param>
/// <param name="innerException">The inner <see cref="Exception"/> for the nase <see cref="Exception"/></param>
public JobException(ErrorCode errorCode, Exception innerException) : base(errorCode.Describe(), innerException)
{
}
}
}
+7 -1
View File
@@ -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)
{