/// <inheritdoc />
        public async Task ChangeVersion(Version version, Stream customVersionStream, CancellationToken cancellationToken)
        {
            if (version == null)
            {
                throw new ArgumentNullException(nameof(version));
            }

            var versionKey = await InstallVersion(version, customVersionStream, cancellationToken).ConfigureAwait(false);

            using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
            {
                await ioManager.WriteAllBytes(ActiveVersionFileName, Encoding.UTF8.GetBytes(versionKey), cancellationToken).ConfigureAwait(false);

                await eventConsumer.HandleEvent(
                    EventType.ByondActiveVersionChange,
                    new List <string>
                {
                    ActiveVersion != null
                                                        ? VersionKey(ActiveVersion, true)
                                                        : null,
                    versionKey,
                },
                    cancellationToken)
                .ConfigureAwait(false);

                // We reparse the version key because it could be changed after a custom install.
                ActiveVersion = Version.Parse(versionKey);
            }
        }
Example #2
0
        private async Task Consume()
        {
            var token = _cancellationToken.Token;
            var serializerSettings = JsonConverterExtensions.CreateSettings();

            while (!token.IsCancellationRequested)
            {
                if (_consumer.Consume(out var message, 1000))
                {
                    var sw = Stopwatch.StartNew();
                    try
                    {
                        dynamic evnt = JsonConvert.DeserializeObject(message.Value, serializerSettings);
                        if (evnt as IEvent == null)
                        {
                            continue;
                        }
                        evnt.Enrich(message.ToEventMetadata());
                        await _handler.HandleEvent(evnt);

                        _logger.Debug($"Processed event {evnt.GetType().FullName} in {sw.ElapsedMilliseconds} ms.");
                    }
                    catch (JsonSerializationException e)
                    {
                        _logger.Error(e, "Could not deserialize event!");
                    }
                    catch (Exception e)
                    {
                        _logger.Error(e, "Could not process event!");
                    }
                }
            }
        }
        /// <summary>
        /// Pull the repository and compile for every set of given <paramref name="minutes"/>
        /// </summary>
        /// <param name="minutes">How many minutes the operation should repeat. Does not include running time</param>
        /// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
        /// <returns>A <see cref="Task"/> representing the running operation</returns>
                #pragma warning disable CA1502 // TODO: Decomplexify
        async Task TimerLoop(uint minutes, CancellationToken cancellationToken)
        {
            while (true)
            {
                try
                {
                    await Task.Delay(TimeSpan.FromMinutes(minutes > Int32.MaxValue ? Int32.MaxValue : (int)minutes), cancellationToken).ConfigureAwait(false);

                    logger.LogInformation("Beginning auto update...");
                    await eventConsumer.HandleEvent(EventType.InstanceAutoUpdateStart, new List <string>(), cancellationToken).ConfigureAwait(false);

                    try
                    {
                        Models.User user = null;
                        await databaseContextFactory.UseContext(async (db) => user = await db.Users.Where(x => x.CanonicalName == Api.Models.User.AdminName.ToUpperInvariant()).FirstAsync(cancellationToken).ConfigureAwait(false)).ConfigureAwait(false);

                        var repositoryUpdateJob = new Job
                        {
                            Instance = new Models.Instance
                            {
                                Id = metadata.Id
                            },
                            Description      = "Scheduled repository update",
                            CancelRightsType = RightsType.Repository,
                            CancelRight      = (ulong)RepositoryRights.CancelPendingChanges,
                            StartedBy        = user
                        };

                        string deploySha = null;
                        await jobManager.RegisterOperation(repositoryUpdateJob, async (paramJob, databaseContext, progressReporter, jobCancellationToken) =>
                        {
                            var repositorySettingsTask = databaseContext.RepositorySettings.Where(x => x.InstanceId == metadata.Id).FirstAsync(jobCancellationToken);

                            // assume 5 steps with synchronize
                            const int ProgressSections = 7;
                            const int ProgressStep     = 100 / ProgressSections;

                            const int NumSteps = 3;
                            var doneSteps      = 0;

                            Action <int> NextProgressReporter()
                            {
                                var tmpDoneSteps = doneSteps;
                                ++doneSteps;
                                return(progress => progressReporter((progress + (100 * tmpDoneSteps)) / NumSteps));
                            }

                            using (var repo = await RepositoryManager.LoadRepository(jobCancellationToken).ConfigureAwait(false))
                            {
                                if (repo == null)
                                {
                                    logger.LogTrace("Aborting repo update, no repository!");
                                    return;
                                }

                                var startSha = repo.Head;
                                if (!repo.Tracking)
                                {
                                    logger.LogTrace("Aborting repo update, active ref not tracking any remote branch!");
                                    deploySha = startSha;
                                    return;
                                }

                                var repositorySettings = await repositorySettingsTask.ConfigureAwait(false);

                                // the main point of auto update is to pull the remote
                                await repo.FetchOrigin(repositorySettings.AccessUser, repositorySettings.AccessToken, NextProgressReporter(), jobCancellationToken).ConfigureAwait(false);

                                RevisionInformation currentRevInfo = null;
                                bool hasDbChanges = false;

                                Task <RevisionInformation> LoadRevInfo() => databaseContext.RevisionInformations
                                .Where(x => x.CommitSha == startSha && x.Instance.Id == metadata.Id)
                                .Include(x => x.ActiveTestMerges).ThenInclude(x => x.TestMerge)
                                .FirstOrDefaultAsync(cancellationToken);

                                async Task UpdateRevInfo(string currentHead, bool onOrigin)
                                {
                                    if (currentRevInfo == null)
                                    {
                                        currentRevInfo = await LoadRevInfo().ConfigureAwait(false);
                                    }

                                    if (currentRevInfo == default)
                                    {
                                        logger.LogWarning(Repository.Repository.OriginTrackingErrorTemplate, currentHead);
                                        onOrigin = true;
                                    }

                                    var attachedInstance = new Models.Instance
                                    {
                                        Id = metadata.Id
                                    };
                                    var oldRevInfo = currentRevInfo;
                                    currentRevInfo = new RevisionInformation
                                    {
                                        CommitSha       = currentHead,
                                        OriginCommitSha = onOrigin ? currentHead : oldRevInfo.OriginCommitSha,
                                        Instance        = attachedInstance
                                    };
                                    if (!onOrigin)
                                    {
                                        currentRevInfo.ActiveTestMerges = new List <RevInfoTestMerge>(oldRevInfo.ActiveTestMerges);
                                    }

                                    databaseContext.Instances.Attach(attachedInstance);
                                    databaseContext.RevisionInformations.Add(currentRevInfo);
                                    hasDbChanges = true;
                                }

                                // take appropriate auto update actions
                                bool shouldSyncTracked;
                                if (repositorySettings.AutoUpdatesKeepTestMerges.Value)
                                {
                                    logger.LogTrace("Preserving test merges...");

                                    var currentRevInfoTask = LoadRevInfo();

                                    var result = await repo.MergeOrigin(repositorySettings.CommitterName, repositorySettings.CommitterEmail, NextProgressReporter(), jobCancellationToken).ConfigureAwait(false);

                                    if (!result.HasValue)
                                    {
                                        throw new JobException("Merge conflict while preserving test merges!");
                                    }

                                    currentRevInfo = await currentRevInfoTask.ConfigureAwait(false);

                                    var lastRevInfoWasOriginCommit = currentRevInfo == default || currentRevInfo.CommitSha == currentRevInfo.OriginCommitSha;
                                    var stillOnOrigin = result.Value && lastRevInfoWasOriginCommit;

                                    var currentHead = repo.Head;
                                    if (currentHead != startSha)
                                    {
                                        await UpdateRevInfo(currentHead, stillOnOrigin).ConfigureAwait(false);
                                        shouldSyncTracked = stillOnOrigin;
                                    }
                                    else
                                    {
                                        shouldSyncTracked = false;
                                    }
                                }
                                else
                                {
                                    logger.LogTrace("Not preserving test merges...");
                                    await repo.ResetToOrigin(NextProgressReporter(), jobCancellationToken).ConfigureAwait(false);

                                    var currentHead = repo.Head;

                                    currentRevInfo = await databaseContext.RevisionInformations
                                                     .Where(x => x.CommitSha == currentHead && x.Instance.Id == metadata.Id)
                                                     .FirstOrDefaultAsync(jobCancellationToken).ConfigureAwait(false);

                                    if (currentHead != startSha && currentRevInfo != default)
                                    {
                                        await UpdateRevInfo(currentHead, true).ConfigureAwait(false);
                                    }

                                    shouldSyncTracked = true;
                                }

                                // synch if necessary
                                if (repositorySettings.AutoUpdatesSynchronize.Value && startSha != repo.Head)
                                {
                                    var pushedOrigin = await repo.Sychronize(repositorySettings.AccessUser, repositorySettings.AccessToken, repositorySettings.CommitterName, repositorySettings.CommitterEmail, NextProgressReporter(), shouldSyncTracked, jobCancellationToken).ConfigureAwait(false);
                                    var currentHead  = repo.Head;
                                    if (currentHead != currentRevInfo.CommitSha)
                                    {
                                        await UpdateRevInfo(currentHead, pushedOrigin).ConfigureAwait(false);
                                    }
                                }

                                if (hasDbChanges)
                                {
                                    try
                                    {
                                        await databaseContext.Save(cancellationToken).ConfigureAwait(false);
                                    }
                                    catch
                                    {
                                        await repo.ResetToSha(startSha, progressReporter, default).ConfigureAwait(false);
                                        throw;
                                    }
                                }

                                progressReporter(5 * ProgressStep);
                                deploySha = repo.Head;
                            }
                        }, cancellationToken).ConfigureAwait(false);

                        await jobManager.WaitForJobCompletion(repositoryUpdateJob, user, cancellationToken, default).ConfigureAwait(false);

                        if (deploySha == null)
                        {
                            logger.LogTrace("Aborting auto update, repository error!");
                            continue;
                        }

                        if (deploySha == LatestCompileJob()?.RevisionInformation.CommitSha)
                        {
                            logger.LogTrace("Aborting auto update, same revision as latest CompileJob");
                            continue;
                        }

                        // finally set up the job
                        var compileProcessJob = new Job
                        {
                            StartedBy        = user,
                            Instance         = repositoryUpdateJob.Instance,
                            Description      = "Scheduled code deployment",
                            CancelRightsType = RightsType.DreamMaker,
                            CancelRight      = (ulong)DreamMakerRights.CancelCompile
                        };

                        await jobManager.RegisterOperation(compileProcessJob, CompileProcess, cancellationToken).ConfigureAwait(false);

                        await jobManager.WaitForJobCompletion(compileProcessJob, user, cancellationToken, default).ConfigureAwait(false);
                    }
                    catch (OperationCanceledException)
                    {
                        logger.LogDebug("Cancelled auto update job!");
                        throw;
                    }
                    catch (Exception e)
                    {
                        logger.LogWarning("Error in auto update loop! Exception: {0}", e);
                        continue;
                    }
                }
                catch (OperationCanceledException)
                {
                    break;
                }
            }

            logger.LogTrace("Leaving auto update loop...");
        }
