Ejemplo n.º 1
0
 /// <summary>
 /// Change a given <paramref name="securityLevel"/> into the appropriate DreamDaemon command line word
 /// </summary>
 /// <param name="securityLevel">The <see cref="DreamDaemonSecurity"/> level to change</param>
 /// <returns>A <see cref="string"/> representation of the command line parameter</returns>
 static string SecurityWord(DreamDaemonSecurity securityLevel)
 {
     return(securityLevel switch
     {
         DreamDaemonSecurity.Safe => "safe",
         DreamDaemonSecurity.Trusted => "trusted",
         DreamDaemonSecurity.Ultrasafe => "ultrasafe",
         _ => throw new ArgumentOutOfRangeException(nameof(securityLevel), securityLevel, String.Format(CultureInfo.InvariantCulture, "Bad DreamDaemon security level: {0}", securityLevel)),
     });
Ejemplo n.º 2
0
        async Task <DreamDaemon> DeployTestDme(string dmeName, DreamDaemonSecurity deploymentSecurity, CancellationToken cancellationToken)
        {
            await instanceClient.DreamMaker.Update(new DreamMaker
            {
                ApiValidationSecurityLevel = deploymentSecurity,
                ProjectName = $"tests/DMAPI/{dmeName}"
            }, cancellationToken);

            var compileJobJob = await instanceClient.DreamMaker.Compile(cancellationToken);

            await WaitForJob(compileJobJob, 90, false, cancellationToken);

            return(await instanceClient.DreamDaemon.Read(cancellationToken));
        }
        /// <summary>
        /// Change a given <paramref name="securityLevel"/> into the appropriate DreamDaemon command line word
        /// </summary>
        /// <param name="securityLevel">The <see cref="DreamDaemonSecurity"/> level to change</param>
        /// <returns>A <see cref="string"/> representation of the command line parameter</returns>
        static string SecurityWord(DreamDaemonSecurity securityLevel)
        {
            switch (securityLevel)
            {
            case DreamDaemonSecurity.Safe:
                return("safe");

            case DreamDaemonSecurity.Trusted:
                return("trusted");

            case DreamDaemonSecurity.Ultrasafe:
                return("ultrasafe");

            default:
                throw new ArgumentOutOfRangeException(nameof(securityLevel), securityLevel, String.Format(CultureInfo.InvariantCulture, "Bad DreamDaemon security level: {0}", securityLevel));
            }
        }
Ejemplo n.º 4
0
        /// <summary>
        /// Run a quick DD instance to test the DMAPI is installed on the target code
        /// </summary>
        /// <param name="timeout">The timeout in seconds for validation</param>
        /// <param name="securityLevel">The <see cref="DreamDaemonSecurity"/> level to use to validate the API</param>
        /// <param name="job">The <see cref="Models.CompileJob"/> for the operation</param>
        /// <param name="byondLock">The current <see cref="IByondExecutableLock"/></param>
        /// <param name="portToUse">The port to use for API validation</param>
        /// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
        /// <returns>A <see cref="Task{TResult}"/> resulting in <see langword="true"/> if the DMAPI was successfully validated, <see langword="false"/> otherwise</returns>
        async Task <bool> VerifyApi(uint timeout, DreamDaemonSecurity securityLevel, Models.CompileJob job, IByondExecutableLock byondLock, ushort portToUse, CancellationToken cancellationToken)
        {
            logger.LogTrace("Verifying DMAPI...");
            var launchParameters = new DreamDaemonLaunchParameters
            {
                AllowWebClient = false,
                PrimaryPort    = portToUse,
                SecurityLevel  = securityLevel,                   //all it needs to read the file and exit
                StartupTimeout = timeout
            };

            var dirA     = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName);
            var provider = new TemporaryDmbProvider(ioManager.ResolvePath(dirA), String.Concat(job.DmeName, DmbExtension), job);

            var timeoutAt = DateTimeOffset.Now.AddSeconds(timeout);

            using (var controller = await sessionControllerFactory.LaunchNew(launchParameters, provider, byondLock, true, true, true, cancellationToken).ConfigureAwait(false))
            {
                var launchResult = await controller.LaunchResult.ConfigureAwait(false);

                var now = DateTimeOffset.Now;
                if (now < timeoutAt && launchResult.StartupTime.HasValue)
                {
                    var timeoutTask = Task.Delay(timeoutAt - now, cancellationToken);

                    await Task.WhenAny(controller.Lifetime, timeoutTask).ConfigureAwait(false);

                    cancellationToken.ThrowIfCancellationRequested();
                }

                if (!controller.Lifetime.IsCompleted)
                {
                    logger.LogDebug("API validation timed out!");
                    return(false);
                }

                var validated = controller.ApiValidated;
                logger.LogTrace("API valid: {0}", validated);
                return(validated);
            }
        }
