Beispiel #1
0
        /// <summary>
        /// Creates a default <see cref="Models.Instance"/> from <paramref name="initialSettings"/>.
        /// </summary>
        /// <param name="initialSettings">The <see cref="InstanceCreateRequest"/>.</param>
        /// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
        /// <returns>A <see cref="Task{TResult}"/> resulting in the new <see cref="Models.Instance"/> or <see langword="null"/> if ports could not be allocated.</returns>
        async Task <Models.Instance> CreateDefaultInstance(InstanceCreateRequest initialSettings, CancellationToken cancellationToken)
        {
            var ddPort = await portAllocator.GetAvailablePort(1, false, cancellationToken).ConfigureAwait(false);

            if (!ddPort.HasValue)
            {
                return(null);
            }

            // try to use the old default if possible
            const ushort DefaultDreamDaemonPort = 1337;

            if (ddPort.Value < DefaultDreamDaemonPort)
            {
                ddPort = await portAllocator.GetAvailablePort(DefaultDreamDaemonPort, false, cancellationToken).ConfigureAwait(false) ?? ddPort;
            }

            const ushort DefaultApiValidationPort = 1339;
            var          dmPort = await portAllocator
                                  .GetAvailablePort(
                Math.Min((ushort)(ddPort.Value + 1), DefaultApiValidationPort),
                false,
                cancellationToken)
                                  .ConfigureAwait(false);

            if (!dmPort.HasValue)
            {
                return(null);
            }

            // try to use the old default if possible
            if (dmPort < DefaultApiValidationPort)
            {
                dmPort = await portAllocator.GetAvailablePort(DefaultApiValidationPort, false, cancellationToken).ConfigureAwait(false) ?? dmPort;
            }

            return(new Models.Instance
            {
                ConfigurationType = initialSettings.ConfigurationType ?? ConfigurationType.Disallowed,
                DreamDaemonSettings = new DreamDaemonSettings
                {
                    AllowWebClient = false,
                    AutoStart = false,
                    Port = ddPort,
                    SecurityLevel = DreamDaemonSecurity.Safe,
                    StartupTimeout = 60,
                    HeartbeatSeconds = 60,
                    TopicRequestTimeout = generalConfiguration.ByondTopicTimeout,
                    AdditionalParameters = String.Empty,
                },
                DreamMakerSettings = new DreamMakerSettings
                {
                    ApiValidationPort = dmPort,
                    ApiValidationSecurityLevel = DreamDaemonSecurity.Safe,
                    RequireDMApiValidation = true
                },
                Name = initialSettings.Name,
                Online = false,
                Path = initialSettings.Path,
                AutoUpdateInterval = initialSettings.AutoUpdateInterval ?? 0,
                ChatBotLimit = initialSettings.ChatBotLimit ?? Models.Instance.DefaultChatBotLimit,
                RepositorySettings = new RepositorySettings
                {
                    CommitterEmail = Components.Repository.Repository.DefaultCommitterEmail,
                    CommitterName = Components.Repository.Repository.DefaultCommitterName,
                    PushTestMergeCommits = false,
                    ShowTestMergeCommitters = false,
                    AutoUpdatesKeepTestMerges = false,
                    AutoUpdatesSynchronize = false,
                    PostTestMergeComment = false,
                    CreateGitHubDeployments = false
                },
                InstancePermissionSets = new List <InstancePermissionSet>                // give this user full privileges on the instance
                {
                    InstanceAdminPermissionSet(null)
                },
                SwarmIdentifer = swarmConfiguration.Identifier,
            });
        }
