Instance auto start and stop crons

Closes #1969
This commit is contained in:
Jordan Dominion
2024-11-03 12:29:22 -05:00
parent fd2245075f
commit 15893ca40b
24 changed files with 5136 additions and 117 deletions
+4 -4
View File
@@ -5,15 +5,15 @@
<PropertyGroup>
<TgsCoreVersion>6.12.0</TgsCoreVersion>
<TgsConfigVersion>5.4.0</TgsConfigVersion>
<TgsRestVersion>10.11.0</TgsRestVersion>
<TgsRestVersion>10.12.0</TgsRestVersion>
<TgsGraphQLVersion>0.5.0</TgsGraphQLVersion>
<TgsCommonLibraryVersion>7.0.0</TgsCommonLibraryVersion>
<TgsApiLibraryVersion>16.2.0</TgsApiLibraryVersion>
<TgsClientVersion>19.2.0</TgsClientVersion>
<TgsApiLibraryVersion>16.3.0</TgsApiLibraryVersion>
<TgsClientVersion>19.3.0</TgsClientVersion>
<TgsDmapiVersion>7.3.0</TgsDmapiVersion>
<TgsInteropVersion>5.10.0</TgsInteropVersion>
<TgsHostWatchdogVersion>1.5.0</TgsHostWatchdogVersion>
<TgsSwarmProtocolVersion>7.0.0</TgsSwarmProtocolVersion>
<TgsSwarmProtocolVersion>8.0.0</TgsSwarmProtocolVersion>
<TgsContainerScriptVersion>1.2.1</TgsContainerScriptVersion>
<TgsMigratorVersion>2.0.0</TgsMigratorVersion>
<TgsNugetNetFramework>netstandard2.0</TgsNugetNetFramework>
+17 -1
View File
@@ -40,7 +40,7 @@ namespace Tgstation.Server.Api.Models
/// </summary>
/// <remarks>Updates will not be triggered if the previous update is still running. Incompatible with <see cref="AutoUpdateInterval"/>.</remarks>
[Required]
[StringLength(Limits.MaximumStringLength)]
[StringLength(Limits.CronStringLength)]
public string? AutoUpdateCron { get; set; }
/// <summary>
@@ -48,5 +48,21 @@ namespace Tgstation.Server.Api.Models
/// </summary>
[Required]
public ushort? ChatBotLimit { get; set; }
/// <summary>
/// A cron expression indicating when the game server should start. Must be a valid 6 part cron schedule (SECONDS MINUTES HOURS DAY/MONTH MONTH DAY/WEEK). Empty <see cref="string"/> disables.
/// </summary>
/// <remarks>This will have no effect if the game server is already running when it fires.</remarks>
[Required]
[StringLength(Limits.CronStringLength)]
public string? AutoStartCron { get; set; }
/// <summary>
/// A cron expression indicating when the game server should stop. Must be a valid 6 part cron schedule (SECONDS MINUTES HOURS DAY/MONTH MONTH DAY/WEEK). Empty <see cref="string"/> disables.
/// </summary>
/// <remarks>This will have no effect if the game server is not running when it fires.</remarks>
[Required]
[StringLength(Limits.CronStringLength)]
public string? AutoStopCron { get; set; }
}
}
@@ -12,6 +12,11 @@ namespace Tgstation.Server.Api.Models
/// </summary>
public const int MaximumStringLength = 10000;
/// <summary>
/// Length limit for cron strings in fields.
/// </summary>
public const int CronStringLength = 1000;
/// <summary>
/// Length limit for <see cref="NamedEntity.Name"/>s.
/// </summary>
@@ -67,5 +67,15 @@ namespace Tgstation.Server.Api.Rights
/// User can give themselves or their group full <see cref="InstancePermissionSetRights"/> on ALL instances.
/// </summary>
GrantPermissions = 1 << 10,
/// <summary>
/// User can change <see cref="Models.Instance.AutoStartCron"/>.
/// </summary>
SetAutoStart = 1 << 11,
/// <summary>
/// User can change <see cref="Models.Instance.AutoStopCron"/>.
/// </summary>
SetAutoStop = 1 << 12,
}
}
@@ -51,5 +51,19 @@ namespace Tgstation.Server.Host.Components
/// <param name="newCron">The new auto-update cron schedule.</param>
/// <returns>A <see cref="ValueTask"/> representing the running operation.</returns>
ValueTask ScheduleAutoUpdate(uint newInterval, string? newCron);
/// <summary>
/// Change the server auto-start timing for the <see cref="IInstanceCore"/>.
/// </summary>
/// <param name="newCron">The new auto-start cron schedule.</param>
/// <returns>A <see cref="ValueTask"/> representing the running operation.</returns>
ValueTask ScheduleServerStart(string? newCron);
/// <summary>
/// Change the server auto-stop timing for the <see cref="IInstanceCore"/>.
/// </summary>
/// <param name="newCron">The new auto-stop cron schedule.</param>
/// <returns>A <see cref="ValueTask"/> representing the running operation.</returns>
ValueTask ScheduleServerStop(string? newCron);
}
}
+191 -75
View File
@@ -89,19 +89,39 @@ namespace Tgstation.Server.Host.Components
readonly Api.Models.Instance metadata;
/// <summary>
/// <see langword="lock"/> <see cref="object"/> for <see cref="timerCts"/> and <see cref="timerTask"/>.
/// <see langword="lock"/> <see cref="object"/> for <see cref="autoUpdateCts"/> and <see cref="autoUpdateTask"/>.
/// </summary>
readonly object timerLock;
/// <summary>
/// The auto update <see cref="Task"/>.
/// The auto-update <see cref="Task"/>.
/// </summary>
Task? timerTask;
Task? autoUpdateTask;
/// <summary>
/// <see cref="CancellationTokenSource"/> for <see cref="timerTask"/>.
/// <see cref="CancellationTokenSource"/> for <see cref="autoUpdateTask"/>.
/// </summary>
CancellationTokenSource? timerCts;
CancellationTokenSource? autoUpdateCts;
/// <summary>
/// The auto-start <see cref="Task"/>.
/// </summary>
Task? autoStartTask;
/// <summary>
/// <see cref="CancellationTokenSource"/> for <see cref="autoStartTask"/>.
/// </summary>
CancellationTokenSource? autoStartCts;
/// <summary>
/// The auto-stop <see cref="Task"/>.
/// </summary>
Task? autoStopTask;
/// <summary>
/// <see cref="CancellationTokenSource"/> for <see cref="autoStopTask"/>.
/// </summary>
CancellationTokenSource? autoStopCts;
/// <summary>
/// Initializes a new instance of the <see cref="Instance"/> class.
@@ -159,7 +179,9 @@ namespace Tgstation.Server.Host.Components
{
var chatDispose = Chat.DisposeAsync();
var watchdogDispose = Watchdog.DisposeAsync();
timerCts?.Dispose();
autoUpdateCts?.Dispose();
autoStartCts?.Dispose();
autoStopCts?.Dispose();
Configuration.Dispose();
dmbFactory.Dispose();
RepositoryManager.Dispose();
@@ -187,6 +209,8 @@ namespace Tgstation.Server.Host.Components
{
await Task.WhenAll(
ScheduleAutoUpdate(metadata.Require(x => x.AutoUpdateInterval), metadata.AutoUpdateCron).AsTask(),
ScheduleServerStart(null).AsTask(),
ScheduleServerStop(null).AsTask(),
Configuration.StartAsync(cancellationToken),
EngineManager.StartAsync(cancellationToken),
Chat.StartAsync(cancellationToken),
@@ -223,14 +247,14 @@ namespace Tgstation.Server.Host.Components
Task toWait;
lock (timerLock)
if (timerTask != null)
if (autoUpdateTask != null)
{
logger.LogTrace("Cancelling auto-update task");
timerCts!.Cancel();
timerCts.Dispose();
toWait = timerTask;
timerTask = null;
timerCts = null;
autoUpdateCts!.Cancel();
autoUpdateCts.Dispose();
toWait = autoUpdateTask;
autoUpdateTask = null;
autoUpdateCts = null;
}
else
toWait = Task.CompletedTask;
@@ -245,14 +269,95 @@ namespace Tgstation.Server.Host.Components
lock (timerLock)
{
// race condition, just quit
if (timerTask != null)
if (autoUpdateTask != null)
{
logger.LogWarning("Aborting auto-update scheduling change due to race condition!");
return;
}
timerCts = new CancellationTokenSource();
timerTask = TimerLoop(newInterval, newCron, timerCts.Token);
autoUpdateCts = new CancellationTokenSource();
autoUpdateTask = TimerLoop(AutoUpdateAction, "auto-update", newInterval, newCron, autoUpdateCts.Token);
}
}
/// <inheritdoc />
public async ValueTask ScheduleServerStart(string? newCron)
{
Task toWait;
lock (timerLock)
if (autoStartTask != null)
{
logger.LogTrace("Cancelling auto-start task");
autoStartCts!.Cancel();
autoStartCts.Dispose();
toWait = autoStartTask;
autoStartTask = null;
autoStartCts = null;
}
else
toWait = Task.CompletedTask;
await toWait;
if (String.IsNullOrWhiteSpace(newCron))
{
logger.LogTrace("Auto-start disabled. Not starting task.");
return;
}
lock (timerLock)
{
// race condition, just quit
if (autoStartTask != null)
{
logger.LogWarning("Aborting auto-start scheduling change due to race condition!");
return;
}
autoStartCts = new CancellationTokenSource();
autoStartTask = TimerLoop(Watchdog.Launch, "auto-start", 0, newCron, autoStartCts.Token);
}
}
/// <inheritdoc />
public async ValueTask ScheduleServerStop(string? newCron)
{
Task toWait;
lock (timerLock)
if (autoStopTask != null)
{
logger.LogTrace("Cancelling auto-stop task");
autoStopCts!.Cancel();
autoStopCts.Dispose();
toWait = autoStopTask;
autoStopTask = null;
autoStopCts = null;
}
else
toWait = Task.CompletedTask;
await toWait;
if (String.IsNullOrWhiteSpace(newCron))
{
logger.LogTrace("Auto-stop disabled. Not stoping task.");
return;
}
lock (timerLock)
{
// race condition, just quit
if (autoStopTask != null)
{
logger.LogWarning("Aborting auto-stop scheduling change due to race condition!");
return;
}
autoStopCts = new CancellationTokenSource();
autoStopTask = TimerLoop(
async cancellationToken => await Watchdog.Terminate(true, cancellationToken),
"auto-stop",
0,
newCron,
autoStopCts.Token);
}
}
@@ -485,14 +590,15 @@ namespace Tgstation.Server.Host.Components
#pragma warning restore CA1502 // Cyclomatic complexity
/// <summary>
/// Pull the repository and compile for every set of given <paramref name="minutes"/>.
/// Runs a <paramref name="timerAction"/> every set of given <paramref name="minutes"/> or on a given <paramref name="cron"/> schedule.
/// </summary>
/// <param name="timerAction">The action to take when the timer elapses.</param>
/// <param name="description">A description of the <paramref name="timerAction"/>.</param>
/// <param name="minutes">How many minutes the operation should repeat. Does not include running time.</param>
/// <param name="cron">Alternative cron schedule.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
#pragma warning disable CA1502 // TODO: Decomplexify
async Task TimerLoop(uint minutes, string? cron, CancellationToken cancellationToken)
async Task TimerLoop(Func<CancellationToken, ValueTask> timerAction, string description, uint minutes, string? cron, CancellationToken cancellationToken)
{
logger.LogDebug("Entering auto-update loop");
while (true)
@@ -519,76 +625,86 @@ namespace Tgstation.Server.Host.Components
delay = TimeSpan.FromMinutes(minutes);
}
logger.LogInformation("Next auto-update will occur at {time}", DateTimeOffset.UtcNow + delay);
logger.LogInformation("Next {desc} will occur at {time}", description, DateTimeOffset.UtcNow + delay);
await asyncDelayer.Delay(delay, cancellationToken);
logger.LogInformation("Beginning auto update...");
await eventConsumer.HandleEvent(EventType.InstanceAutoUpdateStart, Enumerable.Empty<string>(), true, cancellationToken);
var repositoryUpdateJob = Job.Create(Api.Models.JobCode.RepositoryAutoUpdate, null, metadata, RepositoryRights.CancelPendingChanges);
await jobManager.RegisterOperation(
repositoryUpdateJob,
RepositoryAutoUpdateJob,
cancellationToken);
var repoUpdateJobResult = await jobManager.WaitForJobCompletion(repositoryUpdateJob, null, cancellationToken, cancellationToken);
if (repoUpdateJobResult == false)
{
logger.LogWarning("Aborting auto-update due to repository update error!");
continue;
}
Job compileProcessJob;
using (var repo = await RepositoryManager.LoadRepository(cancellationToken))
{
if (repo == null)
throw new JobException(Api.Models.ErrorCode.RepoMissing);
var deploySha = repo.Head;
if (deploySha == null)
{
logger.LogTrace("Aborting auto update, repository error!");
continue;
}
if (deploySha == (await LatestCompileJob())?.RevisionInformation.CommitSha)
{
logger.LogTrace("Aborting auto update, same revision as latest CompileJob");
continue;
}
// finally set up the job
compileProcessJob = Job.Create(Api.Models.JobCode.AutomaticDeployment, null, metadata, DreamMakerRights.CancelCompile);
await jobManager.RegisterOperation(
compileProcessJob,
(core, databaseContextFactory, job, progressReporter, jobCancellationToken) =>
{
if (core != this)
throw new InvalidOperationException(DifferentCoreExceptionMessage);
return DreamMaker.DeploymentProcess(
job,
databaseContextFactory,
progressReporter,
jobCancellationToken);
},
cancellationToken);
}
await jobManager.WaitForJobCompletion(compileProcessJob, null, default, cancellationToken);
await timerAction(cancellationToken);
}
catch (OperationCanceledException)
{
logger.LogDebug("Cancelled auto update loop!");
logger.LogDebug("Cancelled {desc} loop!", description);
break;
}
catch (Exception e)
{
logger.LogError(e, "Error in auto update loop!");
logger.LogError(e, "Error in {desc} loop!", description);
continue;
}
logger.LogTrace("Leaving auto update loop...");
logger.LogTrace("Leaving {desc} loop...", description);
}
/// <summary>
/// Pulls the repository and compiles.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="ValueTask"/> representing the running operation.</returns>
async ValueTask AutoUpdateAction(CancellationToken cancellationToken)
{
logger.LogInformation("Beginning auto update...");
await eventConsumer.HandleEvent(EventType.InstanceAutoUpdateStart, Enumerable.Empty<string>(), true, cancellationToken);
var repositoryUpdateJob = Job.Create(Api.Models.JobCode.RepositoryAutoUpdate, null, metadata, RepositoryRights.CancelPendingChanges);
await jobManager.RegisterOperation(
repositoryUpdateJob,
RepositoryAutoUpdateJob,
cancellationToken);
var repoUpdateJobResult = await jobManager.WaitForJobCompletion(repositoryUpdateJob, null, cancellationToken, cancellationToken);
if (repoUpdateJobResult == false)
{
logger.LogWarning("Aborting auto-update due to repository update error!");
return;
}
Job compileProcessJob;
using (var repo = await RepositoryManager.LoadRepository(cancellationToken))
{
if (repo == null)
throw new JobException(Api.Models.ErrorCode.RepoMissing);
var deploySha = repo.Head;
if (deploySha == null)
{
logger.LogTrace("Aborting auto update, repository error!");
return;
}
if (deploySha == (await LatestCompileJob())?.RevisionInformation.CommitSha)
{
logger.LogTrace("Aborting auto update, same revision as latest CompileJob");
return;
}
// finally set up the job
compileProcessJob = Job.Create(Api.Models.JobCode.AutomaticDeployment, null, metadata, DreamMakerRights.CancelCompile);
await jobManager.RegisterOperation(
compileProcessJob,
(core, databaseContextFactory, job, progressReporter, jobCancellationToken) =>
{
if (core != this)
throw new InvalidOperationException(DifferentCoreExceptionMessage);
return DreamMaker.DeploymentProcess(
job,
databaseContextFactory,
progressReporter,
jobCancellationToken);
},
cancellationToken);
}
await jobManager.WaitForJobCompletion(compileProcessJob, null, default, cancellationToken);
}
#pragma warning restore CA1502
}
}
@@ -59,5 +59,11 @@ namespace Tgstation.Server.Host.Components
/// <inheritdoc />
public ValueTask<CompileJob?> LatestCompileJob() => Instance.LatestCompileJob();
/// <inheritdoc />
public ValueTask ScheduleServerStart(string? newCron) => Instance.ScheduleServerStart(newCron);
/// <inheritdoc />
public ValueTask ScheduleServerStop(string? newCron) => Instance.ScheduleServerStop(newCron);
}
}
@@ -124,7 +124,7 @@ namespace Tgstation.Server.Host.Controllers
/// Attempt to perform a server upgrade.
/// </summary>
/// <param name="model">The <see cref="ServerUpdateRequest"/>.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <param name="cancellationToken">The <see cref="Cancellati6onToken"/> for the operation.</param>
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in the <see cref="IActionResult"/> for the operation.</returns>
/// <response code="202">Update has been started successfully.</response>
/// <response code="410">The requested release version could not be found in the target GitHub repository.</response>
@@ -20,6 +20,7 @@ using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Request;
using Tgstation.Server.Api.Models.Response;
using Tgstation.Server.Api.Rights;
using Tgstation.Server.Common.Extensions;
using Tgstation.Server.Host.Components;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.Controllers.Results;
@@ -341,7 +342,15 @@ namespace Tgstation.Server.Host.Controllers
/// <response code="202">Instance updated successfully and relocation job created.</response>
/// <response code="410">The database entity for the requested instance could not be retrieved. The instance was likely detached.</response>
[HttpPost]
[TgsAuthorize(InstanceManagerRights.Relocate | InstanceManagerRights.Rename | InstanceManagerRights.SetAutoUpdate | InstanceManagerRights.SetConfiguration | InstanceManagerRights.SetOnline | InstanceManagerRights.SetChatBotLimit)]
[TgsAuthorize(
InstanceManagerRights.Relocate
| InstanceManagerRights.Rename
| InstanceManagerRights.SetAutoUpdate
| InstanceManagerRights.SetConfiguration
| InstanceManagerRights.SetOnline
| InstanceManagerRights.SetChatBotLimit
| InstanceManagerRights.SetAutoStart
| InstanceManagerRights.SetAutoStop)]
[ProducesResponseType(typeof(InstanceResponse), 200)]
[ProducesResponseType(typeof(InstanceResponse), 202)]
[ProducesResponseType(typeof(ErrorMessageResponse), 410)]
@@ -422,13 +431,18 @@ namespace Tgstation.Server.Host.Controllers
var oldAutoUpdateInterval = originalModel.AutoUpdateInterval!.Value;
var oldAutoUpdateCron = originalModel.AutoUpdateCron;
var oldAutoStartCron = originalModel.AutoStartCron;
var oldAutoStopCron = originalModel.AutoStopCron;
var earlyOut = ValidateCronSetting(model);
if (earlyOut != null)
return earlyOut;
var changedAutoInterval = model.AutoUpdateInterval.HasValue && oldAutoUpdateInterval != model.AutoUpdateInterval;
var changedAutoCron = model.AutoUpdateCron != null && oldAutoUpdateCron != model.AutoUpdateCron;
var changedAutoUpdateInterval = model.AutoUpdateInterval.HasValue && oldAutoUpdateInterval != model.AutoUpdateInterval;
var changedAutoUpdateCron = model.AutoUpdateCron != null && oldAutoUpdateCron != model.AutoUpdateCron;
var changedAutoStart = model.AutoStartCron != null && oldAutoStartCron != model.AutoStartCron;
var changedAutoStop = model.AutoStopCron != null && oldAutoStopCron != model.AutoStopCron;
var renamed = model.Name != null && originalModel.Name != model.Name;
@@ -437,7 +451,9 @@ namespace Tgstation.Server.Host.Controllers
|| CheckModified(x => x.ConfigurationType, InstanceManagerRights.SetConfiguration)
|| CheckModified(x => x.Name, InstanceManagerRights.Rename)
|| CheckModified(x => x.Online, InstanceManagerRights.SetOnline)
|| CheckModified(x => x.ChatBotLimit, InstanceManagerRights.SetChatBotLimit))
|| CheckModified(x => x.ChatBotLimit, InstanceManagerRights.SetChatBotLimit)
|| CheckModified(x => x.AutoStartCron, InstanceManagerRights.SetAutoStart)
|| CheckModified(x => x.AutoStopCron, InstanceManagerRights.SetAutoStop))
return Forbid();
if (model.ChatBotLimit.HasValue)
@@ -452,9 +468,9 @@ namespace Tgstation.Server.Host.Controllers
return Conflict(new ErrorMessageResponse(ErrorCode.ChatBotMax));
}
if (changedAutoCron)
if (changedAutoUpdateCron)
model.AutoUpdateInterval = 0;
else if (changedAutoInterval)
else if (changedAutoUpdateInterval)
model.AutoUpdateCron = String.Empty;
await DatabaseContext.Save(cancellationToken);
@@ -518,13 +534,27 @@ namespace Tgstation.Server.Host.Controllers
api.MoveJob = job.ToApi();
}
if (changedAutoInterval || changedAutoCron)
var changedAutoUpdate = changedAutoUpdateInterval || changedAutoUpdateCron;
if (changedAutoUpdate || changedAutoStart || changedAutoStop)
{
// ignoring retval because we don't care if it's offline
await WithComponentInstanceNullable(
async componentInstance =>
{
await componentInstance.ScheduleAutoUpdate(model.AutoUpdateInterval!.Value, model.AutoUpdateCron);
var autoUpdateTask = changedAutoUpdate
? componentInstance.ScheduleAutoUpdate(model.AutoUpdateInterval!.Value, model.AutoUpdateCron)
: ValueTask.CompletedTask;
var autoStartTask = changedAutoStart
? componentInstance.ScheduleServerStart(model.AutoStartCron)
: ValueTask.CompletedTask;
var autoStopTask = changedAutoStop
? componentInstance.ScheduleServerStop(model.AutoStopCron)
: ValueTask.CompletedTask;
await ValueTaskExtensions.WhenAll(autoUpdateTask, autoStartTask, autoStopTask);
return null;
},
originalModel);
@@ -769,6 +799,8 @@ namespace Tgstation.Server.Host.Controllers
Path = initialSettings.Path,
AutoUpdateInterval = initialSettings.AutoUpdateInterval ?? 0,
AutoUpdateCron = initialSettings.AutoUpdateCron ?? String.Empty,
AutoStartCron = initialSettings.AutoStartCron ?? String.Empty,
AutoStopCron = initialSettings.AutoStopCron ?? String.Empty,
ChatBotLimit = initialSettings.ChatBotLimit ?? Models.Instance.DefaultChatBotLimit,
RepositorySettings = new Models.RepositorySettings
{
@@ -1,4 +1,4 @@
using System;
using System;
using System.Globalization;
using System.Linq;
using System.Reflection;
@@ -451,22 +451,22 @@ namespace Tgstation.Server.Host.Database
/// <summary>
/// Used by unit tests to remind us to setup the correct MSSQL migration downgrades.
/// </summary>
internal static readonly Type MSLatestMigration = typeof(MSAddDMApiValidationMode);
internal static readonly Type MSLatestMigration = typeof(MSAddAutoStartAndStop);
/// <summary>
/// Used by unit tests to remind us to setup the correct MYSQL migration downgrades.
/// </summary>
internal static readonly Type MYLatestMigration = typeof(MYAddDMApiValidationMode);
internal static readonly Type MYLatestMigration = typeof(MYAddAutoStartAndStop);
/// <summary>
/// Used by unit tests to remind us to setup the correct PostgresSQL migration downgrades.
/// </summary>
internal static readonly Type PGLatestMigration = typeof(PGAddDMApiValidationMode);
internal static readonly Type PGLatestMigration = typeof(PGAddAutoStartAndStop);
/// <summary>
/// Used by unit tests to remind us to setup the correct SQLite migration downgrades.
/// </summary>
internal static readonly Type SLLatestMigration = typeof(SLAddDMApiValidationMode);
internal static readonly Type SLLatestMigration = typeof(SLAddAutoStartAndStop);
/// <summary>
/// Gets the name of the migration to run for migrating down to a given <paramref name="targetVersion"/> for the <paramref name="currentDatabaseType"/>.
@@ -482,6 +482,16 @@ namespace Tgstation.Server.Host.Database
string BadDatabaseType() => throw new ArgumentException($"Invalid DatabaseType: {currentDatabaseType}", nameof(currentDatabaseType));
// !!! DON'T FORGET TO UPDATE THE SWARM PROTOCOL MAJOR VERSION !!!
if (targetVersion < new Version(6, 12, 0))
targetMigration = currentDatabaseType switch
{
DatabaseType.MySql => nameof(MYAddDMApiValidationMode),
DatabaseType.PostgresSql => nameof(PGAddDMApiValidationMode),
DatabaseType.SqlServer => nameof(MSAddDMApiValidationMode),
DatabaseType.Sqlite => nameof(SLAddDMApiValidationMode),
_ => BadDatabaseType(),
};
if (targetVersion < new Version(6, 7, 0))
targetMigration = currentDatabaseType switch
{
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,84 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Tgstation.Server.Host.Database
{
/// <inheritdoc />
public partial class MSAddAutoStartAndStop : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
ArgumentNullException.ThrowIfNull(migrationBuilder);
migrationBuilder.AlterColumn<long>(
name: "UserId",
table: "OAuthConnections",
type: "bigint",
nullable: false,
defaultValue: 0L,
oldClrType: typeof(long),
oldType: "bigint",
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "AutoUpdateCron",
table: "Instances",
type: "nvarchar(1000)",
maxLength: 1000,
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(max)",
oldMaxLength: 10000);
migrationBuilder.AddColumn<string>(
name: "AutoStartCron",
table: "Instances",
type: "nvarchar(1000)",
maxLength: 1000,
nullable: false,
defaultValue: String.Empty);
migrationBuilder.AddColumn<string>(
name: "AutoStopCron",
table: "Instances",
type: "nvarchar(1000)",
maxLength: 1000,
nullable: false,
defaultValue: String.Empty);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
ArgumentNullException.ThrowIfNull(migrationBuilder);
migrationBuilder.DropColumn(
name: "AutoStartCron",
table: "Instances");
migrationBuilder.DropColumn(
name: "AutoStopCron",
table: "Instances");
migrationBuilder.AlterColumn<long>(
name: "UserId",
table: "OAuthConnections",
type: "bigint",
nullable: true,
oldClrType: typeof(long),
oldType: "bigint");
migrationBuilder.AlterColumn<string>(
name: "AutoUpdateCron",
table: "Instances",
type: "nvarchar(max)",
maxLength: 10000,
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(1000)",
oldMaxLength: 1000);
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,90 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Tgstation.Server.Host.Database
{
/// <inheritdoc />
public partial class MYAddAutoStartAndStop : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
ArgumentNullException.ThrowIfNull(migrationBuilder);
migrationBuilder.AlterColumn<long>(
name: "UserId",
table: "OAuthConnections",
type: "bigint",
nullable: false,
defaultValue: 0L,
oldClrType: typeof(long),
oldType: "bigint",
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "AutoUpdateCron",
table: "Instances",
type: "varchar(1000)",
maxLength: 1000,
nullable: false,
oldClrType: typeof(string),
oldType: "varchar(10000)",
oldMaxLength: 10000)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AddColumn<string>(
name: "AutoStartCron",
table: "Instances",
type: "varchar(1000)",
maxLength: 1000,
nullable: false,
defaultValue: String.Empty)
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AddColumn<string>(
name: "AutoStopCron",
table: "Instances",
type: "varchar(1000)",
maxLength: 1000,
nullable: false,
defaultValue: String.Empty)
.Annotation("MySql:CharSet", "utf8mb4");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
ArgumentNullException.ThrowIfNull(migrationBuilder);
migrationBuilder.DropColumn(
name: "AutoStartCron",
table: "Instances");
migrationBuilder.DropColumn(
name: "AutoStopCron",
table: "Instances");
migrationBuilder.AlterColumn<long>(
name: "UserId",
table: "OAuthConnections",
type: "bigint",
nullable: true,
oldClrType: typeof(long),
oldType: "bigint");
migrationBuilder.AlterColumn<string>(
name: "AutoUpdateCron",
table: "Instances",
type: "varchar(10000)",
maxLength: 10000,
nullable: false,
oldClrType: typeof(string),
oldType: "varchar(1000)",
oldMaxLength: 1000)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,84 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Tgstation.Server.Host.Database
{
/// <inheritdoc />
public partial class PGAddAutoStartAndStop : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
ArgumentNullException.ThrowIfNull(migrationBuilder);
migrationBuilder.AlterColumn<long>(
name: "UserId",
table: "OAuthConnections",
type: "bigint",
nullable: false,
defaultValue: 0L,
oldClrType: typeof(long),
oldType: "bigint",
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "AutoUpdateCron",
table: "Instances",
type: "character varying(1000)",
maxLength: 1000,
nullable: false,
oldClrType: typeof(string),
oldType: "character varying(10000)",
oldMaxLength: 10000);
migrationBuilder.AddColumn<string>(
name: "AutoStartCron",
table: "Instances",
type: "character varying(1000)",
maxLength: 1000,
nullable: false,
defaultValue: String.Empty);
migrationBuilder.AddColumn<string>(
name: "AutoStopCron",
table: "Instances",
type: "character varying(1000)",
maxLength: 1000,
nullable: false,
defaultValue: String.Empty);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
ArgumentNullException.ThrowIfNull(migrationBuilder);
migrationBuilder.DropColumn(
name: "AutoStartCron",
table: "Instances");
migrationBuilder.DropColumn(
name: "AutoStopCron",
table: "Instances");
migrationBuilder.AlterColumn<long>(
name: "UserId",
table: "OAuthConnections",
type: "bigint",
nullable: true,
oldClrType: typeof(long),
oldType: "bigint");
migrationBuilder.AlterColumn<string>(
name: "AutoUpdateCron",
table: "Instances",
type: "character varying(10000)",
maxLength: 10000,
nullable: false,
oldClrType: typeof(string),
oldType: "character varying(1000)",
oldMaxLength: 1000);
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,64 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Tgstation.Server.Host.Database
{
/// <inheritdoc />
public partial class SLAddAutoStartAndStop : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
ArgumentNullException.ThrowIfNull(migrationBuilder);
migrationBuilder.AlterColumn<long>(
name: "UserId",
table: "OAuthConnections",
type: "INTEGER",
nullable: false,
defaultValue: 0L,
oldClrType: typeof(long),
oldType: "INTEGER",
oldNullable: true);
migrationBuilder.AddColumn<string>(
name: "AutoStartCron",
table: "Instances",
type: "TEXT",
maxLength: 1000,
nullable: false,
defaultValue: String.Empty);
migrationBuilder.AddColumn<string>(
name: "AutoStopCron",
table: "Instances",
type: "TEXT",
maxLength: 1000,
nullable: false,
defaultValue: String.Empty);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
ArgumentNullException.ThrowIfNull(migrationBuilder);
migrationBuilder.DropColumn(
name: "AutoStartCron",
table: "Instances");
migrationBuilder.DropColumn(
name: "AutoStopCron",
table: "Instances");
migrationBuilder.AlterColumn<long>(
name: "UserId",
table: "OAuthConnections",
type: "INTEGER",
nullable: true,
oldClrType: typeof(long),
oldType: "INTEGER");
}
}
}
@@ -4,7 +4,7 @@ using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace Tgstation.Server.Host.Database.Migrations
namespace Tgstation.Server.Host.Database
{
[DbContext(typeof(MySqlDatabaseContext))]
partial class MySqlDatabaseContextModelSnapshot : ModelSnapshot
@@ -13,7 +13,7 @@ namespace Tgstation.Server.Host.Database.Migrations
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.8")
.HasAnnotation("ProductVersion", "8.0.10")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder);
@@ -318,10 +318,20 @@ namespace Tgstation.Server.Host.Database.Migrations
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<long?>("Id"));
b.Property<string>("AutoStartCron")
.IsRequired()
.HasMaxLength(1000)
.HasColumnType("varchar(1000)");
b.Property<string>("AutoStopCron")
.IsRequired()
.HasMaxLength(1000)
.HasColumnType("varchar(1000)");
b.Property<string>("AutoUpdateCron")
.IsRequired()
.HasMaxLength(10000)
.HasColumnType("varchar(10000)");
.HasMaxLength(1000)
.HasColumnType("varchar(1000)");
b.Property<uint?>("AutoUpdateInterval")
.IsRequired()
@@ -489,7 +499,7 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<int>("Provider")
.HasColumnType("int");
b.Property<long?>("UserId")
b.Property<long>("UserId")
.HasColumnType("bigint");
b.HasKey("Id");
@@ -980,7 +990,8 @@ namespace Tgstation.Server.Host.Database.Migrations
b.HasOne("Tgstation.Server.Host.Models.User", "User")
.WithMany("OAuthConnections")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
@@ -4,7 +4,7 @@ using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace Tgstation.Server.Host.Database.Migrations
namespace Tgstation.Server.Host.Database
{
[DbContext(typeof(PostgresSqlDatabaseContext))]
partial class PostgresSqlDatabaseContextModelSnapshot : ModelSnapshot
@@ -13,7 +13,7 @@ namespace Tgstation.Server.Host.Database.Migrations
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.8")
.HasAnnotation("ProductVersion", "8.0.10")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
@@ -291,10 +291,20 @@ namespace Tgstation.Server.Host.Database.Migrations
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long?>("Id"));
b.Property<string>("AutoStartCron")
.IsRequired()
.HasMaxLength(1000)
.HasColumnType("character varying(1000)");
b.Property<string>("AutoStopCron")
.IsRequired()
.HasMaxLength(1000)
.HasColumnType("character varying(1000)");
b.Property<string>("AutoUpdateCron")
.IsRequired()
.HasMaxLength(10000)
.HasColumnType("character varying(10000)");
.HasMaxLength(1000)
.HasColumnType("character varying(1000)");
b.Property<long>("AutoUpdateInterval")
.HasColumnType("bigint");
@@ -448,7 +458,7 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<int>("Provider")
.HasColumnType("integer");
b.Property<long?>("UserId")
b.Property<long>("UserId")
.HasColumnType("bigint");
b.HasKey("Id");
@@ -903,7 +913,8 @@ namespace Tgstation.Server.Host.Database.Migrations
b.HasOne("Tgstation.Server.Host.Models.User", "User")
.WithMany("OAuthConnections")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
@@ -4,7 +4,7 @@ using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace Tgstation.Server.Host.Database.Migrations
namespace Tgstation.Server.Host.Database
{
[DbContext(typeof(SqlServerDatabaseContext))]
partial class SqlServerDatabaseContextModelSnapshot : ModelSnapshot
@@ -13,7 +13,7 @@ namespace Tgstation.Server.Host.Database.Migrations
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.8")
.HasAnnotation("ProductVersion", "8.0.10")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
@@ -293,10 +293,20 @@ namespace Tgstation.Server.Host.Database.Migrations
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long?>("Id"));
b.Property<string>("AutoStartCron")
.IsRequired()
.HasMaxLength(1000)
.HasColumnType("nvarchar(1000)");
b.Property<string>("AutoStopCron")
.IsRequired()
.HasMaxLength(1000)
.HasColumnType("nvarchar(1000)");
b.Property<string>("AutoUpdateCron")
.IsRequired()
.HasMaxLength(10000)
.HasColumnType("nvarchar(max)");
.HasMaxLength(1000)
.HasColumnType("nvarchar(1000)");
b.Property<long>("AutoUpdateInterval")
.HasColumnType("bigint");
@@ -451,7 +461,7 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<int>("Provider")
.HasColumnType("int");
b.Property<long?>("UserId")
b.Property<long>("UserId")
.HasColumnType("bigint");
b.HasKey("Id");
@@ -909,7 +919,8 @@ namespace Tgstation.Server.Host.Database.Migrations
b.HasOne("Tgstation.Server.Host.Models.User", "User")
.WithMany("OAuthConnections")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
@@ -4,7 +4,7 @@ using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace Tgstation.Server.Host.Database.Migrations
namespace Tgstation.Server.Host.Database
{
[DbContext(typeof(SqliteDatabaseContext))]
partial class SqliteDatabaseContextModelSnapshot : ModelSnapshot
@@ -12,7 +12,7 @@ namespace Tgstation.Server.Host.Database.Migrations
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.8");
modelBuilder.HasAnnotation("ProductVersion", "8.0.10");
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
{
@@ -284,9 +284,19 @@ namespace Tgstation.Server.Host.Database.Migrations
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("AutoStartCron")
.IsRequired()
.HasMaxLength(1000)
.HasColumnType("TEXT");
b.Property<string>("AutoStopCron")
.IsRequired()
.HasMaxLength(1000)
.HasColumnType("TEXT");
b.Property<string>("AutoUpdateCron")
.IsRequired()
.HasMaxLength(10000)
.HasMaxLength(1000)
.HasColumnType("TEXT");
b.Property<uint?>("AutoUpdateInterval")
@@ -437,7 +447,7 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<int>("Provider")
.HasColumnType("INTEGER");
b.Property<long?>("UserId")
b.Property<long>("UserId")
.HasColumnType("INTEGER");
b.HasKey("Id");
@@ -876,7 +886,8 @@ namespace Tgstation.Server.Host.Database.Migrations
b.HasOne("Tgstation.Server.Host.Models.User", "User")
.WithMany("OAuthConnections")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
@@ -76,6 +76,8 @@ namespace Tgstation.Server.Host.Models
Path = Path,
Online = Online,
ChatBotLimit = ChatBotLimit,
AutoStartCron = AutoStartCron,
AutoStopCron = AutoStopCron,
};
}
}
@@ -109,6 +109,8 @@ namespace Tgstation.Server.Tests
ChatBotLimit = 1,
ChatSettings = new List<Host.Models.ChatBot>(),
ConfigurationType = ConfigurationType.HostWrite,
AutoStartCron = String.Empty,
AutoStopCron = String.Empty,
DreamDaemonSettings = new Host.Models.DreamDaemonSettings
{
AllowWebClient = false,