Ejemplo n.º 5
0
        async Task <DreamDaemon> DeployTestDme(string dmeName, DreamDaemonSecurity deploymentSecurity, bool requireApi, CancellationToken cancellationToken)
        {
            var refreshed = await instanceClient.DreamMaker.Update(new DreamMaker
            {
                ApiValidationSecurityLevel = deploymentSecurity,
                ProjectName            = $"tests/DMAPI/{dmeName}",
                RequireDMApiValidation = requireApi
            }, cancellationToken);

            Assert.AreEqual(deploymentSecurity, refreshed.ApiValidationSecurityLevel);
            Assert.AreEqual(requireApi, refreshed.RequireDMApiValidation);

            var compileJobJob = await instanceClient.DreamMaker.Compile(cancellationToken);

            await WaitForJob(compileJobJob, 90, false, null, cancellationToken);

            var ddInfo = await instanceClient.DreamDaemon.Read(cancellationToken);

            if (requireApi)
            {
                Assert.IsNotNull((ddInfo.StagedCompileJob ?? ddInfo.ActiveCompileJob).DMApiVersion);
            }
            return(ddInfo);
        }
Ejemplo n.º 6
0
        /// <summary>
        /// Run a quick DD instance to test the DMAPI is installed on the target code
        /// </summary>
        /// <param name="timeout">The timeout in seconds for validation</param>
        /// <param name="securityLevel">The <see cref="DreamDaemonSecurity"/> level to use to validate the API</param>
        /// <param name="job">The <see cref="Models.CompileJob"/> for the operation</param>
        /// <param name="byondLock">The current <see cref="IByondExecutableLock"/></param>
        /// <param name="portToUse">The port to use for API validation</param>
        /// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
        /// <returns>A <see cref="Task"/> representing the running operation</returns>
        async Task VerifyApi(uint timeout, DreamDaemonSecurity securityLevel, Models.CompileJob job, IByondExecutableLock byondLock, ushort portToUse, CancellationToken cancellationToken)
        {
            logger.LogTrace("Verifying DMAPI...");
            var launchParameters = new DreamDaemonLaunchParameters
            {
                AllowWebClient = false,
                PrimaryPort    = portToUse,
                SecurityLevel  = securityLevel,
                StartupTimeout = timeout
            };

            var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName);

            job.MinimumSecurityLevel = securityLevel;             // needed for the TempDmbProvider
            var timeoutAt = DateTimeOffset.Now.AddSeconds(timeout);

            using (var provider = new TemporaryDmbProvider(ioManager.ResolvePath(dirA), String.Concat(job.DmeName, DmbExtension), job))
                using (var controller = await sessionControllerFactory.LaunchNew(launchParameters, provider, byondLock, true, true, true, cancellationToken).ConfigureAwait(false))
                {
                    var launchResult = await controller.LaunchResult.ConfigureAwait(false);

                    var now = DateTimeOffset.Now;
                    if (now < timeoutAt && launchResult.StartupTime.HasValue)
                    {
                        var timeoutTask = Task.Delay(timeoutAt - now, cancellationToken);

                        await Task.WhenAny(controller.Lifetime, timeoutTask).ConfigureAwait(false);

                        cancellationToken.ThrowIfCancellationRequested();
                    }

                    if (controller.Lifetime.IsCompleted)
                    {
                        var validationStatus = controller.ApiValidationStatus;
                        logger.LogTrace("API validation status: {0}", validationStatus);
                        switch (validationStatus)
                        {
                        case ApiValidationStatus.RequiresUltrasafe:
                            job.MinimumSecurityLevel = DreamDaemonSecurity.Ultrasafe;
                            return;

                        case ApiValidationStatus.RequiresSafe:
                            if (securityLevel == DreamDaemonSecurity.Ultrasafe)
                            {
                                throw new JobException("This game must be run with at least the 'Safe' DreamDaemon security level!");
                            }
                            job.MinimumSecurityLevel = DreamDaemonSecurity.Safe;
                            return;

                        case ApiValidationStatus.RequiresTrusted:
                            if (securityLevel != DreamDaemonSecurity.Trusted)
                            {
                                throw new JobException("This game must be run with at least the 'Trusted' DreamDaemon security level!");
                            }
                            job.MinimumSecurityLevel = DreamDaemonSecurity.Trusted;
                            return;

                        case ApiValidationStatus.NeverValidated:
                            break;

                        case ApiValidationStatus.BadValidationRequest:
                            throw new JobException("Recieved an unrecognized API validation request from DreamDaemon!");

                        case ApiValidationStatus.UnaskedValidationRequest:
                        default:
                            throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Session controller returned unexpected ApiValidationStatus: {0}", validationStatus));
                        }
                    }

                    throw new JobException("DMAPI validation timed out!");
                }
        }
