From 2c588908e96adbf5a77a113f00fb449e7063dffe Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 4 Sep 2018 09:58:54 -0400 Subject: [PATCH 01/47] Implement new verb for deleting empty directories, remove the old delete on empty behaviour --- .../Rights/ConfigurationRights.cs | 6 +- .../Components/ConfigurationClient.cs | 5 +- .../Components/IConfigurationClient.cs | 10 +- .../Components/StaticFiles/Configuration.cs | 122 +++++++++++------- .../Components/StaticFiles/IConfiguration.cs | 9 ++ .../Controllers/ConfigurationController.cs | 24 ++++ .../IO/ISynchronousIOManager.cs | 7 + .../IO/SynchronousIOManager.cs | 25 ++-- 8 files changed, 149 insertions(+), 59 deletions(-) diff --git a/src/Tgstation.Server.Api/Rights/ConfigurationRights.cs b/src/Tgstation.Server.Api/Rights/ConfigurationRights.cs index 45b02e528f..c05a40923b 100644 --- a/src/Tgstation.Server.Api/Rights/ConfigurationRights.cs +++ b/src/Tgstation.Server.Api/Rights/ConfigurationRights.cs @@ -23,6 +23,10 @@ namespace Tgstation.Server.Api.Rights /// /// User may list files /// - List = 4 + List = 4, + /// + /// User may delete empty folders + /// + Delete = 8 } } diff --git a/src/Tgstation.Server.Client/Components/ConfigurationClient.cs b/src/Tgstation.Server.Client/Components/ConfigurationClient.cs index 4f35d86321..0d4574db60 100644 --- a/src/Tgstation.Server.Client/Components/ConfigurationClient.cs +++ b/src/Tgstation.Server.Client/Components/ConfigurationClient.cs @@ -30,6 +30,9 @@ namespace Tgstation.Server.Client.Components this.instance = instance ?? throw new ArgumentNullException(nameof(instance)); } + /// + public Task DeleteEmptyDirectory(string directory, CancellationToken cancellationToken) => apiClient.Delete(Routes.Configuration + directory ?? throw new ArgumentNullException(nameof(directory)), cancellationToken); + /// public Task> List(string directory, CancellationToken cancellationToken) { @@ -49,4 +52,4 @@ namespace Tgstation.Server.Client.Components /// public Task Write(ConfigurationFile file, CancellationToken cancellationToken) => apiClient.Update(Routes.Configuration, file ?? throw new ArgumentNullException(nameof(file)), instance.Id, cancellationToken); } -} \ No newline at end of file +} diff --git a/src/Tgstation.Server.Client/Components/IConfigurationClient.cs b/src/Tgstation.Server.Client/Components/IConfigurationClient.cs index 950c346775..ec8a5c9396 100644 --- a/src/Tgstation.Server.Client/Components/IConfigurationClient.cs +++ b/src/Tgstation.Server.Client/Components/IConfigurationClient.cs @@ -31,7 +31,15 @@ namespace Tgstation.Server.Client.Components /// /// The file to write /// The for the operation - /// A representing the running operation + /// A resulting in the new Task Write(ConfigurationFile file, CancellationToken cancellationToken); + + /// + /// Delete an empty + /// + /// The path to directory to delete + /// The for the operation + /// A representing the running operation + Task DeleteEmptyDirectory(string directory, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs index 90c7a4d218..1e0a42c7bc 100644 --- a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs +++ b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs @@ -103,36 +103,40 @@ namespace Tgstation.Server.Host.Components.StaticFiles /// public async Task CopyDMFilesTo(string dmeFile, string destination, CancellationToken cancellationToken) { - await EnsureDirectories(cancellationToken).ConfigureAwait(false); - //just assume no other fs race conditions here - var dmeExistsTask = ioManager.FileExists(ioManager.ConcatPath(CodeModificationsSubdirectory, dmeFile), cancellationToken); - var headFileExistsTask = ioManager.FileExists(ioManager.ConcatPath(CodeModificationsSubdirectory, CodeModificationsHeadFile), cancellationToken); - var tailFileExistsTask = ioManager.FileExists(ioManager.ConcatPath(CodeModificationsSubdirectory, CodeModificationsTailFile), cancellationToken); - - await Task.WhenAll(dmeExistsTask, headFileExistsTask, tailFileExistsTask).ConfigureAwait(false); - - if (!dmeExistsTask.Result && !headFileExistsTask.Result && !tailFileExistsTask.Result) - return null; - - var copyTask = ioManager.CopyDirectory(CodeModificationsSubdirectory, destination, null, cancellationToken); - - if (dmeExistsTask.Result) + using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) { + await EnsureDirectories(cancellationToken).ConfigureAwait(false); + + //just assume no other fs race conditions here + var dmeExistsTask = ioManager.FileExists(ioManager.ConcatPath(CodeModificationsSubdirectory, dmeFile), cancellationToken); + var headFileExistsTask = ioManager.FileExists(ioManager.ConcatPath(CodeModificationsSubdirectory, CodeModificationsHeadFile), cancellationToken); + var tailFileExistsTask = ioManager.FileExists(ioManager.ConcatPath(CodeModificationsSubdirectory, CodeModificationsTailFile), cancellationToken); + + await Task.WhenAll(dmeExistsTask, headFileExistsTask, tailFileExistsTask).ConfigureAwait(false); + + if (!dmeExistsTask.Result && !headFileExistsTask.Result && !tailFileExistsTask.Result) + return null; + + var copyTask = ioManager.CopyDirectory(CodeModificationsSubdirectory, destination, null, cancellationToken); + + if (dmeExistsTask.Result) + { + await copyTask.ConfigureAwait(false); + return new ServerSideModifications(null, null, true); + } + + if (!headFileExistsTask.Result && !tailFileExistsTask.Result) + { + await copyTask.ConfigureAwait(false); + return null; + } + + string IncludeLine(string filePath) => String.Format(CultureInfo.InvariantCulture, "#include \"{0}\"", filePath); + await copyTask.ConfigureAwait(false); - return new ServerSideModifications(null, null, true); + return new ServerSideModifications(headFileExistsTask.Result ? IncludeLine(CodeModificationsHeadFile) : null, tailFileExistsTask.Result ? IncludeLine(CodeModificationsTailFile) : null, false); } - - if (!headFileExistsTask.Result && !tailFileExistsTask.Result) - { - await copyTask.ConfigureAwait(false); - return null; - } - - string IncludeLine(string filePath) => String.Format(CultureInfo.InvariantCulture, "#include \"{0}\"", filePath); - - await copyTask.ConfigureAwait(false); - return new ServerSideModifications(headFileExistsTask.Result ? IncludeLine(CodeModificationsHeadFile) : null, tailFileExistsTask.Result ? IncludeLine(CodeModificationsTailFile) : null, false); } string ValidateConfigRelativePath(string configurationRelativePath) @@ -180,10 +184,11 @@ namespace Tgstation.Server.Host.Components.StaticFiles })); } - if (systemIdentity == null) - ListImpl(); - else - await systemIdentity.RunImpersonated(ListImpl, cancellationToken).ConfigureAwait(false); + using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) + if (systemIdentity == null) + ListImpl(); + else + await systemIdentity.RunImpersonated(ListImpl, cancellationToken).ConfigureAwait(false); return result; } @@ -240,10 +245,11 @@ namespace Tgstation.Server.Host.Components.StaticFiles } } - if (systemIdentity == null) - await Task.Factory.StartNew(ReadImpl, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); - else - await systemIdentity.RunImpersonated(ReadImpl, cancellationToken).ConfigureAwait(false); + using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) + if (systemIdentity == null) + await Task.Factory.StartNew(ReadImpl, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); + else + await systemIdentity.RunImpersonated(ReadImpl, cancellationToken).ConfigureAwait(false); return result; } @@ -251,7 +257,6 @@ namespace Tgstation.Server.Host.Components.StaticFiles /// public async Task SymlinkStaticFilesTo(string destination, CancellationToken cancellationToken) { - await EnsureDirectories(cancellationToken).ConfigureAwait(false); async Task SymlinkBase(bool files) { Task> task; @@ -275,7 +280,11 @@ namespace Tgstation.Server.Host.Components.StaticFiles })).ConfigureAwait(false); } - await Task.WhenAll(SymlinkBase(true), SymlinkBase(false)).ConfigureAwait(false); + using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) + { + await EnsureDirectories(cancellationToken).ConfigureAwait(false); + await Task.WhenAll(SymlinkBase(true), SymlinkBase(false)).ConfigureAwait(false); + } } /// @@ -328,13 +337,11 @@ namespace Tgstation.Server.Host.Components.StaticFiles } } - if (systemIdentity == null) - await Task.Factory.StartNew(WriteImpl, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); - else - await systemIdentity.RunImpersonated(WriteImpl, cancellationToken).ConfigureAwait(false); - - if (result != null && data == null) //make sure these directories always exist - await EnsureDirectories(cancellationToken).ConfigureAwait(false); + using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) + if (systemIdentity == null) + await Task.Factory.StartNew(WriteImpl, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); + else + await systemIdentity.RunImpersonated(WriteImpl, cancellationToken).ConfigureAwait(false); return result; } @@ -347,10 +354,12 @@ namespace Tgstation.Server.Host.Components.StaticFiles bool? result = null; void DoCreate() => result = synchronousIOManager.CreateDirectory(path, cancellationToken); - if (systemIdentity == null) - await Task.Factory.StartNew(DoCreate, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); - else - await systemIdentity.RunImpersonated(DoCreate, cancellationToken).ConfigureAwait(false); + + using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) + if (systemIdentity == null) + await Task.Factory.StartNew(DoCreate, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); + else + await systemIdentity.RunImpersonated(DoCreate, cancellationToken).ConfigureAwait(false); return result.Value; } @@ -387,5 +396,24 @@ namespace Tgstation.Server.Host.Components.StaticFiles } return true; } + + /// + public async Task DeleteDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken) + { + await EnsureDirectories(cancellationToken).ConfigureAwait(false); + var path = ValidateConfigRelativePath(configurationRelativePath); + + var result = false; + using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) + { + void CheckDeleteImpl() => result = synchronousIOManager.DeleteDirectory(path); + + if (systemIdentity != null) + await systemIdentity.RunImpersonated(CheckDeleteImpl, cancellationToken).ConfigureAwait(false); + else + CheckDeleteImpl(); + } + return result; + } } } diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs b/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs index ebccc9c24e..29b8b66da0 100644 --- a/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs +++ b/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs @@ -57,6 +57,15 @@ namespace Tgstation.Server.Host.Components.StaticFiles /// A resulting in if the directory already existed, otherwise Task CreateDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken); + /// + /// Attempt to delete an empty directory at + /// + /// The path of the empty directory to delete + /// The for the operation. If , the operation will be performed as the user of the + /// The for the operation + /// if the directory was empty and deleted, otherwise + Task DeleteDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken); + /// /// Writes to a given /// diff --git a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs index 34ded3ea45..f1ad7587a1 100644 --- a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs +++ b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs @@ -168,5 +168,29 @@ namespace Tgstation.Server.Host.Controllers return Forbid(); } } + + [HttpDelete("{*directoryPath}")] + [TgsAuthorize(ConfigurationRights.Delete)] + public async Task Delete(string directoryPath, CancellationToken cancellationToken) + { + if (ForbidDueToModeConflicts()) + return Forbid(); + + try + { + return await instanceManager.GetInstance(Instance).Configuration.DeleteDirectory(directoryPath, AuthenticationContext.SystemIdentity, cancellationToken).ConfigureAwait(false) ? (IActionResult)Ok() : Conflict(new ErrorMessage + { + Message = "Directory not empty!" + }); + } + catch (NotImplementedException) + { + return StatusCode((int)HttpStatusCode.NotImplemented); + } + catch (UnauthorizedAccessException) + { + return Forbid(); + } + } } } diff --git a/src/Tgstation.Server.Host/IO/ISynchronousIOManager.cs b/src/Tgstation.Server.Host/IO/ISynchronousIOManager.cs index f361004dfe..19ad6fba61 100644 --- a/src/Tgstation.Server.Host/IO/ISynchronousIOManager.cs +++ b/src/Tgstation.Server.Host/IO/ISynchronousIOManager.cs @@ -39,6 +39,13 @@ namespace Tgstation.Server.Host.IO /// A array representing the contents of the file at byte[] ReadFile(string path); + /// + /// Deletes a directory at if it's empty + /// + /// The path of the directory to delete + /// if the directory does not exist or is empty and was deleted. otherwise + bool DeleteDirectory(string path); + /// /// Write to a file at a given . /// diff --git a/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs b/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs index 9a60da2313..fa394a644c 100644 --- a/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs +++ b/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs @@ -21,6 +21,22 @@ namespace Tgstation.Server.Host.IO return false; } + /// + public bool DeleteDirectory(string path) + { + if (File.Exists(path)) + return false; + + if (!Directory.Exists(path)) + return true; + + if (Directory.EnumerateFileSystemEntries(path).Any()) + return false; + + Directory.Delete(path); + return true; + } + /// public IEnumerable GetDirectories(string path, CancellationToken cancellationToken) { @@ -109,16 +125,7 @@ namespace Tgstation.Server.Host.IO } } if (data == null) - { File.Delete(path); - if (!cancellationToken.IsCancellationRequested) - //delete the entire folder if possible - try - { - Directory.Delete(directory); - } - catch (IOException) { } - } return true; } } From 9d0389749919217a234ae1b12242d9068a9913f0 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 4 Sep 2018 09:59:46 -0400 Subject: [PATCH 02/47] Bump client preview version --- src/Tgstation.Server.Client/Tgstation.Server.Client.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj index 89a7ceabe4..fc21bbffea 100644 --- a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj +++ b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj @@ -3,7 +3,7 @@ netstandard2.0 Full - 4.0.0.0-preview11 + 4.0.0.0-preview9101 true Cyberboss /tg/station 13 From 55220fe9248af5874f4b1bb3e8ccb05deaa3254d Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 4 Sep 2018 11:52:54 -0400 Subject: [PATCH 03/47] Fix ServerErrorException constructor --- src/Tgstation.Server.Client/ServerErrorException.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Client/ServerErrorException.cs b/src/Tgstation.Server.Client/ServerErrorException.cs index f5a243678c..940f3513a7 100644 --- a/src/Tgstation.Server.Client/ServerErrorException.cs +++ b/src/Tgstation.Server.Client/ServerErrorException.cs @@ -1,5 +1,6 @@ using System; using System.Net; +using Tgstation.Server.Api.Models; namespace Tgstation.Server.Client { @@ -22,7 +23,11 @@ namespace Tgstation.Server.Client /// Construct an with /// /// The raw HTML response of the - public ServerErrorException(string html) : base(null, HttpStatusCode.InternalServerError) + public ServerErrorException(string html) : base(new ErrorMessage + { + Message = "An internal server error occurred!", + SeverApiVersion = null + }, HttpStatusCode.InternalServerError) { Html = html; } From cbfe68b6693da76c7c34973633d189591273d6c6 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 4 Sep 2018 11:58:05 -0400 Subject: [PATCH 04/47] Various things: Cancelling an in progress job now returns Accepted() Offlining an instance now takes a User parameter and properly cancels jobs Can no longer erroneously detach an online instance Fix instance move job instancing and db change Instance move jobs are now read by querying the instance metadata Instance move jobs are cancelled by any update to an instance --- .../Components/IInstanceManager.cs | 9 +-- .../Components/InstanceManager.cs | 30 ++++++++- .../Controllers/InstanceController.cs | 63 +++++++++++++------ .../Controllers/JobController.cs | 4 +- src/Tgstation.Server.Host/Core/IJobManager.cs | 6 +- src/Tgstation.Server.Host/Core/JobManager.cs | 7 ++- 6 files changed, 87 insertions(+), 32 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/IInstanceManager.cs b/src/Tgstation.Server.Host/Components/IInstanceManager.cs index b6f9143587..186a2ac25a 100644 --- a/src/Tgstation.Server.Host/Components/IInstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/IInstanceManager.cs @@ -1,6 +1,6 @@ -using Microsoft.AspNetCore.Http; -using System.Threading; +using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components { @@ -12,7 +12,7 @@ namespace Tgstation.Server.Host.Components /// /// Get the associated with given /// - /// The of the desired + /// The of the desired /// The associated with the given IInstance GetInstance(Models.Instance metadata); @@ -28,9 +28,10 @@ namespace Tgstation.Server.Host.Components /// Offline an /// /// The of the desired + /// The performing the operation /// The for the operation /// A representing the running operation - Task OfflineInstance(Models.Instance metadata, CancellationToken cancellationToken); + Task OfflineInstance(Models.Instance metadata, User user, CancellationToken cancellationToken); /// /// Move an diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs index 541d16a977..1165c0c390 100644 --- a/src/Tgstation.Server.Host/Components/InstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs @@ -124,12 +124,21 @@ namespace Tgstation.Server.Host.Components throw new InvalidOperationException("Cannot move an online instance!"); var oldPath = instance.Path; await ioManager.CopyDirectory(oldPath, newPath, null, cancellationToken).ConfigureAwait(false); - instance.Path = ioManager.ResolvePath(newPath); + await databaseContextFactory.UseContext(db => + { + var targetInstance = new Models.Instance + { + Id = instance.Id + }; + db.Instances.Attach(targetInstance); + targetInstance.Path = newPath; + return db.Save(cancellationToken); + }).ConfigureAwait(false); await ioManager.DeleteDirectory(oldPath, cancellationToken).ConfigureAwait(false); } /// - public async Task OfflineInstance(Models.Instance metadata, CancellationToken cancellationToken) + public async Task OfflineInstance(Models.Instance metadata, Models.User user, CancellationToken cancellationToken) { if (metadata == null) throw new ArgumentNullException(nameof(metadata)); @@ -143,6 +152,23 @@ namespace Tgstation.Server.Host.Components } try { + //we are the one responsible for cancelling his jobs + var tasks = new List(); + await databaseContextFactory.UseContext(async db => + { + var jobs = db.Jobs.Where(x => x.Instance.Id == metadata.Id).Select(x => new Models.Job + { + Id = x.Id + }).ToAsyncEnumerable(); + await jobs.ForEachAsync(job => + { + lock (tasks) + tasks.Add(jobManager.CancelJob(job, user, true, cancellationToken)); + }, cancellationToken).ConfigureAwait(false); + }).ConfigureAwait(false); + + await Task.WhenAll(tasks).ConfigureAwait(false); + await instance.StopAsync(cancellationToken).ConfigureAwait(false); } finally diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs index eae5142ff3..0cd80d48f6 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs @@ -34,6 +34,8 @@ namespace Tgstation.Server.Host.Controllers /// const string InstanceAttachFileName = "TGS4_ALLOW_INSTANCE_ATTACH"; + const string MoveInstanceJobPrefix = "Move instance ID "; + /// /// The for the /// @@ -202,6 +204,11 @@ namespace Tgstation.Server.Host.Controllers .FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); if (originalModel == default) return StatusCode((int)HttpStatusCode.Gone); + if (originalModel.Online.Value) + return Conflict(new ErrorMessage + { + Message = "Cannot detach an online instance!" + }); if (originalModel.WatchdogReattachInformation != null) { @@ -226,6 +233,18 @@ namespace Tgstation.Server.Host.Controllers { var instanceQuery = DatabaseContext.Instances.Where(x => x.Id == model.Id); + var moveJob = await instanceQuery + .SelectMany(x => x.Jobs). + Where(x => !x.StoppedAt.HasValue && x.Description.StartsWith(MoveInstanceJobPrefix, StringComparison.Ordinal)) + .Select(x => new Models.Job + { + Id = x.Id + }).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); + + if (moveJob != default) + //cancel it now + await jobManager.CancelJob(moveJob, AuthenticationContext.User, true, cancellationToken).ConfigureAwait(false); + var usersInstanceUserTask = instanceQuery.SelectMany(x => x.InstanceUsers).Where(x => x.UserId == AuthenticationContext.User.Id).FirstOrDefaultAsync(cancellationToken); var originalModel = await instanceQuery @@ -296,7 +315,7 @@ namespace Tgstation.Server.Host.Controllers try { if (originalOnline && model.Online == false) - await instanceManager.OfflineInstance(originalModel, cancellationToken).ConfigureAwait(false); + await instanceManager.OfflineInstance(originalModel, AuthenticationContext.User, cancellationToken).ConfigureAwait(false); else if (!originalOnline && model.Online == true) { //force autostart false here because we don't want any long running jobs right now @@ -321,26 +340,14 @@ namespace Tgstation.Server.Host.Controllers { var job = new Models.Job { - Description = String.Format(CultureInfo.InvariantCulture, "Move instance ID {0} from {1} to {2}", Instance.Id, Instance.Path, rawPath), - Instance = Instance, + Description = String.Format(CultureInfo.InvariantCulture, MoveInstanceJobPrefix + "{0} from {1} to {2}", originalModel.Id, originalModel.Path, rawPath), + Instance = originalModel, CancelRightsType = RightsType.InstanceManager, CancelRight = (ulong)InstanceManagerRights.Relocate, StartedBy = AuthenticationContext.User }; - await jobManager.RegisterOperation(job, async (paramJob, serviceProvider, progressHandler, ct) => - { - try - { - await instanceManager.MoveInstance(Instance, rawPath, ct).ConfigureAwait(false); - } - catch - { - originalModel.Path = originalModelPath; - await DatabaseContext.Save(default).ConfigureAwait(false); - throw; - } - }, cancellationToken).ConfigureAwait(false); + await jobManager.RegisterOperation(job, (paramJob, serviceProvider, progressHandler, ct) => instanceManager.MoveInstance(originalModel, rawPath, ct), cancellationToken).ConfigureAwait(false); api.MoveJob = job.ToApi(); } @@ -354,8 +361,19 @@ namespace Tgstation.Server.Host.Controllers IQueryable query = DatabaseContext.Instances; if (!AuthenticationContext.User.InstanceManagerRights.Value.HasFlag(InstanceManagerRights.List)) query = query.Where(x => x.InstanceUsers.Any(y => y.UserId == AuthenticationContext.User.Id)).Where(x => x.InstanceUsers.Any(y => y.AnyRights)); + + var moveJobTasks = query + .SelectMany(x => x.Jobs) + .Where(x => !x.StoppedAt.HasValue && x.Description.StartsWith(MoveInstanceJobPrefix, StringComparison.Ordinal)) + .Include(x => x.StartedBy).ThenInclude(x => x.CreatedBy) + .Include(x => x.Instance) + .ToListAsync(cancellationToken); var instances = await query.ToListAsync(cancellationToken).ConfigureAwait(false); - return Json(instances.Select(x => x.ToApi())); + var apis = instances.Select(x => x.ToApi()); + var moveJobs = await moveJobTasks.ConfigureAwait(false); + foreach(var I in moveJobs) + apis.Where(x => x.Id == I.Instance.Id).First().MoveJob = I.ToApi(); //if this .First() fails i will personally murder kevinz000 because I just know he is somehow responsible + return Json(apis); } /// @@ -368,6 +386,11 @@ namespace Tgstation.Server.Host.Controllers if (cantList) query = query.Include(x => x.InstanceUsers); + var moveJobTask = query + .SelectMany(x => x.Jobs) + .Where(x => !x.StoppedAt.HasValue && x.Description.StartsWith(MoveInstanceJobPrefix, StringComparison.Ordinal)) + .Include(x => x.StartedBy).ThenInclude(x => x.CreatedBy) + .FirstOrDefaultAsync(cancellationToken); var instance = await query.FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); if (instance == null) @@ -375,8 +398,10 @@ namespace Tgstation.Server.Host.Controllers if (cantList && !instance.InstanceUsers.Any(x => x.UserId == AuthenticationContext.User.Id && x.AnyRights)) return Forbid(); - - return Json(instance.ToApi()); + + var api = instance.ToApi(); + api.MoveJob = (await moveJobTask.ConfigureAwait(false)).ToApi(); + return Json(api); } } } diff --git a/src/Tgstation.Server.Host/Controllers/JobController.cs b/src/Tgstation.Server.Host/Controllers/JobController.cs index 07c54687cc..cd9aee4d46 100644 --- a/src/Tgstation.Server.Host/Controllers/JobController.cs +++ b/src/Tgstation.Server.Host/Controllers/JobController.cs @@ -71,8 +71,8 @@ namespace Tgstation.Server.Host.Controllers if (job.CancelRight.HasValue && job.CancelRightsType.HasValue && (AuthenticationContext.GetRight(job.CancelRightsType.Value) & job.CancelRight.Value) == 0) return Forbid(); - await jobManager.CancelJob(job, AuthenticationContext.User, cancellationToken).ConfigureAwait(false); - return Ok(); + await jobManager.CancelJob(job, AuthenticationContext.User, false, cancellationToken).ConfigureAwait(false); + return Accepted(); } /// diff --git a/src/Tgstation.Server.Host/Core/IJobManager.cs b/src/Tgstation.Server.Host/Core/IJobManager.cs index 51d9a2b999..b849d3ea8a 100644 --- a/src/Tgstation.Server.Host/Core/IJobManager.cs +++ b/src/Tgstation.Server.Host/Core/IJobManager.cs @@ -1,5 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Hosting; using System; using System.Threading; using System.Threading.Tasks; @@ -31,8 +30,9 @@ namespace Tgstation.Server.Host.Core /// /// The to cancel /// The who cancelled the + /// If the operation should wait until the job exits before completing /// The for the operation /// A representing a running operation - Task CancelJob(Job job, User user, CancellationToken cancellationToken); + Task CancelJob(Job job, User user, bool blocking, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Core/JobManager.cs b/src/Tgstation.Server.Host/Core/JobManager.cs index fc08c985b8..7dfa3cccf7 100644 --- a/src/Tgstation.Server.Host/Core/JobManager.cs +++ b/src/Tgstation.Server.Host/Core/JobManager.cs @@ -187,13 +187,14 @@ namespace Tgstation.Server.Host.Core } /// - public async Task CancelJob(Job job, User user, CancellationToken cancellationToken) + public async Task CancelJob(Job job, User user, bool blocking, CancellationToken cancellationToken) { if (job == null) throw new ArgumentNullException(nameof(job)); if (user == null) throw new ArgumentNullException(nameof(user)); - CheckGetJob(job).Cancel(); //this will ensure the db update is only done once + var jobObject = CheckGetJob(job); + jobObject.Cancel(); //this will ensure the db update is only done once using (var scope = serviceProvider.CreateScope()) { var databaseContext = scope.ServiceProvider.GetRequiredService(); @@ -205,6 +206,8 @@ namespace Tgstation.Server.Host.Core //let either startup or cancellation set job.cancelled await databaseContext.Save(cancellationToken).ConfigureAwait(false); } + if (blocking) + await jobObject.Wait(cancellationToken).ConfigureAwait(false); } /// From c9794a55a292ae82fceb3a4882997038a8a8564e Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 4 Sep 2018 11:58:29 -0400 Subject: [PATCH 05/47] Renamed some InstanceManagerClient functions --- src/Tgstation.Server.Client/IInstanceManagerClient.cs | 8 ++++---- src/Tgstation.Server.Client/InstanceManagerClient.cs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Tgstation.Server.Client/IInstanceManagerClient.cs b/src/Tgstation.Server.Client/IInstanceManagerClient.cs index f983ce3d44..73d60a3893 100644 --- a/src/Tgstation.Server.Client/IInstanceManagerClient.cs +++ b/src/Tgstation.Server.Client/IInstanceManagerClient.cs @@ -19,12 +19,12 @@ namespace Tgstation.Server.Client Task> List(CancellationToken cancellationToken); /// - /// Create an + /// Create or attach an /// /// The to create. will be ignored /// The for the operation - /// A resulting in the created - Task Create(Instance instance, CancellationToken cancellationToken); + /// A resulting in the created or attached + Task CreateOrAttach(Instance instance, CancellationToken cancellationToken); /// /// Relocates, renamed, and/or on/offlines an @@ -48,7 +48,7 @@ namespace Tgstation.Server.Client /// The to delete /// The for the operation /// A representing the running operation - Task Delete(Instance instance, CancellationToken cancellationToken); + Task Detach(Instance instance, CancellationToken cancellationToken); /// /// Create an for a given diff --git a/src/Tgstation.Server.Client/InstanceManagerClient.cs b/src/Tgstation.Server.Client/InstanceManagerClient.cs index f8343c5c91..0aad027e0b 100644 --- a/src/Tgstation.Server.Client/InstanceManagerClient.cs +++ b/src/Tgstation.Server.Client/InstanceManagerClient.cs @@ -33,10 +33,10 @@ namespace Tgstation.Server.Client } /// - public Task Create(Instance instance, CancellationToken cancellationToken) => apiClient.Create(Routes.InstanceManager, instance ?? throw new ArgumentNullException(nameof(instance)), cancellationToken); + public Task CreateOrAttach(Instance instance, CancellationToken cancellationToken) => apiClient.Create(Routes.InstanceManager, instance ?? throw new ArgumentNullException(nameof(instance)), cancellationToken); /// - public Task Delete(Instance instance, CancellationToken cancellationToken) => apiClient.Delete(Routes.SetID(Routes.InstanceManager, instance?.Id ?? throw new ArgumentNullException(nameof(instance))), cancellationToken); + public Task Detach(Instance instance, CancellationToken cancellationToken) => apiClient.Delete(Routes.SetID(Routes.InstanceManager, instance?.Id ?? throw new ArgumentNullException(nameof(instance))), cancellationToken); /// public Task> List(CancellationToken cancellationToken) => apiClient.Read>(Routes.List(Routes.InstanceManager), cancellationToken); From dce16f210916ec424efe6de1c759f303178e48ef Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 4 Sep 2018 12:06:01 -0400 Subject: [PATCH 06/47] Update documentation about instance move jobs --- docs/API.dox | 2 ++ src/Tgstation.Server.Api/Models/Instance.cs | 1 + 2 files changed, 3 insertions(+) diff --git a/docs/API.dox b/docs/API.dox index af9deb1ebe..ddc5028bd4 100644 --- a/docs/API.dox +++ b/docs/API.dox @@ -210,6 +210,8 @@ DELETE "/Instance/{InstanceId}" => OK Some requests return @ref Tgstation.Server.Api.Models.Job objects. These are long running tasks the server will perform asyncronously and can be polled for status. +Note that the job representing instance move operations must be queried and cancelled differently than others. See the documentation of @ref Tgstation.Server.Api.Models.Instance.MoveJob for details + To list all jobs in an Instance use the following request I GET "/Job/List" => Array of @ref Tgstation.Server.Api.Models.Job diff --git a/src/Tgstation.Server.Api/Models/Instance.cs b/src/Tgstation.Server.Api/Models/Instance.cs index 474016bacb..0349ce9fce 100644 --- a/src/Tgstation.Server.Api/Models/Instance.cs +++ b/src/Tgstation.Server.Api/Models/Instance.cs @@ -46,6 +46,7 @@ namespace Tgstation.Server.Api.Models /// /// The representing a change of /// + /// Due to how s are children of s but moving one requires the to be offline, interactions with this are performed in a non-standard fashion. The is read by querying the again (either via list or ID lookup) and cancelled by making any sort of update to the . Once the comes back it can be queried like a normal job [NotMapped] public Job MoveJob { get; set; } From 2fe22971ad4fc014075daab7680187fd6db2e97e Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 4 Sep 2018 12:06:26 -0400 Subject: [PATCH 07/47] Bump API preview version --- src/Tgstation.Server.Api/Tgstation.Server.Api.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj index fc51067823..20370a3c63 100644 --- a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj +++ b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj @@ -17,7 +17,7 @@ 4.0.0.0 json web api tgstation-server tgstation ss13 byond Prototype release - 4.0.0.0-preview5 + 4.0.0.0-preview6001 From b8fd7589a2231ca7e2ccd32e4aee6450797952ea Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 4 Sep 2018 12:12:41 -0400 Subject: [PATCH 08/47] Fix GetId MoveJob reads NullReferenceException --- src/Tgstation.Server.Host/Controllers/InstanceController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs index 0cd80d48f6..43eb73c9d7 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs @@ -400,7 +400,7 @@ namespace Tgstation.Server.Host.Controllers return Forbid(); var api = instance.ToApi(); - api.MoveJob = (await moveJobTask.ConfigureAwait(false)).ToApi(); + api.MoveJob = (await moveJobTask.ConfigureAwait(false))?.ToApi(); return Json(api); } } From 757a722f64948ce624211a4bd944b2fa6399cda1 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 4 Sep 2018 12:14:19 -0400 Subject: [PATCH 09/47] Add missing instance specification to ConfigurationClient.DeleteEmptyDirectory --- src/Tgstation.Server.Client/Components/ConfigurationClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Client/Components/ConfigurationClient.cs b/src/Tgstation.Server.Client/Components/ConfigurationClient.cs index 0d4574db60..4aa4d82330 100644 --- a/src/Tgstation.Server.Client/Components/ConfigurationClient.cs +++ b/src/Tgstation.Server.Client/Components/ConfigurationClient.cs @@ -31,7 +31,7 @@ namespace Tgstation.Server.Client.Components } /// - public Task DeleteEmptyDirectory(string directory, CancellationToken cancellationToken) => apiClient.Delete(Routes.Configuration + directory ?? throw new ArgumentNullException(nameof(directory)), cancellationToken); + public Task DeleteEmptyDirectory(string directory, CancellationToken cancellationToken) => apiClient.Delete(Routes.Configuration + directory ?? throw new ArgumentNullException(nameof(directory)), instance.Id, cancellationToken); /// public Task> List(string directory, CancellationToken cancellationToken) From 1c5a134a1cb9ebb8255110645f3febf8e9abc8a8 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 4 Sep 2018 14:31:58 -0400 Subject: [PATCH 10/47] Various Configuration fixups --- docs/API.dox | 12 +++++- src/Tgstation.Server.Client/ApiClient.cs | 3 ++ .../Components/ConfigurationClient.cs | 25 ++++++++---- .../Components/IConfigurationClient.cs | 4 +- src/Tgstation.Server.Client/IApiClient.cs | 1 + .../Components/StaticFiles/Configuration.cs | 2 + .../Controllers/ConfigurationController.cs | 40 ++++++++++++++----- .../IO/DefaultIOManager.cs | 3 ++ src/Tgstation.Server.Host/IO/IIOManager.cs | 7 ++++ .../IO/SynchronousIOManager.cs | 2 +- 10 files changed, 75 insertions(+), 24 deletions(-) diff --git a/docs/API.dox b/docs/API.dox index ddc5028bd4..49c19ce3ee 100644 --- a/docs/API.dox +++ b/docs/API.dox @@ -418,11 +418,19 @@ To create, write, and delete files use the following request I POST "/Config" @ref Tgstation.Server.Api.Models.ConfigurationFile => @ref Tgstation.Server.Api.Models.ConfigurationFile -When creating a file, only @ref Tgstation.Server.Api.Models.ConfigurationFile.Path and @ref Tgstation.Server.Api.Models.ConfigurationFile.Content should be specified. Any necessary preceeding directories will be created if possible. +When creating a file, only @ref Tgstation.Server.Api.Models.ConfigurationFile.Path and @ref Tgstation.Server.Api.Models.ConfigurationFile.Content should be specified If the file already exists, the @ref Tgstation.Server.Api.Models.ConfigurationFile.LastReadHash field must also be present with the last version recieved from the server for that file. If this does not match at the time of the request, 409 will be returned, indicating the file has changed since it was last viewed by the client. -To delete a file set @ref Tgstation.Server.Api.Models.ConfigurationFile.Content to null in the request. If deleting a file leaves a directory empty, they too will be deleted. +To delete a file set @ref Tgstation.Server.Api.Models.ConfigurationFile.Content to null in the request. + + +To delete an empty directory use the following request + +I DELETE "/Config" @ref Tgstation.Server.Api.Models.ConfigurationFile => OK + +Where the request @ref Tgstation.Server.Api.Models.ConfigurationFile.Path is set to the directory to delete + @subsection api_dog Watchdog diff --git a/src/Tgstation.Server.Client/ApiClient.cs b/src/Tgstation.Server.Client/ApiClient.cs index 8405ac5517..e20a143c47 100644 --- a/src/Tgstation.Server.Client/ApiClient.cs +++ b/src/Tgstation.Server.Client/ApiClient.cs @@ -197,6 +197,9 @@ namespace Tgstation.Server.Client /// public Task Delete(string route, long instanceId, CancellationToken cancellationToken) => RunRequest(route, null, HttpMethod.Delete, instanceId, cancellationToken); + /// + public Task Delete(string route, TBody body, long instanceId, CancellationToken cancellationToken) => RunRequest(route, body, HttpMethod.Delete, instanceId, cancellationToken); + /// public Task Delete(string route, long instanceId, CancellationToken cancellationToken) => RunRequest(route, null, HttpMethod.Delete, instanceId, cancellationToken); diff --git a/src/Tgstation.Server.Client/Components/ConfigurationClient.cs b/src/Tgstation.Server.Client/Components/ConfigurationClient.cs index 4aa4d82330..d3f1a5bccf 100644 --- a/src/Tgstation.Server.Client/Components/ConfigurationClient.cs +++ b/src/Tgstation.Server.Client/Components/ConfigurationClient.cs @@ -19,6 +19,20 @@ namespace Tgstation.Server.Client.Components /// readonly Instance instance; + /// + /// Sanitize a path for use in a GET + /// + /// The path to sanitize + /// The sanitized path + static string SanitizeGetPath(string path) + { + if (path == null) + path = String.Empty; + if (path.Length == 0 || path[0] != '/') + path = '/' + path; + return path; + } + /// /// Construct a /// @@ -31,22 +45,17 @@ namespace Tgstation.Server.Client.Components } /// - public Task DeleteEmptyDirectory(string directory, CancellationToken cancellationToken) => apiClient.Delete(Routes.Configuration + directory ?? throw new ArgumentNullException(nameof(directory)), instance.Id, cancellationToken); + public Task DeleteEmptyDirectory(ConfigurationFile directory, CancellationToken cancellationToken) => apiClient.Delete(Routes.Configuration, directory, instance.Id, cancellationToken); /// - public Task> List(string directory, CancellationToken cancellationToken) - { - if (directory == null) - directory = String.Empty; - return apiClient.Read>(Routes.List(Routes.Configuration) + directory, instance.Id, cancellationToken); - } + public Task> List(string directory, CancellationToken cancellationToken) => apiClient.Read>(Routes.List(Routes.Configuration) + SanitizeGetPath(directory), instance.Id, cancellationToken); /// public Task Read(ConfigurationFile file, CancellationToken cancellationToken) { if (file == null) throw new ArgumentNullException(nameof(file)); - return apiClient.Read(Routes.Configuration + file.Path, instance.Id, cancellationToken); + return apiClient.Read(Routes.Configuration + SanitizeGetPath(file.Path), instance.Id, cancellationToken); } /// diff --git a/src/Tgstation.Server.Client/Components/IConfigurationClient.cs b/src/Tgstation.Server.Client/Components/IConfigurationClient.cs index ec8a5c9396..2d2337ff50 100644 --- a/src/Tgstation.Server.Client/Components/IConfigurationClient.cs +++ b/src/Tgstation.Server.Client/Components/IConfigurationClient.cs @@ -37,9 +37,9 @@ namespace Tgstation.Server.Client.Components /// /// Delete an empty /// - /// The path to directory to delete + /// The representing the directory to delete /// The for the operation /// A representing the running operation - Task DeleteEmptyDirectory(string directory, CancellationToken cancellationToken); + Task DeleteEmptyDirectory(ConfigurationFile directory, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Client/IApiClient.cs b/src/Tgstation.Server.Client/IApiClient.cs index 8359972f02..82b1298122 100644 --- a/src/Tgstation.Server.Client/IApiClient.cs +++ b/src/Tgstation.Server.Client/IApiClient.cs @@ -35,6 +35,7 @@ namespace Tgstation.Server.Client Task Read(string route, long instanceId, CancellationToken cancellationToken); Task Update(string route, TBody body, long instanceId, CancellationToken cancellationToken); Task Delete(string route, long instanceId, CancellationToken cancellationToken); + Task Delete(string route, TBody body, long instanceId, CancellationToken cancellationToken); Task Delete(string route, long instanceId, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs index 1e0a42c7bc..69846570a3 100644 --- a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs +++ b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs @@ -144,6 +144,8 @@ namespace Tgstation.Server.Host.Components.StaticFiles var nullOrEmptyCheck = String.IsNullOrEmpty(configurationRelativePath); if (nullOrEmptyCheck) configurationRelativePath = "."; + if (configurationRelativePath[0] == Path.DirectorySeparatorChar || configurationRelativePath[0] == Path.AltDirectorySeparatorChar) + configurationRelativePath = '.' + configurationRelativePath; var resolved = ioManager.ResolvePath(configurationRelativePath); var local = !nullOrEmptyCheck ? ioManager.ResolvePath(".") : null; if (!nullOrEmptyCheck && resolved.Length < local.Length) //.. fuccbois diff --git a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs index f1ad7587a1..8e36f841c7 100644 --- a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs +++ b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.Logging; using System; using System.IO; +using System.Linq; using System.Net; using System.Threading; using System.Threading.Tasks; @@ -9,6 +10,7 @@ using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Components; +using Tgstation.Server.Host.IO; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; @@ -25,29 +27,39 @@ namespace Tgstation.Server.Host.Controllers /// readonly IInstanceManager instanceManager; + /// + /// The for the + /// + readonly IIOManager ioManager; + /// /// Construct a /// /// The for the /// The for the /// The value of + /// The value of /// The for the - public ConfigurationController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IInstanceManager instanceManager, ILogger logger) : base(databaseContext, authenticationContextFactory, logger, true) + public ConfigurationController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IInstanceManager instanceManager, IIOManager ioManager, ILogger logger) : base(databaseContext, authenticationContextFactory, logger, true) { this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); + this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); } /// - /// If a should be returned from actions due to conflicts with one or both of the or the + /// If a should be returned from actions due to conflicts with one or both of the or the or a given tries to access parent directories /// + /// The path to validate if any /// if a should be returned, otherwise - bool ForbidDueToModeConflicts() => Instance.ConfigurationType == ConfigurationType.Disallowed || (Instance.ConfigurationType == ConfigurationType.SystemIdentityWrite && AuthenticationContext.SystemIdentity == null); + bool ForbidDueToModeConflicts(string path) => Instance.ConfigurationType == ConfigurationType.Disallowed || (Instance.ConfigurationType == ConfigurationType.SystemIdentityWrite && AuthenticationContext.SystemIdentity == null) || (path != null && ioManager.PathContainsParentAccess(path)); /// [TgsAuthorize(ConfigurationRights.Write)] public override async Task Update([FromBody] ConfigurationFile model, CancellationToken cancellationToken) { - if (ForbidDueToModeConflicts()) + if (model == null) + throw new ArgumentNullException(nameof(model)); + if (ForbidDueToModeConflicts(model.Path)) return Forbid(); var config = instanceManager.GetInstance(Instance).Configuration; @@ -88,7 +100,7 @@ namespace Tgstation.Server.Host.Controllers [TgsAuthorize(ConfigurationRights.Read)] public async Task File(string filePath, CancellationToken cancellationToken) { - if (ForbidDueToModeConflicts()) + if (ForbidDueToModeConflicts(filePath)) return Forbid(); try @@ -123,7 +135,7 @@ namespace Tgstation.Server.Host.Controllers [TgsAuthorize(ConfigurationRights.List)] public async Task Directory(string directoryPath, CancellationToken cancellationToken) { - if (ForbidDueToModeConflicts()) + if (ForbidDueToModeConflicts(directoryPath)) return Forbid(); try @@ -152,7 +164,10 @@ namespace Tgstation.Server.Host.Controllers [TgsAuthorize(ConfigurationRights.Write)] public override async Task Create([FromBody] ConfigurationFile model, CancellationToken cancellationToken) { - if (ForbidDueToModeConflicts()) + if (model == null) + throw new ArgumentNullException(nameof(model)); + + if (ForbidDueToModeConflicts(model.Path)) return Forbid(); try @@ -169,16 +184,19 @@ namespace Tgstation.Server.Host.Controllers } } - [HttpDelete("{*directoryPath}")] + [HttpDelete] [TgsAuthorize(ConfigurationRights.Delete)] - public async Task Delete(string directoryPath, CancellationToken cancellationToken) + public async Task Delete([FromBody] ConfigurationFile directory, CancellationToken cancellationToken) { - if (ForbidDueToModeConflicts()) + if (directory == null) + throw new ArgumentNullException(nameof(directory)); + + if (ForbidDueToModeConflicts(directory.Path)) return Forbid(); try { - return await instanceManager.GetInstance(Instance).Configuration.DeleteDirectory(directoryPath, AuthenticationContext.SystemIdentity, cancellationToken).ConfigureAwait(false) ? (IActionResult)Ok() : Conflict(new ErrorMessage + return await instanceManager.GetInstance(Instance).Configuration.DeleteDirectory(directory.Path, AuthenticationContext.SystemIdentity, cancellationToken).ConfigureAwait(false) ? (IActionResult)Ok() : Conflict(new ErrorMessage { Message = "Directory not empty!" }); diff --git a/src/Tgstation.Server.Host/IO/DefaultIOManager.cs b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs index 15fd161b0d..dee0d3eecf 100644 --- a/src/Tgstation.Server.Host/IO/DefaultIOManager.cs +++ b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs @@ -310,5 +310,8 @@ namespace Tgstation.Server.Host.IO using (var archive = new ZipArchive(ms, ZipArchiveMode.Read)) archive.ExtractToDirectory(path); }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + + /// + public bool PathContainsParentAccess(string path) => path?.Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }).Any(x => x == "..") ?? throw new ArgumentNullException(nameof(path)); } } diff --git a/src/Tgstation.Server.Host/IO/IIOManager.cs b/src/Tgstation.Server.Host/IO/IIOManager.cs index da63c6d117..4ce21a7c97 100644 --- a/src/Tgstation.Server.Host/IO/IIOManager.cs +++ b/src/Tgstation.Server.Host/IO/IIOManager.cs @@ -31,6 +31,13 @@ namespace Tgstation.Server.Host.IO /// The file name portion of string GetFileNameWithoutExtension(string path); + /// + /// Check if a contains the '..' parent directory accessor + /// + /// The path to check + /// if contains a '..' accessor, otherwise + bool PathContainsParentAccess(string path); + /// /// Copies a directory from to /// diff --git a/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs b/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs index fa394a644c..d31e652e5d 100644 --- a/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs +++ b/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs @@ -104,7 +104,7 @@ namespace Tgstation.Server.Host.IO using (var sha1 = new SHA1Managed()) #pragma warning restore CA5350 // Do not use insecure cryptographic algorithm SHA1. { - string GetSha1(byte[] dataToHash) => dataToHash.Length != 0 ? String.Join("", sha1.ComputeHash(dataToHash).Select(b => b.ToString("x2", CultureInfo.InvariantCulture))) : null; + string GetSha1(byte[] dataToHash) => dataToHash != null && dataToHash.Length != 0 ? String.Join("", sha1.ComputeHash(dataToHash).Select(b => b.ToString("x2", CultureInfo.InvariantCulture))) : null; var originalSha1 = GetSha1(originalBytes); if (originalSha1 != sha1InOut) { From 58051797cb75cf2e48f53d2e7a65b57116e64389 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 4 Sep 2018 14:54:10 -0400 Subject: [PATCH 11/47] Update stable nuget packages --- .../Tgstation.Server.Host.csproj | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index 68e70510d1..6ed2f231b1 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -27,18 +27,18 @@ - - - - + + + + all compile; build; native; contentfiles; analyzers - - - - + + + + @@ -46,7 +46,7 @@ - + From b90171e917a8aef0d9a9e1a9b4c5eb9b6e1de18d Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 4 Sep 2018 15:22:47 -0400 Subject: [PATCH 12/47] More integration tests --- .../AdministrationTest.cs | 9 +- .../Instance/ConfigurationTest.cs | 63 ++++++++++ .../Instance/InstanceTest.cs | 25 ++++ .../InstanceManagerTest.cs | 116 ++++++++++++++++++ .../Tgstation.Server.Tests/IntegrationTest.cs | 7 +- tests/Tgstation.Server.Tests/TestingServer.cs | 10 +- 6 files changed, 221 insertions(+), 9 deletions(-) create mode 100644 tests/Tgstation.Server.Tests/Instance/ConfigurationTest.cs create mode 100644 tests/Tgstation.Server.Tests/Instance/InstanceTest.cs create mode 100644 tests/Tgstation.Server.Tests/InstanceManagerTest.cs diff --git a/tests/Tgstation.Server.Tests/AdministrationTest.cs b/tests/Tgstation.Server.Tests/AdministrationTest.cs index ef20efe158..9bad0c0168 100644 --- a/tests/Tgstation.Server.Tests/AdministrationTest.cs +++ b/tests/Tgstation.Server.Tests/AdministrationTest.cs @@ -1,6 +1,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Runtime.InteropServices; +using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Client; @@ -15,14 +16,14 @@ namespace Tgstation.Server.Tests this.client = client ?? throw new ArgumentNullException(nameof(client)); } - public async Task Run() + public async Task Run(CancellationToken cancellationToken) { - await TestRead().ConfigureAwait(false); + await TestRead(cancellationToken).ConfigureAwait(false); } - async Task TestRead() + async Task TestRead(CancellationToken cancellationToken) { - var model = await client.Read(default).ConfigureAwait(false); + var model = await client.Read(cancellationToken).ConfigureAwait(false); Assert.AreEqual(RuntimeInformation.IsOSPlatform(OSPlatform.Windows), model.WindowsHost); //uhh not much else to do diff --git a/tests/Tgstation.Server.Tests/Instance/ConfigurationTest.cs b/tests/Tgstation.Server.Tests/Instance/ConfigurationTest.cs new file mode 100644 index 0000000000..df33996edb --- /dev/null +++ b/tests/Tgstation.Server.Tests/Instance/ConfigurationTest.cs @@ -0,0 +1,63 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models; +using Tgstation.Server.Client; +using Tgstation.Server.Client.Components; + +namespace Tgstation.Server.Tests.Instance +{ + sealed class ConfigurationTest + { + readonly IConfigurationClient configurationClient; + readonly Api.Models.Instance instance; + + public ConfigurationTest(IConfigurationClient configurationClient, Api.Models.Instance instance) + { + this.configurationClient = configurationClient ?? throw new ArgumentNullException(nameof(configurationClient)); + this.instance = instance ?? throw new ArgumentNullException(nameof(instance)); + } + + bool FileExists(ConfigurationFile file) + { + var tmp = (file.Path?.StartsWith('/') ?? false) ? '.' + file.Path : file.Path; + var path = Path.Combine(instance.Path, "Configuration", tmp); + return File.Exists(path); + } + + async Task TestDeleteDirectory(CancellationToken cancellationToken) + { + //try to delete non-existent + var TestDir = new ConfigurationFile + { + Path = "/TestDeleteDir" + }; + + await configurationClient.DeleteEmptyDirectory(TestDir, cancellationToken).ConfigureAwait(false); + + //try to delete non-empty + var file = await configurationClient.Write(new ConfigurationFile + { + Content = Encoding.UTF8.GetBytes("Hello world!"), + Path = TestDir.Path + "/test.txt" + }, cancellationToken).ConfigureAwait(false); + + Assert.IsTrue(FileExists(file)); + + await Assert.ThrowsExceptionAsync(() => configurationClient.DeleteEmptyDirectory(TestDir, cancellationToken)).ConfigureAwait(false); + + file.Content = null; + await configurationClient.Write(file, cancellationToken).ConfigureAwait(false); + + await configurationClient.DeleteEmptyDirectory(TestDir, cancellationToken).ConfigureAwait(false); + } + + public async Task Run(CancellationToken cancellationToken) + { + await TestDeleteDirectory(cancellationToken).ConfigureAwait(false); + } + } +} diff --git a/tests/Tgstation.Server.Tests/Instance/InstanceTest.cs b/tests/Tgstation.Server.Tests/Instance/InstanceTest.cs new file mode 100644 index 0000000000..cd65f015f2 --- /dev/null +++ b/tests/Tgstation.Server.Tests/Instance/InstanceTest.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Client.Components; + +namespace Tgstation.Server.Tests.Instance +{ + sealed class InstanceTest + { + readonly IInstanceClient instanceClient; + + public InstanceTest(IInstanceClient instanceClient) + { + this.instanceClient = instanceClient ?? throw new ArgumentNullException(nameof(instanceClient)); + } + + public async Task RunTests(CancellationToken cancellationToken) + { + var configTests = new ConfigurationTest(instanceClient.Configuration, instanceClient.Metadata); + await configTests.Run(cancellationToken).ConfigureAwait(false); + } + } +} diff --git a/tests/Tgstation.Server.Tests/InstanceManagerTest.cs b/tests/Tgstation.Server.Tests/InstanceManagerTest.cs new file mode 100644 index 0000000000..3464217fd6 --- /dev/null +++ b/tests/Tgstation.Server.Tests/InstanceManagerTest.cs @@ -0,0 +1,116 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.IO; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models; +using Tgstation.Server.Client; +using Tgstation.Server.Host.Controllers; +using Tgstation.Server.Tests.Instance; + +namespace Tgstation.Server.Tests +{ + sealed class InstanceManagerTest + { + readonly IInstanceManagerClient instanceManagerClient; + readonly string testRootPath; + + long counter; + + public InstanceManagerTest(IInstanceManagerClient instanceManagerClient, string testRootPath) + { + this.instanceManagerClient = instanceManagerClient ?? throw new ArgumentNullException(nameof(instanceManagerClient)); + this.testRootPath = testRootPath ?? throw new ArgumentNullException(nameof(testRootPath)); + + counter = 0; + } + + Task CreateTestInstance(CancellationToken cancellationToken) => instanceManagerClient.CreateOrAttach(new Api.Models.Instance + { + Name = "TestInstance-" + ++counter, + Path = Path.Combine(testRootPath, Guid.NewGuid().ToString()), + Online = true + }, cancellationToken); + + public async Task Run(CancellationToken cancellationToken) + { + var firstTest = await CreateTestInstance(cancellationToken).ConfigureAwait(false); + //instances always start offline + Assert.AreEqual(false, firstTest.Online); + //check it exists + Assert.IsTrue(Directory.Exists(firstTest.Path)); + + //cant create instances in existent directories + var testNonEmpty = Path.Combine(testRootPath, Guid.NewGuid().ToString()); + Directory.CreateDirectory(testNonEmpty); + await Assert.ThrowsExceptionAsync(() => instanceManagerClient.CreateOrAttach(new Api.Models.Instance + { + Path = testNonEmpty, + Name = "NonEmptyTest" + }, cancellationToken)).ConfigureAwait(false); + await Assert.ThrowsExceptionAsync(() => instanceManagerClient.CreateOrAttach(firstTest, cancellationToken)).ConfigureAwait(false); + + //can't move to existent directories + await Assert.ThrowsExceptionAsync(() => instanceManagerClient.Update(new Api.Models.Instance + { + Id = firstTest.Id, + Path = testNonEmpty + }, cancellationToken)).ConfigureAwait(false); + + //test basic move + Directory.Delete(testNonEmpty); + var initialPath = firstTest.Path; + firstTest = await instanceManagerClient.Update(new Api.Models.Instance + { + Id = firstTest.Id, + Path = testNonEmpty + }, cancellationToken).ConfigureAwait(false); + + Assert.IsNotNull(firstTest.MoveJob); + + do + { + firstTest = await instanceManagerClient.GetId(firstTest, cancellationToken).ConfigureAwait(false); + await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false); + } while (firstTest.MoveJob != null); + + + //online it for real for component tests + firstTest.Online = true; + firstTest.ConfigurationType = ConfigurationType.HostWrite; + firstTest = await instanceManagerClient.Update(firstTest, cancellationToken).ConfigureAwait(false); + Assert.AreEqual(true, firstTest.Online); + Assert.AreEqual(ConfigurationType.HostWrite, firstTest.ConfigurationType); + + //can't move online instance + await Assert.ThrowsExceptionAsync(() => instanceManagerClient.Update(new Api.Models.Instance + { + Id = firstTest.Id, + Path = initialPath + }, cancellationToken)).ConfigureAwait(false); + + var testSuite1 = new InstanceTest(instanceManagerClient.CreateClient(firstTest)); + await testSuite1.RunTests(cancellationToken).ConfigureAwait(false); + + //can't detach online instance + await Assert.ThrowsExceptionAsync(() => instanceManagerClient.Detach(firstTest, cancellationToken)).ConfigureAwait(false); + + firstTest.Online = false; + firstTest = await instanceManagerClient.Update(firstTest, cancellationToken).ConfigureAwait(false); + await instanceManagerClient.Detach(firstTest, cancellationToken).ConfigureAwait(false); + + var instanceAttachFileName = (string)typeof(InstanceController).GetField("InstanceAttachFileName", BindingFlags.NonPublic | BindingFlags.Static).GetValue(null); + var attachPath = Path.Combine(firstTest.Path, instanceAttachFileName); + Assert.IsTrue(File.Exists(attachPath)); + + //can recreate detached instance + firstTest = await instanceManagerClient.CreateOrAttach(firstTest, cancellationToken).ConfigureAwait(false); + + //but only if the attach file exists + await instanceManagerClient.Detach(firstTest, cancellationToken).ConfigureAwait(false); + File.Delete(attachPath); + await Assert.ThrowsExceptionAsync(() => instanceManagerClient.CreateOrAttach(firstTest, cancellationToken)).ConfigureAwait(false); + } + } +} diff --git a/tests/Tgstation.Server.Tests/IntegrationTest.cs b/tests/Tgstation.Server.Tests/IntegrationTest.cs index b9d7277265..018cd8d897 100644 --- a/tests/Tgstation.Server.Tests/IntegrationTest.cs +++ b/tests/Tgstation.Server.Tests/IntegrationTest.cs @@ -1,5 +1,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using System; +using System.IO; using System.Net.Http.Headers; using System.Reflection; using System.Threading; @@ -22,7 +23,8 @@ namespace Tgstation.Server.Tests using (var server = new TestingServer()) using (var serverCts = new CancellationTokenSource()) { - var serverTask = server.RunAsync(serverCts.Token); + var cancellationToken = serverCts.Token; + var serverTask = server.RunAsync(cancellationToken); try { using (var adminClient = await clientFactory.CreateServerClient(server.Url, User.AdminName, User.DefaultAdminPassword).ConfigureAwait(false)) @@ -32,7 +34,8 @@ namespace Tgstation.Server.Tests Assert.AreEqual(ApiHeaders.Version, serverInfo.ApiVersion); Assert.AreEqual(typeof(IServer).Assembly.GetName().Version, serverInfo.Version); - await new AdministrationTest(adminClient.Administration).Run().ConfigureAwait(false); + await new AdministrationTest(adminClient.Administration).Run(cancellationToken).ConfigureAwait(false); + await new InstanceManagerTest(adminClient.Instances, server.Directory).Run(cancellationToken).ConfigureAwait(false); } } finally diff --git a/tests/Tgstation.Server.Tests/TestingServer.cs b/tests/Tgstation.Server.Tests/TestingServer.cs index c943f1d821..4e1df5f740 100644 --- a/tests/Tgstation.Server.Tests/TestingServer.cs +++ b/tests/Tgstation.Server.Tests/TestingServer.cs @@ -10,6 +10,8 @@ namespace Tgstation.Server.Tests sealed class TestingServer : IServer { public Uri Url { get; } + + public string Directory { get; } public bool RestartRequested => realServer.RestartRequested; readonly IServer realServer; @@ -17,8 +19,10 @@ namespace Tgstation.Server.Tests public TestingServer() { - databasePath = Path.GetTempFileName(); - File.Delete(databasePath); + Directory = Path.GetTempFileName(); + File.Delete(Directory); + System.IO.Directory.CreateDirectory(Directory); + databasePath = Path.Combine(Directory, "TGS.db3"); Url = new Uri("http://localhost:5001"); realServer = new ServerFactory().CreateServer(new string[] { @@ -33,7 +37,7 @@ namespace Tgstation.Server.Tests public void Dispose() { realServer.Dispose(); - File.Delete(databasePath); + System.IO.Directory.Delete(Directory, true); } public Task RunAsync(CancellationToken cancellationToken) => realServer.RunAsync(cancellationToken); From c5b21be9bab418c847dd28a22df02762542c2ee8 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 4 Sep 2018 15:58:09 -0400 Subject: [PATCH 13/47] Removes sqlite Way too much of a multithreaded environment to handle this correctly --- .../Configuration/DatabaseType.cs | 6 +-- src/Tgstation.Server.Host/Core/Application.cs | 9 ++--- .../SqliteDesignTimeDbContextFactory.cs | 15 -------- .../Models/SqliteDatabaseContext.cs | 38 ------------------- .../Tgstation.Server.Host.csproj | 1 - 5 files changed, 4 insertions(+), 65 deletions(-) delete mode 100644 src/Tgstation.Server.Host/Models/Migrations/SqliteDesignTimeDbContextFactory.cs delete mode 100644 src/Tgstation.Server.Host/Models/SqliteDatabaseContext.cs diff --git a/src/Tgstation.Server.Host/Configuration/DatabaseType.cs b/src/Tgstation.Server.Host/Configuration/DatabaseType.cs index bbc8be7798..c536486427 100644 --- a/src/Tgstation.Server.Host/Configuration/DatabaseType.cs +++ b/src/Tgstation.Server.Host/Configuration/DatabaseType.cs @@ -12,10 +12,6 @@ /// /// Use MySQL/MariaDB /// - MySql, - /// - /// Use SQLite 3 - /// - Sqlite + MySql } } diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 556c2d1fbe..c2bbf8d6c2 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -149,22 +149,19 @@ namespace Tgstation.Server.Host.Core builder.EnableSensitiveDataLogging(); }; - switch (databaseConfiguration?.DatabaseType ?? DatabaseType.Sqlite) + var dbType = databaseConfiguration?.DatabaseType; + switch (dbType) { case DatabaseType.MySql: services.AddDbContext(ConfigureDatabase); services.AddScoped(x => x.GetRequiredService()); break; - case DatabaseType.Sqlite: - services.AddDbContext(ConfigureDatabase); - services.AddScoped(x => x.GetRequiredService()); - break; case DatabaseType.SqlServer: services.AddDbContext(ConfigureDatabase); services.AddScoped(x => x.GetRequiredService()); break; default: - throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid {0}!", nameof(DatabaseType))); + throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid {0}: {1}!", nameof(DatabaseType), dbType)); } services.AddScoped(); diff --git a/src/Tgstation.Server.Host/Models/Migrations/SqliteDesignTimeDbContextFactory.cs b/src/Tgstation.Server.Host/Models/Migrations/SqliteDesignTimeDbContextFactory.cs deleted file mode 100644 index bf8b3c379b..0000000000 --- a/src/Tgstation.Server.Host/Models/Migrations/SqliteDesignTimeDbContextFactory.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Microsoft.AspNetCore.Identity; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Design; -using Microsoft.Extensions.Logging; -using Tgstation.Server.Host.Security; - -namespace Tgstation.Server.Host.Models.Migrations -{ - /// - sealed class SqliteDesignTimeDbContextFactory : IDesignTimeDbContextFactory - { - /// - public SqliteDatabaseContext CreateDbContext(string[] args) => new SqliteDatabaseContext(new DbContextOptions(), DesignTimeDbContextFactoryHelpers.GetDbContextOptions(), new DatabaseSeeder(new CryptographySuite(new PasswordHasher())), new LoggerFactory().CreateLogger()); - } -} diff --git a/src/Tgstation.Server.Host/Models/SqliteDatabaseContext.cs b/src/Tgstation.Server.Host/Models/SqliteDatabaseContext.cs deleted file mode 100644 index 224f08ff0b..0000000000 --- a/src/Tgstation.Server.Host/Models/SqliteDatabaseContext.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using System; -using Tgstation.Server.Host.Configuration; - -namespace Tgstation.Server.Host.Models -{ - /// - /// for Sqlite - /// - sealed class SqliteDatabaseContext : DatabaseContext - { - /// - /// Construct a - /// - /// The for the - /// The of for the - /// The for the - /// The for the - public SqliteDatabaseContext(DbContextOptions dbContextOptions, IOptions databaseConfiguration, IDatabaseSeeder databaseSeeder, ILogger logger) : base(dbContextOptions, databaseConfiguration, databaseSeeder, logger) - { } - - /// - protected override void OnConfiguring(DbContextOptionsBuilder options) - { - base.OnConfiguring(options); - //on the off chance that connection string is null here we default to a db file next to the executable since this is the default database - if (ConnectionString == null) - { - Logger.LogWarning("No database configured! Defaulting to SQLite in the working directory!"); - options.UseSqlite("Data Source=TgsDatabase.db3"); - } - else - options.UseSqlite(ConnectionString); - } - } -} diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index 6ed2f231b1..af69de349b 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -37,7 +37,6 @@ - From 18c9b23a321a2c40c551b3c5e4aa8ff19fc50390 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 4 Sep 2018 15:58:37 -0400 Subject: [PATCH 14/47] Add missing XML doc comments --- .../Controllers/ConfigurationController.cs | 9 +++++++-- src/Tgstation.Server.Host/IO/ISynchronousIOManager.cs | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs index 8e36f841c7..d60ae85a35 100644 --- a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs +++ b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs @@ -2,7 +2,6 @@ using Microsoft.Extensions.Logging; using System; using System.IO; -using System.Linq; using System.Net; using System.Threading; using System.Threading.Tasks; @@ -183,7 +182,13 @@ namespace Tgstation.Server.Host.Controllers return Forbid(); } } - + + /// + /// Deletes an empty + /// + /// A representing the path to the directory to delete + /// The for the operation + /// A resulting in the of the operation [HttpDelete] [TgsAuthorize(ConfigurationRights.Delete)] public async Task Delete([FromBody] ConfigurationFile directory, CancellationToken cancellationToken) diff --git a/src/Tgstation.Server.Host/IO/ISynchronousIOManager.cs b/src/Tgstation.Server.Host/IO/ISynchronousIOManager.cs index 19ad6fba61..5b457fd840 100644 --- a/src/Tgstation.Server.Host/IO/ISynchronousIOManager.cs +++ b/src/Tgstation.Server.Host/IO/ISynchronousIOManager.cs @@ -40,7 +40,7 @@ namespace Tgstation.Server.Host.IO byte[] ReadFile(string path); /// - /// Deletes a directory at if it's empty + /// Deletes a directory at if it's empty /// /// The path of the directory to delete /// if the directory does not exist or is empty and was deleted. otherwise From 0edee4822359694eb4236f137632f257581ce5d2 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 4 Sep 2018 15:58:56 -0400 Subject: [PATCH 15/47] Add DropDatabase configuration setting God help me... --- .../Configuration/DatabaseConfiguration.cs | 5 +++++ src/Tgstation.Server.Host/Models/DatabaseContext.cs | 6 ++++++ src/Tgstation.Server.Host/appsettings.json | 1 + 3 files changed, 12 insertions(+) diff --git a/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs b/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs index 07f8690601..f397a2a61a 100644 --- a/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs +++ b/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs @@ -29,5 +29,10 @@ /// If the database should use direct table creation instead of automatic migrations. Should not be used in production! /// public bool NoMigrations { get; set; } + + /// + /// If the database should be deleted on application startup. Should not be used in production! + /// + public bool DropDatabase { get; set; } } } diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs index c8401f054f..f887c2926b 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -140,6 +140,12 @@ namespace Tgstation.Server.Host.Models { Logger.LogInformation("Migrating database..."); + if (databaseConfiguration.DropDatabase) + { + Logger.LogCritical("DropDatabase configuration option set! Dropping any existing database..."); + await Database.EnsureDeletedAsync(cancellationToken).ConfigureAwait(false); + } + var wasEmpty = false; if (databaseConfiguration.NoMigrations) { diff --git a/src/Tgstation.Server.Host/appsettings.json b/src/Tgstation.Server.Host/appsettings.json index 1c91417297..e975062243 100644 --- a/src/Tgstation.Server.Host/appsettings.json +++ b/src/Tgstation.Server.Host/appsettings.json @@ -30,6 +30,7 @@ }, "Database": { "NoMigrations": false, + "DropDatabase": false, "DatabaseType": "SqlServer", "ResetAdminPassword": false, "ConnectionString": "Data Source=(local);Initial Catalog=TGS;Integrated Security=True" From cad9890254791e8d2fba57883efbdf97817d454b Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 4 Sep 2018 15:59:12 -0400 Subject: [PATCH 16/47] Update TODO list --- v4_prototype_TODO.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v4_prototype_TODO.txt b/v4_prototype_TODO.txt index dca5dbe542..fe9e1f9109 100644 --- a/v4_prototype_TODO.txt +++ b/v4_prototype_TODO.txt @@ -10,4 +10,4 @@ Test auto update Test auto start -Remove auto folder delete from static file stuff before mso stumbles on it and lynches you +Migrations From 4ba5fb1397386d557e4406447bd919c22cd3c2a0 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 4 Sep 2018 15:59:44 -0400 Subject: [PATCH 17/47] Remove Sqlite mentions from README.md --- README.md | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index c7445607c2..235c65c0c7 100644 --- a/README.md +++ b/README.md @@ -36,17 +36,20 @@ Note that due to the nature of docker. If the container restarts, you will be se ### Configuring Create an `appsettings.Production.json` file next to `appsettings.json`. This will override the default settings in appsettings.json with your production settings. There are a few keys meant to be changed by hosts: - - `General:LogFileDirectory`: Override the default directory where server logs are stored. Default is C:/ProgramData/tgstation-server/logs on Windows, /usr/share/tgstation-server/logs otherwise - - `General:MinimumPasswordLength`: Minimum password length requirement for database users - - `Logging:LogLevel:Default`: Can be one of `Trace`, `Debug`, `Information`, `Warning`, `Error`, or `Critical`. Restricts what is put into the log files. Currently `Debug` is reccommended for help with error reporting. - - `Database:DatabaseType`: Can be one of `SqlServer`, `MySql`, or `Sqlite`. Note that, at the time of this writing, there is a [blocking bug with the MySQL DBAL provider](https://bugs.mysql.com/bug.php?id=89855) which prevents its usage - - `Database:ConnectionString`: Connection string for your database. For SQLite this should be in the form `Data Source=/path/to/database.ext`. Otherwise, click [here](https://www.developerfusion.com/tools/sql-connection-string/) for an SQL Server generator or see [here](https://www.connectionstrings.com/mysql/) for a MySQL guide. + +- `General:LogFileDirectory`: Override the default directory where server logs are stored. Default is C:/ProgramData/tgstation-server/logs on Windows, /usr/share/tgstation-server/logs otherwise + +- `General:MinimumPasswordLength`: Minimum password length requirement for database users + +- `Logging:LogLevel:Default`: Can be one of `Trace`, `Debug`, `Information`, `Warning`, `Error`, or `Critical`. Restricts what is put into the log files. Currently `Debug` is reccommended for help with error reporting. + +- `Database:DatabaseType`: Can be one of `SqlServer` or `MySql`. Note that, at the time of this writing, there is a [blocking bug with the MySQL DBAL provider](https://bugs.mysql.com/bug.php?id=89855) which prevents its usage + +- `Database:ConnectionString`: Connection string for your database. Click [here](https://www.developerfusion.com/tools/sql-connection-string/) for an SQL Server generator or see [here](https://www.connectionstrings.com/mysql/) for a MySQL guide. ### Database Configuration -If you are using an SQLite database just ensure the specified database file doesn't exist for first time setup and you may skip this section. - -For dedicated SQL servers, the user created for the application will need the privilege to create databases for the first run. Once the initial set of migrations is run, the create right may be revoked. +The user created for the application will need the privilege to create databases on the first run. Once the initial set of migrations is run, the create right may be revoked. The user should maintain DDL rights though for applying future migrations ### Starting From a13e87da4a02a8b9a0b04661f329b3320e0da52b Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 4 Sep 2018 16:00:03 -0400 Subject: [PATCH 18/47] Fix integration test to work without Sqlite --- .github/CONTRIBUTING.md | 2 ++ .../Tgstation.Server.Tests/IntegrationTest.cs | 21 +++++++++++++++++- tests/Tgstation.Server.Tests/TestingServer.cs | 22 ++++++++++++++----- 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 987e8c33d5..6361999b7a 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -26,6 +26,8 @@ You need the Dotnet SDK to compile the main server and command line programs. In The recommended IDE is visual studio 2017 which has installation options for both of these. +In order to run the integration tests you must have the environment variables `TGS4_TEST_DATABASE_TYPE` set to `MySql` or `SqlServer` and `TGS4_TEST_CONNECTION_STRING` set appropriately + ## Meet the Team **Headcoder** diff --git a/tests/Tgstation.Server.Tests/IntegrationTest.cs b/tests/Tgstation.Server.Tests/IntegrationTest.cs index 018cd8d897..054414d29f 100644 --- a/tests/Tgstation.Server.Tests/IntegrationTest.cs +++ b/tests/Tgstation.Server.Tests/IntegrationTest.cs @@ -27,7 +27,26 @@ namespace Tgstation.Server.Tests var serverTask = server.RunAsync(cancellationToken); try { - using (var adminClient = await clientFactory.CreateServerClient(server.Url, User.AdminName, User.DefaultAdminPassword).ConfigureAwait(false)) + IServerClient adminClient; + + var giveUpAt = DateTimeOffset.Now.AddSeconds(30); + do + { + try + { + adminClient = await clientFactory.CreateServerClient(server.Url, User.AdminName, User.DefaultAdminPassword).ConfigureAwait(false); + break; + } + catch (ServiceUnavailableException) + { + //migrating, to be expected + if (DateTimeOffset.Now > giveUpAt) + throw; + await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken); + } + } while (true); + + using (adminClient) { var serverInfo = await adminClient.Version(default).ConfigureAwait(false); diff --git a/tests/Tgstation.Server.Tests/TestingServer.cs b/tests/Tgstation.Server.Tests/TestingServer.cs index 4e1df5f740..1b1b3905f5 100644 --- a/tests/Tgstation.Server.Tests/TestingServer.cs +++ b/tests/Tgstation.Server.Tests/TestingServer.cs @@ -1,4 +1,5 @@ -using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; using System.Globalization; using System.IO; using System.Threading; @@ -15,21 +16,32 @@ namespace Tgstation.Server.Tests public bool RestartRequested => realServer.RestartRequested; readonly IServer realServer; - readonly string databasePath; public TestingServer() { Directory = Path.GetTempFileName(); File.Delete(Directory); System.IO.Directory.CreateDirectory(Directory); - databasePath = Path.Combine(Directory, "TGS.db3"); Url = new Uri("http://localhost:5001"); + + //so we need a db + //we have to rely on env vars + var databaseType = Environment.GetEnvironmentVariable("TGS4_TEST_DATABASE_TYPE"); + var connectionString = Environment.GetEnvironmentVariable("TGS4_TEST_CONNECTION_STRING"); + + if (String.IsNullOrEmpty(databaseType)) + Assert.Fail("No database type configured in env var TGS4_TEST_DATABASE_TYPE!"); + + if (String.IsNullOrEmpty(connectionString)) + Assert.Fail("No connection string configured in env var TGS4_TEST_CONNECTION_STRING!"); + realServer = new ServerFactory().CreateServer(new string[] { "--urls", Url.ToString(), - "Database:DatabaseType=Sqlite", - String.Format(CultureInfo.InvariantCulture, "Database:ConnectionString=Data Source={0}", databasePath) + String.Format(CultureInfo.InvariantCulture, "Database:DatabaseType={0}", databaseType), + String.Format(CultureInfo.InvariantCulture, "Database:ConnectionString={0}", connectionString), + "Database:DropDatabase=true" ,"Database:NoMigrations=true" //TODO: remove this when migrations are added }, null); } From 389f03d2e22bf6babedcfecf2bad9f6371ccf2cf Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 4 Sep 2018 16:00:19 -0400 Subject: [PATCH 19/47] Fix integration test on appveyor --- appveyor.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index 88a4f72c54..ba7eed27ba 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -9,6 +9,9 @@ branches: - master skip_tags: true image: Visual Studio 2017 +environment: + TGS4_TEST_DATABASE_TYPE: SqlServer + TGS4_TEST_CONNECTION_STRING: Server=(local)\SQL2017;Database=master;User ID=sa;Password=Password12! configuration: - Debug - Release @@ -24,6 +27,8 @@ cache: - ~\.nuget\packages -> **\*.csproj - C:\ProgramData\chocolatey\bin -> appveyor.yml - C:\ProgramData\chocolatey\lib -> appveyor.yml +services: + - mssql2017 install: - choco install doxygen.portable codecov graphviz.portable opencover.portable - nuget restore tgstation-server.sln From 6bb940dea87029a26ac058541deee5eeb68f05be Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 4 Sep 2018 16:01:49 -0400 Subject: [PATCH 20/47] Remove testing from docker build --- build/Dockerfile | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/build/Dockerfile b/build/Dockerfile index a0da9591bd..e4e1f9eeea 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -16,25 +16,6 @@ COPY tests/Tgstation.Server.Host.Console.Tests/Tgstation.Server.Host.Console.Tes RUN dotnet restore -nowarn:MSB3202,nu1503 -p:RestoreUseSkipNonexistentTargets=false COPY . . - -WORKDIR /src/tests/Tgstation.Server.Api.Tests -RUN dotnet test -c Release - -WORKDIR /src/tests/Tgstation.Server.Host.Tests -RUN dotnet test -c Release - -WORKDIR /src/tests/Tgstation.Server.Host.Watchdog.Tests -RUN dotnet test -c Release - -WORKDIR /src/tests/Tgstation.Server.Host.Console.Tests -RUN dotnet test -c Release - -WORKDIR /src/tests/Tgstation.Server.Tests -RUN dotnet test -c Release - -WORKDIR /src/src/Tgstation.Server.Host.Console -RUN dotnet publish -c Release -o /app - WORKDIR /src/src/Tgstation.Server.Host RUN dotnet publish -c Release -o /app/lib/Default && mv /app/lib/Default/appsettings* /app From 00d1b12d584a3319b99cd23d855c29bcf9f3ba22 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 4 Sep 2018 16:16:10 -0400 Subject: [PATCH 21/47] Prevent errors relating to job cancellation --- .../Controllers/JobController.cs | 4 ++-- src/Tgstation.Server.Host/Core/IJobManager.cs | 4 ++-- src/Tgstation.Server.Host/Core/JobManager.cs | 18 ++++++++++++++---- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/Tgstation.Server.Host/Controllers/JobController.cs b/src/Tgstation.Server.Host/Controllers/JobController.cs index cd9aee4d46..d959a9a0eb 100644 --- a/src/Tgstation.Server.Host/Controllers/JobController.cs +++ b/src/Tgstation.Server.Host/Controllers/JobController.cs @@ -71,8 +71,8 @@ namespace Tgstation.Server.Host.Controllers if (job.CancelRight.HasValue && job.CancelRightsType.HasValue && (AuthenticationContext.GetRight(job.CancelRightsType.Value) & job.CancelRight.Value) == 0) return Forbid(); - await jobManager.CancelJob(job, AuthenticationContext.User, false, cancellationToken).ConfigureAwait(false); - return Accepted(); + var cancelled = await jobManager.CancelJob(job, AuthenticationContext.User, false, cancellationToken).ConfigureAwait(false); + return cancelled ? (IActionResult)Accepted() : StatusCode((int)HttpStatusCode.Gone); } /// diff --git a/src/Tgstation.Server.Host/Core/IJobManager.cs b/src/Tgstation.Server.Host/Core/IJobManager.cs index b849d3ea8a..0d5a16842e 100644 --- a/src/Tgstation.Server.Host/Core/IJobManager.cs +++ b/src/Tgstation.Server.Host/Core/IJobManager.cs @@ -32,7 +32,7 @@ namespace Tgstation.Server.Host.Core /// The who cancelled the /// If the operation should wait until the job exits before completing /// The for the operation - /// A representing a running operation - Task CancelJob(Job job, User user, bool blocking, CancellationToken cancellationToken); + /// A resulting in if the was cancelled, if it couldn't be found + Task CancelJob(Job job, User user, bool blocking, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Core/JobManager.cs b/src/Tgstation.Server.Host/Core/JobManager.cs index 7dfa3cccf7..47ac244289 100644 --- a/src/Tgstation.Server.Host/Core/JobManager.cs +++ b/src/Tgstation.Server.Host/Core/JobManager.cs @@ -187,14 +187,23 @@ namespace Tgstation.Server.Host.Core } /// - public async Task CancelJob(Job job, User user, bool blocking, CancellationToken cancellationToken) + public async Task CancelJob(Job job, User user, bool blocking, CancellationToken cancellationToken) { if (job == null) throw new ArgumentNullException(nameof(job)); if (user == null) throw new ArgumentNullException(nameof(user)); - var jobObject = CheckGetJob(job); - jobObject.Cancel(); //this will ensure the db update is only done once + JobHandler handler; + try + { + handler = CheckGetJob(job); + } + catch (InvalidOperationException) + { + //this is fine + return false; + } + handler.Cancel(); //this will ensure the db update is only done once using (var scope = serviceProvider.CreateScope()) { var databaseContext = scope.ServiceProvider.GetRequiredService(); @@ -207,7 +216,8 @@ namespace Tgstation.Server.Host.Core await databaseContext.Save(cancellationToken).ConfigureAwait(false); } if (blocking) - await jobObject.Wait(cancellationToken).ConfigureAwait(false); + await handler.Wait(cancellationToken).ConfigureAwait(false); + return true; } /// From 05f667b0bcc38cd7a1cadead7c09e328f26dd7de Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 4 Sep 2018 16:18:37 -0400 Subject: [PATCH 22/47] Extend integration test migration timeout --- tests/Tgstation.Server.Tests/IntegrationTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Tgstation.Server.Tests/IntegrationTest.cs b/tests/Tgstation.Server.Tests/IntegrationTest.cs index 054414d29f..f90a7e9151 100644 --- a/tests/Tgstation.Server.Tests/IntegrationTest.cs +++ b/tests/Tgstation.Server.Tests/IntegrationTest.cs @@ -29,7 +29,7 @@ namespace Tgstation.Server.Tests { IServerClient adminClient; - var giveUpAt = DateTimeOffset.Now.AddSeconds(30); + var giveUpAt = DateTimeOffset.Now.AddSeconds(60); do { try From d1d6aa4861faad829158e4dec9d3f869664c2150 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 4 Sep 2018 16:25:15 -0400 Subject: [PATCH 23/47] Add dotnet tests to travis --- .travis.yml | 44 +++++++++++++++++++++++++++++--------------- build/test_core.sh | 28 ++++++++++++++++++++++++++++ tgstation-server.sln | 3 ++- 3 files changed, 59 insertions(+), 16 deletions(-) create mode 100755 build/test_core.sh diff --git a/.travis.yml b/.travis.yml index 3643b77270..75c883bbba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,24 +2,38 @@ language: generic sudo: false git: depth: 1 -env: - global: - - BYOND_MAJOR="511" - - BYOND_MINOR="1385" - - DMEName="tests/DMAPI/travistester.dme" -cache: - directories: - - $HOME/BYOND-${BYOND_MAJOR}.${BYOND_MINOR} +matrix: + include: + - env: + - DMAPI=true + - BYOND_MAJOR="511" + - BYOND_MINOR="1385" + - DMEName="tests/DMAPI/travistester.dme" + name: "DMAPI Unit Tests" + cache: + directories: + - $HOME/BYOND-${BYOND_MAJOR}.${BYOND_MINOR} -addons: - apt: - packages: - - libc6-i386 - - libstdc++6:i386 + addons: + apt: + packages: + - libc6-i386 + - libstdc++6:i386 + - env: + - DMAPI=false + name: "Linux Build" + dotnet: 2.1.300 + services: + - mysql + cache: + directories: + - $HOME/.nuget/packages install: - - build/install_byond.sh + - if [ $DMAPI = true ]; then build/install_byond.sh fi + - if [ $DMAPI = false ]; then nuget restore tgstation-server.sln fi script: - - tests/DMAPI/build_byond.sh + - if [ $DMAPI = true ]; then tests/DMAPI/build_byond.sh || travis_terminate 1 fi + - if [ $DMAPI = false ]; then build/test_core.sh fi diff --git a/build/test_core.sh b/build/test_core.sh new file mode 100755 index 0000000000..b73a692b69 --- /dev/null +++ b/build/test_core.sh @@ -0,0 +1,28 @@ +#!/bin/bash +set -e + +cd tests/Tgstation.Server.Api.Tests + +dotnet test + +cd ../Tgstation.Server.Client.Tests + +dotnet test + +cd ../Tgstation.Server.Host.Tests + +dotnet test + +cd ../Tgstation.Server.Host.Watchdog.Tests + +dotnet test + +cd ../Tgstation.Server.Host.Console.Tests + +dotnet test + +cd ../Tgstation.Server.Host.Tests + +export TGS4_TEST_DATABASE_TYPE=MySql +export TGS4_TEST_CONNECTION_STRING="server=127.0.0.1;uid=root;pwd=;database=tgs_test" +dotnet test diff --git a/tgstation-server.sln b/tgstation-server.sln index bf94dec0a7..9f414672ca 100644 --- a/tgstation-server.sln +++ b/tgstation-server.sln @@ -25,6 +25,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{6FF654E6 build\Dockerfile = build\Dockerfile build\Doxyfile = build\Doxyfile build\install_byond.sh = build\install_byond.sh + build\test_core.sh = build\test_core.sh build\tgs.docker.sh = build\tgs.docker.sh build\tgs.ico = build\tgs.ico build\UploadCoverage.ps1 = build\UploadCoverage.ps1 @@ -112,7 +113,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{E821 .github\ISSUE_TEMPLATE.md = .github\ISSUE_TEMPLATE.md EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tgstation.Server.Tests", "tests\Tgstation.Server.Tests\Tgstation.Server.Tests.csproj", "{09056964-1C74-445A-96EC-33F6DFC07916}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Tests", "tests\Tgstation.Server.Tests\Tgstation.Server.Tests.csproj", "{09056964-1C74-445A-96EC-33F6DFC07916}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution From 9d6b2c7f78cfcdbf7e063e53eac0353ebcb07a72 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 4 Sep 2018 16:59:22 -0400 Subject: [PATCH 24/47] Switch to Pomelo MySQL EFCore provider --- README.md | 6 +++++- .../Configuration/DatabaseConfiguration.cs | 5 +++++ .../Configuration/DatabaseType.cs | 8 ++++++-- src/Tgstation.Server.Host/Core/Application.cs | 1 + .../Models/DatabaseContext.cs | 17 ++++++----------- .../Models/MySqlDatabaseContext.cs | 7 ++++++- .../Models/SqlServerDatabaseContext.cs | 2 +- .../Tgstation.Server.Host.csproj | 2 +- src/Tgstation.Server.Host/appsettings.json | 1 + 9 files changed, 32 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 235c65c0c7..fa8078e467 100644 --- a/README.md +++ b/README.md @@ -43,12 +43,16 @@ Create an `appsettings.Production.json` file next to `appsettings.json`. This wi - `Logging:LogLevel:Default`: Can be one of `Trace`, `Debug`, `Information`, `Warning`, `Error`, or `Critical`. Restricts what is put into the log files. Currently `Debug` is reccommended for help with error reporting. -- `Database:DatabaseType`: Can be one of `SqlServer` or `MySql`. Note that, at the time of this writing, there is a [blocking bug with the MySQL DBAL provider](https://bugs.mysql.com/bug.php?id=89855) which prevents its usage +- `Database:DatabaseType`: Can be one of `SqlServer`, `MariaDB`, or `MySql` + +- `Database:MySqlServerVersion`: The version of MySql/MariaDB the database resides on, can be left as null for attempted auto detection. Used by the MySQL/MariaDB provider for selection of [certain features](https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql/blob/2.1.1/src/EFCore.MySql/Storage/Internal/ServerVersion.cs) ignore at your own risk. A string in the form `..` - `Database:ConnectionString`: Connection string for your database. Click [here](https://www.developerfusion.com/tools/sql-connection-string/) for an SQL Server generator or see [here](https://www.connectionstrings.com/mysql/) for a MySQL guide. ### Database Configuration +If using MySQL, our provider library [recommends you set 'utf8mb4' as your default charset](https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql#1-recommended-server-charset) disregard at your own risk. + The user created for the application will need the privilege to create databases on the first run. Once the initial set of migrations is run, the create right may be revoked. The user should maintain DDL rights though for applying future migrations ### Starting diff --git a/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs b/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs index f397a2a61a..1bbe9365d7 100644 --- a/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs +++ b/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs @@ -34,5 +34,10 @@ /// If the database should be deleted on application startup. Should not be used in production! /// public bool DropDatabase { get; set; } + + /// + /// The form of the of a target MySQL/MariaDB server + /// + public string MySqlServerVersion { get; set; } } } diff --git a/src/Tgstation.Server.Host/Configuration/DatabaseType.cs b/src/Tgstation.Server.Host/Configuration/DatabaseType.cs index c536486427..2ca7d8fafc 100644 --- a/src/Tgstation.Server.Host/Configuration/DatabaseType.cs +++ b/src/Tgstation.Server.Host/Configuration/DatabaseType.cs @@ -10,8 +10,12 @@ /// SqlServer, /// - /// Use MySQL/MariaDB + /// Use MySQL /// - MySql + MySql, + /// + /// Use MariaDB + /// + MariaDB } } diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index c2bbf8d6c2..a556fcc952 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -153,6 +153,7 @@ namespace Tgstation.Server.Host.Core switch (dbType) { case DatabaseType.MySql: + case DatabaseType.MariaDB: services.AddDbContext(ConfigureDatabase); services.AddScoped(x => x.GetRequiredService()); break; diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs index f887c2926b..a3d9817592 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -66,15 +66,10 @@ namespace Tgstation.Server.Host.Models /// protected ILogger Logger { get; } - /// - /// The connection string for the - /// - protected string ConnectionString => databaseConfiguration.ConnectionString; - /// /// The for the /// - readonly DatabaseConfiguration databaseConfiguration; + protected DatabaseConfiguration DatabaseConfiguration { get; } /// /// The for the @@ -85,12 +80,12 @@ namespace Tgstation.Server.Host.Models /// Construct a /// /// The for the - /// The containing the value of + /// The containing the value of /// The value of /// The value of public DatabaseContext(DbContextOptions dbContextOptions, IOptions databaseConfigurationOptions, IDatabaseSeeder databaseSeeder, ILogger logger) : base(dbContextOptions) { - databaseConfiguration = databaseConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(databaseConfigurationOptions)); + DatabaseConfiguration = databaseConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(databaseConfigurationOptions)); this.databaseSeeder = databaseSeeder ?? throw new ArgumentNullException(nameof(databaseSeeder)); Logger = logger ?? throw new ArgumentNullException(nameof(logger)); } @@ -140,14 +135,14 @@ namespace Tgstation.Server.Host.Models { Logger.LogInformation("Migrating database..."); - if (databaseConfiguration.DropDatabase) + if (DatabaseConfiguration.DropDatabase) { Logger.LogCritical("DropDatabase configuration option set! Dropping any existing database..."); await Database.EnsureDeletedAsync(cancellationToken).ConfigureAwait(false); } var wasEmpty = false; - if (databaseConfiguration.NoMigrations) + if (DatabaseConfiguration.NoMigrations) { Logger.LogWarning("Using all or nothing migration strategy!"); await Database.EnsureCreatedAsync(cancellationToken).ConfigureAwait(false); @@ -169,7 +164,7 @@ namespace Tgstation.Server.Host.Models else { Logger.LogDebug("No migrations applied!"); - if (databaseConfiguration.ResetAdminPassword) + if (DatabaseConfiguration.ResetAdminPassword) { Logger.LogWarning("Enabling and resetting admin password due to configuration!"); await databaseSeeder.ResetAdminPassword(this, cancellationToken).ConfigureAwait(false); diff --git a/src/Tgstation.Server.Host/Models/MySqlDatabaseContext.cs b/src/Tgstation.Server.Host/Models/MySqlDatabaseContext.cs index eb6f0c81cd..d95fce3224 100644 --- a/src/Tgstation.Server.Host/Models/MySqlDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/MySqlDatabaseContext.cs @@ -1,6 +1,8 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Pomelo.EntityFrameworkCore.MySql.Infrastructure; +using System; using Tgstation.Server.Host.Configuration; namespace Tgstation.Server.Host.Models @@ -24,7 +26,10 @@ namespace Tgstation.Server.Host.Models protected override void OnConfiguring(DbContextOptionsBuilder options) { base.OnConfiguring(options); - options.UseMySQL(ConnectionString); + if (DatabaseConfiguration.MySqlServerVersion != null) + options.UseMySql(DatabaseConfiguration.ConnectionString, mySqlOptions => mySqlOptions.ServerVersion(Version.Parse(DatabaseConfiguration.MySqlServerVersion), DatabaseConfiguration.DatabaseType == DatabaseType.MariaDB ? ServerType.MariaDb : ServerType.MySql)); + else + options.UseMySql(DatabaseConfiguration.ConnectionString); } } } diff --git a/src/Tgstation.Server.Host/Models/SqlServerDatabaseContext.cs b/src/Tgstation.Server.Host/Models/SqlServerDatabaseContext.cs index b0f7ae1372..d3d5aa8260 100644 --- a/src/Tgstation.Server.Host/Models/SqlServerDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/SqlServerDatabaseContext.cs @@ -24,7 +24,7 @@ namespace Tgstation.Server.Host.Models protected override void OnConfiguring(DbContextOptionsBuilder options) { base.OnConfiguring(options); - options.UseSqlServer(ConnectionString); + options.UseSqlServer(DatabaseConfiguration.ConnectionString); } } } diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index af69de349b..e18c381e94 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -39,8 +39,8 @@ - + diff --git a/src/Tgstation.Server.Host/appsettings.json b/src/Tgstation.Server.Host/appsettings.json index e975062243..7ea6760060 100644 --- a/src/Tgstation.Server.Host/appsettings.json +++ b/src/Tgstation.Server.Host/appsettings.json @@ -33,6 +33,7 @@ "DropDatabase": false, "DatabaseType": "SqlServer", "ResetAdminPassword": false, + "MySqlServerVersion": null, "ConnectionString": "Data Source=(local);Initial Catalog=TGS;Integrated Security=True" } } From bbea5bc1765ca80a4ffc6936ecb0ea11e10a355c Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 4 Sep 2018 17:01:40 -0400 Subject: [PATCH 25/47] Remove duplicate environment key from appveypr.yml --- appveyor.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index ba7eed27ba..0da112f22f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,6 +2,8 @@ version: '{build}' pull_requests: do_not_increment_build_number: true environment: + TGS4_TEST_DATABASE_TYPE: SqlServer + TGS4_TEST_CONNECTION_STRING: Server=(local)\SQL2017;Database=master;User ID=sa;Password=Password12! repo_token: secure: lJNGAXwiB5HlWdthz3K4PetqpTG5IEAyRgKaiKxFMQ8HW8CcOjRtB97B05op7BsK branches: @@ -9,9 +11,6 @@ branches: - master skip_tags: true image: Visual Studio 2017 -environment: - TGS4_TEST_DATABASE_TYPE: SqlServer - TGS4_TEST_CONNECTION_STRING: Server=(local)\SQL2017;Database=master;User ID=sa;Password=Password12! configuration: - Debug - Release From 1b4919781942dc6d2c03501963bb7c1b9c444335 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Wed, 5 Sep 2018 09:43:08 -0400 Subject: [PATCH 26/47] Fix travis.yml indentation --- .travis.yml | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/.travis.yml b/.travis.yml index 75c883bbba..784183714b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,25 +10,25 @@ matrix: - BYOND_MAJOR="511" - BYOND_MINOR="1385" - DMEName="tests/DMAPI/travistester.dme" - name: "DMAPI Unit Tests" - cache: - directories: - - $HOME/BYOND-${BYOND_MAJOR}.${BYOND_MINOR} + name: "DMAPI Unit Tests" + cache: + directories: + - $HOME/BYOND-${BYOND_MAJOR}.${BYOND_MINOR} - addons: - apt: - packages: - - libc6-i386 - - libstdc++6:i386 + addons: + apt: + packages: + - libc6-i386 + - libstdc++6:i386 - env: - DMAPI=false - name: "Linux Build" - dotnet: 2.1.300 - services: - - mysql - cache: - directories: - - $HOME/.nuget/packages + name: "Linux Build" + dotnet: 2.1.300 + services: + - mysql + cache: + directories: + - $HOME/.nuget/packages install: - if [ $DMAPI = true ]; then build/install_byond.sh fi From ab89ecd0e2eab1d25756a1797ef3c10fa48cd122 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 5 Sep 2018 11:16:02 -0400 Subject: [PATCH 27/47] Eat your semicolons kids --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 784183714b..f9ae9a82d4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,9 +31,9 @@ matrix: - $HOME/.nuget/packages install: - - if [ $DMAPI = true ]; then build/install_byond.sh fi - - if [ $DMAPI = false ]; then nuget restore tgstation-server.sln fi + - if [ $DMAPI = true ]; then build/install_byond.sh; fi + - if [ $DMAPI = false ]; then nuget restore tgstation-server.sln; fi script: - - if [ $DMAPI = true ]; then tests/DMAPI/build_byond.sh || travis_terminate 1 fi - - if [ $DMAPI = false ]; then build/test_core.sh fi + - if [ $DMAPI = true ]; then tests/DMAPI/build_byond.sh || travis_terminate 1; fi + - if [ $DMAPI = false ]; then build/test_core.sh; fi From 54f8249695dc8f96c3ba48a73d5c2688692aa2e2 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 5 Sep 2018 11:32:02 -0400 Subject: [PATCH 28/47] dotnet > nuget --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f9ae9a82d4..24e6d1866e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,7 +32,7 @@ matrix: install: - if [ $DMAPI = true ]; then build/install_byond.sh; fi - - if [ $DMAPI = false ]; then nuget restore tgstation-server.sln; fi + - if [ $DMAPI = false ]; then dotnet restore tgstation-server.sln; fi script: - if [ $DMAPI = true ]; then tests/DMAPI/build_byond.sh || travis_terminate 1; fi From 120bc022b2b5591ddc3412013448f3e433d80774 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 5 Sep 2018 16:47:13 -0400 Subject: [PATCH 29/47] More travis fixes --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 24e6d1866e..300081b00b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,3 @@ -language: generic sudo: false git: depth: 1 @@ -11,6 +10,7 @@ matrix: - BYOND_MINOR="1385" - DMEName="tests/DMAPI/travistester.dme" name: "DMAPI Unit Tests" + language: generic cache: directories: - $HOME/BYOND-${BYOND_MAJOR}.${BYOND_MINOR} @@ -23,6 +23,7 @@ matrix: - env: - DMAPI=false name: "Linux Build" + language: csharp dotnet: 2.1.300 services: - mysql From 90d58777bb5c997a092a4fae5aaec5bde307d748 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 5 Sep 2018 16:58:11 -0400 Subject: [PATCH 30/47] Remove failing test --- .../Core/TestApplication.cs | 46 ------------------- 1 file changed, 46 deletions(-) delete mode 100644 tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs diff --git a/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs b/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs deleted file mode 100644 index 10e043be13..0000000000 --- a/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Microsoft.AspNetCore; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using Tgstation.Server.Host.IO; - -namespace Tgstation.Server.Host.Core.Tests -{ - [TestClass] - public sealed class TestApplication : IServerControl - { - public Task ApplyUpdate(byte[] updateZipData, IIOManager ioManager, CancellationToken cancellationToken) => throw new NotImplementedException(); - - public void RegisterForRestart(Action action) - { - } - - public bool Restart() => throw new NotImplementedException(); - - [TestMethod] - public async Task TestSuccessfulStartup() - { - var dbName = Path.GetTempFileName(); - try - { - using (var webHost = WebHost.CreateDefaultBuilder(new string[] { "Database:DatabaseType=Sqlite", "Database:ConnectionString=Data Source=" + dbName }) //force it to use sqlite - .UseStartup() - .ConfigureServices((serviceCollection) => serviceCollection.AddSingleton(this)) - .Build() - ) - { - await webHost.StartAsync().ConfigureAwait(false); - await webHost.StopAsync().ConfigureAwait(false); - } - } - finally - { - File.Delete(dbName); - } - } - } -} From 17c637edd68734440f1835381f7d062af3a6e1c3 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 6 Sep 2018 09:29:03 -0400 Subject: [PATCH 31/47] Add mono: none to speed up travis build --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 300081b00b..b955f4f8e5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,6 +24,7 @@ matrix: - DMAPI=false name: "Linux Build" language: csharp + mono: none dotnet: 2.1.300 services: - mysql From 98fbc497ccc0d617c99c25c83bf70d1421125b6b Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 6 Sep 2018 09:29:58 -0400 Subject: [PATCH 32/47] Add missing integration tests to travis --- build/test_core.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/test_core.sh b/build/test_core.sh index b73a692b69..a9477dfb24 100755 --- a/build/test_core.sh +++ b/build/test_core.sh @@ -21,7 +21,7 @@ cd ../Tgstation.Server.Host.Console.Tests dotnet test -cd ../Tgstation.Server.Host.Tests +cd ../Tgstation.Server.Tests export TGS4_TEST_DATABASE_TYPE=MySql export TGS4_TEST_CONNECTION_STRING="server=127.0.0.1;uid=root;pwd=;database=tgs_test" From 5578a9a6918d514d3f9cb4dfdb3bf84ccd3c6719 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 6 Sep 2018 09:34:26 -0400 Subject: [PATCH 33/47] Add OSX builds --- .travis.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b955f4f8e5..f0705d4454 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,7 +30,10 @@ matrix: - mysql cache: directories: - - $HOME/.nuget/packages + - $HOME/.nuget/packagesos: + os: + - linux + - osx install: - if [ $DMAPI = true ]; then build/install_byond.sh; fi From 6966c2ef97599594cbb3152b9ae8375c70c21308 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 6 Sep 2018 09:34:39 -0400 Subject: [PATCH 34/47] Fix nuget cache directory --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f0705d4454..50230a0ab3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,7 +30,7 @@ matrix: - mysql cache: directories: - - $HOME/.nuget/packagesos: + - $HOME/.nuget/packages: os: - linux - osx From 3df893a0f0d7a88323aab5414cbbfbd7a35c7c59 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 6 Sep 2018 09:39:29 -0400 Subject: [PATCH 35/47] Fix appveyor connection string --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 0da112f22f..0643b1b74c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -3,7 +3,7 @@ pull_requests: do_not_increment_build_number: true environment: TGS4_TEST_DATABASE_TYPE: SqlServer - TGS4_TEST_CONNECTION_STRING: Server=(local)\SQL2017;Database=master;User ID=sa;Password=Password12! + TGS4_TEST_CONNECTION_STRING: Server=(local)\SQL2017;Initial Catalog=TGS_Test;User ID=sa;Password=Password12! repo_token: secure: lJNGAXwiB5HlWdthz3K4PetqpTG5IEAyRgKaiKxFMQ8HW8CcOjRtB97B05op7BsK branches: From 813baa68362473c3f8c596d81c9c4a3a2256aa6b Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 6 Sep 2018 09:53:50 -0400 Subject: [PATCH 36/47] Some migration fixups --- .../Migrations/DesignTimeDbContextFactoryHelpers.cs | 9 +++++++-- src/Tgstation.Server.Host/Tgstation.Server.Host.csproj | 10 ++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Models/Migrations/DesignTimeDbContextFactoryHelpers.cs b/src/Tgstation.Server.Host/Models/Migrations/DesignTimeDbContextFactoryHelpers.cs index c633cc1ec4..b4b4d13ef2 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/DesignTimeDbContextFactoryHelpers.cs +++ b/src/Tgstation.Server.Host/Models/Migrations/DesignTimeDbContextFactoryHelpers.cs @@ -14,14 +14,19 @@ namespace Tgstation.Server.Host.Models.Migrations /// /// Path to the json file to use for migrations configuration /// - const string MigrationsJson = "appsettings.Development.json"; + const string RootJson = "appsettings.json"; + /// + /// Path to the development json file to use for migrations configuration + /// + const string DevJson = "appsettings.Development.json"; /// public static IOptions GetDbContextOptions() { var builder = new ConfigurationBuilder(); builder.SetBasePath(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)); - builder.AddJsonFile(MigrationsJson); + builder.AddJsonFile(RootJson); + builder.AddJsonFile(DevJson); var configuration = builder.Build(); return Options.Create(configuration.GetSection(DatabaseConfiguration.Section).Get()); } diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index e18c381e94..779628f0bb 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -38,6 +38,7 @@ + @@ -65,4 +66,13 @@ + + + PreserveNewest + + + PreserveNewest + + + From ea76c433050b16bd7f19056171edc399584a227d Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 6 Sep 2018 10:07:50 -0400 Subject: [PATCH 37/47] Add SqlServer initial migration --- ...20180906135553_MSInitialCreate.Designer.cs | 671 ++++++++++++++++++ .../20180906135553_MSInitialCreate.cs | 652 +++++++++++++++++ .../SqlServerDatabaseContextModelSnapshot.cs | 665 +++++++++++++++++ 3 files changed, 1988 insertions(+) create mode 100644 src/Tgstation.Server.Host/Models/Migrations/20180906135553_MSInitialCreate.Designer.cs create mode 100644 src/Tgstation.Server.Host/Models/Migrations/20180906135553_MSInitialCreate.cs create mode 100644 src/Tgstation.Server.Host/Models/Migrations/SqlServerDatabaseContextModelSnapshot.cs diff --git a/src/Tgstation.Server.Host/Models/Migrations/20180906135553_MSInitialCreate.Designer.cs b/src/Tgstation.Server.Host/Models/Migrations/20180906135553_MSInitialCreate.Designer.cs new file mode 100644 index 0000000000..dae77c6047 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/Migrations/20180906135553_MSInitialCreate.Designer.cs @@ -0,0 +1,671 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Tgstation.Server.Host.Models.Migrations +{ + [DbContext(typeof(SqlServerDatabaseContext))] + [Migration("20180906135553_MSInitialCreate")] + partial class MSInitialCreate + { + /// + /// Builds the target model + /// + /// The to use + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.2-rtm-30932") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ConnectionString") + .IsRequired(); + + b.Property("Enabled"); + + b.Property("InstanceId"); + + b.Property("Name") + .IsRequired(); + + b.Property("Provider"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("ChatBots"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ChatSettingsId"); + + b.Property("DiscordChannelId") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("IrcChannel"); + + b.Property("IsAdminChannel") + .IsRequired(); + + b.Property("IsUpdatesChannel") + .IsRequired(); + + b.Property("IsWatchdogChannel") + .IsRequired(); + + b.Property("Tag"); + + b.HasKey("Id"); + + b.HasIndex("ChatSettingsId", "DiscordChannelId") + .IsUnique() + .HasFilter("[DiscordChannelId] IS NOT NULL"); + + b.HasIndex("ChatSettingsId", "IrcChannel") + .IsUnique() + .HasFilter("[IrcChannel] IS NOT NULL"); + + b.ToTable("ChatChannels"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ByondVersion") + .IsRequired(); + + b.Property("DirectoryName"); + + b.Property("DmeName"); + + b.Property("JobId"); + + b.Property("Output"); + + b.Property("RevisionInformationId"); + + b.HasKey("Id"); + + b.HasIndex("DirectoryName"); + + b.HasIndex("JobId"); + + b.HasIndex("RevisionInformationId"); + + b.ToTable("CompileJobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AccessToken"); + + b.Property("AllowWebClient") + .IsRequired(); + + b.Property("AutoStart") + .IsRequired(); + + b.Property("InstanceId"); + + b.Property("PrimaryPort"); + + b.Property("ProcessId"); + + b.Property("SecondaryPort"); + + b.Property("SecurityLevel"); + + b.Property("SoftRestart") + .IsRequired(); + + b.Property("SoftShutdown") + .IsRequired(); + + b.Property("StartupTimeout"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamDaemonSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ApiValidationPort"); + + b.Property("InstanceId"); + + b.Property("ProjectName"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamMakerSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AutoUpdateInterval"); + + b.Property("ConfigurationType"); + + b.Property("Name") + .IsRequired(); + + b.Property("Online") + .IsRequired(); + + b.Property("Path") + .IsRequired(); + + b.Property("WatchdogReattachInformationId"); + + b.HasKey("Id"); + + b.HasIndex("Path") + .IsUnique(); + + b.HasIndex("WatchdogReattachInformationId"); + + b.ToTable("Instances"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ByondRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("ChatBotRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("ConfigurationRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("DreamDaemonRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("DreamMakerRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("InstanceId"); + + b.Property("InstanceUserRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("RepositoryRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("UserId", "InstanceId") + .IsUnique(); + + b.ToTable("InstanceUsers"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CancelRight") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("CancelRightsType") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("Cancelled") + .IsRequired(); + + b.Property("CancelledById"); + + b.Property("Description") + .IsRequired(); + + b.Property("ExceptionDetails"); + + b.Property("InstanceId"); + + b.Property("StartedAt") + .IsRequired(); + + b.Property("StartedById"); + + b.Property("StoppedAt"); + + b.HasKey("Id"); + + b.HasIndex("CancelledById"); + + b.HasIndex("InstanceId"); + + b.HasIndex("StartedById"); + + b.ToTable("Jobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AccessIdentifier") + .IsRequired(); + + b.Property("ChatChannelsJson") + .IsRequired(); + + b.Property("ChatCommandsJson") + .IsRequired(); + + b.Property("CompileJobId"); + + b.Property("IsPrimary"); + + b.Property("Port"); + + b.Property("ProcessId"); + + b.Property("RebootState"); + + b.Property("ServerCommandsJson") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("CompileJobId"); + + b.ToTable("ReattachInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AccessToken"); + + b.Property("AccessUser"); + + b.Property("AutoUpdatesKeepTestMerges") + .IsRequired(); + + b.Property("AutoUpdatesSynchronize") + .IsRequired(); + + b.Property("CommitterEmail") + .IsRequired(); + + b.Property("CommitterName") + .IsRequired(); + + b.Property("InstanceId"); + + b.Property("PushTestMergeCommits") + .IsRequired(); + + b.Property("ShowTestMergeCommitters") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("RepositorySettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("RevisionInformationId"); + + b.Property("TestMergeId"); + + b.HasKey("Id"); + + b.HasIndex("RevisionInformationId"); + + b.HasIndex("TestMergeId"); + + b.ToTable("RevInfoTestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CommitSha") + .IsRequired() + .HasMaxLength(40); + + b.Property("InstanceId"); + + b.Property("OriginCommitSha") + .IsRequired() + .HasMaxLength(40); + + b.HasKey("Id"); + + b.HasIndex("CommitSha") + .IsUnique(); + + b.HasIndex("InstanceId"); + + b.ToTable("RevisionInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Author") + .IsRequired(); + + b.Property("BodyAtMerge") + .IsRequired(); + + b.Property("Comment"); + + b.Property("MergedAt"); + + b.Property("MergedById"); + + b.Property("Number") + .IsRequired(); + + b.Property("PrimaryRevisionInformationId"); + + b.Property("PullRequestRevision") + .IsRequired(); + + b.Property("TitleAtMerge") + .IsRequired(); + + b.Property("Url") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("MergedById"); + + b.HasIndex("PrimaryRevisionInformationId") + .IsUnique() + .HasFilter("[PrimaryRevisionInformationId] IS NOT NULL"); + + b.ToTable("TestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AdministrationRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("CanonicalName") + .IsRequired(); + + b.Property("CreatedAt") + .IsRequired(); + + b.Property("CreatedById"); + + b.Property("Enabled") + .IsRequired(); + + b.Property("InstanceManagerRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("LastPasswordUpdate"); + + b.Property("Name") + .IsRequired(); + + b.Property("PasswordHash"); + + b.Property("SystemIdentifier"); + + b.HasKey("Id"); + + b.HasIndex("CanonicalName") + .IsUnique(); + + b.HasIndex("CreatedById"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.WatchdogReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AlphaId"); + + b.Property("AlphaIsActive"); + + b.Property("BravoId"); + + b.HasKey("Id"); + + b.HasIndex("AlphaId"); + + b.HasIndex("BravoId"); + + b.ToTable("WatchdogReattachInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("ChatSettings") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings") + .WithMany("Channels") + .HasForeignKey("ChatSettingsId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.HasOne("Tgstation.Server.Host.Models.Job", "Job") + .WithMany() + .HasForeignKey("JobId"); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("CompileJobs") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamDaemonSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamMakerSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.HasOne("Tgstation.Server.Host.Models.WatchdogReattachInformation", "WatchdogReattachInformation") + .WithMany() + .HasForeignKey("WatchdogReattachInformationId"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("InstanceUsers") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User") + .WithMany("InstanceUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CancelledBy") + .WithMany() + .HasForeignKey("CancelledById"); + + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("Jobs") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy") + .WithMany() + .HasForeignKey("StartedById") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") + .WithMany() + .HasForeignKey("CompileJobId"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("RepositorySettings") + .HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("ActiveTestMerges") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge") + .WithMany("RevisonInformations") + .HasForeignKey("TestMergeId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("RevisionInformations") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy") + .WithMany("TestMerges") + .HasForeignKey("MergedById") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation") + .WithOne("PrimaryTestMerge") + .HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId") + .OnDelete(DeleteBehavior.SetNull); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy") + .WithMany("CreatedUsers") + .HasForeignKey("CreatedById"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.WatchdogReattachInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.ReattachInformation", "Alpha") + .WithMany() + .HasForeignKey("AlphaId"); + + b.HasOne("Tgstation.Server.Host.Models.ReattachInformation", "Bravo") + .WithMany() + .HasForeignKey("BravoId"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Tgstation.Server.Host/Models/Migrations/20180906135553_MSInitialCreate.cs b/src/Tgstation.Server.Host/Models/Migrations/20180906135553_MSInitialCreate.cs new file mode 100644 index 0000000000..89e0e14b42 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/Migrations/20180906135553_MSInitialCreate.cs @@ -0,0 +1,652 @@ +using System; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Tgstation.Server.Host.Models.Migrations +{ + /// + /// The initial database migration + /// + public partial class MSInitialCreate : Migration + { + /// + /// Applies the migration + /// + /// The to use + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Users", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + Enabled = table.Column(nullable: false), + CreatedAt = table.Column(nullable: false), + SystemIdentifier = table.Column(nullable: true), + Name = table.Column(nullable: false), + AdministrationRights = table.Column(nullable: false), + InstanceManagerRights = table.Column(nullable: false), + PasswordHash = table.Column(nullable: true), + CreatedById = table.Column(nullable: true), + CanonicalName = table.Column(nullable: false), + LastPasswordUpdate = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.Id); + table.ForeignKey( + name: "FK_Users_Users_CreatedById", + column: x => x.CreatedById, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "ChatChannels", + columns: table => new + { + IrcChannel = table.Column(nullable: true), + DiscordChannelId = table.Column(nullable: true), + IsAdminChannel = table.Column(nullable: false), + IsWatchdogChannel = table.Column(nullable: false), + IsUpdatesChannel = table.Column(nullable: false), + Tag = table.Column(nullable: true), + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + ChatSettingsId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ChatChannels", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ReattachInformations", + columns: table => new + { + ChatCommandsJson = table.Column(nullable: false), + ChatChannelsJson = table.Column(nullable: false), + ServerCommandsJson = table.Column(nullable: false), + AccessIdentifier = table.Column(nullable: false), + ProcessId = table.Column(nullable: false), + IsPrimary = table.Column(nullable: false), + Port = table.Column(nullable: false), + RebootState = table.Column(nullable: false), + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + CompileJobId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ReattachInformations", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "WatchdogReattachInformations", + columns: table => new + { + AlphaIsActive = table.Column(nullable: false), + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + AlphaId = table.Column(nullable: true), + BravoId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_WatchdogReattachInformations", x => x.Id); + table.ForeignKey( + name: "FK_WatchdogReattachInformations_ReattachInformations_AlphaId", + column: x => x.AlphaId, + principalTable: "ReattachInformations", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_WatchdogReattachInformations_ReattachInformations_BravoId", + column: x => x.BravoId, + principalTable: "ReattachInformations", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Instances", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + Name = table.Column(nullable: false), + Path = table.Column(nullable: false), + Online = table.Column(nullable: false), + ConfigurationType = table.Column(nullable: false), + AutoUpdateInterval = table.Column(nullable: false), + WatchdogReattachInformationId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Instances", x => x.Id); + table.ForeignKey( + name: "FK_Instances_WatchdogReattachInformations_WatchdogReattachInformationId", + column: x => x.WatchdogReattachInformationId, + principalTable: "WatchdogReattachInformations", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "ChatBots", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + Name = table.Column(nullable: false), + Enabled = table.Column(nullable: true), + Provider = table.Column(nullable: true), + ConnectionString = table.Column(nullable: false), + InstanceId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ChatBots", x => x.Id); + table.ForeignKey( + name: "FK_ChatBots_Instances_InstanceId", + column: x => x.InstanceId, + principalTable: "Instances", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "DreamDaemonSettings", + columns: table => new + { + AllowWebClient = table.Column(nullable: false), + SecurityLevel = table.Column(nullable: false), + PrimaryPort = table.Column(nullable: false), + SecondaryPort = table.Column(nullable: false), + StartupTimeout = table.Column(nullable: false), + AutoStart = table.Column(nullable: false), + SoftRestart = table.Column(nullable: false), + SoftShutdown = table.Column(nullable: false), + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + ProcessId = table.Column(nullable: true), + AccessToken = table.Column(nullable: true), + InstanceId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_DreamDaemonSettings", x => x.Id); + table.ForeignKey( + name: "FK_DreamDaemonSettings_Instances_InstanceId", + column: x => x.InstanceId, + principalTable: "Instances", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "DreamMakerSettings", + columns: table => new + { + ProjectName = table.Column(nullable: true), + ApiValidationPort = table.Column(nullable: false), + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + InstanceId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_DreamMakerSettings", x => x.Id); + table.ForeignKey( + name: "FK_DreamMakerSettings_Instances_InstanceId", + column: x => x.InstanceId, + principalTable: "Instances", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "InstanceUsers", + columns: table => new + { + UserId = table.Column(nullable: false), + InstanceUserRights = table.Column(nullable: false), + ByondRights = table.Column(nullable: false), + DreamDaemonRights = table.Column(nullable: false), + DreamMakerRights = table.Column(nullable: false), + RepositoryRights = table.Column(nullable: false), + ChatBotRights = table.Column(nullable: false), + ConfigurationRights = table.Column(nullable: false), + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + InstanceId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_InstanceUsers", x => x.Id); + table.ForeignKey( + name: "FK_InstanceUsers_Instances_InstanceId", + column: x => x.InstanceId, + principalTable: "Instances", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_InstanceUsers_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Jobs", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + Description = table.Column(nullable: false), + ExceptionDetails = table.Column(nullable: true), + StartedAt = table.Column(nullable: false), + StoppedAt = table.Column(nullable: true), + Cancelled = table.Column(nullable: false), + CancelRightsType = table.Column(nullable: true), + CancelRight = table.Column(nullable: true), + StartedById = table.Column(nullable: false), + CancelledById = table.Column(nullable: true), + InstanceId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Jobs", x => x.Id); + table.ForeignKey( + name: "FK_Jobs_Users_CancelledById", + column: x => x.CancelledById, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Jobs_Instances_InstanceId", + column: x => x.InstanceId, + principalTable: "Instances", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Jobs_Users_StartedById", + column: x => x.StartedById, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "RepositorySettings", + columns: table => new + { + CommitterName = table.Column(nullable: false), + CommitterEmail = table.Column(nullable: false), + AccessUser = table.Column(nullable: true), + AccessToken = table.Column(nullable: true), + PushTestMergeCommits = table.Column(nullable: false), + ShowTestMergeCommitters = table.Column(nullable: false), + AutoUpdatesKeepTestMerges = table.Column(nullable: false), + AutoUpdatesSynchronize = table.Column(nullable: false), + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + InstanceId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RepositorySettings", x => x.Id); + table.ForeignKey( + name: "FK_RepositorySettings_Instances_InstanceId", + column: x => x.InstanceId, + principalTable: "Instances", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "RevisionInformations", + columns: table => new + { + CommitSha = table.Column(maxLength: 40, nullable: false), + OriginCommitSha = table.Column(maxLength: 40, nullable: false), + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + InstanceId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RevisionInformations", x => x.Id); + table.ForeignKey( + name: "FK_RevisionInformations_Instances_InstanceId", + column: x => x.InstanceId, + principalTable: "Instances", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "CompileJobs", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + DmeName = table.Column(nullable: true), + Output = table.Column(nullable: true), + DirectoryName = table.Column(nullable: true), + JobId = table.Column(nullable: true), + RevisionInformationId = table.Column(nullable: false), + ByondVersion = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CompileJobs", x => x.Id); + table.ForeignKey( + name: "FK_CompileJobs_Jobs_JobId", + column: x => x.JobId, + principalTable: "Jobs", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_CompileJobs_RevisionInformations_RevisionInformationId", + column: x => x.RevisionInformationId, + principalTable: "RevisionInformations", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "TestMerges", + columns: table => new + { + Number = table.Column(nullable: false), + PullRequestRevision = table.Column(nullable: false), + Comment = table.Column(nullable: true), + TitleAtMerge = table.Column(nullable: false), + BodyAtMerge = table.Column(nullable: false), + Url = table.Column(nullable: false), + Author = table.Column(nullable: false), + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + MergedAt = table.Column(nullable: false), + MergedById = table.Column(nullable: false), + PrimaryRevisionInformationId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_TestMerges", x => x.Id); + table.ForeignKey( + name: "FK_TestMerges_Users_MergedById", + column: x => x.MergedById, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_TestMerges_RevisionInformations_PrimaryRevisionInformationId", + column: x => x.PrimaryRevisionInformationId, + principalTable: "RevisionInformations", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + }); + + migrationBuilder.CreateTable( + name: "RevInfoTestMerges", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + TestMergeId = table.Column(nullable: false), + RevisionInformationId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RevInfoTestMerges", x => x.Id); + table.ForeignKey( + name: "FK_RevInfoTestMerges_RevisionInformations_RevisionInformationId", + column: x => x.RevisionInformationId, + principalTable: "RevisionInformations", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_RevInfoTestMerges_TestMerges_TestMergeId", + column: x => x.TestMergeId, + principalTable: "TestMerges", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_ChatBots_InstanceId", + table: "ChatBots", + column: "InstanceId"); + + migrationBuilder.CreateIndex( + name: "IX_ChatBots_Name", + table: "ChatBots", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ChatChannels_ChatSettingsId_DiscordChannelId", + table: "ChatChannels", + columns: new[] { "ChatSettingsId", "DiscordChannelId" }, + unique: true, + filter: "[DiscordChannelId] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_ChatChannels_ChatSettingsId_IrcChannel", + table: "ChatChannels", + columns: new[] { "ChatSettingsId", "IrcChannel" }, + unique: true, + filter: "[IrcChannel] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_CompileJobs_DirectoryName", + table: "CompileJobs", + column: "DirectoryName"); + + migrationBuilder.CreateIndex( + name: "IX_CompileJobs_JobId", + table: "CompileJobs", + column: "JobId"); + + migrationBuilder.CreateIndex( + name: "IX_CompileJobs_RevisionInformationId", + table: "CompileJobs", + column: "RevisionInformationId"); + + migrationBuilder.CreateIndex( + name: "IX_DreamDaemonSettings_InstanceId", + table: "DreamDaemonSettings", + column: "InstanceId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_DreamMakerSettings_InstanceId", + table: "DreamMakerSettings", + column: "InstanceId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Instances_Path", + table: "Instances", + column: "Path", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Instances_WatchdogReattachInformationId", + table: "Instances", + column: "WatchdogReattachInformationId"); + + migrationBuilder.CreateIndex( + name: "IX_InstanceUsers_InstanceId", + table: "InstanceUsers", + column: "InstanceId"); + + migrationBuilder.CreateIndex( + name: "IX_InstanceUsers_UserId_InstanceId", + table: "InstanceUsers", + columns: new[] { "UserId", "InstanceId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Jobs_CancelledById", + table: "Jobs", + column: "CancelledById"); + + migrationBuilder.CreateIndex( + name: "IX_Jobs_InstanceId", + table: "Jobs", + column: "InstanceId"); + + migrationBuilder.CreateIndex( + name: "IX_Jobs_StartedById", + table: "Jobs", + column: "StartedById"); + + migrationBuilder.CreateIndex( + name: "IX_ReattachInformations_CompileJobId", + table: "ReattachInformations", + column: "CompileJobId"); + + migrationBuilder.CreateIndex( + name: "IX_RepositorySettings_InstanceId", + table: "RepositorySettings", + column: "InstanceId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_RevInfoTestMerges_RevisionInformationId", + table: "RevInfoTestMerges", + column: "RevisionInformationId"); + + migrationBuilder.CreateIndex( + name: "IX_RevInfoTestMerges_TestMergeId", + table: "RevInfoTestMerges", + column: "TestMergeId"); + + migrationBuilder.CreateIndex( + name: "IX_RevisionInformations_CommitSha", + table: "RevisionInformations", + column: "CommitSha", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_RevisionInformations_InstanceId", + table: "RevisionInformations", + column: "InstanceId"); + + migrationBuilder.CreateIndex( + name: "IX_TestMerges_MergedById", + table: "TestMerges", + column: "MergedById"); + + migrationBuilder.CreateIndex( + name: "IX_TestMerges_PrimaryRevisionInformationId", + table: "TestMerges", + column: "PrimaryRevisionInformationId", + unique: true, + filter: "[PrimaryRevisionInformationId] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_Users_CanonicalName", + table: "Users", + column: "CanonicalName", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Users_CreatedById", + table: "Users", + column: "CreatedById"); + + migrationBuilder.CreateIndex( + name: "IX_WatchdogReattachInformations_AlphaId", + table: "WatchdogReattachInformations", + column: "AlphaId"); + + migrationBuilder.CreateIndex( + name: "IX_WatchdogReattachInformations_BravoId", + table: "WatchdogReattachInformations", + column: "BravoId"); + + migrationBuilder.AddForeignKey( + name: "FK_ChatChannels_ChatBots_ChatSettingsId", + table: "ChatChannels", + column: "ChatSettingsId", + principalTable: "ChatBots", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_ReattachInformations_CompileJobs_CompileJobId", + table: "ReattachInformations", + column: "CompileJobId", + principalTable: "CompileJobs", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + } + + /// + /// Unapplies the migration + /// + /// The to use + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Jobs_Instances_InstanceId", + table: "Jobs"); + + migrationBuilder.DropForeignKey( + name: "FK_RevisionInformations_Instances_InstanceId", + table: "RevisionInformations"); + + migrationBuilder.DropTable( + name: "ChatChannels"); + + migrationBuilder.DropTable( + name: "DreamDaemonSettings"); + + migrationBuilder.DropTable( + name: "DreamMakerSettings"); + + migrationBuilder.DropTable( + name: "InstanceUsers"); + + migrationBuilder.DropTable( + name: "RepositorySettings"); + + migrationBuilder.DropTable( + name: "RevInfoTestMerges"); + + migrationBuilder.DropTable( + name: "ChatBots"); + + migrationBuilder.DropTable( + name: "TestMerges"); + + migrationBuilder.DropTable( + name: "Instances"); + + migrationBuilder.DropTable( + name: "WatchdogReattachInformations"); + + migrationBuilder.DropTable( + name: "ReattachInformations"); + + migrationBuilder.DropTable( + name: "CompileJobs"); + + migrationBuilder.DropTable( + name: "Jobs"); + + migrationBuilder.DropTable( + name: "RevisionInformations"); + + migrationBuilder.DropTable( + name: "Users"); + } + } +} diff --git a/src/Tgstation.Server.Host/Models/Migrations/SqlServerDatabaseContextModelSnapshot.cs b/src/Tgstation.Server.Host/Models/Migrations/SqlServerDatabaseContextModelSnapshot.cs new file mode 100644 index 0000000000..a17096d672 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/Migrations/SqlServerDatabaseContextModelSnapshot.cs @@ -0,0 +1,665 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Tgstation.Server.Host.Models.Migrations +{ + [DbContext(typeof(SqlServerDatabaseContext))] + partial class SqlServerDatabaseContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.2-rtm-30932") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ConnectionString") + .IsRequired(); + + b.Property("Enabled"); + + b.Property("InstanceId"); + + b.Property("Name") + .IsRequired(); + + b.Property("Provider"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("ChatBots"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ChatSettingsId"); + + b.Property("DiscordChannelId") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("IrcChannel"); + + b.Property("IsAdminChannel") + .IsRequired(); + + b.Property("IsUpdatesChannel") + .IsRequired(); + + b.Property("IsWatchdogChannel") + .IsRequired(); + + b.Property("Tag"); + + b.HasKey("Id"); + + b.HasIndex("ChatSettingsId", "DiscordChannelId") + .IsUnique() + .HasFilter("[DiscordChannelId] IS NOT NULL"); + + b.HasIndex("ChatSettingsId", "IrcChannel") + .IsUnique() + .HasFilter("[IrcChannel] IS NOT NULL"); + + b.ToTable("ChatChannels"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ByondVersion") + .IsRequired(); + + b.Property("DirectoryName"); + + b.Property("DmeName"); + + b.Property("JobId"); + + b.Property("Output"); + + b.Property("RevisionInformationId"); + + b.HasKey("Id"); + + b.HasIndex("DirectoryName"); + + b.HasIndex("JobId"); + + b.HasIndex("RevisionInformationId"); + + b.ToTable("CompileJobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AccessToken"); + + b.Property("AllowWebClient") + .IsRequired(); + + b.Property("AutoStart") + .IsRequired(); + + b.Property("InstanceId"); + + b.Property("PrimaryPort"); + + b.Property("ProcessId"); + + b.Property("SecondaryPort"); + + b.Property("SecurityLevel"); + + b.Property("SoftRestart") + .IsRequired(); + + b.Property("SoftShutdown") + .IsRequired(); + + b.Property("StartupTimeout"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamDaemonSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ApiValidationPort"); + + b.Property("InstanceId"); + + b.Property("ProjectName"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamMakerSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AutoUpdateInterval"); + + b.Property("ConfigurationType"); + + b.Property("Name") + .IsRequired(); + + b.Property("Online") + .IsRequired(); + + b.Property("Path") + .IsRequired(); + + b.Property("WatchdogReattachInformationId"); + + b.HasKey("Id"); + + b.HasIndex("Path") + .IsUnique(); + + b.HasIndex("WatchdogReattachInformationId"); + + b.ToTable("Instances"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ByondRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("ChatBotRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("ConfigurationRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("DreamDaemonRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("DreamMakerRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("InstanceId"); + + b.Property("InstanceUserRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("RepositoryRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("UserId", "InstanceId") + .IsUnique(); + + b.ToTable("InstanceUsers"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CancelRight") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("CancelRightsType") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("Cancelled") + .IsRequired(); + + b.Property("CancelledById"); + + b.Property("Description") + .IsRequired(); + + b.Property("ExceptionDetails"); + + b.Property("InstanceId"); + + b.Property("StartedAt") + .IsRequired(); + + b.Property("StartedById"); + + b.Property("StoppedAt"); + + b.HasKey("Id"); + + b.HasIndex("CancelledById"); + + b.HasIndex("InstanceId"); + + b.HasIndex("StartedById"); + + b.ToTable("Jobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AccessIdentifier") + .IsRequired(); + + b.Property("ChatChannelsJson") + .IsRequired(); + + b.Property("ChatCommandsJson") + .IsRequired(); + + b.Property("CompileJobId"); + + b.Property("IsPrimary"); + + b.Property("Port"); + + b.Property("ProcessId"); + + b.Property("RebootState"); + + b.Property("ServerCommandsJson") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("CompileJobId"); + + b.ToTable("ReattachInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AccessToken"); + + b.Property("AccessUser"); + + b.Property("AutoUpdatesKeepTestMerges") + .IsRequired(); + + b.Property("AutoUpdatesSynchronize") + .IsRequired(); + + b.Property("CommitterEmail") + .IsRequired(); + + b.Property("CommitterName") + .IsRequired(); + + b.Property("InstanceId"); + + b.Property("PushTestMergeCommits") + .IsRequired(); + + b.Property("ShowTestMergeCommitters") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("RepositorySettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("RevisionInformationId"); + + b.Property("TestMergeId"); + + b.HasKey("Id"); + + b.HasIndex("RevisionInformationId"); + + b.HasIndex("TestMergeId"); + + b.ToTable("RevInfoTestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CommitSha") + .IsRequired() + .HasMaxLength(40); + + b.Property("InstanceId"); + + b.Property("OriginCommitSha") + .IsRequired() + .HasMaxLength(40); + + b.HasKey("Id"); + + b.HasIndex("CommitSha") + .IsUnique(); + + b.HasIndex("InstanceId"); + + b.ToTable("RevisionInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Author") + .IsRequired(); + + b.Property("BodyAtMerge") + .IsRequired(); + + b.Property("Comment"); + + b.Property("MergedAt"); + + b.Property("MergedById"); + + b.Property("Number") + .IsRequired(); + + b.Property("PrimaryRevisionInformationId"); + + b.Property("PullRequestRevision") + .IsRequired(); + + b.Property("TitleAtMerge") + .IsRequired(); + + b.Property("Url") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("MergedById"); + + b.HasIndex("PrimaryRevisionInformationId") + .IsUnique() + .HasFilter("[PrimaryRevisionInformationId] IS NOT NULL"); + + b.ToTable("TestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AdministrationRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("CanonicalName") + .IsRequired(); + + b.Property("CreatedAt") + .IsRequired(); + + b.Property("CreatedById"); + + b.Property("Enabled") + .IsRequired(); + + b.Property("InstanceManagerRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("LastPasswordUpdate"); + + b.Property("Name") + .IsRequired(); + + b.Property("PasswordHash"); + + b.Property("SystemIdentifier"); + + b.HasKey("Id"); + + b.HasIndex("CanonicalName") + .IsUnique(); + + b.HasIndex("CreatedById"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.WatchdogReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AlphaId"); + + b.Property("AlphaIsActive"); + + b.Property("BravoId"); + + b.HasKey("Id"); + + b.HasIndex("AlphaId"); + + b.HasIndex("BravoId"); + + b.ToTable("WatchdogReattachInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("ChatSettings") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings") + .WithMany("Channels") + .HasForeignKey("ChatSettingsId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.HasOne("Tgstation.Server.Host.Models.Job", "Job") + .WithMany() + .HasForeignKey("JobId"); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("CompileJobs") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamDaemonSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamMakerSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.HasOne("Tgstation.Server.Host.Models.WatchdogReattachInformation", "WatchdogReattachInformation") + .WithMany() + .HasForeignKey("WatchdogReattachInformationId"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("InstanceUsers") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User") + .WithMany("InstanceUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CancelledBy") + .WithMany() + .HasForeignKey("CancelledById"); + + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("Jobs") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy") + .WithMany() + .HasForeignKey("StartedById") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") + .WithMany() + .HasForeignKey("CompileJobId"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("RepositorySettings") + .HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("ActiveTestMerges") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge") + .WithMany("RevisonInformations") + .HasForeignKey("TestMergeId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("RevisionInformations") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy") + .WithMany("TestMerges") + .HasForeignKey("MergedById") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation") + .WithOne("PrimaryTestMerge") + .HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId") + .OnDelete(DeleteBehavior.SetNull); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy") + .WithMany("CreatedUsers") + .HasForeignKey("CreatedById"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.WatchdogReattachInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.ReattachInformation", "Alpha") + .WithMany() + .HasForeignKey("AlphaId"); + + b.HasOne("Tgstation.Server.Host.Models.ReattachInformation", "Bravo") + .WithMany() + .HasForeignKey("BravoId"); + }); +#pragma warning restore 612, 618 + } + } +} From 0c920e85c3561723aa671e6ba5194c2c112a99a3 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 6 Sep 2018 10:21:00 -0400 Subject: [PATCH 38/47] Add dotnet ef core CLI --- src/Tgstation.Server.Host/Tgstation.Server.Host.csproj | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index 779628f0bb..ef03589724 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -35,7 +35,7 @@ all compile; build; native; contentfiles; analyzers - + @@ -48,6 +48,10 @@ + + + + From 07460585cc13bfd8d62952e1e2d9de02a2a79c9d Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 6 Sep 2018 10:41:39 -0400 Subject: [PATCH 39/47] Add MySQL migrations --- .../20180906135553_MSInitialCreate.cs | 2 +- ...20180906143029_MYInitialCreate.Designer.cs | 644 +++++++++++++++++ .../20180906143029_MYInitialCreate.cs | 649 ++++++++++++++++++ .../MySqlDatabaseContextModelSnapshot.cs | 638 +++++++++++++++++ 4 files changed, 1932 insertions(+), 1 deletion(-) create mode 100644 src/Tgstation.Server.Host/Models/Migrations/20180906143029_MYInitialCreate.Designer.cs create mode 100644 src/Tgstation.Server.Host/Models/Migrations/20180906143029_MYInitialCreate.cs create mode 100644 src/Tgstation.Server.Host/Models/Migrations/MySqlDatabaseContextModelSnapshot.cs diff --git a/src/Tgstation.Server.Host/Models/Migrations/20180906135553_MSInitialCreate.cs b/src/Tgstation.Server.Host/Models/Migrations/20180906135553_MSInitialCreate.cs index 89e0e14b42..57f363fa2a 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/20180906135553_MSInitialCreate.cs +++ b/src/Tgstation.Server.Host/Models/Migrations/20180906135553_MSInitialCreate.cs @@ -5,7 +5,7 @@ using Microsoft.EntityFrameworkCore.Migrations; namespace Tgstation.Server.Host.Models.Migrations { /// - /// The initial database migration + /// The initial database migration for MSSQL /// public partial class MSInitialCreate : Migration { diff --git a/src/Tgstation.Server.Host/Models/Migrations/20180906143029_MYInitialCreate.Designer.cs b/src/Tgstation.Server.Host/Models/Migrations/20180906143029_MYInitialCreate.Designer.cs new file mode 100644 index 0000000000..73566bba2d --- /dev/null +++ b/src/Tgstation.Server.Host/Models/Migrations/20180906143029_MYInitialCreate.Designer.cs @@ -0,0 +1,644 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Tgstation.Server.Host.Models.Migrations +{ + [DbContext(typeof(MySqlDatabaseContext))] + [Migration("20180906143029_MYInitialCreate")] + partial class MYInitialCreate + { + /// + /// Builds the target model + /// + /// The to use + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.2-rtm-30932") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConnectionString") + .IsRequired(); + + b.Property("Enabled"); + + b.Property("InstanceId"); + + b.Property("Name") + .IsRequired(); + + b.Property("Provider"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("ChatBots"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChatSettingsId"); + + b.Property("DiscordChannelId"); + + b.Property("IrcChannel"); + + b.Property("IsAdminChannel") + .IsRequired(); + + b.Property("IsUpdatesChannel") + .IsRequired(); + + b.Property("IsWatchdogChannel") + .IsRequired(); + + b.Property("Tag"); + + b.HasKey("Id"); + + b.HasIndex("ChatSettingsId", "DiscordChannelId") + .IsUnique(); + + b.HasIndex("ChatSettingsId", "IrcChannel") + .IsUnique(); + + b.ToTable("ChatChannels"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ByondVersion") + .IsRequired(); + + b.Property("DirectoryName"); + + b.Property("DmeName"); + + b.Property("JobId"); + + b.Property("Output"); + + b.Property("RevisionInformationId"); + + b.HasKey("Id"); + + b.HasIndex("DirectoryName"); + + b.HasIndex("JobId"); + + b.HasIndex("RevisionInformationId"); + + b.ToTable("CompileJobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessToken"); + + b.Property("AllowWebClient") + .IsRequired(); + + b.Property("AutoStart") + .IsRequired(); + + b.Property("InstanceId"); + + b.Property("PrimaryPort") + .IsRequired(); + + b.Property("ProcessId"); + + b.Property("SecondaryPort") + .IsRequired(); + + b.Property("SecurityLevel"); + + b.Property("SoftRestart") + .IsRequired(); + + b.Property("SoftShutdown") + .IsRequired(); + + b.Property("StartupTimeout") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamDaemonSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ApiValidationPort") + .IsRequired(); + + b.Property("InstanceId"); + + b.Property("ProjectName"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamMakerSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoUpdateInterval") + .IsRequired(); + + b.Property("ConfigurationType"); + + b.Property("Name") + .IsRequired(); + + b.Property("Online") + .IsRequired(); + + b.Property("Path") + .IsRequired(); + + b.Property("WatchdogReattachInformationId"); + + b.HasKey("Id"); + + b.HasIndex("Path") + .IsUnique(); + + b.HasIndex("WatchdogReattachInformationId"); + + b.ToTable("Instances"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ByondRights"); + + b.Property("ChatBotRights"); + + b.Property("ConfigurationRights"); + + b.Property("DreamDaemonRights"); + + b.Property("DreamMakerRights"); + + b.Property("InstanceId"); + + b.Property("InstanceUserRights"); + + b.Property("RepositoryRights"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("UserId", "InstanceId") + .IsUnique(); + + b.ToTable("InstanceUsers"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CancelRight"); + + b.Property("CancelRightsType"); + + b.Property("Cancelled") + .IsRequired(); + + b.Property("CancelledById"); + + b.Property("Description") + .IsRequired(); + + b.Property("ExceptionDetails"); + + b.Property("InstanceId"); + + b.Property("StartedAt") + .IsRequired(); + + b.Property("StartedById"); + + b.Property("StoppedAt"); + + b.HasKey("Id"); + + b.HasIndex("CancelledById"); + + b.HasIndex("InstanceId"); + + b.HasIndex("StartedById"); + + b.ToTable("Jobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessIdentifier") + .IsRequired(); + + b.Property("ChatChannelsJson") + .IsRequired(); + + b.Property("ChatCommandsJson") + .IsRequired(); + + b.Property("CompileJobId"); + + b.Property("IsPrimary"); + + b.Property("Port"); + + b.Property("ProcessId"); + + b.Property("RebootState"); + + b.Property("ServerCommandsJson") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("CompileJobId"); + + b.ToTable("ReattachInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessToken"); + + b.Property("AccessUser"); + + b.Property("AutoUpdatesKeepTestMerges") + .IsRequired(); + + b.Property("AutoUpdatesSynchronize") + .IsRequired(); + + b.Property("CommitterEmail") + .IsRequired(); + + b.Property("CommitterName") + .IsRequired(); + + b.Property("InstanceId"); + + b.Property("PushTestMergeCommits") + .IsRequired(); + + b.Property("ShowTestMergeCommitters") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("RepositorySettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("RevisionInformationId"); + + b.Property("TestMergeId"); + + b.HasKey("Id"); + + b.HasIndex("RevisionInformationId"); + + b.HasIndex("TestMergeId"); + + b.ToTable("RevInfoTestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CommitSha") + .IsRequired() + .HasMaxLength(40); + + b.Property("InstanceId"); + + b.Property("OriginCommitSha") + .IsRequired() + .HasMaxLength(40); + + b.HasKey("Id"); + + b.HasIndex("CommitSha") + .IsUnique(); + + b.HasIndex("InstanceId"); + + b.ToTable("RevisionInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Author") + .IsRequired(); + + b.Property("BodyAtMerge") + .IsRequired(); + + b.Property("Comment"); + + b.Property("MergedAt"); + + b.Property("MergedById"); + + b.Property("Number") + .IsRequired(); + + b.Property("PrimaryRevisionInformationId"); + + b.Property("PullRequestRevision") + .IsRequired(); + + b.Property("TitleAtMerge") + .IsRequired(); + + b.Property("Url") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("MergedById"); + + b.HasIndex("PrimaryRevisionInformationId") + .IsUnique(); + + b.ToTable("TestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AdministrationRights"); + + b.Property("CanonicalName") + .IsRequired(); + + b.Property("CreatedAt") + .IsRequired(); + + b.Property("CreatedById"); + + b.Property("Enabled") + .IsRequired(); + + b.Property("InstanceManagerRights"); + + b.Property("LastPasswordUpdate"); + + b.Property("Name") + .IsRequired(); + + b.Property("PasswordHash"); + + b.Property("SystemIdentifier"); + + b.HasKey("Id"); + + b.HasIndex("CanonicalName") + .IsUnique(); + + b.HasIndex("CreatedById"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.WatchdogReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AlphaId"); + + b.Property("AlphaIsActive"); + + b.Property("BravoId"); + + b.HasKey("Id"); + + b.HasIndex("AlphaId"); + + b.HasIndex("BravoId"); + + b.ToTable("WatchdogReattachInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("ChatSettings") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings") + .WithMany("Channels") + .HasForeignKey("ChatSettingsId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.HasOne("Tgstation.Server.Host.Models.Job", "Job") + .WithMany() + .HasForeignKey("JobId"); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("CompileJobs") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamDaemonSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamMakerSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.HasOne("Tgstation.Server.Host.Models.WatchdogReattachInformation", "WatchdogReattachInformation") + .WithMany() + .HasForeignKey("WatchdogReattachInformationId"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("InstanceUsers") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User") + .WithMany("InstanceUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CancelledBy") + .WithMany() + .HasForeignKey("CancelledById"); + + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("Jobs") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy") + .WithMany() + .HasForeignKey("StartedById") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") + .WithMany() + .HasForeignKey("CompileJobId"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("RepositorySettings") + .HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("ActiveTestMerges") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge") + .WithMany("RevisonInformations") + .HasForeignKey("TestMergeId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("RevisionInformations") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy") + .WithMany("TestMerges") + .HasForeignKey("MergedById") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation") + .WithOne("PrimaryTestMerge") + .HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId") + .OnDelete(DeleteBehavior.SetNull); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy") + .WithMany("CreatedUsers") + .HasForeignKey("CreatedById"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.WatchdogReattachInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.ReattachInformation", "Alpha") + .WithMany() + .HasForeignKey("AlphaId"); + + b.HasOne("Tgstation.Server.Host.Models.ReattachInformation", "Bravo") + .WithMany() + .HasForeignKey("BravoId"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Tgstation.Server.Host/Models/Migrations/20180906143029_MYInitialCreate.cs b/src/Tgstation.Server.Host/Models/Migrations/20180906143029_MYInitialCreate.cs new file mode 100644 index 0000000000..02e3d313d1 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/Migrations/20180906143029_MYInitialCreate.cs @@ -0,0 +1,649 @@ +using System; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Tgstation.Server.Host.Models.Migrations +{ + /// + /// The initial database migration for MySQL/MariaDB + /// + public partial class MYInitialCreate : Migration + { + /// + /// Applies the migration + /// + /// The to use + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Users", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Enabled = table.Column(nullable: false), + CreatedAt = table.Column(nullable: false), + SystemIdentifier = table.Column(nullable: true), + Name = table.Column(nullable: false), + AdministrationRights = table.Column(nullable: false), + InstanceManagerRights = table.Column(nullable: false), + PasswordHash = table.Column(nullable: true), + CreatedById = table.Column(nullable: true), + CanonicalName = table.Column(nullable: false), + LastPasswordUpdate = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.Id); + table.ForeignKey( + name: "FK_Users_Users_CreatedById", + column: x => x.CreatedById, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "ChatChannels", + columns: table => new + { + IrcChannel = table.Column(nullable: true), + DiscordChannelId = table.Column(nullable: true), + IsAdminChannel = table.Column(nullable: false), + IsWatchdogChannel = table.Column(nullable: false), + IsUpdatesChannel = table.Column(nullable: false), + Tag = table.Column(nullable: true), + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + ChatSettingsId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ChatChannels", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ReattachInformations", + columns: table => new + { + ChatCommandsJson = table.Column(nullable: false), + ChatChannelsJson = table.Column(nullable: false), + ServerCommandsJson = table.Column(nullable: false), + AccessIdentifier = table.Column(nullable: false), + ProcessId = table.Column(nullable: false), + IsPrimary = table.Column(nullable: false), + Port = table.Column(nullable: false), + RebootState = table.Column(nullable: false), + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + CompileJobId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ReattachInformations", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "WatchdogReattachInformations", + columns: table => new + { + AlphaIsActive = table.Column(nullable: false), + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + AlphaId = table.Column(nullable: true), + BravoId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_WatchdogReattachInformations", x => x.Id); + table.ForeignKey( + name: "FK_WatchdogReattachInformations_ReattachInformations_AlphaId", + column: x => x.AlphaId, + principalTable: "ReattachInformations", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_WatchdogReattachInformations_ReattachInformations_BravoId", + column: x => x.BravoId, + principalTable: "ReattachInformations", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Instances", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Name = table.Column(nullable: false), + Path = table.Column(nullable: false), + Online = table.Column(nullable: false), + ConfigurationType = table.Column(nullable: false), + AutoUpdateInterval = table.Column(nullable: false), + WatchdogReattachInformationId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Instances", x => x.Id); + table.ForeignKey( + name: "FK_Instances_WatchdogReattachInformations_WatchdogReattachInfor~", + column: x => x.WatchdogReattachInformationId, + principalTable: "WatchdogReattachInformations", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "ChatBots", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Name = table.Column(nullable: false), + Enabled = table.Column(nullable: true), + Provider = table.Column(nullable: true), + ConnectionString = table.Column(nullable: false), + InstanceId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ChatBots", x => x.Id); + table.ForeignKey( + name: "FK_ChatBots_Instances_InstanceId", + column: x => x.InstanceId, + principalTable: "Instances", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "DreamDaemonSettings", + columns: table => new + { + AllowWebClient = table.Column(nullable: false), + SecurityLevel = table.Column(nullable: false), + PrimaryPort = table.Column(nullable: false), + SecondaryPort = table.Column(nullable: false), + StartupTimeout = table.Column(nullable: false), + AutoStart = table.Column(nullable: false), + SoftRestart = table.Column(nullable: false), + SoftShutdown = table.Column(nullable: false), + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + ProcessId = table.Column(nullable: true), + AccessToken = table.Column(nullable: true), + InstanceId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_DreamDaemonSettings", x => x.Id); + table.ForeignKey( + name: "FK_DreamDaemonSettings_Instances_InstanceId", + column: x => x.InstanceId, + principalTable: "Instances", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "DreamMakerSettings", + columns: table => new + { + ProjectName = table.Column(nullable: true), + ApiValidationPort = table.Column(nullable: false), + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + InstanceId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_DreamMakerSettings", x => x.Id); + table.ForeignKey( + name: "FK_DreamMakerSettings_Instances_InstanceId", + column: x => x.InstanceId, + principalTable: "Instances", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "InstanceUsers", + columns: table => new + { + UserId = table.Column(nullable: false), + InstanceUserRights = table.Column(nullable: false), + ByondRights = table.Column(nullable: false), + DreamDaemonRights = table.Column(nullable: false), + DreamMakerRights = table.Column(nullable: false), + RepositoryRights = table.Column(nullable: false), + ChatBotRights = table.Column(nullable: false), + ConfigurationRights = table.Column(nullable: false), + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + InstanceId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_InstanceUsers", x => x.Id); + table.ForeignKey( + name: "FK_InstanceUsers_Instances_InstanceId", + column: x => x.InstanceId, + principalTable: "Instances", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_InstanceUsers_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Jobs", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Description = table.Column(nullable: false), + ExceptionDetails = table.Column(nullable: true), + StartedAt = table.Column(nullable: false), + StoppedAt = table.Column(nullable: true), + Cancelled = table.Column(nullable: false), + CancelRightsType = table.Column(nullable: true), + CancelRight = table.Column(nullable: true), + StartedById = table.Column(nullable: false), + CancelledById = table.Column(nullable: true), + InstanceId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Jobs", x => x.Id); + table.ForeignKey( + name: "FK_Jobs_Users_CancelledById", + column: x => x.CancelledById, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Jobs_Instances_InstanceId", + column: x => x.InstanceId, + principalTable: "Instances", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Jobs_Users_StartedById", + column: x => x.StartedById, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "RepositorySettings", + columns: table => new + { + CommitterName = table.Column(nullable: false), + CommitterEmail = table.Column(nullable: false), + AccessUser = table.Column(nullable: true), + AccessToken = table.Column(nullable: true), + PushTestMergeCommits = table.Column(nullable: false), + ShowTestMergeCommitters = table.Column(nullable: false), + AutoUpdatesKeepTestMerges = table.Column(nullable: false), + AutoUpdatesSynchronize = table.Column(nullable: false), + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + InstanceId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RepositorySettings", x => x.Id); + table.ForeignKey( + name: "FK_RepositorySettings_Instances_InstanceId", + column: x => x.InstanceId, + principalTable: "Instances", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "RevisionInformations", + columns: table => new + { + CommitSha = table.Column(maxLength: 40, nullable: false), + OriginCommitSha = table.Column(maxLength: 40, nullable: false), + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + InstanceId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RevisionInformations", x => x.Id); + table.ForeignKey( + name: "FK_RevisionInformations_Instances_InstanceId", + column: x => x.InstanceId, + principalTable: "Instances", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "CompileJobs", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + DmeName = table.Column(nullable: true), + Output = table.Column(nullable: true), + DirectoryName = table.Column(nullable: true), + JobId = table.Column(nullable: true), + RevisionInformationId = table.Column(nullable: false), + ByondVersion = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CompileJobs", x => x.Id); + table.ForeignKey( + name: "FK_CompileJobs_Jobs_JobId", + column: x => x.JobId, + principalTable: "Jobs", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_CompileJobs_RevisionInformations_RevisionInformationId", + column: x => x.RevisionInformationId, + principalTable: "RevisionInformations", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "TestMerges", + columns: table => new + { + Number = table.Column(nullable: false), + PullRequestRevision = table.Column(nullable: false), + Comment = table.Column(nullable: true), + TitleAtMerge = table.Column(nullable: false), + BodyAtMerge = table.Column(nullable: false), + Url = table.Column(nullable: false), + Author = table.Column(nullable: false), + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + MergedAt = table.Column(nullable: false), + MergedById = table.Column(nullable: false), + PrimaryRevisionInformationId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_TestMerges", x => x.Id); + table.ForeignKey( + name: "FK_TestMerges_Users_MergedById", + column: x => x.MergedById, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_TestMerges_RevisionInformations_PrimaryRevisionInformationId", + column: x => x.PrimaryRevisionInformationId, + principalTable: "RevisionInformations", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + }); + + migrationBuilder.CreateTable( + name: "RevInfoTestMerges", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + TestMergeId = table.Column(nullable: false), + RevisionInformationId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RevInfoTestMerges", x => x.Id); + table.ForeignKey( + name: "FK_RevInfoTestMerges_RevisionInformations_RevisionInformationId", + column: x => x.RevisionInformationId, + principalTable: "RevisionInformations", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_RevInfoTestMerges_TestMerges_TestMergeId", + column: x => x.TestMergeId, + principalTable: "TestMerges", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_ChatBots_InstanceId", + table: "ChatBots", + column: "InstanceId"); + + migrationBuilder.CreateIndex( + name: "IX_ChatBots_Name", + table: "ChatBots", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ChatChannels_ChatSettingsId_DiscordChannelId", + table: "ChatChannels", + columns: new[] { "ChatSettingsId", "DiscordChannelId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ChatChannels_ChatSettingsId_IrcChannel", + table: "ChatChannels", + columns: new[] { "ChatSettingsId", "IrcChannel" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_CompileJobs_DirectoryName", + table: "CompileJobs", + column: "DirectoryName"); + + migrationBuilder.CreateIndex( + name: "IX_CompileJobs_JobId", + table: "CompileJobs", + column: "JobId"); + + migrationBuilder.CreateIndex( + name: "IX_CompileJobs_RevisionInformationId", + table: "CompileJobs", + column: "RevisionInformationId"); + + migrationBuilder.CreateIndex( + name: "IX_DreamDaemonSettings_InstanceId", + table: "DreamDaemonSettings", + column: "InstanceId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_DreamMakerSettings_InstanceId", + table: "DreamMakerSettings", + column: "InstanceId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Instances_Path", + table: "Instances", + column: "Path", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Instances_WatchdogReattachInformationId", + table: "Instances", + column: "WatchdogReattachInformationId"); + + migrationBuilder.CreateIndex( + name: "IX_InstanceUsers_InstanceId", + table: "InstanceUsers", + column: "InstanceId"); + + migrationBuilder.CreateIndex( + name: "IX_InstanceUsers_UserId_InstanceId", + table: "InstanceUsers", + columns: new[] { "UserId", "InstanceId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Jobs_CancelledById", + table: "Jobs", + column: "CancelledById"); + + migrationBuilder.CreateIndex( + name: "IX_Jobs_InstanceId", + table: "Jobs", + column: "InstanceId"); + + migrationBuilder.CreateIndex( + name: "IX_Jobs_StartedById", + table: "Jobs", + column: "StartedById"); + + migrationBuilder.CreateIndex( + name: "IX_ReattachInformations_CompileJobId", + table: "ReattachInformations", + column: "CompileJobId"); + + migrationBuilder.CreateIndex( + name: "IX_RepositorySettings_InstanceId", + table: "RepositorySettings", + column: "InstanceId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_RevInfoTestMerges_RevisionInformationId", + table: "RevInfoTestMerges", + column: "RevisionInformationId"); + + migrationBuilder.CreateIndex( + name: "IX_RevInfoTestMerges_TestMergeId", + table: "RevInfoTestMerges", + column: "TestMergeId"); + + migrationBuilder.CreateIndex( + name: "IX_RevisionInformations_CommitSha", + table: "RevisionInformations", + column: "CommitSha", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_RevisionInformations_InstanceId", + table: "RevisionInformations", + column: "InstanceId"); + + migrationBuilder.CreateIndex( + name: "IX_TestMerges_MergedById", + table: "TestMerges", + column: "MergedById"); + + migrationBuilder.CreateIndex( + name: "IX_TestMerges_PrimaryRevisionInformationId", + table: "TestMerges", + column: "PrimaryRevisionInformationId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Users_CanonicalName", + table: "Users", + column: "CanonicalName", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Users_CreatedById", + table: "Users", + column: "CreatedById"); + + migrationBuilder.CreateIndex( + name: "IX_WatchdogReattachInformations_AlphaId", + table: "WatchdogReattachInformations", + column: "AlphaId"); + + migrationBuilder.CreateIndex( + name: "IX_WatchdogReattachInformations_BravoId", + table: "WatchdogReattachInformations", + column: "BravoId"); + + migrationBuilder.AddForeignKey( + name: "FK_ChatChannels_ChatBots_ChatSettingsId", + table: "ChatChannels", + column: "ChatSettingsId", + principalTable: "ChatBots", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_ReattachInformations_CompileJobs_CompileJobId", + table: "ReattachInformations", + column: "CompileJobId", + principalTable: "CompileJobs", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + } + + /// + /// Unapplies the migration + /// + /// The to use + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Jobs_Instances_InstanceId", + table: "Jobs"); + + migrationBuilder.DropForeignKey( + name: "FK_RevisionInformations_Instances_InstanceId", + table: "RevisionInformations"); + + migrationBuilder.DropTable( + name: "ChatChannels"); + + migrationBuilder.DropTable( + name: "DreamDaemonSettings"); + + migrationBuilder.DropTable( + name: "DreamMakerSettings"); + + migrationBuilder.DropTable( + name: "InstanceUsers"); + + migrationBuilder.DropTable( + name: "RepositorySettings"); + + migrationBuilder.DropTable( + name: "RevInfoTestMerges"); + + migrationBuilder.DropTable( + name: "ChatBots"); + + migrationBuilder.DropTable( + name: "TestMerges"); + + migrationBuilder.DropTable( + name: "Instances"); + + migrationBuilder.DropTable( + name: "WatchdogReattachInformations"); + + migrationBuilder.DropTable( + name: "ReattachInformations"); + + migrationBuilder.DropTable( + name: "CompileJobs"); + + migrationBuilder.DropTable( + name: "Jobs"); + + migrationBuilder.DropTable( + name: "RevisionInformations"); + + migrationBuilder.DropTable( + name: "Users"); + } + } +} diff --git a/src/Tgstation.Server.Host/Models/Migrations/MySqlDatabaseContextModelSnapshot.cs b/src/Tgstation.Server.Host/Models/Migrations/MySqlDatabaseContextModelSnapshot.cs new file mode 100644 index 0000000000..7c82e23050 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/Migrations/MySqlDatabaseContextModelSnapshot.cs @@ -0,0 +1,638 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Tgstation.Server.Host.Models.Migrations +{ + [DbContext(typeof(MySqlDatabaseContext))] + partial class MySqlDatabaseContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.2-rtm-30932") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConnectionString") + .IsRequired(); + + b.Property("Enabled"); + + b.Property("InstanceId"); + + b.Property("Name") + .IsRequired(); + + b.Property("Provider"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("ChatBots"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChatSettingsId"); + + b.Property("DiscordChannelId"); + + b.Property("IrcChannel"); + + b.Property("IsAdminChannel") + .IsRequired(); + + b.Property("IsUpdatesChannel") + .IsRequired(); + + b.Property("IsWatchdogChannel") + .IsRequired(); + + b.Property("Tag"); + + b.HasKey("Id"); + + b.HasIndex("ChatSettingsId", "DiscordChannelId") + .IsUnique(); + + b.HasIndex("ChatSettingsId", "IrcChannel") + .IsUnique(); + + b.ToTable("ChatChannels"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ByondVersion") + .IsRequired(); + + b.Property("DirectoryName"); + + b.Property("DmeName"); + + b.Property("JobId"); + + b.Property("Output"); + + b.Property("RevisionInformationId"); + + b.HasKey("Id"); + + b.HasIndex("DirectoryName"); + + b.HasIndex("JobId"); + + b.HasIndex("RevisionInformationId"); + + b.ToTable("CompileJobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessToken"); + + b.Property("AllowWebClient") + .IsRequired(); + + b.Property("AutoStart") + .IsRequired(); + + b.Property("InstanceId"); + + b.Property("PrimaryPort") + .IsRequired(); + + b.Property("ProcessId"); + + b.Property("SecondaryPort") + .IsRequired(); + + b.Property("SecurityLevel"); + + b.Property("SoftRestart") + .IsRequired(); + + b.Property("SoftShutdown") + .IsRequired(); + + b.Property("StartupTimeout") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamDaemonSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ApiValidationPort") + .IsRequired(); + + b.Property("InstanceId"); + + b.Property("ProjectName"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamMakerSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoUpdateInterval") + .IsRequired(); + + b.Property("ConfigurationType"); + + b.Property("Name") + .IsRequired(); + + b.Property("Online") + .IsRequired(); + + b.Property("Path") + .IsRequired(); + + b.Property("WatchdogReattachInformationId"); + + b.HasKey("Id"); + + b.HasIndex("Path") + .IsUnique(); + + b.HasIndex("WatchdogReattachInformationId"); + + b.ToTable("Instances"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ByondRights"); + + b.Property("ChatBotRights"); + + b.Property("ConfigurationRights"); + + b.Property("DreamDaemonRights"); + + b.Property("DreamMakerRights"); + + b.Property("InstanceId"); + + b.Property("InstanceUserRights"); + + b.Property("RepositoryRights"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("UserId", "InstanceId") + .IsUnique(); + + b.ToTable("InstanceUsers"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CancelRight"); + + b.Property("CancelRightsType"); + + b.Property("Cancelled") + .IsRequired(); + + b.Property("CancelledById"); + + b.Property("Description") + .IsRequired(); + + b.Property("ExceptionDetails"); + + b.Property("InstanceId"); + + b.Property("StartedAt") + .IsRequired(); + + b.Property("StartedById"); + + b.Property("StoppedAt"); + + b.HasKey("Id"); + + b.HasIndex("CancelledById"); + + b.HasIndex("InstanceId"); + + b.HasIndex("StartedById"); + + b.ToTable("Jobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessIdentifier") + .IsRequired(); + + b.Property("ChatChannelsJson") + .IsRequired(); + + b.Property("ChatCommandsJson") + .IsRequired(); + + b.Property("CompileJobId"); + + b.Property("IsPrimary"); + + b.Property("Port"); + + b.Property("ProcessId"); + + b.Property("RebootState"); + + b.Property("ServerCommandsJson") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("CompileJobId"); + + b.ToTable("ReattachInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessToken"); + + b.Property("AccessUser"); + + b.Property("AutoUpdatesKeepTestMerges") + .IsRequired(); + + b.Property("AutoUpdatesSynchronize") + .IsRequired(); + + b.Property("CommitterEmail") + .IsRequired(); + + b.Property("CommitterName") + .IsRequired(); + + b.Property("InstanceId"); + + b.Property("PushTestMergeCommits") + .IsRequired(); + + b.Property("ShowTestMergeCommitters") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("RepositorySettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("RevisionInformationId"); + + b.Property("TestMergeId"); + + b.HasKey("Id"); + + b.HasIndex("RevisionInformationId"); + + b.HasIndex("TestMergeId"); + + b.ToTable("RevInfoTestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CommitSha") + .IsRequired() + .HasMaxLength(40); + + b.Property("InstanceId"); + + b.Property("OriginCommitSha") + .IsRequired() + .HasMaxLength(40); + + b.HasKey("Id"); + + b.HasIndex("CommitSha") + .IsUnique(); + + b.HasIndex("InstanceId"); + + b.ToTable("RevisionInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Author") + .IsRequired(); + + b.Property("BodyAtMerge") + .IsRequired(); + + b.Property("Comment"); + + b.Property("MergedAt"); + + b.Property("MergedById"); + + b.Property("Number") + .IsRequired(); + + b.Property("PrimaryRevisionInformationId"); + + b.Property("PullRequestRevision") + .IsRequired(); + + b.Property("TitleAtMerge") + .IsRequired(); + + b.Property("Url") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("MergedById"); + + b.HasIndex("PrimaryRevisionInformationId") + .IsUnique(); + + b.ToTable("TestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AdministrationRights"); + + b.Property("CanonicalName") + .IsRequired(); + + b.Property("CreatedAt") + .IsRequired(); + + b.Property("CreatedById"); + + b.Property("Enabled") + .IsRequired(); + + b.Property("InstanceManagerRights"); + + b.Property("LastPasswordUpdate"); + + b.Property("Name") + .IsRequired(); + + b.Property("PasswordHash"); + + b.Property("SystemIdentifier"); + + b.HasKey("Id"); + + b.HasIndex("CanonicalName") + .IsUnique(); + + b.HasIndex("CreatedById"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.WatchdogReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AlphaId"); + + b.Property("AlphaIsActive"); + + b.Property("BravoId"); + + b.HasKey("Id"); + + b.HasIndex("AlphaId"); + + b.HasIndex("BravoId"); + + b.ToTable("WatchdogReattachInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("ChatSettings") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings") + .WithMany("Channels") + .HasForeignKey("ChatSettingsId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.HasOne("Tgstation.Server.Host.Models.Job", "Job") + .WithMany() + .HasForeignKey("JobId"); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("CompileJobs") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamDaemonSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamMakerSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.HasOne("Tgstation.Server.Host.Models.WatchdogReattachInformation", "WatchdogReattachInformation") + .WithMany() + .HasForeignKey("WatchdogReattachInformationId"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("InstanceUsers") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User") + .WithMany("InstanceUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CancelledBy") + .WithMany() + .HasForeignKey("CancelledById"); + + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("Jobs") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy") + .WithMany() + .HasForeignKey("StartedById") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") + .WithMany() + .HasForeignKey("CompileJobId"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("RepositorySettings") + .HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("ActiveTestMerges") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge") + .WithMany("RevisonInformations") + .HasForeignKey("TestMergeId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("RevisionInformations") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy") + .WithMany("TestMerges") + .HasForeignKey("MergedById") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation") + .WithOne("PrimaryTestMerge") + .HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId") + .OnDelete(DeleteBehavior.SetNull); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy") + .WithMany("CreatedUsers") + .HasForeignKey("CreatedById"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.WatchdogReattachInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.ReattachInformation", "Alpha") + .WithMany() + .HasForeignKey("AlphaId"); + + b.HasOne("Tgstation.Server.Host.Models.ReattachInformation", "Bravo") + .WithMany() + .HasForeignKey("BravoId"); + }); +#pragma warning restore 612, 618 + } + } +} From 976d1b478ba8304bd66f74779522eac60cd15018 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 6 Sep 2018 10:41:55 -0400 Subject: [PATCH 40/47] Adds missing things to Docker json --- src/Tgstation.Server.Host/appsettings.Docker.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/appsettings.Docker.json b/src/Tgstation.Server.Host/appsettings.Docker.json index 233889ca3f..d90c93f5a5 100644 --- a/src/Tgstation.Server.Host/appsettings.Docker.json +++ b/src/Tgstation.Server.Host/appsettings.Docker.json @@ -3,7 +3,8 @@ "LogFileDirectory": "/tgs_logs" }, "Database": { - "DatabaseType": "SqlServer", - "ConnectionString": "" + "DatabaseType": "SqlServer or MySQL or MariaDB", + "ConnectionString": "", + "MySqlServerVersion": "" } } From a3608924581cd2e376ac549fdd3ee9d638c46d23 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 6 Sep 2018 10:51:29 -0400 Subject: [PATCH 41/47] Fix README grammar --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fa8078e467..603fa83bbc 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Generally, updates force a live tracking of the configured git repo, resetting l #### Docker -tgstation-server supports running in a docker container on linux systems and is the preferred deployment method to avoid [native dependency hell with libgit2](https://github.com/libgit2/libgit2sharp/issues/1533). The official image repository is located at https://hub.docker.com/r/tgstation/server it can be build locally, however, by running `docker build . -f build/Dockerfile` in the repository root. +tgstation-server supports running in a docker container on linux systems and is the preferred deployment method to avoid [native dependency hell with libgit2](https://github.com/libgit2/libgit2sharp/issues/1533). The official image repository is located at https://hub.docker.com/r/tgstation/server it can be built locally, however, by running `docker build . -f build/Dockerfile` in the repository root. To create a container run `docker create --restart=always -p :80 -v /path/to/your/appsettings.Production.json:/config_data -v path/to/your/log/folder:/tgs_logs tgstation/server` with any additional options you desire (i.e. You'll have to expose more ports in order to actually host servers, add a volume to create instances on, and create a volume for the SQLite database if that is what you're using). From c875ae7e33d3e4c9f21a3e77f886307531b2818c Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 6 Sep 2018 10:52:01 -0400 Subject: [PATCH 42/47] Removes EnsureCreatedAsync --- .../Configuration/DatabaseConfiguration.cs | 5 ---- .../Models/DatabaseContext.cs | 28 +++++++------------ .../appsettings.Development.json | 3 -- src/Tgstation.Server.Host/appsettings.json | 3 +- tests/Tgstation.Server.Tests/TestingServer.cs | 1 - 5 files changed, 11 insertions(+), 29 deletions(-) diff --git a/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs b/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs index 1bbe9365d7..d5c4a34359 100644 --- a/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs +++ b/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs @@ -25,11 +25,6 @@ /// public string ConnectionString { get; set; } - /// - /// If the database should use direct table creation instead of automatic migrations. Should not be used in production! - /// - public bool NoMigrations { get; set; } - /// /// If the database should be deleted on application startup. Should not be used in production! /// diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs index a3d9817592..c79f697839 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -133,26 +133,22 @@ namespace Tgstation.Server.Host.Models /// public async Task Initialize(CancellationToken cancellationToken) { - Logger.LogInformation("Migrating database..."); - if (DatabaseConfiguration.DropDatabase) { Logger.LogCritical("DropDatabase configuration option set! Dropping any existing database..."); await Database.EnsureDeletedAsync(cancellationToken).ConfigureAwait(false); } - var wasEmpty = false; - if (DatabaseConfiguration.NoMigrations) + var migrations = await Database.GetAppliedMigrationsAsync(cancellationToken).ConfigureAwait(false); + var wasEmpty = !migrations.Any(); + + if (wasEmpty || (await Database.GetPendingMigrationsAsync(cancellationToken).ConfigureAwait(false)).Any()) { - Logger.LogWarning("Using all or nothing migration strategy!"); - await Database.EnsureCreatedAsync(cancellationToken).ConfigureAwait(false); - } - else - { - var migrations = await Database.GetAppliedMigrationsAsync(cancellationToken).ConfigureAwait(false); - wasEmpty = !migrations.Any(); + Logger.LogInformation("Migrating database..."); await Database.MigrateAsync(cancellationToken).ConfigureAwait(false); } + else + Logger.LogDebug("No migrations to apply."); wasEmpty |= (await Users.CountAsync(cancellationToken).ConfigureAwait(false)) == 0; @@ -161,14 +157,10 @@ namespace Tgstation.Server.Host.Models Logger.LogInformation("Seeding database..."); await databaseSeeder.SeedDatabase(this, cancellationToken).ConfigureAwait(false); } - else + else if (DatabaseConfiguration.ResetAdminPassword) { - Logger.LogDebug("No migrations applied!"); - if (DatabaseConfiguration.ResetAdminPassword) - { - Logger.LogWarning("Enabling and resetting admin password due to configuration!"); - await databaseSeeder.ResetAdminPassword(this, cancellationToken).ConfigureAwait(false); - } + Logger.LogWarning("Enabling and resetting admin password due to configuration!"); + await databaseSeeder.ResetAdminPassword(this, cancellationToken).ConfigureAwait(false); } } diff --git a/src/Tgstation.Server.Host/appsettings.Development.json b/src/Tgstation.Server.Host/appsettings.Development.json index d40e70e835..bf605320bf 100644 --- a/src/Tgstation.Server.Host/appsettings.Development.json +++ b/src/Tgstation.Server.Host/appsettings.Development.json @@ -1,8 +1,5 @@ { "General": { "DisableFileLogging": true - }, - "Database": { - "NoMigrations": true } } diff --git a/src/Tgstation.Server.Host/appsettings.json b/src/Tgstation.Server.Host/appsettings.json index 7ea6760060..d16d0bb4f5 100644 --- a/src/Tgstation.Server.Host/appsettings.json +++ b/src/Tgstation.Server.Host/appsettings.json @@ -29,8 +29,7 @@ "UpdatePackageAssetName": "ServerUpdatePackage.zip" }, "Database": { - "NoMigrations": false, - "DropDatabase": false, + "DropDatabase": false, "DatabaseType": "SqlServer", "ResetAdminPassword": false, "MySqlServerVersion": null, diff --git a/tests/Tgstation.Server.Tests/TestingServer.cs b/tests/Tgstation.Server.Tests/TestingServer.cs index 1b1b3905f5..3e30fbfa06 100644 --- a/tests/Tgstation.Server.Tests/TestingServer.cs +++ b/tests/Tgstation.Server.Tests/TestingServer.cs @@ -42,7 +42,6 @@ namespace Tgstation.Server.Tests String.Format(CultureInfo.InvariantCulture, "Database:DatabaseType={0}", databaseType), String.Format(CultureInfo.InvariantCulture, "Database:ConnectionString={0}", connectionString), "Database:DropDatabase=true" - ,"Database:NoMigrations=true" //TODO: remove this when migrations are added }, null); } From 3efa36600800e143c0104274d096cad7bc1ed42f Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 6 Sep 2018 10:52:51 -0400 Subject: [PATCH 43/47] Remove this TODO --- v4_prototype_TODO.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/v4_prototype_TODO.txt b/v4_prototype_TODO.txt index fe9e1f9109..d5b9817fe9 100644 --- a/v4_prototype_TODO.txt +++ b/v4_prototype_TODO.txt @@ -9,5 +9,3 @@ Test reattachment Test auto update Test auto start - -Migrations From c5d2a56812d5c808b92f516f684dab813bb2350a Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 6 Sep 2018 10:58:01 -0400 Subject: [PATCH 44/47] Still dealing with this issue in CURRENT YEAR. Other CONTRIBUTING updates --- .github/CONTRIBUTING.md | 33 +++++++++---------- .../Tgstation.Server.Host.csproj | 4 --- 2 files changed, 15 insertions(+), 22 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 6361999b7a..221309a906 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -6,6 +6,18 @@ Hello and welcome to /tg/station servers's contributing page. You are here becau First things first, we want to make it clear how you can contribute (if you've never contributed before), as well as the kinds of powers the team has over your additions, to avoid any unpleasant surprises if your pull request is closed for a reason you didn't foresee. +## Meet the Team + +**Headcoder** + +The Headcoder is responsible for controlling, adding, and removing maintainers from the project. In addition to filling the role of a normal maintainer, they have sole authority on who becomes a maintainer, as well as who remains a maintainer and who does not. + +**Maintainers** + +Maintainers are quality control. If a proposed pull request doesn't meet the following specifications, they can request you to change it, or simply just close the pull request. Maintainers are required to give a reason for closing the pull request. + +Maintainers can revert your changes if they feel they are not worth maintaining or if they did not live up to the quality specifications. + ## Getting Started /tg/station doesn't have a list of goals and features to add; we instead allow freedom for contributors to suggest and create their ideas for the server. That doesn't mean we aren't determined to squash bugs, which unfortunately pop up a lot due to the deep complexity of the game. Here are some useful starting guides, if you want to contribute or if you want to know what challenges you can tackle with zero knowledge about the game's code structure. @@ -22,23 +34,11 @@ You can of course, as always, ask for help at [#coderbus](irc://irc.rizon.net/co ### Development Environment -You need the Dotnet SDK to compile the main server and command line programs. In order to build the service version and control panel you also need a .NET 4.7.1 build chain +You need the Dotnet 2.1 SDK to compile the main server and command line programs. In order to build the service version you also need a .NET 4.7.1 build chain -The recommended IDE is visual studio 2017 which has installation options for both of these. +The recommended IDE is Visual Studio 2017 which has installation options for both of these. -In order to run the integration tests you must have the environment variables `TGS4_TEST_DATABASE_TYPE` set to `MySql` or `SqlServer` and `TGS4_TEST_CONNECTION_STRING` set appropriately - -## Meet the Team - -**Headcoder** - -The Headcoder is responsible for controlling, adding, and removing maintainers from the project. In addition to filling the role of a normal maintainer, they have sole authority on who becomes a maintainer, as well as who remains a maintainer and who does not. - -**Maintainers** - -Maintainers are quality control. If a proposed pull request doesn't meet the following specifications, they can request you to change it, or simply just close the pull request. Maintainers are required to give a reason for closing the pull request. - -Maintainers can revert your changes if they feel they are not worth maintaining or if they did not live up to the quality specifications. +In order to run the integration tests you must have the environment variables `TGS4_TEST_DATABASE_TYPE` set to `MySql`, `MariaDB` or `SqlServer` and `TGS4_TEST_CONNECTION_STRING` set appropriately ## Specifications @@ -152,6 +152,3 @@ Do not add any of the following in a Pull Request or risk getting the PR closed: * National Socialist Party of Germany content, National Socialist Party of Germany related content, or National Socialist Party of Germany references Just becuase something isn't on this list doesn't mean that it's acceptable. Use common sense above all else. - -## A word on Git -Yes, we know that the files have a tonne of mixed Windows and Linux line endings. Attempts to fix this have been met with less than stellar success, and as such we have decided to give up caring until there comes a time when it matters. diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index ef03589724..a14d1d076f 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -48,10 +48,6 @@ - - - - From 64fb5c3e41b880100307a4db001695aa1cd80ad3 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 6 Sep 2018 11:02:23 -0400 Subject: [PATCH 45/47] Fix EFCore being PrivateAssets --- src/Tgstation.Server.Host/Tgstation.Server.Host.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index a14d1d076f..fe16fe434e 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -35,8 +35,8 @@ all compile; build; native; contentfiles; analyzers - - + + From 8c7c14694353f4b79db43e1174ba36b3d5d25b33 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 6 Sep 2018 11:07:25 -0400 Subject: [PATCH 46/47] Fix unset MySqlServerVersion error --- src/Tgstation.Server.Host/Models/MySqlDatabaseContext.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Models/MySqlDatabaseContext.cs b/src/Tgstation.Server.Host/Models/MySqlDatabaseContext.cs index d95fce3224..99657144aa 100644 --- a/src/Tgstation.Server.Host/Models/MySqlDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/MySqlDatabaseContext.cs @@ -26,7 +26,7 @@ namespace Tgstation.Server.Host.Models protected override void OnConfiguring(DbContextOptionsBuilder options) { base.OnConfiguring(options); - if (DatabaseConfiguration.MySqlServerVersion != null) + if (!String.IsNullOrEmpty(DatabaseConfiguration.MySqlServerVersion)) options.UseMySql(DatabaseConfiguration.ConnectionString, mySqlOptions => mySqlOptions.ServerVersion(Version.Parse(DatabaseConfiguration.MySqlServerVersion), DatabaseConfiguration.DatabaseType == DatabaseType.MariaDB ? ServerType.MariaDb : ServerType.MySql)); else options.UseMySql(DatabaseConfiguration.ConnectionString); From 2ef73e7fa2739df059ba9c3439c933736e713a39 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 6 Sep 2018 11:15:12 -0400 Subject: [PATCH 47/47] Fix failing appveyor tests not failing the build --- appveyor.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 0643b1b74c..9bd18e311d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -37,16 +37,16 @@ build: verbosity: minimal publish_nuget: true test_script: - - OpenCover.Console.exe -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx /p:DebugType=full tests/Tgstation.Server.Api.Tests/Tgstation.Server.Api.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Api.Tests*]*" -output:".\api_coverage.xml" -oldstyle + - OpenCover.Console.exe -returntargetcode -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx /p:DebugType=full tests/Tgstation.Server.Api.Tests/Tgstation.Server.Api.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Api.Tests*]*" -output:".\api_coverage.xml" -oldstyle - ps: $wc = New-Object 'System.Net.WebClient' - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\tests\Tgstation.Server.Api.Tests\TestResults\results.trx)) - - OpenCover.Console.exe -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx /p:DebugType=full tests/Tgstation.Server.Client.Tests/Tgstation.Server.Client.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Client.Tests*]*" -output:".\client_coverage.xml" -oldstyle + - OpenCover.Console.exe -returntargetcode -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx /p:DebugType=full tests/Tgstation.Server.Client.Tests/Tgstation.Server.Client.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Client.Tests*]*" -output:".\client_coverage.xml" -oldstyle - ps: $wc = New-Object 'System.Net.WebClient' - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\tests\Tgstation.Server.Client.Tests\TestResults\results.trx)) - - OpenCover.Console.exe -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx /p:DebugType=full tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Host.Tests*]*" -output:".\host_coverage.xml" -oldstyle + - OpenCover.Console.exe -returntargetcode -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx /p:DebugType=full tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Host.Tests*]*" -output:".\host_coverage.xml" -oldstyle - ps: $wc = New-Object 'System.Net.WebClient' - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\tests\Tgstation.Server.Host.Tests\TestResults\results.trx)) - - OpenCover.Console.exe -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx /p:DebugType=full tests/Tgstation.Server.Host.Console.Tests/Tgstation.Server.Host.Console.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Host.Console.Tests*]*" -output:".\console_coverage.xml" -oldstyle + - OpenCover.Console.exe -returntargetcode -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx /p:DebugType=full tests/Tgstation.Server.Host.Console.Tests/Tgstation.Server.Host.Console.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Host.Console.Tests*]*" -output:".\console_coverage.xml" -oldstyle - ps: $wc = New-Object 'System.Net.WebClient' - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\tests\Tgstation.Server.Host.Console.Tests\TestResults\results.trx)) - set path=%ProgramFiles(x86)%\Microsoft Visual Studio\2017\TestAgent\Common7\IDE\CommonExtensions\Microsoft\TestWindow;%path% @@ -54,10 +54,10 @@ test_script: - vstest.console /logger:trx;LogFileName=results.trx "tests\Tgstation.Server.Host.Service.Tests\bin\%CONFIGURATION%\Tgstation.Server.Host.Service.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)) - - OpenCover.Console.exe -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx /p:DebugType=full tests/Tgstation.Server.Host.Watchdog.Tests/Tgstation.Server.Host.Watchdog.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Host.Watchdog.Tests*]*" -output:".\watchdog_coverage.xml" -oldstyle + - OpenCover.Console.exe -returntargetcode -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx /p:DebugType=full tests/Tgstation.Server.Host.Watchdog.Tests/Tgstation.Server.Host.Watchdog.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Host.Watchdog.Tests*]*" -output:".\watchdog_coverage.xml" -oldstyle - ps: $wc = New-Object 'System.Net.WebClient' - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\tests\Tgstation.Server.Host.Watchdog.Tests\TestResults\results.trx)) - - OpenCover.Console.exe -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx /p:DebugType=full tests/Tgstation.Server.Tests/Tgstation.Server.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Tests*]*" -output:".\server_coverage.xml" -oldstyle + - OpenCover.Console.exe -returntargetcode -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx /p:DebugType=full tests/Tgstation.Server.Tests/Tgstation.Server.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Tests*]*" -output:".\server_coverage.xml" -oldstyle - ps: $wc = New-Object 'System.Net.WebClient' - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\tests\Tgstation.Server.Tests\TestResults\results.trx)) after_test: