/// <inheritdoc /> public async Task CompileProcess(Job job, IDatabaseContext databaseContext, Action <int> progressReporter, CancellationToken cancellationToken) { //DO NOT FOLLOW THE SUGGESTION FOR A THROW EXPRESSION HERE if (job == null) { throw new ArgumentNullException(nameof(job)); } if (databaseContext == null) { throw new ArgumentNullException(nameof(databaseContext)); } if (progressReporter == null) { throw new ArgumentNullException(nameof(progressReporter)); } var ddSettingsTask = databaseContext.DreamDaemonSettings.Where(x => x.InstanceId == metadata.Id).Select(x => new DreamDaemonSettings { StartupTimeout = x.StartupTimeout, }).FirstOrDefaultAsync(cancellationToken); var compileJobsTask = databaseContext.CompileJobs .Where(x => x.Job.Instance.Id == metadata.Id) .OrderByDescending(x => x.Job.StoppedAt) //TODO: Replace with this select when the issues linked in https://github.com/tgstation/tgstation-server/issues/737 are fixed //.Select(x => x.Job.StoppedAt.Value - x.Job.StartedAt.Value) .Select(x => new Job { StoppedAt = x.Job.StoppedAt, StartedAt = x.Job.StartedAt }) .Take(10) .ToListAsync(cancellationToken); var dreamMakerSettings = await databaseContext.DreamMakerSettings.Where(x => x.InstanceId == metadata.Id).FirstAsync(cancellationToken).ConfigureAwait(false); if (dreamMakerSettings == default) { throw new JobException("Missing DreamMakerSettings in DB!"); } var ddSettings = await ddSettingsTask.ConfigureAwait(false); if (ddSettings == default) { throw new JobException("Missing DreamDaemonSettings in DB!"); } CompileJob compileJob; RevisionInformation revInfo; using (var repo = await RepositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false)) { if (repo == null) { throw new JobException("Missing Repository!"); } var repoSha = repo.Head; revInfo = await databaseContext.RevisionInformations.Where(x => x.CommitSha == repoSha && x.Instance.Id == metadata.Id).Include(x => x.ActiveTestMerges).ThenInclude(x => x.TestMerge).FirstOrDefaultAsync().ConfigureAwait(false); if (revInfo == default) { revInfo = new RevisionInformation { CommitSha = repoSha, OriginCommitSha = repoSha, Instance = new Models.Instance { Id = metadata.Id } }; logger.LogWarning(Repository.Repository.OriginTrackingErrorTemplate, repoSha); databaseContext.Instances.Attach(revInfo.Instance); } TimeSpan?averageSpan = null; var previousCompileJobs = await compileJobsTask.ConfigureAwait(false); if (previousCompileJobs.Count != 0) { var totalSpan = TimeSpan.Zero; foreach (var I in previousCompileJobs) { totalSpan += I.StoppedAt.Value - I.StartedAt.Value; } averageSpan = totalSpan / previousCompileJobs.Count; } compileJob = await DreamMaker.Compile(revInfo, dreamMakerSettings, ddSettings.StartupTimeout.Value, repo, progressReporter, averageSpan, cancellationToken).ConfigureAwait(false); } compileJob.Job = job; databaseContext.CompileJobs.Add(compileJob); //will be saved by job context job.PostComplete = ct => CompileJobConsumer.LoadCompileJob(compileJob, ct); }
/// <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> async Task TimerLoop(int minutes, CancellationToken cancellationToken) { while (true) { try { await Task.Delay(new TimeSpan(0, minutes, 0), cancellationToken).ConfigureAwait(false); try { CompileJob job = null; //need this the whole time await databaseContextFactory.UseContext(async (db) => { //start up queries we'll need in the future var instanceQuery = db.Instances.Where(x => x.Id == metadata.Id); var ddSettingsTask = instanceQuery.Select(x => x.DreamDaemonSettings).Select(x => new DreamDaemonSettings { StartupTimeout = x.StartupTimeout, SecurityLevel = x.SecurityLevel }).FirstAsync(cancellationToken); var dmSettingsTask = instanceQuery.Select(x => x.DreamMakerSettings).FirstAsync(cancellationToken); var repositorySettingsTask = instanceQuery.Select(x => x.RepositorySettings).FirstAsync(cancellationToken); using (var repo = await RepositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false)) { if (repo == null) { return; } //start the rev info query var startSha = repo.Head; var revInfoTask = instanceQuery.SelectMany(x => x.RevisionInformations).Where(x => x.CommitSha == startSha).FirstOrDefaultAsync(cancellationToken); //need repo setting to fetch var repositorySettings = await repositorySettingsTask.ConfigureAwait(false); await repo.FetchOrigin(repositorySettings.AccessUser, repositorySettings.AccessToken, null, cancellationToken).ConfigureAwait(false); //take appropriate auto update actions bool shouldSyncTracked; if (repositorySettings.AutoUpdatesKeepTestMerges.Value) { var result = await repo.MergeOrigin(repositorySettings.CommitterName, repositorySettings.CommitterEmail, cancellationToken).ConfigureAwait(false); if (!result.HasValue) { return; } shouldSyncTracked = result.Value; } else { await repo.ResetToOrigin(cancellationToken).ConfigureAwait(false); shouldSyncTracked = true; } //synch if necessary if (repositorySettings.AutoUpdatesSynchronize.Value && startSha != repo.Head) { await repo.Sychronize(repositorySettings.AccessUser, repositorySettings.AccessToken, shouldSyncTracked, cancellationToken).ConfigureAwait(false); } //finish other queries var dmSettings = await dmSettingsTask.ConfigureAwait(false); var ddSettings = await ddSettingsTask.ConfigureAwait(false); var revInfo = await revInfoTask.ConfigureAwait(false); //null rev info handling if (revInfo == default) { var currentSha = repo.Head; revInfo = new RevisionInformation { CommitSha = currentSha, OriginCommitSha = currentSha, Instance = new Models.Instance { Id = metadata.Id } }; db.Instances.Attach(revInfo.Instance); } //finally start compile job = await DreamMaker.Compile(revInfo, dmSettings, ddSettings.SecurityLevel.Value, ddSettings.StartupTimeout.Value, repo, cancellationToken).ConfigureAwait(false); } db.CompileJobs.Add(job); await db.Save(cancellationToken).ConfigureAwait(false); }).ConfigureAwait(false); await CompileJobConsumer.LoadCompileJob(job, cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException) { throw; } catch (Exception e) { logger.LogWarning("Error in auto update loop! Exception: {0}", e); continue; } } catch (OperationCanceledException) { break; } } }
/// <inheritdoc /> public async Task CompileProcess(Job job, IDatabaseContext databaseContext, Action <int> progressReporter, CancellationToken cancellationToken) { //DO NOT FOLLOW THE SUGGESTION FOR A THROW EXPRESSION HERE if (job == null) { throw new ArgumentNullException(nameof(job)); } if (databaseContext == null) { throw new ArgumentNullException(nameof(databaseContext)); } if (progressReporter == null) { throw new ArgumentNullException(nameof(progressReporter)); } var ddSettingsTask = databaseContext.DreamDaemonSettings.Where(x => x.InstanceId == metadata.Id).Select(x => new DreamDaemonSettings { StartupTimeout = x.StartupTimeout, }).FirstOrDefaultAsync(cancellationToken); var compileJobsTask = databaseContext.CompileJobs .Where(x => x.Job.Instance.Id == metadata.Id) .OrderByDescending(x => x.Job.StoppedAt) //TODO: Replace with this select when the issues linked in https://github.com/tgstation/tgstation-server/issues/737 are fixed //.Select(x => x.Job.StoppedAt.Value - x.Job.StartedAt.Value) .Select(x => new Job { StoppedAt = x.Job.StoppedAt, StartedAt = x.Job.StartedAt }) .Take(10) .ToListAsync(cancellationToken); var dreamMakerSettings = await databaseContext.DreamMakerSettings.Where(x => x.InstanceId == metadata.Id).FirstAsync(cancellationToken).ConfigureAwait(false); if (dreamMakerSettings == default) { throw new JobException("Missing DreamMakerSettings in DB!"); } var ddSettings = await ddSettingsTask.ConfigureAwait(false); if (ddSettings == default) { throw new JobException("Missing DreamDaemonSettings in DB!"); } Task <RepositorySettings> repositorySettingsTask = null; string repoOwner = null; string repoName = null; CompileJob compileJob; RevisionInformation revInfo; using (var repo = await RepositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false)) { if (repo == null) { throw new JobException("Missing Repository!"); } if (repo.IsGitHubRepository) { repoOwner = repo.GitHubOwner; repoName = repo.GitHubRepoName; repositorySettingsTask = databaseContext.RepositorySettings.Where(x => x.InstanceId == metadata.Id).Select(x => new RepositorySettings { AccessToken = x.AccessToken, ShowTestMergeCommitters = x.ShowTestMergeCommitters }).FirstOrDefaultAsync(cancellationToken); } var repoSha = repo.Head; revInfo = await databaseContext.RevisionInformations.Where(x => x.CommitSha == repoSha && x.Instance.Id == metadata.Id).Include(x => x.ActiveTestMerges).ThenInclude(x => x.TestMerge).ThenInclude(x => x.MergedBy).FirstOrDefaultAsync().ConfigureAwait(false); if (revInfo == default) { revInfo = new RevisionInformation { CommitSha = repoSha, OriginCommitSha = repoSha, Instance = new Models.Instance { Id = metadata.Id } }; logger.LogWarning(Repository.Repository.OriginTrackingErrorTemplate, repoSha); databaseContext.Instances.Attach(revInfo.Instance); } TimeSpan?averageSpan = null; var previousCompileJobs = await compileJobsTask.ConfigureAwait(false); if (previousCompileJobs.Count != 0) { var totalSpan = TimeSpan.Zero; foreach (var I in previousCompileJobs) { totalSpan += I.StoppedAt.Value - I.StartedAt.Value; } averageSpan = totalSpan / previousCompileJobs.Count; } compileJob = await DreamMaker.Compile(revInfo, dreamMakerSettings, ddSettings.StartupTimeout.Value, repo, progressReporter, averageSpan, cancellationToken).ConfigureAwait(false); } compileJob.Job = job; databaseContext.CompileJobs.Add(compileJob); //will be saved by job context job.PostComplete = ct => CompileJobConsumer.LoadCompileJob(compileJob, ct); if (repositorySettingsTask != null) { var repositorySettings = await repositorySettingsTask.ConfigureAwait(false); if (repositorySettings == default) { throw new JobException("Missing repository settings!"); } if (repositorySettings.AccessToken != null) { //potential for commenting on a test merge change var outgoingCompileJob = LatestCompileJob(); if (outgoingCompileJob != null && outgoingCompileJob.RevisionInformation.CommitSha != compileJob.RevisionInformation.CommitSha) { var gitHubClient = gitHubClientFactory.CreateClient(repositorySettings.AccessToken); async Task CommentOnPR(int prNumber, string comment) { try { await gitHubClient.Issue.Comment.Create(repoOwner, repoName, prNumber, comment).ConfigureAwait(false); } catch (ApiException e) { logger.LogWarning("Error posting GitHub comment! Exception: {0}", e); } } var tasks = new List <Task>(); string FormatTestMerge(TestMerge testMerge, bool updated) => String.Format(CultureInfo.InvariantCulture, "#### Test Merge {4}{0}{0}##### Server Instance{0}{5}{1}{0}{0}##### Revision{0}Origin: {6}{0}Pull Request: {2}{0}Server: {7}{3}", Environment.NewLine, repositorySettings.ShowTestMergeCommitters.Value ? String.Format(CultureInfo.InvariantCulture, "{0}{0}##### Merged By{0}{1}", Environment.NewLine, testMerge.MergedBy.Name) : String.Empty, testMerge.PullRequestRevision, testMerge.Comment != null ? String.Format(CultureInfo.InvariantCulture, "{0}{0}##### Comment{0}{1}", Environment.NewLine, testMerge.Comment) : String.Empty, updated ? "Updated" : "Deployed", metadata.Name, compileJob.RevisionInformation.OriginCommitSha, compileJob.RevisionInformation.CommitSha ); //added prs foreach (var I in compileJob .RevisionInformation .ActiveTestMerges .Select(x => x.TestMerge) .Where(x => !outgoingCompileJob .RevisionInformation .ActiveTestMerges .Any(y => y.TestMerge.Number == x.Number))) { tasks.Add(CommentOnPR(I.Number.Value, FormatTestMerge(I, false))); } //removed prs foreach (var I in outgoingCompileJob .RevisionInformation .ActiveTestMerges .Select(x => x.TestMerge) .Where(x => !compileJob .RevisionInformation .ActiveTestMerges .Any(y => y.TestMerge.Number == x.Number))) { tasks.Add(CommentOnPR(I.Number.Value, "#### Test Merge Removed")); } //updated prs foreach (var I in compileJob .RevisionInformation .ActiveTestMerges .Select(x => x.TestMerge) .Where(x => outgoingCompileJob .RevisionInformation .ActiveTestMerges .Any(y => y.TestMerge.Number == x.Number))) { tasks.Add(CommentOnPR(I.Number.Value, FormatTestMerge(I, true))); } if (tasks.Any()) { await Task.WhenAll(tasks).ConfigureAwait(false); } } } } }