From f9ba711bc2b336fd614d4bf762dbe019f1ff3931 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 7 May 2018 14:11:00 -0400 Subject: [PATCH 1/5] Heavy WIP --- .../Internal/DreamDaemonLaunchParameters.cs | 15 ++- .../Models/Internal/DreamDaemonSettings.cs | 12 +- .../Components/IDreamDaemon.cs | 4 + .../Controllers/DreamDaemonController.cs | 121 ++++++++++++++++++ .../Controllers/DreamMakerController.cs | 7 +- .../Controllers/JobController.cs | 14 +- .../Controllers/ModelController.cs | 8 ++ 7 files changed, 168 insertions(+), 13 deletions(-) create mode 100644 src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs diff --git a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs index 8602b302b5..62e0f7eb87 100644 --- a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs +++ b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs @@ -1,4 +1,5 @@ -using Tgstation.Server.Api.Rights; +using System.ComponentModel.DataAnnotations; +using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models.Internal { @@ -12,24 +13,28 @@ namespace Tgstation.Server.Api.Models.Internal /// If the BYOND web client can be used to connect to the game server /// [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetWebClient)] - public bool AllowWebClient { get; set; } + [Required] + public bool? AllowWebClient { get; set; } /// /// The level of /// [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetSecurity)] - public DreamDaemonSecurity SecurityLevel { get; set; } + [Required] + public DreamDaemonSecurity? SecurityLevel { get; set; } /// /// The first port uses. This should be the publically advertised port /// [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetPorts)] - public ushort PrimaryPort { get; set; } + [Required] + public ushort? PrimaryPort { get; set; } /// /// The second port uses /// [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetPorts)] - public ushort SecondaryPort { get; set; } + [Required] + public ushort? SecondaryPort { get; set; } } } \ No newline at end of file diff --git a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonSettings.cs b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonSettings.cs index b4fd279881..72260ee5fc 100644 --- a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonSettings.cs +++ b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonSettings.cs @@ -1,4 +1,5 @@ -using Tgstation.Server.Api.Rights; +using System.ComponentModel.DataAnnotations; +using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models.Internal { @@ -11,18 +12,21 @@ namespace Tgstation.Server.Api.Models.Internal /// If starts when it's starts /// [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetAutoStart)] - public bool AutoStart { get; set; } + [Required] + public bool? AutoStart { get; set; } /// /// If the server is undergoing a soft reset. This may be automatically set by changes to other fields /// [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SoftRestart)] - public bool SoftRestart { get; set; } + [Required] + public bool? SoftRestart { get; set; } /// /// If the server is undergoing a soft shutdown /// [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SoftShutdown)] - public bool SoftShutdown { get; set; } + [Required] + public bool? SoftShutdown { get; set; } } } diff --git a/src/Tgstation.Server.Host/Components/IDreamDaemon.cs b/src/Tgstation.Server.Host/Components/IDreamDaemon.cs index dbd32b9d2a..ae7dee5038 100644 --- a/src/Tgstation.Server.Host/Components/IDreamDaemon.cs +++ b/src/Tgstation.Server.Host/Components/IDreamDaemon.cs @@ -41,6 +41,10 @@ namespace Tgstation.Server.Host.Components /// string AccessToken { get; } + DreamDaemonLaunchParameters LastLaunchParameters { get; } + + Host.Models.CompileJob LastCompileJob { get; } + /// /// Launch DreamDaemon /// diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs new file mode 100644 index 0000000000..d8f082abb6 --- /dev/null +++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs @@ -0,0 +1,121 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System; +using System.Linq; +using System.Net; +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; +using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Models; +using Tgstation.Server.Host.Security; + +namespace Tgstation.Server.Host.Controllers +{ + [Route("/" + nameof(DreamDaemon))] + public sealed class DreamDaemonController : ModelController + { + /// + /// The for the + /// + readonly IJobManager jobManager; + /// + /// The for the + /// + readonly IInstanceManager instanceManager; + + /// + /// Construct a + /// + /// The for the + /// The for the + public DreamDaemonController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager, IInstanceManager instanceManager) : base(databaseContext, authenticationContextFactory) + { + this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); + this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); + } + + /// + [TgsAuthorize(DreamDaemonRights.Start)] + public override async Task Create([FromBody] Api.Models.DreamDaemon model, CancellationToken cancellationToken) + { + var instance = instanceManager.GetInstance(Instance); + + if (instance.DreamDaemon.Running) + return StatusCode(HttpStatusCode.Gone); + + var launchParams = await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).Select(x => new DreamDaemonLaunchParameters + { + AllowWebClient = x.DreamDaemonSettings.AllowWebClient, + PrimaryPort = x.DreamDaemonSettings.PrimaryPort, + SecondaryPort = x.DreamDaemonSettings.SecondaryPort, + SecurityLevel = x.DreamDaemonSettings.SecurityLevel + }).FirstAsync(cancellationToken).ConfigureAwait(false); + + await jobManager.RegisterOperation(new Models.Job + { + Description = "Launch DreamDaemon", + CancelRight = (int)DreamDaemonRights.Shutdown, + CancelRightsType = RightsType.DreamDaemon, + Instance = Instance, + StartedBy = AuthenticationContext.User + }, (job, serviceProvider, innerCt) => instance.DreamDaemon.Launch(launchParams, innerCt), cancellationToken).ConfigureAwait(false); + return Ok(); + } + + /// + [TgsAuthorize(DreamDaemonRights.ReadMetadata | DreamDaemonRights.ReadRevision)] + public override async Task Read(CancellationToken cancellationToken) + { + var dd = instanceManager.GetInstance(Instance).DreamDaemon; + + var metadata = (AuthenticationContext.GetRight(RightsType.DreamDaemon) & (int)DreamDaemonRights.ReadMetadata) != 0; + var revision = (AuthenticationContext.GetRight(RightsType.DreamDaemon) & (int)DreamDaemonRights.ReadRevision) != 0; + + var settings = metadata ? await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).Select(x => x.DreamDaemonSettings).FirstAsync(cancellationToken).ConfigureAwait(false) : null; + Api.Models.DreamDaemon result = new Api.Models.DreamDaemon(); + if(metadata) + { + result.AutoStart = settings.AutoStart; + result.CurrentPort = dd.CurrentPort; + result.CurrentSecurity = dd.CurrentSecurity; + result.PrimaryPort = dd.LastLaunchParameters.PrimaryPort; + result.AllowWebClient = dd.LastLaunchParameters.AllowWebClient; + result.Running = dd.Running; + result.SecondaryPort = dd.LastLaunchParameters.SecondaryPort; + result.SecurityLevel = dd.LastLaunchParameters.SecurityLevel; + result.SoftRestart = dd.SoftRebooting; + result.SoftShutdown = dd.SoftStopping; + }; + if (revision) + result.CompileJob = dd.LastCompileJob.ToApi(); + + return Json(result); + } + + /// + [TgsAuthorize(DreamDaemonRights.Shutdown)] + public override async Task Delete([FromBody] Api.Models.DreamDaemon model, CancellationToken cancellationToken) + { + var instance = instanceManager.GetInstance(Instance); + + if (!instance.DreamDaemon.Running) + return StatusCode(HttpStatusCode.Gone); + + await instance.DreamDaemon.Terminate(false, cancellationToken).ConfigureAwait(false); + return Ok(); + } + + [TgsAuthorize(DreamDaemonRights.SetAutoStart | DreamDaemonRights.SetPorts | DreamDaemonRights.SetSecurity | DreamDaemonRights.SetWebClient | DreamDaemonRights.SoftRestart | DreamDaemonRights.SoftShutdown)] + public override async Task Update([FromBody] Api.Models.DreamDaemon model, CancellationToken cancellationToken) + { + var current = await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).Select(x => x.DreamDaemonSettings).FirstAsync(cancellationToken).ConfigureAwait(false); + + var userRights = (DreamDaemonRights)AuthenticationContext.GetRight(RightsType.DreamDaemon) + if() + } + } +} diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs index f94a51537e..c13cb31e11 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs @@ -17,7 +17,7 @@ namespace Tgstation.Server.Host.Controllers /// /// Controller for managing the compiler /// - [Route("/DreamMaker")] + [Route("/" + nameof(DreamMaker))] public sealed class DreamMakerController : ModelController { /// @@ -30,7 +30,7 @@ namespace Tgstation.Server.Host.Controllers readonly IInstanceManager instanceManager; /// - /// Construct a + /// Construct a /// /// The for the /// The for the @@ -66,7 +66,8 @@ namespace Tgstation.Server.Host.Controllers Description = "Compile active repository code", StartedBy = AuthenticationContext.User, CancelRightsType = RightsType.DreamMaker, - CancelRight = (int)DreamMakerRights.CancelCompile + CancelRight = (int)DreamMakerRights.CancelCompile, + Instance = Instance }; await jobManager.RegisterOperation(job, (paramJob, serviceProvider, ct) => RunCompile(paramJob, serviceProvider, Instance, ct), cancellationToken).ConfigureAwait(false); return Json(job); diff --git a/src/Tgstation.Server.Host/Controllers/JobController.cs b/src/Tgstation.Server.Host/Controllers/JobController.cs index 982fc72cd8..4be80670a2 100644 --- a/src/Tgstation.Server.Host/Controllers/JobController.cs +++ b/src/Tgstation.Server.Host/Controllers/JobController.cs @@ -14,7 +14,7 @@ namespace Tgstation.Server.Host.Controllers /// /// for s /// - [Route("/Job")] + [Route("/" + nameof(Job))] public sealed class JobController : ModelController { /// @@ -60,11 +60,23 @@ namespace Tgstation.Server.Host.Controllers if (job == default(Job)) return NotFound(); + if (job.CancelRight.HasValue && job.CancelRightsType.HasValue && (AuthenticationContext.GetRight(job.CancelRightsType.Value) & job.CancelRight.Value) == 0) + return Forbid(); + if(job.StoppedAt != null) return StatusCode(HttpStatusCode.Gone); await jobManager.CancelJob(job, AuthenticationContext.User, cancellationToken).ConfigureAwait(false); return Ok(); } + + [TgsAuthorize] + public override async Task GetId(long id, CancellationToken cancellationToken) + { + var job = await DatabaseContext.Jobs.Where(x => x.Id == id).Include(x => x.StartedBy).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); + if (job == default(Job)) + return NotFound(); + return Json(job.ToApi()); + } } } diff --git a/src/Tgstation.Server.Host/Controllers/ModelController.cs b/src/Tgstation.Server.Host/Controllers/ModelController.cs index 59d2b5c19a..54d01677da 100644 --- a/src/Tgstation.Server.Host/Controllers/ModelController.cs +++ b/src/Tgstation.Server.Host/Controllers/ModelController.cs @@ -43,6 +43,14 @@ namespace Tgstation.Server.Host.Controllers [HttpGet] public virtual Task Read(CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); + /// + /// Attempt to get a specific a + /// + /// The for the operation + /// A resulting in the of the operation + [HttpGet("/{0}")] + public virtual Task GetId(long id, CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); + /// /// Attempt to update a /// From 75392b436216a473a8566b15996b75dbf4b0be67 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 7 May 2018 14:58:50 -0400 Subject: [PATCH 2/5] Get it compiling but not working --- .../Components/DreamDaemon.cs | 9 +++- .../Components/DreamDaemonExecutor.cs | 4 +- .../Components/DreamMaker.cs | 2 +- .../Components/IDmbProvider.cs | 6 +-- .../Components/IDreamDaemon.cs | 6 +++ .../Components/IWatchdog.cs | 5 ++ .../Components/TemporaryDmbProvider.cs | 2 +- .../Components/Watchdog.cs | 11 ++-- .../Controllers/DreamDaemonController.cs | 53 +++++++++++++++++-- .../Controllers/JobController.cs | 1 + .../Controllers/ModelController.cs | 1 + 11 files changed, 83 insertions(+), 17 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/DreamDaemon.cs b/src/Tgstation.Server.Host/Components/DreamDaemon.cs index 1d6846ccb3..f699150079 100644 --- a/src/Tgstation.Server.Host/Components/DreamDaemon.cs +++ b/src/Tgstation.Server.Host/Components/DreamDaemon.cs @@ -27,6 +27,12 @@ namespace Tgstation.Server.Host.Components /// public bool SoftStopping { get; private set; } + /// + public DreamDaemonLaunchParameters LastLaunchParameters { get; private set; } + + /// + public Host.Models.CompileJob LastCompileJob => watchdog.CurrentCompileJob; + /// /// The for /// @@ -69,7 +75,7 @@ namespace Tgstation.Server.Host.Components this.watchdog = watchdog ?? throw new ArgumentNullException(nameof(watchdog)); currentLaunchParameters = initialSettings ?? throw new ArgumentNullException(nameof(initialSettings)); - autoStart = initialSettings.AutoStart; + autoStart = initialSettings.AutoStart.Value; semaphore = new SemaphoreSlim(1); } @@ -198,6 +204,7 @@ namespace Tgstation.Server.Host.Components await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); try { + LastLaunchParameters = currentLaunchParameters; return currentLaunchParameters; } finally diff --git a/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs b/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs index 0f3a47b026..38bddf9158 100644 --- a/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs +++ b/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs @@ -87,8 +87,8 @@ namespace Tgstation.Server.Host.Components proc.StartInfo.Arguments = String.Format(CultureInfo.InvariantCulture, "{0} -port {1} {2}-close -{3} -verbose -public -params \"{4}={5}&{6}={7}\"", dmbProvider.DmbName, isPrimary ? launchParameters.PrimaryPort : launchParameters.SecondaryPort, - launchParameters.AllowWebClient ? "-webclient " : String.Empty, - SecurityWord(launchParameters.SecurityLevel), + launchParameters.AllowWebClient.Value ? "-webclient " : String.Empty, + SecurityWord(launchParameters.SecurityLevel.Value), DreamDaemonParameters.HostVersion, Application.Version, DreamDaemonParameters.InfoJsonPath, jsonPath); diff --git a/src/Tgstation.Server.Host/Components/DreamMaker.cs b/src/Tgstation.Server.Host/Components/DreamMaker.cs index ffd577f98b..8387a904ad 100644 --- a/src/Tgstation.Server.Host/Components/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/DreamMaker.cs @@ -98,7 +98,7 @@ namespace Tgstation.Server.Host.Components }; using (var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)) - using (var control = interop.CreateRun(launchParameters.PrimaryPort, null, null)) + using (var control = interop.CreateRun(launchParameters.PrimaryPort.Value, null, null)) { var interopInfo = new InteropInfo { diff --git a/src/Tgstation.Server.Host/Components/IDmbProvider.cs b/src/Tgstation.Server.Host/Components/IDmbProvider.cs index 4825fb2914..18a5432bd2 100644 --- a/src/Tgstation.Server.Host/Components/IDmbProvider.cs +++ b/src/Tgstation.Server.Host/Components/IDmbProvider.cs @@ -22,10 +22,10 @@ namespace Tgstation.Server.Host.Components /// The secondary game directory with a trailing directory separator /// string SecondaryDirectory { get; } - + /// - /// The of the .dmb + /// The of the .dmb /// - RevisionInformation RevisionInformation { get; } + CompileJob CompileJob { get; } } } diff --git a/src/Tgstation.Server.Host/Components/IDreamDaemon.cs b/src/Tgstation.Server.Host/Components/IDreamDaemon.cs index ae7dee5038..e435a3af69 100644 --- a/src/Tgstation.Server.Host/Components/IDreamDaemon.cs +++ b/src/Tgstation.Server.Host/Components/IDreamDaemon.cs @@ -41,8 +41,14 @@ namespace Tgstation.Server.Host.Components /// string AccessToken { get; } + /// + /// The most recently used + /// DreamDaemonLaunchParameters LastLaunchParameters { get; } + /// + /// The most recently used + /// Host.Models.CompileJob LastCompileJob { get; } /// diff --git a/src/Tgstation.Server.Host/Components/IWatchdog.cs b/src/Tgstation.Server.Host/Components/IWatchdog.cs index 59882f6c89..5504baa960 100644 --- a/src/Tgstation.Server.Host/Components/IWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/IWatchdog.cs @@ -8,6 +8,11 @@ namespace Tgstation.Server.Host.Components /// interface IWatchdog { + /// + /// The latest used by the + /// + Host.Models.CompileJob CurrentCompileJob { get; } + /// /// Start the /// diff --git a/src/Tgstation.Server.Host/Components/TemporaryDmbProvider.cs b/src/Tgstation.Server.Host/Components/TemporaryDmbProvider.cs index 389256d050..fc4fa15408 100644 --- a/src/Tgstation.Server.Host/Components/TemporaryDmbProvider.cs +++ b/src/Tgstation.Server.Host/Components/TemporaryDmbProvider.cs @@ -18,7 +18,7 @@ namespace Tgstation.Server.Host.Components public string SecondaryDirectory => throw new NotSupportedException(); /// - public RevisionInformation RevisionInformation => null; + public CompileJob CompileJob => null; /// /// Construct a diff --git a/src/Tgstation.Server.Host/Components/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog.cs index a98ce38c27..a5130779c7 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog.cs @@ -10,6 +10,9 @@ namespace Tgstation.Server.Host.Components /// sealed class Watchdog : IWatchdog, IDisposable { + /// + public Host.Models.CompileJob CurrentCompileJob { get; private set; } + /// /// The for the /// @@ -102,8 +105,8 @@ namespace Tgstation.Server.Host.Components interopInfo.ChatCommandsJson = String.Concat(chatJsonGuid, ".commands.json"); //set up revision - interopInfo.Revision = dmb.RevisionInformation; - foreach (var I in dmb.RevisionInformation.TestMerges) + interopInfo.Revision = dmb.CompileJob.RevisionInformation; + foreach (var I in dmb.CompileJob.RevisionInformation.TestMerges) interopInfo.TestMerges.Add(new Models.TestMerge { Author = I.Author, @@ -146,7 +149,7 @@ namespace Tgstation.Server.Host.Components ApiValidateOnly = false, HostPath = Application.HostingPath, InstanceId = instanceId, - NextPort = isPrimary ? launchParameters.SecondaryPort : launchParameters.PrimaryPort, + NextPort = isPrimary ? launchParameters.SecondaryPort.Value : launchParameters.PrimaryPort.Value, //this line feels hacky, change it and remove the instanceManager dep? InstanceName = instanceManager.GetInstance(new Host.Models.Instance { Id = instanceId }).GetMetadata().Name, }; @@ -216,7 +219,7 @@ namespace Tgstation.Server.Host.Components var retryDelay = (int)Math.Min(Math.Pow(2, retries), TimeSpan.FromHours(1).Milliseconds); //max of one hour await Task.Delay(retryDelay, cancellationToken).ConfigureAwait(false); - using (var control = interop.CreateRun(initialLaunchParameters.PrimaryPort, initialLaunchParameters.SecondaryPort, HandleChatMessage)) + using (var control = interop.CreateRun(initialLaunchParameters.PrimaryPort.Value, initialLaunchParameters.SecondaryPort.Value, HandleChatMessage)) { var primaryPrimedTcs = new TaskCompletionSource(); control.OnServerControl += (sender, e) => diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs index d8f082abb6..a4570e2ff7 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs @@ -2,7 +2,9 @@ using Microsoft.EntityFrameworkCore; using System; using System.Linq; +using System.Linq.Expressions; using System.Net; +using System.Reflection; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; @@ -15,7 +17,10 @@ using Tgstation.Server.Host.Security; namespace Tgstation.Server.Host.Controllers { - [Route("/" + nameof(DreamDaemon))] + /// + /// for managing + /// + [Route("/" + nameof(Api.Models.DreamDaemon))] public sealed class DreamDaemonController : ModelController { /// @@ -32,6 +37,8 @@ namespace Tgstation.Server.Host.Controllers /// /// The for the /// The for the + /// The value of + /// The value of public DreamDaemonController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager, IInstanceManager instanceManager) : base(databaseContext, authenticationContextFactory) { this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); @@ -108,14 +115,50 @@ namespace Tgstation.Server.Host.Controllers await instance.DreamDaemon.Terminate(false, cancellationToken).ConfigureAwait(false); return Ok(); } - - [TgsAuthorize(DreamDaemonRights.SetAutoStart | DreamDaemonRights.SetPorts | DreamDaemonRights.SetSecurity | DreamDaemonRights.SetWebClient | DreamDaemonRights.SoftRestart | DreamDaemonRights.SoftShutdown)] + + /// + [TgsAuthorize(DreamDaemonRights.SetAutoStart | DreamDaemonRights.SetPorts | DreamDaemonRights.SetSecurity | DreamDaemonRights.SetWebClient | DreamDaemonRights.SoftRestart | DreamDaemonRights.SoftShutdown | DreamDaemonRights.Start)] public override async Task Update([FromBody] Api.Models.DreamDaemon model, CancellationToken cancellationToken) { var current = await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).Select(x => x.DreamDaemonSettings).FirstAsync(cancellationToken).ConfigureAwait(false); - var userRights = (DreamDaemonRights)AuthenticationContext.GetRight(RightsType.DreamDaemon) - if() + var userRights = (DreamDaemonRights)AuthenticationContext.GetRight(RightsType.DreamDaemon); + + bool CheckModified(Expression> expression, DreamDaemonRights requiredRight) + { + var memberSelectorExpression = (MemberExpression)expression.Body; + var property = (PropertyInfo)memberSelectorExpression.Member; + + var newVal = property.GetValue(model); + if (newVal == null) + return false; + if (!userRights.HasFlag(requiredRight) && property.GetValue(current) != newVal) + return true; + + property.SetValue(current, newVal); + return false; + }; + + if (!CheckModified(x => x.AllowWebClient, DreamDaemonRights.SetWebClient) + || !CheckModified(x => x.AutoStart, DreamDaemonRights.SetAutoStart) + || !CheckModified(x => x.PrimaryPort, DreamDaemonRights.SetPorts) + || !CheckModified(x => x.SecondaryPort, DreamDaemonRights.SetPorts) + || !CheckModified(x => x.SecurityLevel, DreamDaemonRights.SetSecurity) + || !CheckModified(x => x.SoftRestart, DreamDaemonRights.SoftRestart)) + return Forbid(); + + //interaction with soft stop is a bit different + if (model.SoftShutdown.HasValue) + { + if (current.SoftShutdown != model.SoftShutdown && ((!current.SoftShutdown.Value && !userRights.HasFlag(DreamDaemonRights.SoftShutdown)) || (current.SoftShutdown.Value && !userRights.HasFlag(DreamDaemonRights.Start)))) + return Forbid(); + current.SoftShutdown = model.SoftShutdown; + } + + await instanceManager.GetInstance(Instance).DreamDaemon.ChangeSettings(current, cancellationToken).ConfigureAwait(false); + await DatabaseContext.Save(default).ConfigureAwait(false); + + return Ok(); } } } diff --git a/src/Tgstation.Server.Host/Controllers/JobController.cs b/src/Tgstation.Server.Host/Controllers/JobController.cs index 4be80670a2..1dcab2f526 100644 --- a/src/Tgstation.Server.Host/Controllers/JobController.cs +++ b/src/Tgstation.Server.Host/Controllers/JobController.cs @@ -70,6 +70,7 @@ namespace Tgstation.Server.Host.Controllers return Ok(); } + /// [TgsAuthorize] public override async Task GetId(long id, CancellationToken cancellationToken) { diff --git a/src/Tgstation.Server.Host/Controllers/ModelController.cs b/src/Tgstation.Server.Host/Controllers/ModelController.cs index 54d01677da..aec3cf3769 100644 --- a/src/Tgstation.Server.Host/Controllers/ModelController.cs +++ b/src/Tgstation.Server.Host/Controllers/ModelController.cs @@ -47,6 +47,7 @@ namespace Tgstation.Server.Host.Controllers /// Attempt to get a specific a /// /// The for the operation + /// The ID of the model to get /// A resulting in the of the operation [HttpGet("/{0}")] public virtual Task GetId(long id, CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); From 0d539bec549a66a1f3fa0509881f31d4152f6774 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 7 May 2018 15:14:25 -0400 Subject: [PATCH 3/5] I think, by it's nature, Watchdog code is just destined to be shit --- .../Components/DreamDaemonExecutor.cs | 4 +-- .../Components/DreamMaker.cs | 2 +- .../Components/IDreamDaemonExecutor.cs | 3 ++- .../Components/IInteropControl.cs | 2 ++ .../Components/Watchdog.cs | 27 +++++++++++++------ 5 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs b/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs index 38bddf9158..3a54783935 100644 --- a/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs +++ b/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs @@ -56,7 +56,7 @@ namespace Tgstation.Server.Host.Components } /// - public async Task RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, string dreamDaemonPath, IDmbProvider dmbProvider, InteropInfo interopInfo, bool alwaysKill, CancellationToken cancellationToken) + public async Task RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, string dreamDaemonPath, IDmbProvider dmbProvider, InteropInfo interopInfo, bool alwaysKill, bool asDefaultOtherServer, CancellationToken cancellationToken) { if (launchParameters == null) throw new ArgumentNullException(nameof(launchParameters)); @@ -86,7 +86,7 @@ namespace Tgstation.Server.Host.Components proc.StartInfo.Arguments = String.Format(CultureInfo.InvariantCulture, "{0} -port {1} {2}-close -{3} -verbose -public -params \"{4}={5}&{6}={7}\"", dmbProvider.DmbName, - isPrimary ? launchParameters.PrimaryPort : launchParameters.SecondaryPort, + isPrimary && !asDefaultOtherServer ? launchParameters.PrimaryPort : launchParameters.SecondaryPort, launchParameters.AllowWebClient.Value ? "-webclient " : String.Empty, SecurityWord(launchParameters.SecurityLevel.Value), DreamDaemonParameters.HostVersion, Application.Version, diff --git a/src/Tgstation.Server.Host/Components/DreamMaker.cs b/src/Tgstation.Server.Host/Components/DreamMaker.cs index 8387a904ad..e71476b1a4 100644 --- a/src/Tgstation.Server.Host/Components/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/DreamMaker.cs @@ -113,7 +113,7 @@ namespace Tgstation.Server.Host.Components ddTcs.SetResult(null); }; var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName); - var ddTestTask = dreamDaemonExecutor.RunDreamDaemon(launchParameters, null, dreamDaemonPath, new TemporaryDmbProvider(ioManager.ResolvePath(ioManager.GetDirectoryName(dirA)), ioManager.ResolvePath(ioManager.ConcatPath(dirA, String.Concat(job.DmeName, DmbExtension)))), interopInfo, true, cts.Token); + var ddTestTask = dreamDaemonExecutor.RunDreamDaemon(launchParameters, null, dreamDaemonPath, new TemporaryDmbProvider(ioManager.ResolvePath(ioManager.GetDirectoryName(dirA)), ioManager.ResolvePath(ioManager.ConcatPath(dirA, String.Concat(job.DmeName, DmbExtension)))), interopInfo, true, false, cts.Token); await Task.WhenAny(ddTcs.Task, ddTestTask).ConfigureAwait(false); diff --git a/src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs b/src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs index 2f7cbc150d..77980fbe04 100644 --- a/src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs +++ b/src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs @@ -19,8 +19,9 @@ namespace Tgstation.Server.Host.Components /// The for the .dmb to run /// The for the run /// If the resulting process should never be left alive + /// If the ports will be swapped for the launch /// The for the operation /// A representing the lifetime of the process and resulting in the exit code - Task RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, string dreamDaemonPath, IDmbProvider dmbProvider, InteropInfo interopInfo, bool alwaysKill, CancellationToken cancellationToken); + Task RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, string dreamDaemonPath, IDmbProvider dmbProvider, InteropInfo interopInfo, bool alwaysKill, bool asDefaultOtherServer, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/IInteropControl.cs b/src/Tgstation.Server.Host/Components/IInteropControl.cs index 2fc3f5ea6d..43b5751415 100644 --- a/src/Tgstation.Server.Host/Components/IInteropControl.cs +++ b/src/Tgstation.Server.Host/Components/IInteropControl.cs @@ -25,5 +25,7 @@ namespace Tgstation.Server.Host.Components Task ActivateOtherServer(CancellationToken cancellationToken); Task ChangePort(ushort newPort, bool forPrimary, CancellationToken cancellationToken); + + void OnNextSwap(Action action); } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog.cs index a5130779c7..d4403df47c 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog.cs @@ -91,10 +91,21 @@ namespace Tgstation.Server.Host.Components /// The path to the DreamDaemon executable /// The for the operation /// A resulting in the exit code of DreamDaemon - async Task RunServer(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, InteropInfo interopInfo, string dreamDaemonPath, CancellationToken cancellationToken) + async Task RunServer(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, InteropInfo interopInfo, IInteropControl control, string dreamDaemonPath, bool asDefaultOtherServer, CancellationToken cancellationToken) { using (var dmb = await dmbFactory.LockNextDmb(cancellationToken).ConfigureAwait(false)) { + void OnBecomingCurrentServer() => CurrentCompileJob = dmb.CompileJob; + + var runningNow = launchParameters.SecondaryPort == interopInfo.NextPort; + if (asDefaultOtherServer) + runningNow = !runningNow; + + if (runningNow) + OnBecomingCurrentServer(); + else + control.OnNextSwap(OnBecomingCurrentServer); + var chatJsonGuid = Guid.NewGuid(); var isPrimary = interopInfo.NextPort == launchParameters.SecondaryPort; @@ -122,7 +133,7 @@ namespace Tgstation.Server.Host.Components }); using (await chat.TrackJsons(isPrimary ? dmb.PrimaryDirectory : dmb.SecondaryDirectory, interopInfo.ChatChannelsJson, isPrimary ? interopInfo.ChatCommandsJson : null, cancellationToken).ConfigureAwait(false)) - return await dreamDaemonExecutor.RunDreamDaemon(launchParameters, onSuccessfulStartup, dreamDaemonPath, dmb, interopInfo, false, cancellationToken).ConfigureAwait(false); + return await dreamDaemonExecutor.RunDreamDaemon(launchParameters, onSuccessfulStartup, dreamDaemonPath, dmb, interopInfo, false, asDefaultOtherServer, cancellationToken).ConfigureAwait(false); } } @@ -136,7 +147,7 @@ namespace Tgstation.Server.Host.Components /// The for the operation /// A tied to the lifetime of the resulting /// A resulting in the exit code of DreamDaemon - Task StartServer(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, string accessToken, bool isPrimary, CancellationToken cancellationToken, out CancellationTokenSource cancellationTokenSource) + Task StartServer(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, IInteropControl control, bool isPrimary, bool isDefaultOther, CancellationToken cancellationToken, out CancellationTokenSource cancellationTokenSource) { cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); try @@ -145,7 +156,7 @@ namespace Tgstation.Server.Host.Components var interopInfo = new InteropInfo { - AccessToken = accessToken, + AccessToken = isPrimary ? control.PrimaryAccessToken : control.SecondaryAccessToken, ApiValidateOnly = false, HostPath = Application.HostingPath, InstanceId = instanceId, @@ -154,7 +165,7 @@ namespace Tgstation.Server.Host.Components InstanceName = instanceManager.GetInstance(new Host.Models.Instance { Id = instanceId }).GetMetadata().Name, }; - return byond.UseExecutables((dreamMakerPath, dreamDaemonPath) => RunServer(launchParameters, onSuccessfulStartup, interopInfo, dreamDaemonPath, ddToken), false); + return byond.UseExecutables((dreamMakerPath, dreamDaemonPath) => RunServer(launchParameters, onSuccessfulStartup, interopInfo, control, dreamDaemonPath, isDefaultOther, ddToken), false); } catch { @@ -229,7 +240,7 @@ namespace Tgstation.Server.Host.Components }; //start the primary server - var ddPrimaryTask = StartServer(initialLaunchParameters, onSuccessfulStartup, control.PrimaryAccessToken, true, cancellationToken, out CancellationTokenSource primaryCts); + var ddPrimaryTask = StartServer(initialLaunchParameters, onSuccessfulStartup, control, true, false, cancellationToken, out CancellationTokenSource primaryCts); try { //wait to make sure we got this far @@ -256,7 +267,7 @@ namespace Tgstation.Server.Host.Components { if (ddSecondaryTask == null) //start the secondary server - ddSecondaryTask = StartServer(initialLaunchParameters, null, control.SecondaryAccessToken, false, cancellationToken, out secondaryCts); + ddSecondaryTask = StartServer(initialLaunchParameters, null, control, false, false, cancellationToken, out secondaryCts); var newDmbTask = dmbFactory.OnNewerDmb(); @@ -267,7 +278,7 @@ namespace Tgstation.Server.Host.Components void PrimaryRestart() { primaryCts.Dispose(); - ddPrimaryTask = StartServer(initialLaunchParameters, null, control.PrimaryAccessToken, true, cancellationToken, out primaryCts); + ddPrimaryTask = StartServer(initialLaunchParameters, null, control, true, true, cancellationToken, out primaryCts); } void SecondaryRestart() { From c12ef68218fe5b5dd5cc6d9751846d3cfaa0ae56 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 7 May 2018 15:16:46 -0400 Subject: [PATCH 4/5] Fix XMLdoc --- src/Tgstation.Server.Host/Components/Watchdog.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog.cs index d4403df47c..0194890f02 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog.cs @@ -88,7 +88,9 @@ namespace Tgstation.Server.Host.Components /// The for the run /// The to be completed once the server starts if any /// The the server + /// The for the operation /// The path to the DreamDaemon executable + /// If the ports should be swapped /// The for the operation /// A resulting in the exit code of DreamDaemon async Task RunServer(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, InteropInfo interopInfo, IInteropControl control, string dreamDaemonPath, bool asDefaultOtherServer, CancellationToken cancellationToken) @@ -138,12 +140,13 @@ namespace Tgstation.Server.Host.Components } /// - /// Locks in a version and runs a server through + /// Locks in a version and runs a server through /// /// The for the run /// The to be completed once the server starts if any - /// The access token for the server + /// The for the operation /// If a primary server is being launched + /// If the ports should be swapped /// The for the operation /// A tied to the lifetime of the resulting /// A resulting in the exit code of DreamDaemon From 48a183a93627102dbd8c793cced27a8c13c7f2e0 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 28 May 2018 13:13:25 -0400 Subject: [PATCH 5/5] Add missing test results upload --- appveyor.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index ebfe1041dc..98c9cb64f1 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -41,6 +41,8 @@ test_script: - ps: $wc = New-Object 'System.Net.WebClient' - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\TestResults\results.trx)) - vstest.console /logger:trx;LogFileName=results.trx "tests\Tgstation.Server.Host.Tests\bin\%CONFIGURATION%\netcoreapp2.0\Tgstation.Server.Host.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 + - ps: $wc = New-Object 'System.Net.WebClient' + - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\TestResults\results.trx)) - vstest.console /logger:trx;LogFileName=results.trx "tests\Tgstation.Server.Host.Console.Tests\bin\%CONFIGURATION%\netcoreapp2.0\Tgstation.Server.Host.Console.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 - ps: $wc = New-Object 'System.Net.WebClient' - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\TestResults\results.trx))