public override async Task <IActionResult> Create([FromBody] DreamMaker model, CancellationToken cancellationToken) { var job = new Models.Job { Description = "Compile active repository code", StartedBy = AuthenticationContext.User, CancelRightsType = RightsType.DreamMaker, CancelRight = (ulong)DreamMakerRights.CancelCompile, Instance = Instance }; await jobManager.RegisterOperation(job, instanceManager.GetInstance(Instance).CompileProcess, cancellationToken).ConfigureAwait(false); return(Accepted(job.ToApi())); }
/// <inheritdoc /> public Task <DreamMaker> Update(DreamMaker dreamMaker, CancellationToken cancellationToken) => apiClient.Update <DreamMaker, DreamMaker>(Routes.DreamMaker, dreamMaker, instance.Id, cancellationToken);
public async Task <IActionResult> Update([FromBody] DreamMaker model, CancellationToken cancellationToken) { if (model == null) { throw new ArgumentNullException(nameof(model)); } if (model.ApiValidationPort == 0) { throw new InvalidOperationException("ApiValidationPort cannot be 0!"); } var hostModel = await DatabaseContext .DreamMakerSettings .AsQueryable() .Where(x => x.InstanceId == Instance.Id) .FirstOrDefaultAsync(cancellationToken) .ConfigureAwait(false); if (hostModel == null) { return(Gone()); } if (model.ProjectName != null) { if (!AuthenticationContext.InstanceUser.DreamMakerRights.Value.HasFlag(DreamMakerRights.SetDme)) { return(Forbid()); } if (model.ProjectName.Length == 0) { hostModel.ProjectName = null; } else { hostModel.ProjectName = model.ProjectName; } } if (model.ApiValidationPort.HasValue) { if (!AuthenticationContext.InstanceUser.DreamMakerRights.Value.HasFlag(DreamMakerRights.SetApiValidationPort)) { return(Forbid()); } hostModel.ApiValidationPort = model.ApiValidationPort; } if (model.ApiValidationSecurityLevel.HasValue) { if (!AuthenticationContext.InstanceUser.DreamMakerRights.Value.HasFlag(DreamMakerRights.SetSecurityLevel)) { return(Forbid()); } hostModel.ApiValidationSecurityLevel = model.ApiValidationSecurityLevel; } if (model.RequireDMApiValidation.HasValue) { if (!AuthenticationContext.InstanceUser.DreamMakerRights.Value.HasFlag(DreamMakerRights.SetApiValidationRequirement)) { return(Forbid()); } hostModel.RequireDMApiValidation = model.RequireDMApiValidation; } await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); if ((AuthenticationContext.GetRight(RightsType.DreamMaker) & (ulong)DreamMakerRights.Read) == 0) { return(NoContent()); } return(await Read(cancellationToken).ConfigureAwait(false)); }
/// <inheritdoc /> #pragma warning disable CA1506 // TODO: Decomplexify public async Task <IInstance> CreateInstance(IBridgeRegistrar bridgeRegistrar, Models.Instance metadata) { // Create the ioManager for the instance var instanceIoManager = new ResolvingIOManager(ioManager, metadata.Path); // various other ioManagers var repoIoManager = new ResolvingIOManager(instanceIoManager, "Repository"); var byondIOManager = new ResolvingIOManager(instanceIoManager, "Byond"); var gameIoManager = new ResolvingIOManager(instanceIoManager, "Game"); var diagnosticsIOManager = new ResolvingIOManager(instanceIoManager, "Diagnostics"); var configurationIoManager = new ResolvingIOManager(instanceIoManager, "Configuration"); var configuration = new StaticFiles.Configuration( configurationIoManager, synchronousIOManager, symlinkFactory, processExecutor, postWriteHandler, platformIdentifier, fileTransferService, loggerFactory.CreateLogger <StaticFiles.Configuration>()); var eventConsumer = new EventConsumer(configuration); var repoManager = new RepositoryManager( repositoryFactory, repositoryCommands, repoIoManager, eventConsumer, gitRemoteFeaturesFactory, loggerFactory.CreateLogger <Repository.Repository>(), loggerFactory.CreateLogger <RepositoryManager>()); try { var byond = new ByondManager(byondIOManager, byondInstaller, eventConsumer, loggerFactory.CreateLogger <ByondManager>()); var commandFactory = new CommandFactory(assemblyInformationProvider, byond, repoManager, databaseContextFactory, metadata); var chatManager = chatFactory.CreateChatManager(instanceIoManager, commandFactory, metadata.ChatSettings); try { var sessionControllerFactory = new SessionControllerFactory( processExecutor, byond, topicClientFactory, cryptographySuite, assemblyInformationProvider, gameIoManager, chatManager, networkPromptReaper, platformIdentifier, bridgeRegistrar, serverPortProvider, eventConsumer, loggerFactory, loggerFactory.CreateLogger <SessionControllerFactory>(), metadata); var dmbFactory = new DmbFactory( databaseContextFactory, gameIoManager, remoteDeploymentManagerFactory, loggerFactory.CreateLogger <DmbFactory>(), metadata); try { var reattachInfoHandler = new SessionPersistor( databaseContextFactory, dmbFactory, processExecutor, loggerFactory.CreateLogger <SessionPersistor>(), metadata); var watchdog = watchdogFactory.CreateWatchdog( chatManager, dmbFactory, reattachInfoHandler, sessionControllerFactory, gameIoManager, diagnosticsIOManager, eventConsumer, remoteDeploymentManagerFactory, metadata, metadata.DreamDaemonSettings); eventConsumer.SetWatchdog(watchdog); commandFactory.SetWatchdog(watchdog); try { Instance instance = null; var dreamMaker = new DreamMaker( byond, gameIoManager, configuration, sessionControllerFactory, eventConsumer, chatManager, processExecutor, dmbFactory, repoManager, remoteDeploymentManagerFactory, loggerFactory.CreateLogger <DreamMaker>(), metadata); instance = new Instance( metadata, repoManager, byond, dreamMaker, watchdog, chatManager, configuration, dmbFactory, jobManager, eventConsumer, remoteDeploymentManagerFactory, loggerFactory.CreateLogger <Instance>()); return(instance); } catch { await watchdog.DisposeAsync().ConfigureAwait(false); throw; } } catch { dmbFactory.Dispose(); throw; } } catch { await chatManager.DisposeAsync().ConfigureAwait(false); throw; } } catch { repoManager.Dispose(); throw; } }
/// <inheritdoc /> public IInstance CreateInstance(Models.Instance metadata) { //Create the ioManager for the instance var instanceIoManager = new ResolvingIOManager(ioManager, metadata.Path); //various other ioManagers var repoIoManager = new ResolvingIOManager(instanceIoManager, "Repository"); var byondIOManager = new ResolvingIOManager(instanceIoManager, "Byond"); var gameIoManager = new ResolvingIOManager(instanceIoManager, "Game"); var configurationIoManager = new ResolvingIOManager(instanceIoManager, "Configuration"); var configuration = new StaticFiles.Configuration(configurationIoManager, synchronousIOManager, symlinkFactory, processExecutor, postWriteHandler, loggerFactory.CreateLogger <StaticFiles.Configuration>()); var eventConsumer = new EventConsumer(configuration); var dmbFactory = new DmbFactory(databaseContextFactory, gameIoManager, loggerFactory.CreateLogger <DmbFactory>(), metadata.CloneMetadata()); try { var repoManager = new RepositoryManager(metadata.RepositorySettings, repoIoManager, eventConsumer, credentialsProvider, loggerFactory.CreateLogger <Repository.Repository>(), loggerFactory.CreateLogger <RepositoryManager>()); try { var byond = new ByondManager(byondIOManager, byondInstaller, eventConsumer, loggerFactory.CreateLogger <ByondManager>()); var commandFactory = new CommandFactory(application, byond, repoManager, databaseContextFactory, metadata); var chat = chatFactory.CreateChat(instanceIoManager, commandFactory, metadata.ChatSettings); try { var sessionControllerFactory = new SessionControllerFactory(processExecutor, byond, byondTopicSender, cryptographySuite, application, gameIoManager, chat, networkPromptReaper, loggerFactory, metadata.CloneMetadata()); var reattachInfoHandler = new ReattachInfoHandler(databaseContextFactory, dmbFactory, loggerFactory.CreateLogger <ReattachInfoHandler>(), metadata.CloneMetadata()); var watchdog = watchdogFactory.CreateWatchdog(chat, dmbFactory, reattachInfoHandler, configuration, sessionControllerFactory, metadata.CloneMetadata(), metadata.DreamDaemonSettings); eventConsumer.SetWatchdog(watchdog); commandFactory.SetWatchdog(watchdog); try { var dreamMaker = new DreamMaker(byond, gameIoManager, configuration, sessionControllerFactory, dmbFactory, application, eventConsumer, chat, processExecutor, watchdog, loggerFactory.CreateLogger <DreamMaker>()); return(new Instance(metadata.CloneMetadata(), repoManager, byond, dreamMaker, watchdog, chat, configuration, dmbFactory, databaseContextFactory, dmbFactory, jobManager, eventConsumer, gitHubClientFactory, loggerFactory.CreateLogger <Instance>())); } catch { watchdog.Dispose(); throw; } } catch { chat.Dispose(); throw; } } catch { repoManager.Dispose(); throw; } } catch { dmbFactory.Dispose(); throw; } }
public async Task <IActionResult> Update([FromBody] DreamMaker model, CancellationToken cancellationToken) { if (model == null) { throw new ArgumentNullException(nameof(model)); } if (model.ApiValidationPort == 0) { return(BadRequest(new ErrorMessage { Message = "API Validation port cannot be 0!" })); } if (model.ApiValidationSecurityLevel == DreamDaemonSecurity.Ultrasafe) { return(BadRequest(new ErrorMessage { Message = "This version of TGS does not support the ultrasafe DreamDaemon configuration!" })); } var hostModel = await DatabaseContext.DreamMakerSettings.Where(x => x.InstanceId == Instance.Id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); if (hostModel == null) { return(StatusCode((int)HttpStatusCode.Gone)); } if (model.ProjectName != null) { if (!AuthenticationContext.InstanceUser.DreamMakerRights.Value.HasFlag(DreamMakerRights.SetDme)) { return(Forbid()); } if (model.ProjectName.Length == 0) { hostModel.ProjectName = null; } else { hostModel.ProjectName = model.ProjectName; } } if (model.ApiValidationPort.HasValue) { if (!AuthenticationContext.InstanceUser.DreamMakerRights.Value.HasFlag(DreamMakerRights.SetApiValidationPort)) { return(Forbid()); } hostModel.ApiValidationPort = model.ApiValidationPort; } if (model.ApiValidationSecurityLevel.HasValue) { if (!AuthenticationContext.InstanceUser.DreamMakerRights.Value.HasFlag(DreamMakerRights.SetSecurityLevel)) { return(Forbid()); } hostModel.ApiValidationSecurityLevel = model.ApiValidationSecurityLevel; } await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); if ((AuthenticationContext.GetRight(RightsType.DreamMaker) & (ulong)DreamMakerRights.Read) == 0) { return(NoContent()); } return(await Read(cancellationToken).ConfigureAwait(false)); }
/// <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!"); } 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); }
/// <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); } } } } }