Ejemplo n.º 7
0
        /// <summary>
        /// Run a quick DD instance to test the DMAPI is installed on the target code
        /// </summary>
        /// <param name="timeout">The timeout in seconds for validation</param>
        /// <param name="securityLevel">The <see cref="DreamDaemonSecurity"/> level to use to validate the API</param>
        /// <param name="job">The <see cref="CompileJob"/> for the operation</param>
        /// <param name="byondLock">The current <see cref="IByondExecutableLock"/></param>
        /// <param name="portToUse">The port to use for API validation</param>
        /// <param name="requireValidate">If the API validation is required to complete the deployment.</param>
        /// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
        /// <returns>A <see cref="Task"/> representing the running operation</returns>
        async Task VerifyApi(
            uint timeout,
            DreamDaemonSecurity securityLevel,
            Models.CompileJob job,
            IByondExecutableLock byondLock,
            ushort portToUse,
            bool requireValidate,
            CancellationToken cancellationToken)
        {
            logger.LogTrace("Verifying {0}DMAPI...", requireValidate ? "required " : String.Empty);
            var launchParameters = new DreamDaemonLaunchParameters
            {
                AllowWebClient      = false,
                Port                = portToUse,
                SecurityLevel       = securityLevel,
                StartupTimeout      = timeout,
                TopicRequestTimeout = 0,             // not used
                HeartbeatSeconds    = 0              // not used
            };

            job.MinimumSecurityLevel = securityLevel;             // needed for the TempDmbProvider

            ApiValidationStatus validationStatus;

            using (var provider = new TemporaryDmbProvider(ioManager.ResolvePath(job.DirectoryName.ToString()), String.Concat(job.DmeName, DmbExtension), job))
                await using (var controller = await sessionControllerFactory.LaunchNew(provider, byondLock, launchParameters, true, cancellationToken).ConfigureAwait(false))
                {
                    var launchResult = await controller.LaunchResult.ConfigureAwait(false);

                    if (launchResult.StartupTime.HasValue)
                    {
                        await controller.Lifetime.WithToken(cancellationToken).ConfigureAwait(false);
                    }

                    if (!controller.Lifetime.IsCompleted)
                    {
                        await controller.DisposeAsync().ConfigureAwait(false);
                    }

                    validationStatus = controller.ApiValidationStatus;

                    if (requireValidate && validationStatus == ApiValidationStatus.NeverValidated)
                    {
                        throw new JobException(ErrorCode.DreamMakerNeverValidated);
                    }

                    logger.LogTrace("API validation status: {0}", validationStatus);

                    job.DMApiVersion = controller.DMApiVersion;
                }

            switch (validationStatus)
            {
            case ApiValidationStatus.RequiresUltrasafe:
                job.MinimumSecurityLevel = DreamDaemonSecurity.Ultrasafe;
                return;

            case ApiValidationStatus.RequiresSafe:
                job.MinimumSecurityLevel = DreamDaemonSecurity.Safe;
                return;

            case ApiValidationStatus.RequiresTrusted:
                job.MinimumSecurityLevel = DreamDaemonSecurity.Trusted;
                return;

            case ApiValidationStatus.NeverValidated:
                if (requireValidate)
                {
                    throw new JobException(ErrorCode.DreamMakerNeverValidated);
                }
                job.MinimumSecurityLevel = DreamDaemonSecurity.Ultrasafe;
                break;

            case ApiValidationStatus.BadValidationRequest:
            case ApiValidationStatus.Incompatible:
                throw new JobException(ErrorCode.DreamMakerInvalidValidation);

            case ApiValidationStatus.UnaskedValidationRequest:
            default:
                throw new InvalidOperationException(
                          $"Session controller returned unexpected ApiValidationStatus: {validationStatus}");
            }
        }
