2using System.Collections.Generic;
3using System.Diagnostics.CodeAnalysis;
6using System.Linq.Expressions;
7using System.Reflection;
9using System.Threading.Tasks;
11using Microsoft.AspNetCore.Mvc;
12using Microsoft.EntityFrameworkCore;
13using Microsoft.Extensions.Logging;
14using Microsoft.Extensions.Options;
41#pragma warning disable CA1506
102 ILogger<InstanceController> logger,
109 IOptions<GeneralConfiguration> generalConfigurationOptions,
110 IOptions<SwarmConfiguration> swarmConfigurationOptions,
114 authenticationContext,
126 generalConfiguration = generalConfigurationOptions?.Value ??
throw new ArgumentNullException(nameof(generalConfigurationOptions));
127 swarmConfiguration = swarmConfigurationOptions?.Value ??
throw new ArgumentNullException(nameof(swarmConfigurationOptions));
144 ArgumentNullException.ThrowIfNull(model);
146 if (String.IsNullOrWhiteSpace(model.Name) || String.IsNullOrWhiteSpace(model.Path))
150 if (earlyOut !=
null)
154 model.Path = targetInstancePath;
165 .Select(x =>
new Models.Instance
169 .ToListAsync(cancellationToken);
174 var instancePathChecks = instancePaths
175 .Select(otherInstance =>
ioManager.
PathIsChildOf(otherInstance.Path!, targetInstancePath, cancellationToken))
178 await Task.WhenAll(instancePathChecks);
180 if (instancePathChecks.Any(task => task.Result))
187 ?? Enumerable.Empty<Task<bool>>();
188 await Task.WhenAll(pathChecks);
189 if (!pathChecks.All(task => task.Result))
192 async ValueTask<bool> DirExistsAndIsNotEmpty()
200 var files = await filesTask;
201 var dirs = await dirsTask;
203 return files.Concat(dirs).Any();
206 var dirExistsTask = DirExistsAndIsNotEmpty();
207 bool attached =
false;
208 if (await
ioManager.
FileExists(targetInstancePath, cancellationToken) || await dirExistsTask)
215 if (newInstance ==
null)
239 catch (IOException e)
243 AdditionalData = e.Message,
248 "{userName} {attachedOrCreated} instance {instanceName}: {instanceId} ({instancePath})",
250 attached ?
"attached" :
"created",
256 newInstance.InstancePermissionSets.First(),
259 var api = newInstance.ToApi();
260 api.Accessible =
true;
261 return attached ? Json(api) :
Created(api);
274 [ProducesResponseType(204)]
276 public async ValueTask<IActionResult>
Delete(
long id, CancellationToken cancellationToken)
282 .FirstOrDefaultAsync(cancellationToken);
283 if (originalModel ==
default)
285 if (originalModel.Online!.Value)
288 var originalPath = originalModel.Path!;
295 catch (OperationCanceledException)
309 .Where(x => x.Job!.Instance!.Id ==
id)
310 .ExecuteDeleteAsync(cancellationToken);
314 .Where(x => x.RevisionInformation.InstanceId ==
id)
315 .ExecuteDeleteAsync(cancellationToken);
319 .Where(x => x.InstanceId ==
id)
320 .ExecuteDeleteAsync(cancellationToken);
348#pragma warning disable CA1502
351 ArgumentNullException.ThrowIfNull(model);
358 var moveJob = await InstanceQuery()
359 .SelectMany(x => x.Jobs)
360 .Where(x => !x.StoppedAt.HasValue && x.JobCode ==
JobCode.Move)
361 .Select(x =>
new Job(x.Id!.Value))
362 .FirstOrDefaultAsync(cancellationToken);
372 var originalModel = await InstanceQuery()
373 .Include(x => x.RepositorySettings)
374 .Include(x => x.ChatSettings)
375 .ThenInclude(x => x.Channels)
376 .Include(x => x.DreamDaemonSettings)
377 .FirstOrDefaultAsync(cancellationToken);
378 if (originalModel ==
default(Models.Instance))
385 bool CheckModified<T>(Expression<Func<Api.Models.Instance, T>> expression,
InstanceManagerRights requiredRight)
387 var memberSelectorExpression = (MemberExpression)expression.Body;
388 var property = (PropertyInfo)memberSelectorExpression.Member;
390 var newVal =
property.GetValue(model);
393 if (!userRights.HasFlag(requiredRight) &&
property.GetValue(originalModel) != newVal)
396 property.SetValue(originalModel, newVal);
400 string? originalModelPath =
null;
401 string? normalizedPath =
null;
402 var originalOnline = originalModel.Online!.Value;
403 if (model.Path !=
null)
407 if (normalizedPath != originalModel.Path)
411 if (originalOnline && model.Online !=
true)
418 originalModelPath = originalModel.Path;
419 originalModel.Path = normalizedPath;
423 var oldAutoUpdateInterval = originalModel.AutoUpdateInterval!.Value;
424 var oldAutoUpdateCron = originalModel.AutoUpdateCron;
427 if (earlyOut !=
null)
430 var changedAutoInterval = model.AutoUpdateInterval.HasValue && oldAutoUpdateInterval != model.AutoUpdateInterval;
431 var changedAutoCron = model.AutoUpdateCron !=
null && oldAutoUpdateCron != model.AutoUpdateCron;
433 var renamed = model.Name !=
null && originalModel.Name != model.Name;
443 if (model.ChatBotLimit.HasValue)
448 .Where(x => x.InstanceId == originalModel.Id)
449 .CountAsync(cancellationToken);
451 if (countOfExistingChatBots > model.ChatBotLimit.Value)
456 model.AutoUpdateInterval = 0;
457 else if (changedAutoInterval)
458 model.AutoUpdateCron = String.Empty;
466 async componentInstance =>
468 await componentInstance.InstanceRenamed(originalModel.Name!, cancellationToken);
474 var oldAutoStart = originalModel.DreamDaemonSettings!.AutoStart;
477 if (originalOnline && model.Online ==
false)
479 else if (!originalOnline && model.Online ==
true)
483 originalModel.DreamDaemonSettings.AutoStart =
false;
489 if (e is not OperationCanceledException)
490 Logger.LogError(e,
"Error changing instance online state!");
491 originalModel.Online = originalOnline;
492 originalModel.DreamDaemonSettings.AutoStart = oldAutoStart;
493 if (originalModelPath !=
null)
494 originalModel.Path = originalModelPath;
503 Id = originalModel.
Id,
506 var moving = originalModelPath !=
null;
509 var description = $
"Move instance ID {originalModel.Id} from {originalModelPath} to {normalizedPath}";
511 job.Description = description;
515 (core, databaseContextFactory, paramJob, progressHandler, ct)
518 api.MoveJob = job.ToApi();
521 if (changedAutoInterval || changedAutoCron)
525 async componentInstance =>
527 await componentInstance.ScheduleAutoUpdate(model.AutoUpdateInterval!.Value, model.AutoUpdateCron);
534 return moving ? Accepted(api) : Json(api);
536#pragma warning restore CA1502
549 public async ValueTask<IActionResult>
List(
550 [FromQuery]
int? page,
551 [FromQuery]
int? pageSize,
552 CancellationToken cancellationToken)
563 .Where(x => x.InstancePermissionSets.Any(instanceUser =>
572 return query.Select(x => x);
575 var moveJobs = await GetBaseQuery()
576 .SelectMany(x => x.Jobs)
577 .Where(x => !x.StoppedAt.HasValue && x.JobCode ==
JobCode.Move)
578 .Include(x => x.StartedBy!)
579 .ThenInclude(x => x.CreatedBy)
580 .Include(x => x.Instance)
581 .ToListAsync(cancellationToken);
583 var needsUpdate =
false;
585 () => ValueTask.FromResult(
588 .OrderBy(x => x.Id))),
592 instance.MoveJob = moveJobs.FirstOrDefault(x => x.Instance!.Id == instance.Id)?.ToApi();
617 public async ValueTask<IActionResult>
GetId(
long id, CancellationToken cancellationToken)
628 query = query.Include(x => x.InstancePermissionSets);
632 var instance = await QueryForUser().FirstOrDefaultAsync(cancellationToken);
634 if (instance ==
null)
650 var api = instance.ToApi();
652 var moveJob = await QueryForUser()
653 .SelectMany(x => x.Jobs)
654 .Where(x => !x.StoppedAt.HasValue && x.JobCode ==
JobCode.Move)
655 .Include(x => x.StartedBy!)
656 .ThenInclude(x => x.CreatedBy)
657 .Include(x => x.Instance)
658 .FirstOrDefaultAsync(cancellationToken);
659 api.MoveJob = moveJob?.ToApi();
673 [ProducesResponseType(204)]
675 public async ValueTask<IActionResult>
GrantPermissions(
long id, CancellationToken cancellationToken)
683 var usersInstancePermissionSet = await BaseQuery()
684 .SelectMany(x => x.InstancePermissionSets)
686 .FirstOrDefaultAsync(cancellationToken);
687 if (usersInstancePermissionSet ==
default)
690 var instanceExists = await BaseQuery()
691 .AnyAsync(cancellationToken);
697 instanceAdminUser.InstanceId = id;
717 if (!ddPort.HasValue)
721 const ushort DefaultDreamDaemonPort = 1337;
722 if (ddPort.Value < DefaultDreamDaemonPort)
725 const ushort DefaultApiValidationPort = 1339;
728 Math.Min((ushort)(ddPort.Value + 1), DefaultApiValidationPort),
731 if (!dmPort.HasValue)
735 if (dmPort < DefaultApiValidationPort)
743 AllowWebClient =
false,
746 OpenDreamTopicPort = 0,
750 HealthCheckSeconds = 60,
751 DumpOnHealthCheckRestart =
false,
753 AdditionalParameters = String.Empty,
754 StartProfiler =
false,
761 ApiValidationPort = dmPort,
763 RequireDMApiValidation =
true,
764 Timeout = TimeSpan.FromHours(1),
765 CompilerAdditionalArguments =
null,
767 Name = initialSettings.
Name,
769 Path = initialSettings.
Path,
770 AutoUpdateInterval = initialSettings.AutoUpdateInterval ?? 0,
771 AutoUpdateCron = initialSettings.AutoUpdateCron ?? String.Empty,
772 ChatBotLimit = initialSettings.ChatBotLimit ?? Models.Instance.DefaultChatBotLimit,
775 CommitterEmail = Components.Repository.Repository.DefaultCommitterEmail,
776 CommitterName = Components.Repository.Repository.DefaultCommitterName,
777 PushTestMergeCommits =
false,
778 ShowTestMergeCommitters =
false,
779 AutoUpdatesKeepTestMerges =
false,
780 AutoUpdatesSynchronize =
false,
781 PostTestMergeComment =
false,
782 CreateGitHubDeployments =
false,
783 UpdateSubmodules =
true,
785 InstancePermissionSets =
new List<InstancePermissionSet>
812 return permissionSetToModify;
820 [
return: NotNullIfNotNull(nameof(path))]
844 .AnyAsync(cancellationToken);
854 if (!String.IsNullOrWhiteSpace(instance.AutoUpdateCron))
856 if ((instance.AutoUpdateInterval.HasValue && instance.AutoUpdateInterval.Value != 0)
857 || (CrontabSchedule.TryParse(
858 instance.AutoUpdateCron,
859 new CrontabSchedule.ParseOptions
861 IncludingSeconds = true,
865 instance.AutoUpdateInterval = 0;
868 instance.AutoUpdateCron = String.Empty;
virtual ? long Id
The ID of the entity.
string? Path
The path to where the Instance is located. Can only be changed while the Instance is offline....
string? Identifier
The server's identifier.
virtual ? string Name
The name of the entity represented by the NamedEntity.
Represents a set of server permissions.
InstanceManagerRights? InstanceManagerRights
The Rights.InstanceManagerRights for the user.
A request to create an Instance.
A request to update an Instance.
Represents an error message returned by the server.
Server response for Instances.
Represents a paginated set of models.
Routes to a server actions.
const string InstanceManager
The Models.Instance controller.
const string List
The postfix for list operations.
General configuration options.
uint ByondTopicTimeout
The timeout in milliseconds for sending and receiving topics to/from DreamDaemon. Note that a single ...
Configuration for the server swarm system.
ObjectResult Created(object payload)
Generic 201 response with a given payload .
ILogger< ApiController > Logger
The ILogger for the ApiController.
ApiController for operations on IInstanceCores.
async ValueTask< IActionResult?> WithComponentInstanceNullable(Func< IInstanceCore, ValueTask< IActionResult?> > action, Models.Instance? instance=null)
Run a given action with the relevant IInstance.
IInstanceOperations InstanceOperations
Access the IInstanceOperations instance.
readonly IInstanceManager instanceManager
The IInstanceManager for the ComponentInterfacingController.
bool ValidateInstanceOnlineStatus(Api.Models.Instance metadata)
Corrects discrepencies between the Api.Models.Instance.Online status of IInstances in the database vs...
ApiController for managing Components.Instances.
async ValueTask< Models.Instance?> CreateDefaultInstance(InstanceCreateRequest initialSettings, CancellationToken cancellationToken)
Creates a default Models.Instance from initialSettings .
async ValueTask< IActionResult > GetId(long id, CancellationToken cancellationToken)
Get a specific Api.Models.Instance.
InstanceController(IDatabaseContext databaseContext, IAuthenticationContext authenticationContext, ILogger< InstanceController > logger, IInstanceManager instanceManager, IJobManager jobManager, IIOManager ioManager, IPlatformIdentifier platformIdentifier, IPortAllocator portAllocator, IPermissionsUpdateNotifyee permissionsUpdateNotifyee, IOptions< GeneralConfiguration > generalConfigurationOptions, IOptions< SwarmConfiguration > swarmConfigurationOptions, IApiHeadersProvider apiHeaders)
Initializes a new instance of the InstanceController class.
readonly IJobManager jobManager
The IJobManager for the InstanceController.
readonly GeneralConfiguration generalConfiguration
The GeneralConfiguration for the InstanceController.
BadRequestObjectResult? ValidateCronSetting(Api.Models.Instance instance)
Validates a given instance 's Api.Models.Instance.AutoUpdateCron setting.
string? NormalizePath(string? path)
Normalize a given path for an instance.
readonly IPortAllocator portAllocator
The IPortAllocator for the InstanceController.
async ValueTask< IActionResult > Update([FromBody] InstanceUpdateRequest model, CancellationToken cancellationToken)
Modify an Api.Models.Instance's settings.
InstancePermissionSet InstanceAdminPermissionSet(InstancePermissionSet? permissionSetToModify)
Generate an InstancePermissionSet with full rights.
readonly SwarmConfiguration swarmConfiguration
The SwarmConfiguration for the InstanceController.
async ValueTask< IActionResult > List([FromQuery] int? page, [FromQuery] int? pageSize, CancellationToken cancellationToken)
List Api.Models.Instances.
const string InstanceAttachFileName
File name to allow attaching instances.
async ValueTask< IActionResult > Create([FromBody] InstanceCreateRequest model, CancellationToken cancellationToken)
Create or attach an Api.Models.Instance.
readonly IPlatformIdentifier platformIdentifier
The IPlatformIdentifier for the InstanceController.
readonly IPermissionsUpdateNotifyee permissionsUpdateNotifyee
The IPermissionsUpdateNotifyee for the InstanceController.
async ValueTask< IActionResult > Delete(long id, CancellationToken cancellationToken)
Detach an Api.Models.Instance with the given id .
async ValueTask CheckAccessible(InstanceResponse instanceResponse, CancellationToken cancellationToken)
Populate the InstanceResponse.Accessible property of a given instanceResponse .
readonly IIOManager ioManager
The IIOManager for the InstanceController.
async ValueTask< IActionResult > GrantPermissions(long id, CancellationToken cancellationToken)
Gives the current user full permissions on a given instance id .
Helper for returning paginated models.
Backend abstract implementation of IDatabaseContext.
DbSet< Instance > Instances
The Instances in the DatabaseContext.
DbSet< InstancePermissionSet > InstancePermissionSets
The InstancePermissionSets in the DatabaseContext.
DbSet< CompileJob > CompileJobs
The CompileJobs in the DatabaseContext.
Task Save(CancellationToken cancellationToken)
Saves changes made to the IDatabaseContext. A Task representing the running operation.
DbSet< ChatBot > ChatBots
The ChatBots in the DatabaseContext.
DbSet< RevInfoTestMerge > RevInfoTestMerges
The RevInfoTestMerges in the DatabaseContext.
DbSet< RevisionInformation > RevisionInformations
The RevisionInformations in the DatabaseContext.
IIOManager that resolves paths to Environment.CurrentDirectory.
const string CurrentDirectory
Path to the current working directory for the IIOManager.
Represents an Api.Models.Instance in the database.
static Job Create(JobCode code, User? startedBy, Api.Models.Instance instance)
Creates a new job for registering in the Jobs.IJobService.
User User
The authenticated user.
PermissionSet PermissionSet
The User's effective PermissionSet.
ulong GetRight(RightsType rightsType)
Get the value of a given rightsType . The value of rightsType . Note that if InstancePermissionSet is...
ValueTask OnlineInstance(Models.Instance metadata, CancellationToken cancellationToken)
Online an IInstance.
ValueTask MoveInstance(Models.Instance metadata, string oldPath, CancellationToken cancellationToken)
Move an IInstance.
ValueTask OfflineInstance(Models.Instance metadata, User user, CancellationToken cancellationToken)
Offline an IInstance.
Interface for using filesystems.
Task< IReadOnlyList< string > > GetFiles(string path, CancellationToken cancellationToken)
Returns full file names in a given path .
Task< bool > PathIsChildOf(string parentPath, string childPath, CancellationToken cancellationToken)
Check if a given parentPath is a parent of a given parentPath .
string ResolvePath()
Retrieve the full path of the current working directory.
string ConcatPath(params string[] paths)
Combines an array of strings into a path.
Task< IReadOnlyList< string > > GetDirectories(string path, CancellationToken cancellationToken)
Returns full directory names in a given path .
Task CreateDirectory(string path, CancellationToken cancellationToken)
Create a directory at path .
Task DeleteFile(string path, CancellationToken cancellationToken)
Deletes a file at path .
ValueTask WriteAllBytes(string path, byte[] contents, CancellationToken cancellationToken)
Writes some contents to a file at path overwriting previous content.
Task< bool > FileExists(string path, CancellationToken cancellationToken)
Check that the file at path exists.
Task< bool > DirectoryExists(string path, CancellationToken cancellationToken)
Check that the directory at path exists.
Manages the runtime of Jobs.
ValueTask< Job?> CancelJob(Job job, User? user, bool blocking, CancellationToken cancellationToken)
Cancels a give job .
ValueTask RegisterOperation(Job job, JobEntrypoint operation, CancellationToken cancellationToken)
Registers a given Job and begins running it.
Represents the currently authenticated Models.User.
Receives notifications about permissions updates.
ValueTask InstancePermissionSetCreated(InstancePermissionSet instancePermissionSet, CancellationToken cancellationToken)
Called when a given instancePermissionSet is successfully created.
Gets unassigned ports for use by TGS.
ValueTask< ushort?> GetAvailablePort(ushort basePort, bool checkOne, CancellationToken cancellationToken)
Gets a port not currently in use by TGS.
ErrorCode
Types of Response.ErrorMessageResponses that the API may return.
DreamDaemonVisibility
The visibility setting for DreamDaemon.
JobCode
The different types of Response.JobResponse.
ConfigurationType
The type of configuration allowed on an Instance.
DreamDaemonSecurity
DreamDaemon's security level.
ChatBotRights
Rights for chat bots.
ConfigurationRights
Rights for Models.IConfigurationFiles.
DreamMakerRights
Rights for deployment.
RightsType
The type of rights a model uses.
EngineRights
Rights for engine version management.
RepositoryRights
Rights for the git repository.
InstancePermissionSetRights
Rights for an Models.Instance.
DreamDaemonRights
Rights for managing DreamDaemon.
InstanceManagerRights
Rights for managing Models.Instances.