示例#1
0
        public override async Task <IActionResult> Update([FromBody] Api.Models.DreamMaker model, CancellationToken cancellationToken)
        {
            var hostModel = new DreamMakerSettings
            {
                InstanceId = Instance.Id
            };

            DatabaseContext.DreamMakerSettings.Attach(hostModel);

            if (model.ProjectName != null)
            {
                if (!AuthenticationContext.InstanceUser.DreamMakerRights.Value.HasFlag(DreamMakerRights.SetDme))
                {
                    return(Forbid());
                }
                if (model.ProjectName.Length == 0)
                {
                    hostModel.ProjectName = null;
                }
                else
                {
                    hostModel.ProjectName = model.ProjectName;
                }
            }

            if (model.ApiValidationPort.HasValue)
            {
                if (!AuthenticationContext.InstanceUser.DreamMakerRights.Value.HasFlag(DreamMakerRights.SetApiValidationPort))
                {
                    return(Forbid());
                }
                hostModel.ApiValidationPort = model.ApiValidationPort;
            }

            await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);

            return(await Read(cancellationToken).ConfigureAwait(false));
        }
示例#2
0
        /// <inheritdoc />
                #pragma warning disable CA1506
        public async Task DeploymentProcess(
            Models.Job job,
            IDatabaseContextFactory databaseContextFactory,
            Action <int> progressReporter,
            CancellationToken cancellationToken)
        {
#pragma warning disable IDE0016 // Use 'throw' expression
            if (job == null)
            {
                throw new ArgumentNullException(nameof(job));
            }
#pragma warning restore IDE0016 // Use 'throw' expression
            if (databaseContextFactory == null)
            {
                throw new ArgumentNullException(nameof(databaseContextFactory));
            }
            if (progressReporter == null)
            {
                throw new ArgumentNullException(nameof(progressReporter));
            }

            lock (deploymentLock)
            {
                if (deploying)
                {
                    throw new JobException(ErrorCode.DreamMakerCompileJobInProgress);
                }
                deploying = true;
            }

            currentChatCallback     = null;
            currentDreamMakerOutput = null;
            Models.CompileJob compileJob = null;
            try
            {
                string   repoOwner   = null;
                string   repoName    = null;
                TimeSpan?averageSpan = null;
                Models.RepositorySettings  repositorySettings = null;
                Models.DreamDaemonSettings ddSettings         = null;
                DreamMakerSettings         dreamMakerSettings = null;
                IRepository repo = null;
                Models.RevisionInformation revInfo = null;
                await databaseContextFactory.UseContext(
                    async databaseContext =>
                {
                    averageSpan = await CalculateExpectedDeploymentTime(databaseContext, cancellationToken).ConfigureAwait(false);

                    ddSettings = await databaseContext
                                 .DreamDaemonSettings
                                 .AsQueryable()
                                 .Where(x => x.InstanceId == metadata.Id)
                                 .Select(x => new Models.DreamDaemonSettings
                    {
                        StartupTimeout = x.StartupTimeout,
                    })
                                 .FirstOrDefaultAsync(cancellationToken)
                                 .ConfigureAwait(false);
                    if (ddSettings == default)
                    {
                        throw new JobException(ErrorCode.InstanceMissingDreamDaemonSettings);
                    }

                    dreamMakerSettings = await databaseContext
                                         .DreamMakerSettings
                                         .AsQueryable()
                                         .Where(x => x.InstanceId == metadata.Id)
                                         .FirstAsync(cancellationToken)
                                         .ConfigureAwait(false);
                    if (dreamMakerSettings == default)
                    {
                        throw new JobException(ErrorCode.InstanceMissingDreamMakerSettings);
                    }

                    repositorySettings = await databaseContext
                                         .RepositorySettings
                                         .AsQueryable()
                                         .Where(x => x.InstanceId == metadata.Id)
                                         .Select(x => new Models.RepositorySettings
                    {
                        AccessToken             = x.AccessToken,
                        ShowTestMergeCommitters = x.ShowTestMergeCommitters,
                        PushTestMergeCommits    = x.PushTestMergeCommits,
                        PostTestMergeComment    = x.PostTestMergeComment
                    })
                                         .FirstOrDefaultAsync(cancellationToken)
                                         .ConfigureAwait(false);
                    if (repositorySettings == default)
                    {
                        throw new JobException(ErrorCode.InstanceMissingRepositorySettings);
                    }

                    repo = await repositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false);
                    try
                    {
                        if (repo == null)
                        {
                            throw new JobException(ErrorCode.RepoMissing);
                        }

                        if (repo.IsGitHubRepository)
                        {
                            repoOwner = repo.GitHubOwner;
                            repoName  = repo.GitHubRepoName;
                        }

                        var repoSha = repo.Head;
                        revInfo     = await databaseContext
                                      .RevisionInformations
                                      .AsQueryable()
                                      .Where(x => x.CommitSha == repoSha && x.Instance.Id == metadata.Id)
                                      .Include(x => x.ActiveTestMerges)
                                      .ThenInclude(x => x.TestMerge)
                                      .ThenInclude(x => x.MergedBy)
                                      .FirstOrDefaultAsync(cancellationToken)
                                      .ConfigureAwait(false);

                        if (revInfo == default)
                        {
                            revInfo = new Models.RevisionInformation
                            {
                                CommitSha       = repoSha,
                                OriginCommitSha = repoSha,
                                Instance        = new Models.Instance
                                {
                                    Id = metadata.Id
                                },
                                ActiveTestMerges = new List <RevInfoTestMerge>()
                            };

                            logger.LogInformation(Repository.Repository.OriginTrackingErrorTemplate, repoSha);
                            databaseContext.Instances.Attach(revInfo.Instance);
                            await databaseContext.Save(cancellationToken).ConfigureAwait(false);
                        }
                    }
                    catch
                    {
                        repo?.Dispose();
                        throw;
                    }
                })
                .ConfigureAwait(false);

                var likelyPushedTestMergeCommit =
                    repositorySettings.PushTestMergeCommits.Value &&
                    repositorySettings.AccessToken != null &&
                    repositorySettings.AccessUser != null;
                using (repo)
                    compileJob = await Compile(
                        revInfo,
                        dreamMakerSettings,
                        ddSettings.StartupTimeout.Value,
                        repo,
                        progressReporter,
                        averageSpan,
                        likelyPushedTestMergeCommit,
                        cancellationToken)
                                 .ConfigureAwait(false);

                var activeCompileJob = compileJobConsumer.LatestCompileJob();
                try
                {
                    await databaseContextFactory.UseContext(
                        async databaseContext =>
                    {
                        compileJob.Job = new Models.Job
                        {
                            Id = job.Id
                        };
                        compileJob.RevisionInformation = new Models.RevisionInformation
                        {
                            Id = revInfo.Id
                        };

                        databaseContext.Jobs.Attach(compileJob.Job);
                        databaseContext.RevisionInformations.Attach(compileJob.RevisionInformation);
                        databaseContext.CompileJobs.Add(compileJob);

                        // The difficulty with compile jobs is they have a two part commit
                        await databaseContext.Save(cancellationToken).ConfigureAwait(false);
                        logger.LogTrace("Created CompileJob {0}", compileJob.Id);
                        try
                        {
                            await compileJobConsumer.LoadCompileJob(compileJob, cancellationToken).ConfigureAwait(false);
                        }
                        catch
                        {
                            // So we need to un-commit the compile job if the above throws
                            databaseContext.CompileJobs.Remove(compileJob);

                            // DCT: Cancellation token is for job, operation must run regardless
                            await databaseContext.Save(default).ConfigureAwait(false);
                            throw;
                        }
示例#3
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;
            }
        }