Ejemplo n.º 8
0
        /// <inheritdoc />
        public async Task <Models.CompileJob> Compile(Models.RevisionInformation revisionInformation, DreamMakerSettings dreamMakerSettings, DreamDaemonSecurity securityLevel, uint apiValidateTimeout, IRepository repository, CancellationToken cancellationToken)
        {
            if (revisionInformation == null)
            {
                throw new ArgumentNullException(nameof(revisionInformation));
            }

            if (dreamMakerSettings == null)
            {
                throw new ArgumentNullException(nameof(dreamMakerSettings));
            }

            if (repository == null)
            {
                throw new ArgumentNullException(nameof(repository));
            }

            if (securityLevel == DreamDaemonSecurity.Ultrasafe)
            {
                throw new ArgumentOutOfRangeException(nameof(securityLevel), securityLevel, "Cannot compile with ultrasafe security!");
            }

            logger.LogTrace("Begin Compile");

            var job = new Models.CompileJob
            {
                DirectoryName       = Guid.NewGuid(),
                DmeName             = dreamMakerSettings.ProjectName,
                RevisionInformation = revisionInformation
            };

            logger.LogTrace("Compile output GUID: {0}", job.DirectoryName);

            lock (this)
            {
                if (Status != CompilerStatus.Idle)
                {
                    throw new JobException("There is already a compile in progress!");
                }

                Status = CompilerStatus.Copying;
            }

            try
            {
                var    commitInsert = revisionInformation.CommitSha.Substring(0, 7);
                string remoteCommitInsert;
                if (revisionInformation.CommitSha == revisionInformation.OriginCommitSha)
                {
                    commitInsert       = String.Format(CultureInfo.InvariantCulture, "^{0}", commitInsert);
                    remoteCommitInsert = String.Empty;
                }
                else
                {
                    remoteCommitInsert = String.Format(CultureInfo.InvariantCulture, ". Remote commit: ^{0}", revisionInformation.OriginCommitSha.Substring(0, 7));
                }

                var testmergeInsert = revisionInformation.ActiveTestMerges.Count == 0 ? String.Empty : String.Format(CultureInfo.InvariantCulture, " (Test Merges: {0})",
                                                                                                                     String.Join(", ", revisionInformation.ActiveTestMerges.Select(x => x.TestMerge).Select(x =>
                {
                    var result = String.Format(CultureInfo.InvariantCulture, "#{0} at {1}", x.Number, x.PullRequestRevision.Substring(0, 7));
                    if (x.Comment != null)
                    {
                        result += String.Format(CultureInfo.InvariantCulture, " ({0})", x.Comment);
                    }
                    return(result);
                })));

                using (var byondLock = await byond.UseExecutables(null, cancellationToken).ConfigureAwait(false))
                {
                    await chat.SendUpdateMessage(String.Format(CultureInfo.InvariantCulture, "Deploying revision: {0}{1}{2} BYOND Version: {3}", commitInsert, testmergeInsert, remoteCommitInsert, byondLock.Version), cancellationToken).ConfigureAwait(false);

                    async Task CleanupFailedCompile(bool cancelled)
                    {
                        logger.LogTrace("Cleaning compile directory...");
                        Status = CompilerStatus.Cleanup;
                        var chatTask = chat.SendUpdateMessage(cancelled ? "Deploy cancelled!" : "Deploy failed!", cancellationToken);

                        try
                        {
                            await ioManager.DeleteDirectory(job.DirectoryName.ToString(), CancellationToken.None).ConfigureAwait(false);
                        }
                        catch (Exception e)
                        {
                            logger.LogWarning("Error cleaning up compile directory {0}! Exception: {1}", ioManager.ResolvePath(job.DirectoryName.ToString()), e);
                        }

                        await chatTask.ConfigureAwait(false);
                    };

                    try
                    {
                        await ioManager.CreateDirectory(job.DirectoryName.ToString(), cancellationToken).ConfigureAwait(false);

                        var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName);
                        var dirB = ioManager.ConcatPath(job.DirectoryName.ToString(), BDirectoryName);

                        logger.LogTrace("Copying repository to game directory...");
                        //copy the repository
                        var fullDirA   = ioManager.ResolvePath(dirA);
                        var repoOrigin = repository.Origin;
                        using (repository)
                            await repository.CopyTo(fullDirA, cancellationToken).ConfigureAwait(false);

                        Status = CompilerStatus.PreCompile;

                        var resolvedGameDirectory = ioManager.ResolvePath(ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName));
                        await eventConsumer.HandleEvent(EventType.CompileStart, new List <string> {
                            resolvedGameDirectory, repoOrigin
                        }, cancellationToken).ConfigureAwait(false);

                        Status = CompilerStatus.Modifying;

                        if (job.DmeName == null)
                        {
                            logger.LogTrace("Searching for available .dmes...");
                            var path = (await ioManager.GetFilesWithExtension(dirA, DmeExtension, cancellationToken).ConfigureAwait(false)).FirstOrDefault();
                            if (path == default)
                            {
                                throw new JobException("Unable to find any .dme!");
                            }
                            var dmeWithExtension = ioManager.GetFileName(path);
                            job.DmeName = dmeWithExtension.Substring(0, dmeWithExtension.Length - DmeExtension.Length - 1);
                        }

                        logger.LogDebug("Selected {0}.dme for compilation!", job.DmeName);

                        await ModifyDme(job, cancellationToken).ConfigureAwait(false);

                        Status = CompilerStatus.Compiling;

                        //run compiler, verify api
                        job.ByondVersion = byondLock.Version.ToString();

                        var exitCode = await RunDreamMaker(byondLock.DreamMakerPath, job, cancellationToken).ConfigureAwait(false);

                        var apiValidated = false;
                        if (exitCode == 0)
                        {
                            Status = CompilerStatus.Verifying;

                            apiValidated = await VerifyApi(apiValidateTimeout, securityLevel, job, byondLock, dreamMakerSettings.ApiValidationPort.Value, cancellationToken).ConfigureAwait(false);
                        }

                        if (!apiValidated)
                        {
                            //server never validated or compile failed
                            await eventConsumer.HandleEvent(EventType.CompileFailure, new List <string> {
                                resolvedGameDirectory, exitCode == 0 ? "1" : "0"
                            }, cancellationToken).ConfigureAwait(false);

                            throw new JobException(exitCode == 0 ? "Validation of the TGS api failed!" : String.Format(CultureInfo.InvariantCulture, "DM exited with a non-zero code: {0}{1}{2}", exitCode, Environment.NewLine, job.Output));
                        }

                        logger.LogTrace("Running post compile event...");
                        Status = CompilerStatus.PostCompile;
                        await eventConsumer.HandleEvent(EventType.CompileComplete, new List <string> {
                            ioManager.ResolvePath(ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName))
                        }, cancellationToken).ConfigureAwait(false);

                        logger.LogTrace("Duplicating compiled game...");
                        Status = CompilerStatus.Duplicating;

                        //duplicate the dmb et al
                        await ioManager.CopyDirectory(dirA, dirB, null, cancellationToken).ConfigureAwait(false);

                        logger.LogTrace("Applying static game file symlinks...");
                        Status = CompilerStatus.Symlinking;

                        //symlink in the static data
                        var symATask = configuration.SymlinkStaticFilesTo(fullDirA, cancellationToken);
                        var symBTask = configuration.SymlinkStaticFilesTo(ioManager.ResolvePath(dirB), cancellationToken);

                        await Task.WhenAll(symATask, symBTask).ConfigureAwait(false);

                        await chat.SendUpdateMessage("Deployment complete! Changes will be applied on next server reboot.", cancellationToken).ConfigureAwait(false);

                        logger.LogDebug("Compile complete!");
                        return(job);
                    }
                    catch (Exception e)
                    {
                        await CleanupFailedCompile(e is OperationCanceledException).ConfigureAwait(false);

                        throw;
                    }
                }
            }
            catch (OperationCanceledException)
            {
                await eventConsumer.HandleEvent(EventType.CompileCancelled, null, default).ConfigureAwait(false);

                throw;
            }
            finally
            {
                Status = CompilerStatus.Idle;
            }
        }