Beispiel #2
0
        public async Task <IActionResult> Create([FromBody] InstanceCreateRequest model, CancellationToken cancellationToken)
        {
            if (model == null)
            {
                throw new ArgumentNullException(nameof(model));
            }

            if (String.IsNullOrWhiteSpace(model.Name))
            {
                return(BadRequest(new ErrorMessageResponse(ErrorCode.InstanceWhitespaceName)));
            }

            var unNormalizedPath   = model.Path;
            var targetInstancePath = NormalizePath(unNormalizedPath);

            model.Path = targetInstancePath;

            var installationDirectoryPath = NormalizePath(DefaultIOManager.CurrentDirectory);

            bool InstanceIsChildOf(string otherPath)
            {
                if (!targetInstancePath.StartsWith(otherPath, StringComparison.Ordinal))
                {
                    return(false);
                }

                bool sameLength       = targetInstancePath.Length == otherPath.Length;
                char dirSeparatorChar = targetInstancePath.ToCharArray()[Math.Min(otherPath.Length, targetInstancePath.Length - 1)];

                return(sameLength ||
                       dirSeparatorChar == Path.DirectorySeparatorChar ||
                       dirSeparatorChar == Path.AltDirectorySeparatorChar);
            }

            if (InstanceIsChildOf(installationDirectoryPath))
            {
                return(Conflict(new ErrorMessageResponse(ErrorCode.InstanceAtConflictingPath)));
            }

            // Validate it's not a child of any other instance
            IActionResult earlyOut = null;
            ulong         countOfOtherInstances = 0;

            using (var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
            {
                var newCancellationToken = cts.Token;
                try
                {
                    await DatabaseContext
                    .Instances
                    .AsQueryable()
                    .Where(x => x.SwarmIdentifer == swarmConfiguration.Identifier)
                    .Select(x => new Models.Instance
                    {
                        Path = x.Path
                    })
                    .ForEachAsync(
                        otherInstance =>
                    {
                        if (++countOfOtherInstances >= generalConfiguration.InstanceLimit)
                        {
                            earlyOut ??= Conflict(new ErrorMessageResponse(ErrorCode.InstanceLimitReached));
                        }
                        else if (InstanceIsChildOf(otherInstance.Path))
                        {
                            earlyOut ??= Conflict(new ErrorMessageResponse(ErrorCode.InstanceAtConflictingPath));
                        }

                        if (earlyOut != null && !newCancellationToken.IsCancellationRequested)
                        {
                            cts.Cancel();
                        }
                    },
                        newCancellationToken)
                    .ConfigureAwait(false);
                }
                catch (OperationCanceledException)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                }
            }

            if (earlyOut != null)
            {
                return(earlyOut);
            }

            // Last test, ensure it's in the list of valid paths
            if (!(generalConfiguration.ValidInstancePaths?
                  .Select(path => NormalizePath(path))
                  .Any(path => InstanceIsChildOf(path)) ?? true))
            {
                return(BadRequest(new ErrorMessageResponse(ErrorCode.InstanceNotAtWhitelistedPath)));
            }

            async Task <bool> DirExistsAndIsNotEmpty()
            {
                if (!await ioManager.DirectoryExists(model.Path, cancellationToken).ConfigureAwait(false))
                {
                    return(false);
                }

                var filesTask = ioManager.GetFiles(model.Path, cancellationToken);
                var dirsTask  = ioManager.GetDirectories(model.Path, cancellationToken);

                var files = await filesTask.ConfigureAwait(false);

                var dirs = await dirsTask.ConfigureAwait(false);

                return(files.Concat(dirs).Any());
            }

            var  dirExistsTask = DirExistsAndIsNotEmpty();
            bool attached      = false;

            if (await ioManager.FileExists(model.Path, cancellationToken).ConfigureAwait(false) || await dirExistsTask.ConfigureAwait(false))
            {
                if (!await ioManager.FileExists(ioManager.ConcatPath(model.Path, InstanceAttachFileName), cancellationToken).ConfigureAwait(false))
                {
                    return(Conflict(new ErrorMessageResponse(ErrorCode.InstanceAtExistingPath)));
                }
                else
                {
                    attached = true;
                }
            }

            var newInstance = await CreateDefaultInstance(model, cancellationToken).ConfigureAwait(false);

            if (newInstance == null)
            {
                return(Conflict(new ErrorMessageResponse(ErrorCode.NoPortsAvailable)));
            }

            DatabaseContext.Instances.Add(newInstance);
            try
            {
                await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);

                try
                {
                    // actually reserve it now
                    await ioManager.CreateDirectory(unNormalizedPath, cancellationToken).ConfigureAwait(false);

                    await ioManager.DeleteFile(ioManager.ConcatPath(targetInstancePath, InstanceAttachFileName), cancellationToken).ConfigureAwait(false);
                }
                catch
                {
                    // oh shit delete the model
                    DatabaseContext.Instances.Remove(newInstance);

                    // DCT: Operation must always run
                    await DatabaseContext.Save(default).ConfigureAwait(false);
 /// <inheritdoc />
 public Task <InstanceResponse> CreateOrAttach(InstanceCreateRequest instance, CancellationToken cancellationToken) => ApiClient.Create <InstanceCreateRequest, InstanceResponse>(Routes.InstanceManager, instance ?? throw new ArgumentNullException(nameof(instance)), cancellationToken);