Example #4
0
        /// <summary>
        /// Executes and populate a given <paramref name="job"/>
        /// </summary>
        /// <param name="job">The <see cref="Models.CompileJob"/> to run and populate</param>
        /// <param name="dreamMakerSettings">The <see cref="Api.Models.DreamMaker"/> settings 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="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.DreamMaker dreamMakerSettings, IByondExecutableLock byondLock, IRepository repository, uint apiValidateTimeout, CancellationToken cancellationToken)
        {
            var jobPath = job.DirectoryName.ToString();

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

            try
            {
                var dirA = ioManager.ConcatPath(jobPath, ADirectoryName);
                var dirB = ioManager.ConcatPath(jobPath, BDirectoryName);

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

                // repository closed now

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

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

                    var foundPath = foundPaths.FirstOrDefault();
                    if (foundPath == default)
                    {
                        throw new JobException("Unable to find any .dme!");
                    }
                    var dmeWithExtension = ioManager.GetFileName(foundPath);
                    job.DmeName = dmeWithExtension.Substring(0, dmeWithExtension.Length - DmeExtension.Length - 1);
                }
                else
                {
                    var targetDme       = ioManager.ConcatPath(dirA, String.Join('.', job.DmeName, DmeExtension));
                    var targetDmeExists = await ioManager.FileExists(targetDme, cancellationToken).ConfigureAwait(false);

                    if (!targetDmeExists)
                    {
                        throw new JobException("Unable to locate specified .dme!");
                    }
                }

                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(String.Format(CultureInfo.InvariantCulture, "DM exited with a non-zero code: {0}{1}{2}", exitCode, Environment.NewLine, job.Output));
                    }

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

                    throw;
                }

                logger.LogTrace("Running post compile event...");
                await eventConsumer.HandleEvent(EventType.CompileComplete, new List <string> {
                    resolvedADirectory
                }, cancellationToken).ConfigureAwait(false);

                logger.LogTrace("Duplicating compiled game...");

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

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

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

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

                await chat.SendUpdateMessage(String.Format(CultureInfo.InvariantCulture, "Deployment complete!{0}", watchdog.Running ? " Changes will be applied on next server reboot." : String.Empty), cancellationToken).ConfigureAwait(false);

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

                throw;
            }
        }
