Exemplo n.º 1
0
        /// <summary>
        /// Cleans up a failed compile <paramref name="job"/>.
        /// </summary>
        /// <param name="job">The running <see cref="CompileJob"/>.</param>
        /// <param name="remoteDeploymentManager">The <see cref="IRemoteDeploymentManager"/> associated with the <paramref name="job"/>.</param>
        /// <param name="exception">The <see cref="Exception"/> that was thrown.</param>
        /// <returns>A <see cref="Task"/> representing the running operation</returns>
        async Task CleanupFailedCompile(Models.CompileJob job, IRemoteDeploymentManager remoteDeploymentManager, Exception exception)
        {
            async Task CleanDir()
            {
                logger.LogTrace("Cleaning compile directory...");
                var jobPath = job.DirectoryName.ToString();

                try
                {
                    // DCT: None available
                    await ioManager.DeleteDirectory(jobPath, default).ConfigureAwait(false);
                }
                catch (Exception e)
                {
                    logger.LogWarning(e, "Error cleaning up compile directory {0}!", ioManager.ResolvePath(jobPath));
                }
            }

            // DCT: None available
            await Task.WhenAll(
                CleanDir(),
                remoteDeploymentManager.FailDeployment(
                    job,
                    FormatExceptionForUsers(exception),
                    default))
            .ConfigureAwait(false);
        }
Exemplo n.º 2
0
        /// <inheritdoc />
#pragma warning disable CA1506
        public async Task DeploymentProcess(
            Models.Job job,
            IDatabaseContextFactory databaseContextFactory,
            Action <int> progressReporter,
            CancellationToken cancellationToken)
        {
            if (job == null)
            {
                throw new ArgumentNullException(nameof(job));
            }
            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;
                Models.DreamMakerSettings  dreamMakerSettings = null;
                IRepository repo = null;
                IRemoteDeploymentManager   remoteDeploymentManager = 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);
                        }

                        remoteDeploymentManager = remoteDeploymentManagerFactory
                                                  .CreateRemoteDeploymentManager(metadata, repo.RemoteGitProvider.Value);

                        var repoSha = repo.Head;
                        repoOwner   = repo.RemoteRepositoryOwner;
                        repoName    = repo.RemoteRepositoryName;
                        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,
                                Timestamp       = await repo.TimestampCommit(repoSha, cancellationToken).ConfigureAwait(false),
                                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,
                        remoteDeploymentManager,
                        progressReporter,
                        averageSpan,
                        likelyPushedTestMergeCommit,
                        cancellationToken)
                                 .ConfigureAwait(false);

                var activeCompileJob = compileJobConsumer.LatestCompileJob();
                try
                {
                    await databaseContextFactory.UseContext(
                        async databaseContext =>
                    {
                        var fullJob    = compileJob.Job;
                        compileJob.Job = new Models.Job
                        {
                            Id = job.Id,
                        };
                        var fullRevInfo = compileJob.RevisionInformation;
                        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;
                        }
Exemplo n.º 3
0
        /// <summary>
        /// Executes and populate a given <paramref name="job"/>
        /// </summary>
        /// <param name="job">The <see cref="CompileJob"/> to run and populate</param>
        /// <param name="dreamMakerSettings">The <see cref="Api.Models.Internal.DreamMakerSettings"/> to use</param>
        /// <param name="byondLock">The <see cref="IByondExecutableLock"/> to use</param>
        /// <param name="repository">The <see cref="IRepository"/> to use</param>
        /// <param name="remoteDeploymentManager">The <see cref="IRemoteDeploymentManager"/> to use.</param>
        /// <param name="apiValidateTimeout">The timeout for validating the DMAPI</param>
        /// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
        /// <returns>A <see cref="Task"/> representing the running operation</returns>
        async Task RunCompileJob(
            Models.CompileJob job,
            Api.Models.Internal.DreamMakerSettings dreamMakerSettings,
            IByondExecutableLock byondLock,
            IRepository repository,
            IRemoteDeploymentManager remoteDeploymentManager,
            uint apiValidateTimeout,
            CancellationToken cancellationToken)
        {
            var outputDirectory = job.DirectoryName.ToString();

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

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

                // repository closed now

                // run precompile scripts
                await eventConsumer.HandleEvent(
                    EventType.CompileStart,
                    new List <string>
                {
                    resolvedOutputDirectory,
                    repoOrigin.ToString()
                },
                    cancellationToken)
                .ConfigureAwait(false);

                // determine the dme
                if (job.DmeName == null)
                {
                    logger.LogTrace("Searching for available .dmes...");
                    var foundPaths = await ioManager.GetFilesWithExtension(resolvedOutputDirectory, DmeExtension, true, cancellationToken).ConfigureAwait(false);

                    var foundPath = foundPaths.FirstOrDefault();
                    if (foundPath == default)
                    {
                        throw new JobException(ErrorCode.DreamMakerNoDme);
                    }
                    job.DmeName = foundPath.Substring(
                        resolvedOutputDirectory.Length + 1,
                        foundPath.Length - resolvedOutputDirectory.Length - DmeExtension.Length - 2);                         // +1 for . in extension
                }
                else
                {
                    var targetDme       = ioManager.ConcatPath(outputDirectory, String.Join('.', job.DmeName, DmeExtension));
                    var targetDmeExists = await ioManager.FileExists(targetDme, cancellationToken).ConfigureAwait(false);

                    if (!targetDmeExists)
                    {
                        throw new JobException(ErrorCode.DreamMakerMissingDme);
                    }
                }

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

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

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

                // verify api
                try
                {
                    if (exitCode != 0)
                    {
                        throw new JobException(
                                  ErrorCode.DreamMakerExitCode,
                                  new JobException($"Exit code: {exitCode}{Environment.NewLine}{Environment.NewLine}{job.Output}"));
                    }

                    await VerifyApi(
                        apiValidateTimeout,
                        dreamMakerSettings.ApiValidationSecurityLevel.Value,
                        job,
                        byondLock,
                        dreamMakerSettings.ApiValidationPort.Value,
                        dreamMakerSettings.RequireDMApiValidation.Value,
                        cancellationToken)
                    .ConfigureAwait(false);
                }
                catch (JobException)
                {
                    // DD never validated or compile failed
                    await eventConsumer.HandleEvent(
                        EventType.CompileFailure,
                        new List <string>
                    {
                        resolvedOutputDirectory,
                        exitCode == 0 ? "1" : "0"
                    },
                        cancellationToken)
                    .ConfigureAwait(false);

                    throw;
                }

                await eventConsumer.HandleEvent(EventType.CompileComplete, new List <string> {
                    resolvedOutputDirectory
                }, cancellationToken).ConfigureAwait(false);

                logger.LogTrace("Applying static game file symlinks...");

                // symlink in the static data
                await configuration.SymlinkStaticFilesTo(resolvedOutputDirectory, cancellationToken).ConfigureAwait(false);

                logger.LogDebug("Compile complete!");
            }
            catch (Exception ex)
            {
                await CleanupFailedCompile(job, remoteDeploymentManager, ex).ConfigureAwait(false);

                throw;
            }
        }
 public RemoteDeploymentController(IRemoteDeploymentManager hostManager, UserManager <AppUser> userManager, IAdminLogger logger) : base(userManager, logger)
 {
     _manager = hostManager;
 }