public async Task <IActionResult> RefreshFileTree([Required] long id) { var item = await FindAndCheckAccess(id); if (item == null || item.Deleted) { return(NotFound()); } if (item.FileTreeUpdated != null && DateTime.UtcNow - item.FileTreeUpdated.Value < TimeSpan.FromMinutes(3)) { return(BadRequest("This file tree was refreshed very recently")); } var user = HttpContext.AuthenticatedUserOrThrow(); await database.ActionLogEntries.AddAsync(new ActionLogEntry() { Message = $"LFS project file tree refresh requested for {item.Id}", PerformedById = user.Id, }); await database.SaveChangesAsync(); jobClient.Enqueue <RefreshLFSProjectFilesJob>(x => x.Execute(item.Id, CancellationToken.None)); logger.LogInformation("LFS project {Id} file tree refreshed by {Email}", item.Id, user.Email); return(Ok()); }
public async Task <IActionResult> CreateNew([Required][FromBody] AccessKeyDTO newKey) { if (string.IsNullOrWhiteSpace(newKey.Description) || await database.AccessKeys .FirstOrDefaultAsync(a => a.Description == newKey.Description) != null) { return(BadRequest("Description is empty or a key with that description already exists")); } var key = new AccessKey() { Description = newKey.Description, KeyCode = Guid.NewGuid().ToString(), KeyType = newKey.KeyType }; var action = new AdminAction() { Message = $"New access key ({key.Description}) created with scope: {key.KeyType}", PerformedById = HttpContext.AuthenticatedUser() !.Id }; await database.AccessKeys.AddAsync(key); await database.AdminActions.AddAsync(action); await database.SaveChangesAsync(); return(Ok($"Created new key \"{key.Id}\" with code: {key.KeyCode}")); }
public async Task <IActionResult> ConnectLauncher([Required] LauncherLinkCodeCheckForm request) { Response.ContentType = "application/json"; var user = await GetUserForNewLink(request.Code); // Update user to consume the code user.LauncherCodeExpires = DateTime.UtcNow - TimeSpan.FromSeconds(1); user.LauncherLinkCode = null; user.TotalLauncherLinks += 1; // Create a new code, which the user doesn't directly see to avoid it leaking as easily var code = NonceGenerator.GenerateNonce(42); var remoteAddress = HttpContext.Connection.RemoteIpAddress; await database.LauncherLinks.AddAsync(new LauncherLink() { User = user, LinkCode = code, LastIp = remoteAddress?.ToString(), LastConnection = DateTime.UtcNow }); await database.LogEntries.AddAsync(new LogEntry() { Message = $"New launcher link created from: {remoteAddress}", TargetUserId = user.Id }); await database.SaveChangesAsync(); logger.LogInformation("New launcher linked to user {Id} from {RemoteAddress}", user.Id, remoteAddress); return(Created(string.Empty, new LauncherLinkResult(true, code))); }
public async Task Execute(CancellationToken cancellationToken) { var cutoff = DateTime.UtcNow - AppInfo.KeepStackwalkResultsFor; var finishedTasks = await database.StackwalkTasks.Where(s => s.FinishedAt != null && s.FinishedAt < cutoff) .ToListAsync(cancellationToken); if (finishedTasks.Count > 0) { logger.LogInformation("Cleaning finished stackwalk tasks, count: {Count}", finishedTasks.Count); database.StackwalkTasks.RemoveRange(finishedTasks); await database.SaveChangesAsync(cancellationToken); } var cutoff2 = DateTime.UtcNow - AppInfo.DeleteFailedStackwalkAttemptsAfter; var failedTasks = await database.StackwalkTasks.Where(s => s.CreatedAt < cutoff2) .ToListAsync(cancellationToken); if (failedTasks.Count > 0) { logger.LogWarning("Cleaning entirely failed stackwalk tasks, count: {Count}", failedTasks.Count); database.StackwalkTasks.RemoveRange(failedTasks); await database.LogEntries.AddAsync(new LogEntry() { Message = $"Cleared {failedTasks.Count} stackwalk tasks that failed to run entirely", }, cancellationToken); await database.SaveChangesAsync(cancellationToken); } }
public async Task <IActionResult> VerifyBuild([Required] long id, bool siblingsAsWell = true) { var build = await database.DevBuilds.FindAsync(id); if (build == null) { return(NotFound()); } var user = HttpContext.AuthenticatedUser() !; bool didSomething = false; if (siblingsAsWell) { foreach (var sibling in await GetSiblingBuilds(build)) { if (sibling.Verified) { continue; } logger.LogInformation("Marking sibling devbuild {Id} as verified as well", sibling.Id); sibling.Verified = true; sibling.VerifiedById = user.Id; didSomething = true; } } if (!build.Verified) { logger.LogInformation("Marking devbuild {Id} as verified by {Email}", build.Id, user.Email); build.Verified = true; build.VerifiedById = user.Id; didSomething = true; } if (!didSomething) { return(Ok("Nothing needed to be marked as verified")); } await database.ActionLogEntries.AddAsync(new ActionLogEntry() { Message = $"Build {id} marked verified", PerformedById = user.Id }); await database.SaveChangesAsync(); return(Ok()); }
public async Task <ActionResult <PagedResult <SessionDTO> > > DeleteAllUserSessions([Required] long id) { bool admin = HttpContext.HasAuthenticatedUserWithAccess(UserAccessLevel.Admin, AuthenticationScopeRestriction.None); var user = await database.Users.FindAsync(id); var actingUser = HttpContext.AuthenticatedUser() !; if (user == null) { return(NotFound()); } // Has to be an admin or performing an action on their own data if (!admin && actingUser.Id != user.Id) { return(NotFound()); } var sessions = await database.Sessions.Where(s => s.UserId == id).ToListAsync(); if (sessions.Count < 1) { return(Ok()); } if (admin && actingUser.Id != user.Id) { await database.AdminActions.AddAsync(new AdminAction() { Message = "Forced logout", PerformedById = actingUser.Id, TargetUserId = user.Id, }); } else { logger.LogInformation("User ({Email}) deleted their sessions from {RemoteIpAddress}", user.Email, HttpContext.Connection.RemoteIpAddress); } database.Sessions.RemoveRange(sessions); await database.SaveChangesAsync(); await InvalidateSessions(sessions.Select(s => s.Id)); return(Ok()); }
public async Task<IActionResult> CancelRunningJob([Required] long projectId, [Required] long buildId, [Required] long jobId) { var job = await database.CiJobs.Include(j => j.Build!).ThenInclude(b => b.CiProject) .Include(j => j.CiJobOutputSections) .FirstOrDefaultAsync(j => j.CiProjectId == projectId && j.CiBuildId == buildId && j.CiJobId == jobId); if (job == null) return NotFound(); if (job.Build?.CiProject == null) throw new NotLoadedModelNavigationException(); if (job.Build.CiProject.Deleted) return NotFound(); if (job.State == CIJobState.Finished) return BadRequest("Can't cancel a finished job"); long cancelSectionId = 0; foreach (var section in job.CiJobOutputSections) { if (section.CiJobOutputSectionId > cancelSectionId) cancelSectionId = section.CiJobOutputSectionId; } await job.CreateFailureSection(database, "Job canceled by a user", "Canceled", ++cancelSectionId); var user = HttpContext.AuthenticatedUser()!; logger.LogInformation("CI job {ProjectId}-{BuildId}-{JobId} canceled by {Email}", projectId, buildId, jobId, user.Email); // TODO: would be nice to have that non-admin action log type await database.LogEntries.AddAsync(new LogEntry() { Message = $"CI job {projectId}-{buildId}-{jobId} canceled by an user", TargetUserId = user.Id, }); await database.SaveChangesAsync(); jobClient.Enqueue<SetFinishedCIJobStatusJob>(x => x.Execute(projectId, buildId, jobId, false, CancellationToken.None)); return Ok(); }
public async Task Execute(string githubUsername, CancellationToken cancellationToken) { var pullRequests = await database.GithubPullRequests .Where(p => p.ClaSigned != true && p.AuthorUsername == githubUsername).ToListAsync(cancellationToken); foreach (var pullRequest in pullRequests) { pullRequest.ClaSigned = null; } await database.SaveChangesAsync(cancellationToken); foreach (var pullRequest in pullRequests) { // Don't need to run this for closed ones as the open event will cause a re-check anyway if (!pullRequest.Open) { continue; } logger.LogInformation( "New CLA signature made that matches pull request: {Repository}/{GithubId} by {GithubUsername}", pullRequest.Repository, pullRequest.GithubId, githubUsername); // This shouldn't cause any problems regarding data consistency but: // TODO: make separate job that doesn't update PR data other than the signature status jobClient.Enqueue <CheckPullRequestStatusJob>(x => x.Execute(pullRequest.Repository, pullRequest.GithubId, pullRequest.LatestCommit, pullRequest.AuthorUsername, pullRequest.Open, CancellationToken.None)); } }
public async Task Execute(CancellationToken cancellationToken) { var cutoff = DateTime.UtcNow - detectionTime; bool jobNeeded = false; foreach (var server in await database.ControlledServers.Where(s => s.UpdatedAt < cutoff && s.Status == ServerStatus.Running).ToListAsync(cancellationToken)) { logger.LogWarning("Server {Id} has been left running, last updated: {UpdatedAt}", server.Id, server.UpdatedAt); jobNeeded = true; } if (jobNeeded) { await database.LogEntries.AddAsync(new LogEntry() { Message = "Detected servers that are left in running state, trying to fix by re-running handle job" }, cancellationToken); await database.SaveChangesAsync(cancellationToken); jobClient.Enqueue <HandleControlledServerJobsJob>(x => x.Execute(CancellationToken.None)); } }
public async Task Execute(long itemId, CancellationToken cancellationToken) { var item = await database.StorageItems.Include(i => i.StorageItemVersions) .FirstOrDefaultAsync(i => i.Id == itemId, cancellationToken); if (item == null) { logger.LogError("Failed to get StorageItem ({ItemId}) for CI image locking", itemId); return; } if (item.WriteAccess == FileAccess.Nobody && item.Special) { logger.LogInformation("Skipping lock on StorageItem ({ItemId}) as it's already in nobody write mode", itemId); return; } // Queue jobs to delete other than the first uploaded version var lowestVersion = item.StorageItemVersions.Where(v => !v.Uploading).Min(v => v.Version); foreach (var version in item.StorageItemVersions) { if (version.Version == lowestVersion) { if (version.Protected != true || version.Keep != true) { version.Protected = true; version.Keep = true; version.BumpUpdatedAt(); } } else { // This version needs to be deleted jobClient.Schedule <DeleteStorageItemVersionJob>(x => x.Execute(version.Id, CancellationToken.None), TimeSpan.FromSeconds(30)); } } item.WriteAccess = FileAccess.Nobody; item.Special = true; item.BumpUpdatedAt(); // Also mark all parent folders as special so that they can't be deleted foreach (var parent in await item.GetParentsRecursively(database)) { cancellationToken.ThrowIfCancellationRequested(); if (parent.Special != true) { parent.Special = true; logger.LogInformation("CI image parent folder ({Id}) will be marked as special", parent.Id); } } await database.SaveChangesAsync(cancellationToken); logger.LogInformation("Use as CI image has locked writing to StorageItem ({ItemId})", itemId); }
protected async Task OnJobEnded(BaseServer server, CiJob job) { ReleaseServerReservation(server); job.RunningOnServerId = -1; job.RunningOnServerIsExternal = null; // After running the job, the changes saving should not be skipped await Database.SaveChangesAsync(); // Send status to github var status = GithubAPI.CommitStatus.Success; string statusDescription = "Checks succeeded"; if (!job.Succeeded) { status = GithubAPI.CommitStatus.Failure; statusDescription = "Some checks failed"; } if (job.Build?.CiProject == null) { throw new NotLoadedModelNavigationException(); } if (!await StatusReporter.SetCommitStatus(job.Build.CiProject.RepositoryFullName, job.Build.CommitHash, status, StatusReporter.CreateStatusUrlForJob(job), statusDescription, job.JobName)) { Logger.LogError("Failed to set commit status for build's job: {JobName}", job.JobName); } JobClient.Enqueue <CheckOverallBuildStatusJob>(x => x.Execute(job.CiProjectId, job.CiBuildId, CancellationToken.None)); }
public async Task Execute(long meetingId, long pollId, CancellationToken cancellationToken) { var poll = await database.MeetingPolls.FindAsync(meetingId, pollId); if (poll == null) { logger.LogError("Can't compute results for non-existent poll: {MeetingId}-{PollId}", meetingId, pollId); return; } if (poll.ClosedAt == null) { throw new Exception("Can't calculate results for a poll that is not closed"); } // This will work until we have more than hundreds of thousands of votes per poll var votes = await database.MeetingPollVotes.Where(v => v.MeetingId == poll.MeetingId && v.PollId == poll.PollId) .ToListAsync(cancellationToken); poll.CalculateResults(votes); await database.SaveChangesAsync(cancellationToken); logger.LogInformation("Poll results computed for: {MeetingId}-{PollId}", meetingId, pollId); }
public async Task <IActionResult> Logout([FromForm] LogoutFormData request) { var existingSession = await HttpContext.Request.Cookies.GetSession(database); if (existingSession?.User == null) { return(BadRequest("You are not currently logged in")); } if (!csrfVerifier.IsValidCSRFToken(request.CSRF, existingSession.User)) { return(BadRequest("Invalid CSRF token, please try refreshing and then try again")); } // Session version doesn't need to be enforced here as logging out a session should always be safe // (after the above checks) // TODO: if an in-progress signature exists, should the session be just converted to a logged out one? database.Sessions.Remove(existingSession); await database.SaveChangesAsync(); logger.LogInformation("Session {Id} logged out", existingSession.Id); Response.Cookies.Delete(AppInfo.SessionCookieName); return(Redirect("/login")); }
public async Task Execute(long pullRequestId, CancellationToken cancellationToken) { var pullRequest = await database.GithubPullRequests.Include(p => p.AutoComments) .FirstOrDefaultAsync(p => p.Id == pullRequestId, cancellationToken); if (pullRequest == null) { logger.LogError("No pull request with ID {PullRequestId} found to post comments on", pullRequestId); return; } var possibleComments = await database.GithubAutoComments.Where(c => c.Enabled && (string.IsNullOrEmpty(c.Repository) || c.Repository == "*" || c.Repository == pullRequest.Repository)).ToListAsync(cancellationToken); foreach (var possibleComment in possibleComments) { bool post = false; switch (possibleComment.Condition) { case AutoCommentCondition.Always: post = true; break; case AutoCommentCondition.OnceOnPullRequest: // Contains doesn't work here. Has to be an ID check like this post = pullRequest.AutoComments.All(c => c.Id != possibleComment.Id); break; case AutoCommentCondition.IfCLANotSigned: post = pullRequest.AutoComments.All(c => c.Id != possibleComment.Id) && pullRequest.ClaSigned == false; break; case AutoCommentCondition.IfCLABecomesInvalid: // Handled elsewhere (InvalidatePullRequestsWithCLASignaturesJob) break; default: throw new ArgumentOutOfRangeException(); } if (!post) { continue; } jobClient.Enqueue <PostGithubCommentJob>(x => x.Execute(pullRequest.Repository, pullRequest.GithubId, possibleComment.CommentText, CancellationToken.None)); pullRequest.AutoComments.Add(possibleComment); } // Not cancellable here as we have potentially already queued up the comment posts so we don't want to lose // that information // ReSharper disable once MethodSupportsCancellation await database.SaveChangesAsync(); }
public async Task <IActionResult> JoinMeeting([Required] long id) { var access = GetCurrentUserAccess(); var meeting = await GetMeetingWithReadAccess(id, access); if (meeting == null) { return(NotFound()); } // TODO: should admins be able to always join? if (meeting.JoinAccess > access) { return(this.WorkingForbid("You don't have permission to join this meeting")); } if (meeting.EndedAt != null) { return(BadRequest("This meeting has already ended")); } var user = HttpContext.AuthenticatedUser() !; // Fail if already joined if (await GetMeetingMember(meeting.Id, user.Id) != null) { return(BadRequest("You have already joined this meeting")); } // Don't allow if already started and grace period is over if (DateTime.UtcNow > meeting.StartsAt + meeting.JoinGracePeriod) { return(BadRequest("You are too late to join this meeting")); } // Allow join await database.ActionLogEntries.AddAsync(new ActionLogEntry() { Message = $"User joined meeting {meeting.Id}", PerformedById = user.Id, }); await database.MeetingMembers.AddAsync(new MeetingMember() { MeetingId = meeting.Id, UserId = user.Id, }); await database.SaveChangesAsync(); logger.LogInformation("User {Email} joined meeting {Name} ({Id})", user.Email, meeting.Name, meeting.Id); return(Ok()); }
public async Task <ActionResult <EmailVerifyResult> > StartEmailVerifyForCLA( [Required][FromBody] EmailVerificationFinishForm request) { var verifiedToken = emailTokens.ReadAndVerify(request.Token); if (verifiedToken == null) { return(this.WorkingForbid( "Invalid email token given. Please check you used the right link and " + "that it didn't expire yet")); } const string sameBrowserAdvice = "Make sure to use the link in the same browser where you started email verification from."; string redirect; switch (verifiedToken.Type) { case EmailVerificationType.CLA: { var(inProgressSign, session, error) = await GetActiveSignature(); if (error != null) { return(this.WorkingForbid("You don't have an in-progress signature or session. " + sameBrowserAdvice)); } if (!SecurityHelpers.SlowEquals(session !.HashedId, verifiedToken.VerifiedResourceId)) { return(this.WorkingForbid("Current session doesn't match one the email token " + "was sent from. " + sameBrowserAdvice)); } // Valid CLA verified email logger.LogInformation("Email verification of {Email} succeeded for CLA in session {Id}", verifiedToken.SentToEmail, session.Id); inProgressSign !.EmailVerified = true; inProgressSign.Email = verifiedToken.SentToEmail; redirect = "/cla/sign"; break; } default: throw new ArgumentOutOfRangeException(); } await database.SaveChangesAsync(); return(new EmailVerifyResult() { RedirectTo = redirect, }); }
public async Task <IActionResult> Create([Required] long projectId, [Required][FromBody] CreateCISecretForm request) { var project = await database.CiProjects.FindAsync(projectId); if (project == null) { return(NotFound()); } if (await database.CiSecrets.FirstOrDefaultAsync(s => s.CiProjectId == project.Id && s.SecretName == request.SecretName && s.UsedForBuildTypes == request.UsedForBuildTypes) != null) { return(BadRequest("A secret with the given name and type already exists")); } var previousSecretId = await database.CiSecrets.Where(s => s.CiProjectId == project.Id) .MaxAsync(s => (long?)s.CiSecretId) ?? 0; var user = HttpContext.AuthenticatedUser() !; await database.AdminActions.AddAsync(new AdminAction() { Message = $"New secret \"{request.SecretName}\" created for project {project.Id}", PerformedById = user.Id, }); await database.CiSecrets.AddAsync(new CiSecret() { CiProjectId = project.Id, CiSecretId = previousSecretId + 1, SecretName = request.SecretName, SecretContent = request.SecretContent ?? string.Empty, UsedForBuildTypes = request.UsedForBuildTypes, }); await database.SaveChangesAsync(); logger.LogInformation("New secret {SecretName} created by {Email} for {Id}", request.SecretName, user.Email, project.Id); return(Ok()); }
public async Task <IActionResult> DeleteAllLinks([Required] long userId) { var performingUser = HttpContext.AuthenticatedUser() !; // Only admins can delete other user's links if (userId != performingUser.Id && !HttpContext.HasAuthenticatedUserWithAccess(UserAccessLevel.Admin, AuthenticationScopeRestriction.None)) { return(Forbid()); } var linksToDelete = await database.LauncherLinks.Where(l => l.UserId == userId).ToListAsync(); // Skip doing anything if there's nothing to delete if (linksToDelete.Count < 1) { return(Ok()); } if (userId == performingUser.Id) { await database.LogEntries.AddAsync(new LogEntry() { Message = "All launcher links deleted by self", TargetUserId = userId }); } else { await database.AdminActions.AddAsync(new AdminAction() { Message = "All launcher links deleted by an admin", TargetUserId = userId, PerformedById = performingUser.Id }); } database.LauncherLinks.RemoveRange(linksToDelete); await database.SaveChangesAsync(); return(Ok()); }
public async Task Execute(string repository, long pullRequestNumber, string commit, string githubUsername, bool open, CancellationToken cancellationToken) { logger.LogInformation("Update to PR {Repository}/{PullRequestNumber} detected by {GithubUsername}", repository, pullRequestNumber, githubUsername); var pullRequest = await database.GithubPullRequests.FirstOrDefaultAsync(p => p.Repository == repository && p.GithubId == pullRequestNumber, cancellationToken); if (pullRequest == null) { await database.LogEntries.AddAsync(new LogEntry() { Message = $"New Github pull request detected {repository}/{pullRequestNumber}", }, cancellationToken); pullRequest = new GithubPullRequest() { Repository = repository, GithubId = pullRequestNumber, LatestCommit = commit, AuthorUsername = githubUsername, Open = open, ClaSigned = await CheckNewCLASignedStatus(null, githubUsername), }; await database.GithubPullRequests.AddAsync(pullRequest, cancellationToken); } else { pullRequest.LatestCommit = commit; if (pullRequest.Open != open) { pullRequest.Open = open; await database.LogEntries.AddAsync(new LogEntry() { Message = $"Github pull request open state {repository}/{pullRequestNumber} is now {open}", }, cancellationToken); } pullRequest.ClaSigned = await CheckNewCLASignedStatus(pullRequest.ClaSigned, pullRequest.AuthorUsername); pullRequest.BumpUpdatedAt(); } await database.SaveChangesAsync(cancellationToken); jobClient.Enqueue <CheckAutoCommentsToPostJob>(x => x.Execute(pullRequest.Id, CancellationToken.None)); jobClient.Enqueue <SetCLAGithubCommitStatusJob>(x => x.Execute(pullRequest.Id, CancellationToken.None)); }
public async Task Execute(CancellationToken cancellationToken) { if (!ec2Controller.Configured) { return; } var cutoff = DateTime.UtcNow - TimeSpan.FromHours(2); foreach (var server in await database.ControlledServers.Where(s => s.UpdatedAt < cutoff && s.Status != ServerStatus.Stopped && s.Status != ServerStatus.Terminated).ToListAsync(cancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); logger.LogError( "Server {Id} is stuck! Last state change: {UpdatedAt} current state: {Status}, terminating it", server.Id, server.UpdatedAt, server.Status); await database.LogEntries.AddAsync(new LogEntry() { Message = $"Server {server.Id} ({server.InstanceId}) is stuck in state {server.Status} " + $"since {server.UpdatedAt}" }, cancellationToken); if (string.IsNullOrEmpty(server.InstanceId)) { throw new Exception("Can't terminate server with no InstanceId"); } await ec2Controller.TerminateInstance(server.InstanceId); server.Status = ServerStatus.Terminated; if (server.ReservationType != ServerReservationType.None) { logger.LogWarning("Stuck server was reserved for type: {ReservationType}", server.ReservationType); server.ReservationType = ServerReservationType.None; } if (server.RunningSince != null) { server.TotalRuntime += (DateTime.UtcNow - server.RunningSince.Value).TotalSeconds; } server.RunningSince = null; logger.LogInformation("Successfully terminated: {InstanceId}", server.InstanceId); // Not cancellable done as the state to terminated is very important to save // ReSharper disable once MethodSupportsCancellation await database.SaveChangesAsync(); } }
private async Task CheckControlledServers(DateTime cutoff, CancellationToken cancellationToken) { // Only one server is scheduled for maintenance at once to avoid all being unavailable for job running var serverToMaintain = await database.ControlledServers.Where(s => !s.WantsMaintenance && s.Status != ServerStatus.Terminated && s.LastMaintenance < cutoff) .OrderBy(s => s.LastMaintenance).FirstOrDefaultAsync(cancellationToken); if (serverToMaintain == null) { return; } serverToMaintain.WantsMaintenance = true; await database.LogEntries.AddAsync(new LogEntry() { Message = $"Scheduled controlled server {serverToMaintain.Id} for termination due to maintenance", }, cancellationToken); await database.SaveChangesAsync(cancellationToken); }
public async Task <IActionResult> ReProcessCrashDump([Required] long id) { var report = await WithoutLogs(database.CrashReports, true).Where(r => r.Id == id) .FirstOrDefaultAsync(); if (report == null) { return(NotFound()); } if (report.DumpLocalFileName == null) { return(BadRequest("Report no longer has a crash dump file")); } await database.ActionLogEntries.AddAsync(new ActionLogEntry() { Message = $"Report {report.Id} crash dump reprocessing requested", PerformedById = HttpContext.AuthenticatedUserOrThrow().Id, }); await database.SaveChangesAsync(); jobClient.Enqueue <StartStackwalkOnReportJob>(x => x.Execute(report.Id, CancellationToken.None)); return(Ok()); }
public async Task Execute(CancellationToken cancellationToken) { var pullRequests = await database.GithubPullRequests.Include(p => p.AutoComments) .Where(pr => pr.ClaSigned == true) .ToListAsync(cancellationToken); var comments = await database.GithubAutoComments.Where(c => c.Enabled && c.Condition == AutoCommentCondition.IfCLABecomesInvalid).ToListAsync(cancellationToken); foreach (var pullRequest in pullRequests) { logger.LogInformation("Unmarking PR {Repository}/{GithubId} as having CLA signed", pullRequest.Repository, pullRequest.GithubId); pullRequest.ClaSigned = null; } await database.SaveChangesAsync(cancellationToken); // Open PRs need immediately (this way new webhook shouldn't be able to get their new data lost by this) // to be checked if they are still good foreach (var pullRequest in pullRequests) { if (pullRequest.Open) { // TODO: make separate job that doesn't update PR data other than the signature status jobClient.Enqueue <CheckPullRequestStatusJob>(x => x.Execute(pullRequest.Repository, pullRequest.GithubId, pullRequest.LatestCommit, pullRequest.AuthorUsername, pullRequest.Open, CancellationToken.None)); } } if (comments.Count > 0) { await PostComments(pullRequests, comments); } }
// TODO: make this async protected async Task PerformProvisioningCommands(BaseServer server, string command) { Logger.LogInformation("Beginning SSH connect to provision server at: {PublicAddress}", server.PublicAddress); // TODO: there should probably be a maximum number of times this is attempted IBaseSSHAccess sshAccess; try { sshAccess = ConnectWithSSH(server); } catch (SocketException) { Logger.LogWarning("Connection failed (socket exception), server is probably not up yet"); return; } catch (SshOperationTimeoutException) { Logger.LogWarning("Connection failed (ssh timed out), server is probably not up yet"); return; } Logger.LogInformation("Connected, running provisioning command..."); var start = DateTime.UtcNow; var result = sshAccess.RunCommand(command); if (!result.Success) { Logger.LogWarning("Failed provision result ({ExitCode}: {Result}", result.ExitCode, result.Result); throw new Exception($"Provisioning commands failed ({result.ExitCode}): {result.Error}"); } // Now fully provisioned server.ProvisionedFully = true; server.Status = ServerStatus.Running; server.LastMaintenance = DateTime.UtcNow; server.WantsMaintenance = false; server.StatusLastChecked = DateTime.UtcNow; server.ReservationType = ServerReservationType.None; server.BumpUpdatedAt(); await Database.SaveChangesAsync(); var elapsed = DateTime.UtcNow - start; Logger.LogInformation("Completed provisioning for server {Id}, elapsed: {Elapsed}", server.Id, elapsed); }
public async Task Execute(CancellationToken cancellationToken) { // TODO: if we ever have more patrons (unlikely) than can be kept in memory, this needs a different // approach var patrons = await database.Patrons.ToListAsync(cancellationToken); patrons.ForEach(p => p.Marked = false); foreach (var settings in await database.PatreonSettings.ToListAsync(cancellationToken)) { if (settings.Active == false) { continue; } var api = new PatreonCreatorAPI(settings); foreach (var actualPatron in await api.GetPatrons(settings, cancellationToken)) { await PatreonGroupHandler.HandlePatreonPledgeObject(actualPatron.Pledge, actualPatron.User, actualPatron.Reward?.Id, database, jobClient); if (cancellationToken.IsCancellationRequested) { throw new TaskCanceledException(); } } settings.LastRefreshed = DateTime.UtcNow; } foreach (var toDelete in patrons.Where(p => p.Marked == false)) { await database.LogEntries.AddAsync(new LogEntry() { Message = $"Destroying patron ({toDelete.Id}) because it is unmarked " + "(wasn't found from fresh data from Patreon)" }, cancellationToken); logger.LogInformation("Deleted unmarked Patron {Id}", toDelete.Id); database.Patrons.Remove(toDelete); } await database.SaveChangesAsync(cancellationToken); jobClient.Enqueue <ApplyPatronForumGroupsJob>(x => x.Execute(CancellationToken.None)); }
public async Task <IActionResult> Post(RegistrationFormData request) { if (!csrfVerifier.IsValidCSRFToken(request.CSRF, null, false)) { return(BadRequest("Invalid CSRF")); } if (!SecurityHelpers.SlowEquals(request.RegistrationCode, configuration.RegistrationCode)) { return(BadRequest("Invalid registration code")); } if (!request.Email.Contains('@')) { return(BadRequest("Email is invalid")); } // Check for conflicting username or email if (await database.Users.FirstOrDefaultAsync(u => u.UserName == request.Name) != null || await database.Users.FirstOrDefaultAsync(u => u.Email == request.Email) != null) { return(BadRequest("There is already an account associated with the given email or name")); } var password = Passwords.CreateSaltedPasswordHash(request.Password); var user = new User() { Email = request.Email, UserName = request.Name, PasswordHash = password, Local = true }; await database.Users.AddAsync(user); Models.User.OnNewUserCreated(user, jobClient); await database.SaveChangesAsync(); logger.LogInformation("New user registered {Name} ({Email})", request.Name, request.Email); return(Created($"/users/{user.Id}", user.GetInfo(RecordAccessLevel.Private))); }
public async Task Execute(CancellationToken cancellationToken) { var ciJobsNeedingActions = await database.CiJobs.Where(j => j.State != CIJobState.Finished) .ToListAsync(cancellationToken); await serverHandler.CheckServerStatuses(cancellationToken); cancellationToken.ThrowIfCancellationRequested(); bool queuedRecheck = false; // Cancellation tokens are not used from here on out to avoid not saving changes if (!await serverHandler.HandleCIJobs(ciJobsNeedingActions)) { logger.LogInformation( "One or more jobs could not start executing immediately, trying again in 10 seconds"); queuedRecheck = true; jobClient.Schedule <HandleControlledServerJobsJob>(x => x.Execute(CancellationToken.None), TimeSpan.FromSeconds(10)); } // Skip this if we should cancel if (!cancellationToken.IsCancellationRequested) { await serverHandler.ShutdownIdleServers(); } // ReSharper disable once MethodSupportsCancellation await database.SaveChangesAsync(); // If we have active servers, queue a check in 1 minute if (!queuedRecheck && (serverHandler.NewServersAdded || (await serverHandler.GetControlledServers()).Any( s => s.Status == ServerStatus.Provisioning || s.Status == ServerStatus.Running || s.Status == ServerStatus.Stopping || s.Status == ServerStatus.WaitingForStartup))) { jobClient.Schedule <HandleControlledServerJobsJob>(x => x.Execute(CancellationToken.None), TimeSpan.FromSeconds(60)); } // Sleep a tiny amount to ensure that duplicate instances of this job can't hammer a server really hard await Task.Delay(TimeSpan.FromMilliseconds(50), cancellationToken); }
public async Task Execute(long versionId, CancellationToken cancellationToken) { var version = await database.StorageItemVersions.Include(v => v.StorageFile) .FirstOrDefaultAsync(i => i.Id == versionId, cancellationToken); if (version == null) { logger.LogWarning( "Failed to get StorageItemVersion ({VersionId}) for deletion, assuming already deleted", versionId); return; } if (version.StorageFile == null) { throw new NotLoadedModelNavigationException(); } logger.LogInformation("Deleting remote storage object queued for deletion: {StoragePath}", version.StorageFile.StoragePath); await remoteStorage.DeleteObject(version.StorageFile.StoragePath); database.StorageItemVersions.Remove(version); database.StorageFiles.Remove(version.StorageFile); // Not cancellable as we have already deleted the remote item at this point // ReSharper disable once MethodSupportsCancellation await database.SaveChangesAsync(); try { await remoteStorage.DeleteObject(version.StorageFile.UploadPath); logger.LogInformation( "Deleted upload path for a remote storage item that was just deleted: {UploadPath}", version.StorageFile.UploadPath); } catch (Exception e) { logger.LogTrace("Upload path probably didn't exist for the above file, couldn't delete it: {@E}", e); } }
public async Task Execute(long ciProjectId, long ciBuildId, long ciJobId, long serverId, CancellationToken cancellationToken) { var job = await database.CiJobs.FirstOrDefaultAsync( j => j.CiProjectId == ciProjectId && j.CiBuildId == ciBuildId && j.CiJobId == ciJobId, cancellationToken); if (job == null) { logger.LogError("Failed to check if a CI job startup is stuck, can't find the job"); return; } if (job.State == CIJobState.Finished) { return; } var outputSections = await database.CiJobOutputSections.CountAsync(s => s.CiProjectId == ciProjectId && s.CiBuildId == ciBuildId && s.CiJobId == ciJobId, cancellationToken); if (outputSections > 0) { logger.LogInformation("CI job {CIProjectId}-{CIBuildId}-{CIJobId} has connected output", ciProjectId, ciBuildId, ciJobId); return; } logger.LogWarning("Detected CI job {CIProjectId}-{CIBuildId}-{CIJobId} as stuck starting", ciProjectId, ciBuildId, ciJobId); if (job.RunningOnServerId != serverId) { logger.LogError("Wrong RunningOnServerId in job (detected startup is stuck)"); job.RunningOnServerId = serverId; await database.SaveChangesAsync(cancellationToken); } jobClient.Enqueue <SetFinishedCIJobStatusJob>(x => x.Execute(ciProjectId, ciBuildId, ciJobId, false, CancellationToken.None)); }
public async Task Execute(long meetingId, long pollId, CancellationToken cancellationToken) { var poll = await database.MeetingPolls.FindAsync(meetingId, pollId); if (poll == null) { logger.LogError("Can't auto-close a non-existent poll: {MeetingId}-{PollId}", meetingId, pollId); return; } if (poll.AutoCloseAt == null) { logger.LogInformation("Auto-close poll is no longer an auto-close, skipping doing anything"); return; } // If not time yet, reschedule if (poll.AutoCloseAt.Value > DateTime.UtcNow) { logger.LogWarning("Auto-close poll close time is in the future, scheduling a new job to run then"); jobClient.Schedule <CloseAutoClosePollJob>(x => x.Execute(meetingId, pollId, CancellationToken.None), poll.AutoCloseAt.Value); return; } // Don't even log anything if already closed if (poll.ClosedAt != null) { return; } poll.ClosedAt = DateTime.UtcNow; await database.SaveChangesAsync(cancellationToken); logger.LogInformation("Auto-closed poll: {MeetingId}-{PollId}", meetingId, pollId); // Queue a job to calculate the results jobClient.Enqueue <ComputePollResultsJob>(x => x.Execute(meetingId, pollId, CancellationToken.None)); }