Example #5
0
        /// <inheritdoc />
        public async Task <Models.CompileJob> Compile(Models.RevisionInformation revisionInformation, Api.Models.DreamMaker dreamMakerSettings, uint apiValidateTimeout, IRepository repository, Action <int> progressReporter, TimeSpan?estimatedDuration, 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 (progressReporter == null)
            {
                throw new ArgumentNullException(nameof(progressReporter));
            }

            if (dreamMakerSettings.ApiValidationSecurityLevel == DreamDaemonSecurity.Ultrasafe)
            {
                throw new ArgumentOutOfRangeException(nameof(dreamMakerSettings), dreamMakerSettings, "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 (compiling)
                {
                    throw new JobException("There is already a compile job in progress!");
                }
                compiling = true;
            }

            using (var progressCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
            {
                async Task ProgressTask()
                {
                    if (!estimatedDuration.HasValue)
                    {
                        return;
                    }

                    progressReporter(0);
                    var ct            = progressCts.Token;
                    var sleepInterval = estimatedDuration.Value / 100;

                    try
                    {
                        for (var I = 0; I < 99; ++I)
                        {
                            await Task.Delay(sleepInterval, progressCts.Token).ConfigureAwait(false);

                            progressReporter(I + 1);
                        }
                    }
                    catch (OperationCanceledException) { }
                }

                var progressTask = ProgressTask();
                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...");
                            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);

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

                            //determine the dme
                            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);
                            }
                            else if (!await ioManager.FileExists(ioManager.ConcatPath(dirA, String.Join('.', job.DmeName, DmeExtension)), cancellationToken).ConfigureAwait(false))
                            {
                                throw new JobException("Unable to locate specified .dme!");
                            }

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

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

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

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

                            try
                            {
                                if (exitCode != 0)
                                {
                                    throw new JobException(String.Format(CultureInfo.InvariantCulture, "DM exited with a non-zero code: {0}{1}{2}", exitCode, Environment.NewLine, job.Output));
                                }

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

                                throw;
                            }

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

                            logger.LogTrace("Duplicating compiled game...");

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

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

                            //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(String.Format(CultureInfo.InvariantCulture, "Deployment complete!{0}", watchdog.Running ? " Changes will be applied on next server reboot." : String.Empty), 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
                {
                    compiling = false;
                    progressCts.Cancel();
                    await progressTask.ConfigureAwait(false);
                }
            }
        }
        /// <inheritdoc />
                #pragma warning disable CA1506 // TODO: Decomplexify
        public async Task <bool?> AddTestMerge(TestMergeParameters testMergeParameters, string committerName, string committerEmail, string username, string password, Action <int> progressReporter, CancellationToken cancellationToken)
        {
            if (testMergeParameters == null)
            {
                throw new ArgumentNullException(nameof(testMergeParameters));
            }
            if (committerName == null)
            {
                throw new ArgumentNullException(nameof(committerName));
            }
            if (committerEmail == null)
            {
                throw new ArgumentNullException(nameof(committerEmail));
            }
            if (progressReporter == null)
            {
                throw new ArgumentNullException(nameof(progressReporter));
            }

            logger.LogDebug("Begin AddTestMerge: #{0} at {1} ({4}) by <{2} ({3})>", testMergeParameters.Number, testMergeParameters.PullRequestRevision?.Substring(0, 7), committerName, committerEmail, testMergeParameters.Comment);

            if (!IsGitHubRepository)
            {
                throw new InvalidOperationException("Test merging is only available on GitHub hosted origin repositories!");
            }

            var commitMessage = String.Format(CultureInfo.InvariantCulture, "Test merge of pull request #{0}{1}{2}", testMergeParameters.Number.Value, testMergeParameters.Comment != null ? Environment.NewLine : String.Empty, testMergeParameters.Comment ?? String.Empty);

            var prBranchName    = String.Format(CultureInfo.InvariantCulture, "pr-{0}", testMergeParameters.Number);
            var localBranchName = String.Format(CultureInfo.InvariantCulture, "pull/{0}/headrefs/heads/{1}", testMergeParameters.Number, prBranchName);

            var refSpec     = String.Format(CultureInfo.InvariantCulture, "pull/{0}/head:{1}", testMergeParameters.Number, prBranchName);
            var refSpecList = new List <string> {
                refSpec
            };
            var logMessage = String.Format(CultureInfo.InvariantCulture, "Merge remote pull request #{0}", testMergeParameters.Number);

            var originalCommit = repository.Head;

            MergeResult result = null;

            var sig = new Signature(new Identity(committerName, committerEmail), DateTimeOffset.Now);
            await Task.Factory.StartNew(() =>
            {
                try
                {
                    try
                    {
                        logger.LogTrace("Fetching refspec {0}...", refSpec);

                        var remote = repository.Network.Remotes.First();
                        progressReporter(0);
                        Commands.Fetch((LibGit2Sharp.Repository)repository, remote.Name, refSpecList, new FetchOptions
                        {
                            Prune              = true,
                            OnProgress         = (a) => !cancellationToken.IsCancellationRequested,
                            OnTransferProgress = (a) =>
                            {
                                var percentage = 50 * (((float)a.IndexedObjects + a.ReceivedObjects) / (a.TotalObjects * 2));
                                progressReporter((int)percentage);
                                return(!cancellationToken.IsCancellationRequested);
                            },
                            OnUpdateTips        = (a, b, c) => !cancellationToken.IsCancellationRequested,
                            CredentialsProvider = credentialsProvider.GenerateHandler(username, password)
                        }, logMessage);
                    }
                    catch (UserCancelledException) { }

                    cancellationToken.ThrowIfCancellationRequested();

                    repository.RemoveUntrackedFiles();

                    cancellationToken.ThrowIfCancellationRequested();

                    testMergeParameters.PullRequestRevision = repository.Lookup(testMergeParameters.PullRequestRevision ?? localBranchName).Sha;

                    cancellationToken.ThrowIfCancellationRequested();

                    logger.LogTrace("Merging {0} into {1}...", testMergeParameters.PullRequestRevision.Substring(0, 7), Reference);

                    result = repository.Merge(testMergeParameters.PullRequestRevision, sig, new MergeOptions
                    {
                        CommitOnSuccess     = commitMessage == null,
                        FailOnConflict      = true,
                        FastForwardStrategy = FastForwardStrategy.NoFastForward,
                        SkipReuc            = true,
                        OnCheckoutProgress  = (a, completedSteps, totalSteps) => progressReporter(50 + ((int)(((float)completedSteps) / totalSteps * 50)))
                    });
                }
                finally
                {
                    repository.Branches.Remove(localBranchName);
                }

                cancellationToken.ThrowIfCancellationRequested();

                if (result.Status == MergeStatus.Conflicts)
                {
                    var revertTo = originalCommit.CanonicalName ?? originalCommit.Tip.Sha;
                    logger.LogDebug("Merge conflict, aborting and reverting to {0}", revertTo);
                    RawCheckout(revertTo, progressReporter, cancellationToken);
                    cancellationToken.ThrowIfCancellationRequested();
                }

                repository.RemoveUntrackedFiles();
            }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);

            if (result.Status == MergeStatus.Conflicts)
            {
                await eventConsumer.HandleEvent(EventType.RepoMergeConflict, new List <string> {
                    originalCommit.Tip.Sha, testMergeParameters.PullRequestRevision, originalCommit.FriendlyName ?? UnknownReference, prBranchName
                }, cancellationToken).ConfigureAwait(false);

                return(null);
            }

            if (commitMessage != null && result.Status != MergeStatus.UpToDate)
            {
                logger.LogTrace("Committing merge: \"{0}\"...", commitMessage);
                await Task.Factory.StartNew(() => repository.Commit(commitMessage, sig, sig, new CommitOptions
                {
                    PrettifyMessage = true
                }), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
            }

            await eventConsumer.HandleEvent(EventType.RepoMergePullRequest, new List <string> {
                testMergeParameters.Number.ToString(), testMergeParameters.PullRequestRevision, testMergeParameters.Comment
            }, cancellationToken).ConfigureAwait(false);

            return(result.Status != MergeStatus.NonFastForward);
        }
Example #7
0
        /// <summary>
        /// Installs a BYOND <paramref name="version"/> if it isn't already
        /// </summary>
        /// <param name="version">The BYOND <see cref="Version"/> to install</param>
        /// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
        /// <returns>A <see cref="Task"/> representing the running operation</returns>
        async Task InstallVersion(Version version, CancellationToken cancellationToken)
        {
            var  ourTcs = new TaskCompletionSource <object>();
            Task inProgressTask;

            var  versionKey = VersionKey(version);
            bool installed;

            lock (installedVersions)
            {
                installed = installedVersions.TryGetValue(versionKey, out inProgressTask);
                if (!installed)
                {
                    installedVersions.Add(versionKey, ourTcs.Task);
                }
            }

            if (installed)
            {
                using (cancellationToken.Register(() => ourTcs.SetCanceled()))
                {
                    await Task.WhenAny(ourTcs.Task, inProgressTask).ConfigureAwait(false);

                    cancellationToken.ThrowIfCancellationRequested();
                    return;
                }
            }
            else
            {
                logger.LogDebug("Requested BYOND version {0} not currently installed. Doing so now...");
            }

            // okay up to us to install it then
            try
            {
                await eventConsumer.HandleEvent(EventType.ByondInstallStart, new List <string> {
                    versionKey
                }, cancellationToken).ConfigureAwait(false);

                var downloadTask = byondInstaller.DownloadVersion(version, cancellationToken);

                await ioManager.DeleteDirectory(versionKey, cancellationToken).ConfigureAwait(false);

                try
                {
                    var download = await downloadTask.ConfigureAwait(false);

                    await ioManager.CreateDirectory(versionKey, cancellationToken).ConfigureAwait(false);

                    var extractPath = ioManager.ResolvePath(versionKey);
                    logger.LogTrace("Extracting downloaded BYOND zip to {0}...", extractPath);
                    await ioManager.ZipToDirectory(extractPath, download, cancellationToken).ConfigureAwait(false);

                    await byondInstaller.InstallByond(extractPath, version, cancellationToken).ConfigureAwait(false);

                    // make sure to do this last because this is what tells us we have a valid version in the future
                    await ioManager.WriteAllBytes(ioManager.ConcatPath(versionKey, VersionFileName), Encoding.UTF8.GetBytes(version.ToString()), cancellationToken).ConfigureAwait(false);
                }
                catch (WebException e)
                {
                    // since the user can easily provide non-exitent version numbers, we'll turn this into a JobException
                    throw new JobException(ErrorCode.ByondDownloadFail, e);
                }
                catch (OperationCanceledException)
                {
                    throw;
                }
                catch
                {
                    await ioManager.DeleteDirectory(versionKey, cancellationToken).ConfigureAwait(false);

                    throw;
                }

                ourTcs.SetResult(null);
            }
            catch (Exception e)
            {
                if (!(e is OperationCanceledException))
                {
                    await eventConsumer.HandleEvent(EventType.ByondInstallFail, new List <string> {
                        e.Message
                    }, cancellationToken).ConfigureAwait(false);
                }
                lock (installedVersions)
                    installedVersions.Remove(versionKey);
                ourTcs.SetException(e);
                throw;
            }
        }
 private static void PublishToConsumer <TEventMessage>(IEventConsumer <TEventMessage> x, TEventMessage eventMessage)
 {
     x.HandleEvent(eventMessage);
 }
Example #9
0
        /// <inheritdoc />
#pragma warning disable CA1506 // TODO: Decomplexify
        public async Task <bool?> AddTestMerge(
            TestMergeParameters testMergeParameters,
            string committerName,
            string committerEmail,
            string username,
            string password,
            Action <int> progressReporter,
            CancellationToken cancellationToken)
        {
            if (testMergeParameters == null)
            {
                throw new ArgumentNullException(nameof(testMergeParameters));
            }
            if (committerName == null)
            {
                throw new ArgumentNullException(nameof(committerName));
            }
            if (committerEmail == null)
            {
                throw new ArgumentNullException(nameof(committerEmail));
            }
            if (progressReporter == null)
            {
                throw new ArgumentNullException(nameof(progressReporter));
            }

            logger.LogDebug(
                "Begin AddTestMerge: #{0} at {1} ({2}) by <{3} ({4})>",
                testMergeParameters.Number,
                testMergeParameters.TargetCommitSha?.Substring(0, 7),
                testMergeParameters.Comment,
                committerName,
                committerEmail);

            if (RemoteGitProvider == Api.Models.RemoteGitProvider.Unknown)
            {
                throw new InvalidOperationException("Cannot test merge with an Unknown RemoteGitProvider!");
            }

            var commitMessage = String.Format(
                CultureInfo.InvariantCulture,
                "TGS Test merge #{0}{1}{2}",
                testMergeParameters.Number,
                testMergeParameters.Comment != null
                                        ? Environment.NewLine
                                        : String.Empty,
                testMergeParameters.Comment ?? String.Empty);

            var testMergeBranchName = String.Format(CultureInfo.InvariantCulture, "tm-{0}", testMergeParameters.Number);
            var localBranchName     = String.Format(CultureInfo.InvariantCulture, gitRemoteFeatures.TestMergeLocalBranchNameFormatter, testMergeParameters.Number, testMergeBranchName);

            var refSpec     = String.Format(CultureInfo.InvariantCulture, gitRemoteFeatures.TestMergeRefSpecFormatter, testMergeParameters.Number, testMergeBranchName);
            var refSpecList = new List <string> {
                refSpec
            };
            var logMessage = String.Format(CultureInfo.InvariantCulture, "Test merge #{0}", testMergeParameters.Number);

            var originalCommit = libGitRepo.Head;

            MergeResult result = null;

            var sig = new Signature(new Identity(committerName, committerEmail), DateTimeOffset.UtcNow);
            await Task.Factory.StartNew(
                () =>
            {
                try
                {
                    try
                    {
                        logger.LogTrace("Fetching refspec {0}...", refSpec);

                        var remote = libGitRepo.Network.Remotes.First();
                        progressReporter(0);
                        commands.Fetch(
                            libGitRepo,
                            refSpecList,
                            remote,
                            new FetchOptions
                        {
                            Prune              = true,
                            OnProgress         = (a) => !cancellationToken.IsCancellationRequested,
                            OnTransferProgress = (a) =>
                            {
                                var percentage = 50 * (((float)a.IndexedObjects + a.ReceivedObjects) / (a.TotalObjects * 2));
                                progressReporter((int)percentage);
                                return(!cancellationToken.IsCancellationRequested);
                            },
                            OnUpdateTips        = (a, b, c) => !cancellationToken.IsCancellationRequested,
                            CredentialsProvider = credentialsProvider.GenerateCredentialsHandler(username, password),
                        },
                            logMessage);
                    }
                    catch (UserCancelledException)
                    {
                    }
                    catch (LibGit2SharpException ex)
                    {
                        CheckBadCredentialsException(ex);
                    }

                    cancellationToken.ThrowIfCancellationRequested();

                    libGitRepo.RemoveUntrackedFiles();

                    cancellationToken.ThrowIfCancellationRequested();

                    testMergeParameters.TargetCommitSha = libGitRepo.Lookup(testMergeParameters.TargetCommitSha ?? localBranchName).Sha;

                    cancellationToken.ThrowIfCancellationRequested();

                    logger.LogTrace("Merging {0} into {1}...", testMergeParameters.TargetCommitSha.Substring(0, 7), Reference);

                    result = libGitRepo.Merge(testMergeParameters.TargetCommitSha, sig, new MergeOptions
                    {
                        CommitOnSuccess     = commitMessage == null,
                        FailOnConflict      = true,
                        FastForwardStrategy = FastForwardStrategy.NoFastForward,
                        SkipReuc            = true,
                        OnCheckoutProgress  = (a, completedSteps, totalSteps) => progressReporter(50 + ((int)(((float)completedSteps) / totalSteps * 50))),
                    });
                }
                finally
                {
                    libGitRepo.Branches.Remove(localBranchName);
                }

                cancellationToken.ThrowIfCancellationRequested();

                if (result.Status == MergeStatus.Conflicts)
                {
                    var revertTo = originalCommit.CanonicalName ?? originalCommit.Tip.Sha;
                    logger.LogDebug("Merge conflict, aborting and reverting to {0}", revertTo);
                    RawCheckout(revertTo, progressReporter, cancellationToken);
                    cancellationToken.ThrowIfCancellationRequested();
                }

                libGitRepo.RemoveUntrackedFiles();
            },
                cancellationToken,
                DefaultIOManager.BlockingTaskCreationOptions,
                TaskScheduler.Current)
            .ConfigureAwait(false);

            if (result.Status == MergeStatus.Conflicts)
            {
                await eventConsumer.HandleEvent(
                    EventType.RepoMergeConflict,
                    new List <string>
                {
                    originalCommit.Tip.Sha,
                    testMergeParameters.TargetCommitSha,
                    originalCommit.FriendlyName ?? UnknownReference,
                    testMergeBranchName,
                },
                    cancellationToken)
                .ConfigureAwait(false);

                return(null);
            }

            if (commitMessage != null && result.Status != MergeStatus.UpToDate)
            {
                logger.LogTrace("Committing merge: \"{0}\"...", commitMessage);
                await Task.Factory.StartNew(
                    () => libGitRepo.Commit(commitMessage, sig, sig, new CommitOptions
                {
                    PrettifyMessage = true,
                }),
                    cancellationToken,
                    DefaultIOManager.BlockingTaskCreationOptions,
                    TaskScheduler.Current)
                .ConfigureAwait(false);
            }

            await eventConsumer.HandleEvent(
                EventType.RepoAddTestMerge,
                new List <string>
            {
                testMergeParameters.Number.ToString(CultureInfo.InvariantCulture),
                testMergeParameters.TargetCommitSha,
                testMergeParameters.Comment,
            },
                cancellationToken)
            .ConfigureAwait(false);

            return(result.Status != MergeStatus.NonFastForward);
        }
Example #10
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;
            }
        }
