mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-30 16:39:21 +01:00
Add ErrorCodes to all JobExceptions
This commit is contained in:
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user