diff --git a/src/Tgstation.Server.Api/Models/CompileJob.cs b/src/Tgstation.Server.Api/Models/CompileJob.cs
index a3b97ed6f7..359d7a02df 100644
--- a/src/Tgstation.Server.Api/Models/CompileJob.cs
+++ b/src/Tgstation.Server.Api/Models/CompileJob.cs
@@ -8,11 +8,6 @@
///
public User TriggeredBy { get; set; }
- ///
- /// The that cancelled the job if any
- ///
- public User CancelledBy { get; set; }
-
///
/// Git revision the compiler ran on. Not modifiable
///
diff --git a/src/Tgstation.Server.Host/Components/DreamMaker.cs b/src/Tgstation.Server.Host/Components/DreamMaker.cs
index 87508da8ca..eecc1b23d2 100644
--- a/src/Tgstation.Server.Host/Components/DreamMaker.cs
+++ b/src/Tgstation.Server.Host/Components/DreamMaker.cs
@@ -19,11 +19,7 @@ namespace Tgstation.Server.Host.Components
/// Name of the primary directory used for compilation
///
const string ADirectoryName = "A";
-
- ///
- /// The for
- ///
- readonly IRepositoryManager repositoryManager;
+
///
/// The for
///
@@ -48,15 +44,13 @@ namespace Tgstation.Server.Host.Components
///
/// Construct
///
- /// The value of
/// The value of
/// The value of
/// The value of
/// The value of
/// The value of
- public DreamMaker(IRepositoryManager repositoryManager, IIOManager ioManager, IConfiguration configuration, IDreamDaemonExecutor dreamDaemonExecutor, IByond byond, IInterop interop)
+ public DreamMaker(IIOManager ioManager, IConfiguration configuration, IDreamDaemonExecutor dreamDaemonExecutor, IByond byond, IInterop interop)
{
- this.repositoryManager = repositoryManager ?? throw new ArgumentNullException(nameof(repositoryManager));
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
this.dreamDaemonExecutor = dreamDaemonExecutor ?? throw new ArgumentNullException(nameof(dreamDaemonExecutor));
@@ -195,7 +189,7 @@ namespace Tgstation.Server.Host.Components
}
///
- public async Task Compile(string dmeName, CancellationToken cancellationToken)
+ public async Task Compile(string dmeName, IRepository repository, CancellationToken cancellationToken)
{
var job = new Host.Models.CompileJob
{
@@ -203,68 +197,64 @@ namespace Tgstation.Server.Host.Components
StartedAt = DateTimeOffset.Now,
DmeName = dmeName
};
-
- await ioManager.CreateDirectory(job.DirectoryName.ToString(), cancellationToken).ConfigureAwait(false);
- var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName);
- var dirB = ioManager.ConcatPath(job.DirectoryName.ToString(), "B");
-
- async Task CleanupFailedCompile()
- {
- try
- {
- await ioManager.DeleteDirectory(job.DirectoryName.ToString(), CancellationToken.None).ConfigureAwait(false);
- }
- catch { }
- };
-
try
{
- //copy the repository
- var fullDirA = ioManager.ResolvePath(dirA);
- using (var repository = await repositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false))
+ await ioManager.CreateDirectory(job.DirectoryName.ToString(), cancellationToken).ConfigureAwait(false);
+ var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName);
+ var dirB = ioManager.ConcatPath(job.DirectoryName.ToString(), "B");
+
+ async Task CleanupFailedCompile()
{
- job.RevisionInformation = new Host.Models.RevisionInformation
+ try
{
- Commit = repository.Head
- };
- await repository.CopyTo(fullDirA, cancellationToken).ConfigureAwait(false);
- }
+ await ioManager.DeleteDirectory(job.DirectoryName.ToString(), CancellationToken.None).ConfigureAwait(false);
+ }
+ catch { }
+ };
- await ModifyDme(job, cancellationToken).ConfigureAwait(false);
-
- //run compiler, verify api
- var ddVerified = await byond.UseExecutables(async (dreamMakerPath, dreamDaemonPath) =>
+ try
{
- await RunDreamMaker(dreamMakerPath, job, cancellationToken).ConfigureAwait(false);
+ //copy the repository
+ var fullDirA = ioManager.ResolvePath(dirA);
+ using (repository)
+ await repository.CopyTo(fullDirA, cancellationToken).ConfigureAwait(false);
- return await VerifyApi(dreamDaemonPath, job, cancellationToken).ConfigureAwait(false);
- }, true).ConfigureAwait(false);
+ await ModifyDme(job, cancellationToken).ConfigureAwait(false);
- if(!ddVerified)
- {
- //server never validated
- job.FinishedAt = DateTimeOffset.Now;
- await CleanupFailedCompile().ConfigureAwait(false);
+ //run compiler, verify api
+ var ddVerified = await byond.UseExecutables(async (dreamMakerPath, dreamDaemonPath) =>
+ {
+ await RunDreamMaker(dreamMakerPath, job, cancellationToken).ConfigureAwait(false);
+
+ return job.ExitCode == 0 && await VerifyApi(dreamDaemonPath, job, cancellationToken).ConfigureAwait(false);
+ }, true).ConfigureAwait(false);
+
+ if (!ddVerified)
+ //server never validated or compile failed
+ await CleanupFailedCompile().ConfigureAwait(false);
+ else
+ {
+ job.DMApiValidated = true;
+
+ //duplicate the dmb et al
+ await ioManager.CopyDirectory(dirA, dirB, null, cancellationToken).ConfigureAwait(false);
+
+ //symlink in the static data
+ var symATask = configuration.SymlinkStaticFilesTo(fullDirA, cancellationToken);
+ await configuration.SymlinkStaticFilesTo(ioManager.ResolvePath(dirB), cancellationToken).ConfigureAwait(false);
+ await symATask.ConfigureAwait(false);
+ }
return job;
}
-
- job.DMApiValidated = true;
-
- //duplicate the dmb et al
- await ioManager.CopyDirectory(dirA, dirB, null, cancellationToken).ConfigureAwait(false);
-
- //symlink in the static data
- var symATask = configuration.SymlinkStaticFilesTo(fullDirA, cancellationToken);
- await configuration.SymlinkStaticFilesTo(ioManager.ResolvePath(dirB), cancellationToken).ConfigureAwait(false);
- await symATask.ConfigureAwait(false);
-
- job.FinishedAt = DateTimeOffset.Now;
- return job;
+ catch
+ {
+ await CleanupFailedCompile().ConfigureAwait(false);
+ throw;
+ }
}
- catch
+ finally
{
- await CleanupFailedCompile().ConfigureAwait(false);
- throw;
+ job.FinishedAt = DateTimeOffset.Now;
}
}
}
diff --git a/src/Tgstation.Server.Host/Components/IDreamMaker.cs b/src/Tgstation.Server.Host/Components/IDreamMaker.cs
index 8e6036dc55..527add5a8f 100644
--- a/src/Tgstation.Server.Host/Components/IDreamMaker.cs
+++ b/src/Tgstation.Server.Host/Components/IDreamMaker.cs
@@ -14,8 +14,9 @@ namespace Tgstation.Server.Host.Components
/// Starts a compile
///
/// The .dme file to use without the extension
+ /// The to copy from
/// The for the operation
/// A resulting in the partially populated for the operation. In particular, note the field will only have it's field populated
- Task Compile(string dmeName, CancellationToken cancellationToken);
+ Task Compile(string dmeName, IRepository repository, CancellationToken cancellationToken);
}
}
\ No newline at end of file
diff --git a/src/Tgstation.Server.Host/Components/Repository.cs b/src/Tgstation.Server.Host/Components/Repository.cs
index 20d8d569b4..50364ca9b1 100644
--- a/src/Tgstation.Server.Host/Components/Repository.cs
+++ b/src/Tgstation.Server.Host/Components/Repository.cs
@@ -62,7 +62,7 @@ namespace Tgstation.Server.Host.Components
public void Dispose()
{
repository.Dispose();
- onDispose();
+ onDispose.Invoke();
}
///
/// Convert to an "https://@{url} equivalent
diff --git a/src/Tgstation.Server.Host/Components/RepositoryManager.cs b/src/Tgstation.Server.Host/Components/RepositoryManager.cs
index 627ba44cfa..5982887adb 100644
--- a/src/Tgstation.Server.Host/Components/RepositoryManager.cs
+++ b/src/Tgstation.Server.Host/Components/RepositoryManager.cs
@@ -127,7 +127,12 @@ namespace Tgstation.Server.Host.Components
{
repo = new LibGit2Sharp.Repository(ioManager.ResolvePath("."));
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
- return new Repository(repo, ioManager, () => semaphore.Release());
+ var localSemaphore = semaphore;
+ return new Repository(repo, ioManager, () =>
+ {
+ localSemaphore?.Release();
+ localSemaphore = null;
+ });
}
///
diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs
new file mode 100644
index 0000000000..13ce786f5b
--- /dev/null
+++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs
@@ -0,0 +1,93 @@
+using System;
+using System.Linq;
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.DependencyInjection;
+using Tgstation.Server.Api.Rights;
+using Tgstation.Server.Host.Components;
+using Tgstation.Server.Host.Core;
+using Tgstation.Server.Host.Models;
+using Tgstation.Server.Host.Security;
+
+namespace Tgstation.Server.Host.Controllers
+{
+ ///
+ /// Controller for managing the compiler
+ ///
+ [Route("/DreamMaker")]
+ public sealed class DreamMakerController : ModelController
+ {
+ ///
+ /// The for the
+ ///
+ readonly IJobManager jobManager;
+
+ ///
+ /// Construct a
+ ///
+ /// The for the
+ /// The for the
+ /// The value of
+ public DreamMakerController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager) : base(databaseContext, authenticationContextFactory) => this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
+
+ ///
+ [TgsAuthorize(DreamMakerRights.Compile)]
+ public override async Task Create([FromBody] Api.Models.CompileJob model, CancellationToken cancellationToken)
+ {
+ var job = new Job
+ {
+ Description = "Compile active repository code",
+ StartedBy = AuthenticationContext.User
+ };
+ await jobManager.RegisterOperation(job, (serviceProvider, ct) => RunCompile(serviceProvider, Instance, AuthenticationContext.Clone(), ct), cancellationToken).ConfigureAwait(false);
+ return Json(job);
+ }
+
+ ///
+ public override async Task Delete([FromBody] Api.Models.CompileJob model, CancellationToken cancellationToken)
+ {
+ //alias for cancelling the latest job
+ var job = await DatabaseContext.Jobs.OrderByDescending(x => x.StartedAt).Select(x => new Job { Id = x.Id, StoppedAt = x.StoppedAt }).FirstAsync(cancellationToken).ConfigureAwait(false);
+ if (job.StoppedAt != null)
+ return StatusCode(HttpStatusCode.Gone);
+ jobManager.CancelJob(job);
+ return Ok();
+ }
+
+ ///
+ /// Run the compile job and insert it into the database
+ ///
+ /// The for the operation
+ /// The for the operation
+ /// The for the operation
+ /// The for the operation
+ ///
+ static async Task RunCompile(IServiceProvider serviceProvider, Models.Instance instanceModel, IAuthenticationContext authenticationContext, CancellationToken cancellationToken)
+ {
+ var instanceManager = serviceProvider.GetRequiredService();
+ var databaseContext = serviceProvider.GetRequiredService();
+
+ var projectName = await databaseContext.Instances.Where(x => x.Id == instanceModel.Id).Select(x => x.DreamMakerSettings.ProjectName).FirstAsync(cancellationToken).ConfigureAwait(false);
+
+ var instance = instanceManager.GetInstance(instanceModel);
+
+ CompileJob compileJob;
+ Task revInfoTask;
+ using (var repo = await instance.RepositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false))
+ {
+ revInfoTask = databaseContext.RevisionInformations.Where(x => x.Commit == repo.Head).Select(x => new RevisionInformation { Id = x.Id }).FirstAsync();
+ compileJob = await instance.DreamMaker.Compile(projectName, repo, cancellationToken).ConfigureAwait(false);
+ }
+
+ compileJob.TriggeredBy = authenticationContext.User;
+ compileJob.RevisionInformation = await revInfoTask.ConfigureAwait(false);
+
+ databaseContext.CompileJobs.Add(compileJob);
+ //default ct because we don't want to give up after getting this far
+ await databaseContext.Save(default).ConfigureAwait(false);
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Host/Core/IJobManager.cs b/src/Tgstation.Server.Host/Core/IJobManager.cs
index 13d30ad436..ce5798ac3f 100644
--- a/src/Tgstation.Server.Host/Core/IJobManager.cs
+++ b/src/Tgstation.Server.Host/Core/IJobManager.cs
@@ -1,11 +1,15 @@
-using System;
+using Microsoft.Extensions.DependencyInjection;
+using System;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Host.Models;
namespace Tgstation.Server.Host.Core
{
- interface IJobManager
+ ///
+ /// Manages the runtime of s
+ ///
+ public interface IJobManager
{
///
/// Registers a given and begins running it
@@ -14,7 +18,7 @@ namespace Tgstation.Server.Host.Core
/// The operation to run
/// The for the operation
/// A representing a running operation
- Task RegisterOperation(Job job, Func operation, CancellationToken cancellationToken);
+ Task RegisterOperation(Job job, Func operation, CancellationToken cancellationToken);
///
/// Wait for a given to complete
diff --git a/src/Tgstation.Server.Host/Core/JobManager.cs b/src/Tgstation.Server.Host/Core/JobManager.cs
index e81f8ece73..995b544a8f 100644
--- a/src/Tgstation.Server.Host/Core/JobManager.cs
+++ b/src/Tgstation.Server.Host/Core/JobManager.cs
@@ -53,16 +53,23 @@ namespace Tgstation.Server.Host.Core
/// The operation for the
/// The for the operation
/// A representing the running operation
- async Task RunJob(Job job, Func operation, CancellationToken cancellationToken)
+ async Task RunJob(Job job, Func operation, CancellationToken cancellationToken)
{
using (var scope = serviceProvider.CreateScope())
{
- var databaseContext = scope.ServiceProvider.GetRequiredService();
- job = new Job { Id = job.Id };
- databaseContext.Jobs.Attach(job);
+ IDatabaseContext databaseContext = null;
try
{
- await operation(cancellationToken).ConfigureAwait(false);
+ try
+ {
+ await operation(scope.ServiceProvider, cancellationToken).ConfigureAwait(false);
+ }
+ finally
+ {
+ job = new Job { Id = job.Id };
+ databaseContext = scope.ServiceProvider.GetRequiredService();
+ databaseContext.Jobs.Attach(job);
+ }
}
catch (OperationCanceledException)
{
@@ -78,7 +85,7 @@ namespace Tgstation.Server.Host.Core
}
///
- public async Task RegisterOperation(Job job, Func operation, CancellationToken cancellationToken)
+ public async Task RegisterOperation(Job job, Func operation, CancellationToken cancellationToken)
{
using (var scope = serviceProvider.CreateScope())
{
diff --git a/src/Tgstation.Server.Host/Models/CompileJob.cs b/src/Tgstation.Server.Host/Models/CompileJob.cs
index a22e7ec5c3..8a34707703 100644
--- a/src/Tgstation.Server.Host/Models/CompileJob.cs
+++ b/src/Tgstation.Server.Host/Models/CompileJob.cs
@@ -1,6 +1,4 @@
-using System.ComponentModel.DataAnnotations;
-
-namespace Tgstation.Server.Host.Models
+namespace Tgstation.Server.Host.Models
{
///
public sealed class CompileJob : Api.Models.Internal.CompileJob
@@ -10,11 +8,6 @@ namespace Tgstation.Server.Host.Models
///
public User TriggeredBy { get; set; }
- ///
- /// See
- ///
- public User CancelledBy { get; set; }
-
///
/// See
///
diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs
index 5f0ffef80f..3d848add3e 100644
--- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs
+++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs
@@ -22,6 +22,12 @@ namespace Tgstation.Server.Host.Models
///
public DbSet Instances { get; set; }
+ ///
+ public DbSet CompileJobs { get; set; }
+
+ ///
+ public DbSet RevisionInformations { get; set; }
+
///
/// The for s
///
@@ -48,10 +54,6 @@ namespace Tgstation.Server.Host.Models
///
public DbSet DreamMakerSettings { get; set; }
///
- /// The s in the
- ///
- public DbSet CompileJobs { get; set; }
- ///
/// The s in the
///
public DbSet Jobs { get; set; }
@@ -60,10 +62,6 @@ namespace Tgstation.Server.Host.Models
///
public DbSet TestMerges { get; set; }
///
- /// The s in the
- ///
- public DbSet RevisionInformations { get; set; }
- ///
/// The in the
///
public DbSet RepositorySettings { get; set; }
diff --git a/src/Tgstation.Server.Host/Models/IDatabaseContext.cs b/src/Tgstation.Server.Host/Models/IDatabaseContext.cs
index c9076a354b..da1bea8f12 100644
--- a/src/Tgstation.Server.Host/Models/IDatabaseContext.cs
+++ b/src/Tgstation.Server.Host/Models/IDatabaseContext.cs
@@ -24,6 +24,16 @@ namespace Tgstation.Server.Host.Models
///
DbSet Jobs { get; }
+ ///
+ /// The s in the
+ ///
+ DbSet CompileJobs { get; }
+
+ ///
+ /// The s in the
+ ///
+ DbSet RevisionInformations { get; }
+
///
/// Get the in the
///