Example #11
0
        /// <summary>
        /// Installs a BYOND <paramref name="version"/> if it isn't already
        /// </summary>
        /// <param name="version">The BYOND <see cref="Version"/> to install</param>
        /// <param name="customVersionStream">Custom zip file <see cref="Stream"/> to use. Will cause a <see cref="Version.Build"/> number to be added.</param>
        /// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
        /// <returns>A <see cref="Task"/> representing the running operation</returns>
        async Task <string> InstallVersion(Version version, Stream customVersionStream, CancellationToken cancellationToken)
        {
            var    ourTcs = new TaskCompletionSource <object>();
            Task   inProgressTask;
            string versionKey;
            bool   installed;

            lock (installedVersions)
            {
                if (customVersionStream != null)
                {
                    int customInstallationNumber = 1;
                    do
                    {
                        versionKey = $"{VersionKey(version, false)}.{customInstallationNumber++}";
                    }while (installedVersions.ContainsKey(versionKey));
                }
                else
                {
                    versionKey = VersionKey(version, true);
                }

                installed = installedVersions.TryGetValue(versionKey, out inProgressTask);
                if (!installed)
                {
                    installedVersions.Add(versionKey, ourTcs.Task);
                }
            }

            if (installed)
            {
                using (cancellationToken.Register(() => ourTcs.SetCanceled()))
                {
                    await Task.WhenAny(ourTcs.Task, inProgressTask).ConfigureAwait(false);

                    cancellationToken.ThrowIfCancellationRequested();
                    return(versionKey);
                }
            }

            if (customVersionStream != null)
            {
                logger.LogInformation("Installing custom BYOND version as {0}...", versionKey);
            }
            else if (version.Build > 0)
            {
                throw new JobException(ErrorCode.ByondNonExistentCustomVersion);
            }
            else
            {
                logger.LogDebug("Requested BYOND version {0} not currently installed. Doing so now...");
            }

            // okay up to us to install it then
            try
            {
                await eventConsumer.HandleEvent(EventType.ByondInstallStart, new List <string> {
                    versionKey
                }, cancellationToken).ConfigureAwait(false);

                var extractPath = ioManager.ResolvePath(versionKey);
                async Task DirectoryCleanup()
                {
                    await ioManager.DeleteDirectory(extractPath, cancellationToken).ConfigureAwait(false);

                    await ioManager.CreateDirectory(extractPath, cancellationToken).ConfigureAwait(false);
                }

                var directoryCleanupTask = DirectoryCleanup();
                try
                {
                    Stream versionZipStream;
                    Stream downloadedStream = null;
                    if (customVersionStream == null)
                    {
                        var bytes = await byondInstaller.DownloadVersion(version, cancellationToken).ConfigureAwait(false);

                        downloadedStream = new MemoryStream(bytes);
                        versionZipStream = downloadedStream;
                    }
                    else
                    {
                        versionZipStream = customVersionStream;
                    }

                    using (downloadedStream)
                    {
                        await directoryCleanupTask.ConfigureAwait(false);

                        logger.LogTrace("Extracting downloaded BYOND zip to {0}...", extractPath);
                        await ioManager.ZipToDirectory(extractPath, versionZipStream, cancellationToken).ConfigureAwait(false);
                    }

                    await byondInstaller.InstallByond(extractPath, version, cancellationToken).ConfigureAwait(false);

                    // make sure to do this last because this is what tells us we have a valid version in the future
                    await ioManager.WriteAllBytes(
                        ioManager.ConcatPath(versionKey, VersionFileName),
                        Encoding.UTF8.GetBytes(versionKey),
                        cancellationToken)
                    .ConfigureAwait(false);
                }
                catch (WebException e)
                {
                    // since the user can easily provide non-exitent version numbers, we'll turn this into a JobException
                    throw new JobException(ErrorCode.ByondDownloadFail, e);
                }
                catch (OperationCanceledException)
                {
                    throw;
                }
                catch
                {
                    await ioManager.DeleteDirectory(versionKey, cancellationToken).ConfigureAwait(false);

                    throw;
                }

                ourTcs.SetResult(null);
            }
            catch (Exception e)
            {
                if (!(e is OperationCanceledException))
                {
                    await eventConsumer.HandleEvent(EventType.ByondInstallFail, new List <string> {
                        e.Message
                    }, cancellationToken).ConfigureAwait(false);
                }
                lock (installedVersions)
                    installedVersions.Remove(versionKey);
                ourTcs.SetException(e);
                throw;
            }

            return(versionKey);
        }
Example #12
0
        /// <inheritdoc />
        public async Task <bool?> AddTestMerge(TestMergeParameters testMergeParameters, string committerName, string committerEmail, string username, string password, Action <int> progressReporter, CancellationToken cancellationToken)
        {
            if (testMergeParameters == null)
            {
                throw new ArgumentNullException(nameof(testMergeParameters));
            }

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

            if (!IsGitHubRepository)
            {
                throw new InvalidOperationException("Test merging is only available on GitHub hosted origin repositories!");
            }

            var commitMessage = String.Format(CultureInfo.InvariantCulture, "Test merge of pull request #{0}{1}{2}", testMergeParameters.Number.Value, testMergeParameters.Comment != null ? Environment.NewLine : String.Empty, testMergeParameters.Comment ?? String.Empty);


            var prBranchName    = String.Format(CultureInfo.InvariantCulture, "pr-{0}", testMergeParameters.Number);
            var localBranchName = String.Format(CultureInfo.InvariantCulture, "pull/{0}/headrefs/heads/{1}", testMergeParameters.Number, prBranchName);

            var Refspec = new List <string> {
                String.Format(CultureInfo.InvariantCulture, "pull/{0}/head:{1}", testMergeParameters.Number, prBranchName)
            };
            var logMessage = String.Format(CultureInfo.InvariantCulture, "Merge remote pull request #{0}", testMergeParameters.Number);

            var originalCommit = repository.Head;

            MergeResult result = null;

            var sig = new Signature(new Identity(committerName, committerEmail), DateTimeOffset.Now);
            await Task.Factory.StartNew(() =>
            {
                try
                {
                    try
                    {
                        var remote = repository.Network.Remotes.First();
                        Commands.Fetch((LibGit2Sharp.Repository)repository, remote.Name, Refspec, new FetchOptions
                        {
                            Prune              = true,
                            OnProgress         = (a) => !cancellationToken.IsCancellationRequested,
                            OnTransferProgress = (a) =>
                            {
                                var percentage = 100 * (((float)a.IndexedObjects + a.ReceivedObjects) / (a.TotalObjects * 2));
                                progressReporter?.Invoke((int)percentage);
                                return(!cancellationToken.IsCancellationRequested);
                            },
                            OnUpdateTips        = (a, b, c) => !cancellationToken.IsCancellationRequested,
                            CredentialsProvider = (a, b, c) => username != null ? (Credentials) new UsernamePasswordCredentials
                            {
                                Username = username,
                                Password = password
                            } : new DefaultCredentials()
                        }, logMessage);
                    }
                    catch (UserCancelledException) { }

                    cancellationToken.ThrowIfCancellationRequested();

                    testMergeParameters.PullRequestRevision = repository.Lookup(testMergeParameters.PullRequestRevision ?? localBranchName).Sha;

                    cancellationToken.ThrowIfCancellationRequested();

                    result = repository.Merge(testMergeParameters.PullRequestRevision, sig, new MergeOptions
                    {
                        CommitOnSuccess     = commitMessage == null,
                        FailOnConflict      = true,
                        FastForwardStrategy = FastForwardStrategy.NoFastForward,
                        SkipReuc            = true
                    });
                }
                finally
                {
                    repository.Branches.Remove(localBranchName);
                }

                cancellationToken.ThrowIfCancellationRequested();

                if (result.Status == MergeStatus.Conflicts)
                {
                    RawCheckout(originalCommit.CanonicalName ?? originalCommit.Tip.Sha);
                    cancellationToken.ThrowIfCancellationRequested();
                }

                repository.RemoveUntrackedFiles();
            }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);

            if (result.Status == MergeStatus.Conflicts)
            {
                await eventConsumer.HandleEvent(EventType.RepoMergeConflict, new List <string> {
                    originalCommit.Tip.Sha, testMergeParameters.PullRequestRevision, originalCommit.FriendlyName ?? UnknownReference, prBranchName
                }, cancellationToken).ConfigureAwait(false);

                return(false);
            }

            if (commitMessage != null)
            {
                repository.Commit(commitMessage, sig, sig, new CommitOptions
                {
                    PrettifyMessage = true
                });
            }

            return(true);
        }