示例#1
0
        private async Task CreateFailedJob(CiBuild build, string failure, CancellationToken cancellationToken)
        {
            var outputSection = new CiJobOutputSection()
            {
                CiProjectId          = build.CiProjectId,
                CiBuildId            = build.CiBuildId,
                CiJobId              = 1,
                CiJobOutputSectionId = 1,
                Name   = "Invalid configuration",
                Status = CIJobSectionStatus.Failed,
                Output = failure
            };

            outputSection.CalculateOutputLength();

            var job = new CiJob
            {
                CiProjectId = build.CiProjectId,
                CiBuildId   = build.CiBuildId,
                CiJobId     = 1,
                JobName     = "configuration_error",
                FinishedAt  = DateTime.UtcNow,
                Succeeded   = false,
                State       = CIJobState.Finished,

                CiJobOutputSections = new List <CiJobOutputSection>()
                {
                    outputSection
                }
            };

            if (build.CiProject == null)
            {
                throw new NotLoadedModelNavigationException();
            }

            await database.CiJobs.AddAsync(job, cancellationToken);

            await database.CiJobOutputSections.AddAsync(outputSection, cancellationToken);

            await database.SaveChangesAsync(cancellationToken);

            if (!await statusReporter.SetCommitStatus(build.CiProject.RepositoryFullName, build.CommitHash,
                                                      GithubAPI.CommitStatus.Failure, statusReporter.CreateStatusUrlForJob(job), failure,
                                                      job.JobName))
            {
                logger.LogError("Failed to report serious failed commit status with context {JobName}", job.JobName);
            }

            jobClient.Enqueue <CheckOverallBuildStatusJob>(x =>
                                                           x.Execute(build.CiProjectId, build.CiBuildId, CancellationToken.None));
        }
        public void NotifyAboutBuild(CiBuild build, string statusUrl)
        {
            var message = new StringBuilder(100);

            message.Append(build.CiProject?.Name ?? "unknown project");
            message.Append(" build nro ");
            message.Append(build.CiBuildId);

            message.Append(" (for: ");
            message.Append(build.RemoteRef);
            message.Append(')');

            switch (build.Status)
            {
            case BuildStatus.Running:
                message.Append(" is still running");
                break;

            case BuildStatus.Succeeded:
                message.Append(" has succeeded.");
                break;

            case BuildStatus.Failed:
                message.Append(" has failed");
                break;

            case BuildStatus.GoingToFail:
                message.Append(" is going to fail");
                break;

            default:
                throw new ArgumentOutOfRangeException();
            }

            if (build.Status != BuildStatus.Succeeded)
            {
                message.Append(". with ");
                message.Append(build.CiJobs.Count(j => j.Succeeded));
                message.Append('/');
                message.Append(build.CiJobs.Count);

                message.Append(" successful jobs.");
            }

            message.Append(' ');
            message.Append(statusUrl);

            jobClient.Enqueue <SendDiscordWebhookMessageJob>(x => x.Execute("CIBuildNotification",
                                                                            message.ToString(), CancellationToken.None));
        }
示例#3
0
        public async Task <IActionResult> ReceiveWebhook()
        {
            var hook = await database.GithubWebhooks.FindAsync(AppInfo.SingleResourceTableRowId);

            if (hook == null)
            {
                logger.LogWarning("Github webhook secret is not configured, can't process webhook");
                return(BadRequest("Incorrect secret"));
            }

            var payload = await CheckSignature(hook);

            GithubWebhookContent data;

            try
            {
                data = JsonSerializer.Deserialize <GithubWebhookContent>(payload,
                                                                         new JsonSerializerOptions(JsonSerializerDefaults.Web)) ?? throw new NullDecodedJsonException();

                if (data == null)
                {
                    throw new Exception("deserialized value is null");
                }
            }
            catch (Exception e)
            {
                logger.LogWarning("Error deserializing github webhook: {@E}", e);
                throw new HttpResponseException()
                      {
                          Value = new BasicJSONErrorResult("Invalid content",
                                                           "Failed to deserialize payload").ToString()
                      };
            }

            if (!HttpContext.Request.Headers.TryGetValue("X-GitHub-Event", out StringValues typeHeader) ||
                typeHeader.Count != 1)
            {
                throw new HttpResponseException()
                      {
                          Value = new BasicJSONErrorResult("Invalid request", "Missing X-GitHub-Event header").ToString()
                      };
            }

            var type = typeHeader[0];

            // TODO: check type on these first two event detections as well
            if (!string.IsNullOrEmpty(data.Ref) && data.RefType != "branch" && !string.IsNullOrEmpty(data.After))
            {
                // This is a push (commit)
                logger.LogInformation("Received a push event for ref: {Ref}", data.Ref);

                if (data.Deleted || data.After == AppInfo.NoCommitHash)
                {
                    logger.LogInformation("Push was about a deleted thing");
                }
                else
                {
                    if (data.Before == AppInfo.NoCommitHash)
                    {
                        logger.LogInformation(
                            "Received a push (probably a new branch) with no before set, setting to the after commit");
                        data.Before = data.After;
                    }

                    if (data.Repository == null)
                    {
                        throw new HttpResponseException()
                              {
                                  Value = new BasicJSONErrorResult("Invalid request",
                                                                   "Repository is needed for this event type").ToString(),
                              };
                    }

                    bool matched = false;

                    // Detect if this triggers any builds
                    foreach (var project in await database.CiProjects.Where(p =>
                                                                            p.ProjectType == CIProjectType.Github && p.Enabled && !p.Deleted &&
                                                                            p.RepositoryFullName == data.Repository.FullName).ToListAsync())
                    {
                        matched = true;

                        // Detect next id
                        var previousBuildId = await database.CiBuilds.Where(b => b.CiProjectId == project.Id)
                                              .MaxAsync(b => (long?)b.CiBuildId) ?? 0;

                        var build = new CiBuild()
                        {
                            CiProjectId    = project.Id,
                            CiBuildId      = ++previousBuildId,
                            CommitHash     = data.After,
                            RemoteRef      = data.Ref,
                            Branch         = GitRunHelpers.ParseRefBranch(data.Ref),
                            IsSafe         = !GitRunHelpers.IsPullRequestRef(data.Ref),
                            PreviousCommit = data.Before,
                            CommitMessage  = data.HeadCommit?.Message ?? data.Commits?.FirstOrDefault()?.Message,
                            ParsedCommits  = data.Commits,
                        };

                        await database.CiBuilds.AddAsync(build);

                        await database.SaveChangesAsync();

                        jobClient.Enqueue <CheckAndStartCIBuild>(x =>
                                                                 x.Execute(build.CiProjectId, build.CiBuildId, CancellationToken.None));
                    }

                    if (!matched)
                    {
                        logger.LogWarning("Push event didn't match any repos: {Fullname}", data.Repository.FullName);
                    }
                }
            }
            else if (!string.IsNullOrEmpty(data.Ref))
            {
                // This is a branch push (or maybe a tag?)
            }
            else if (type == "pull_request")
            {
                bool matched = false;

                if (data.Repository == null)
                {
                    throw new HttpResponseException()
                          {
                              Value = new BasicJSONErrorResult("Invalid request",
                                                               "Repository is needed for this event type").ToString(),
                          };
                }

                if (data.PullRequest == null)
                {
                    throw new HttpResponseException()
                          {
                              Value = new BasicJSONErrorResult("Invalid request",
                                                               "PullRequest data is needed for this event type").ToString(),
                          };
                }

                jobClient.Enqueue <CheckPullRequestStatusJob>(x => x.Execute(data.Repository.FullName,
                                                                             data.PullRequest.Number, data.PullRequest.Head.Sha, data.PullRequest.User.Login,
                                                                             !data.IsClosedPullRequest, CancellationToken.None));

                // Detect if this PR is for any of our repos
                foreach (var project in await database.CiProjects.Where(p =>
                                                                        p.ProjectType == CIProjectType.Github && p.Enabled && !p.Deleted &&
                                                                        p.RepositoryFullName == data.Repository.FullName).ToListAsync())
                {
                    matched = true;

                    if (data.IsClosedPullRequest)
                    {
                        logger.LogInformation("A pull request was closed");
                    }
                    else if (data.Action == "synchronize" || data.Action == "opened")
                    {
                        // PR content was changed so we should rebuild (we don't react to other actions to avoid
                        // duplicate builds)

                        // TODO: CLA checks for PRs
                        // Queue a CLA check

                        // Only non-primary repo PRs have CI jobs ran on them as main repo commits trigger
                        // the push event
                        if (data.PullRequest.Head.Repo.Id != data.Repository.Id)
                        {
                            logger.LogInformation("Received pull request event from a fork: {FullName}",
                                                  data.PullRequest.Head.Repo.FullName);

                            var headRef = GitRunHelpers.GenerateRefForPullRequest(data.PullRequest.Number);

                            // Detect next id
                            var previousBuildId = await database.CiBuilds.Where(b => b.CiProjectId == project.Id)
                                                  .MaxAsync(b => (long?)b.CiBuildId) ?? 0;

                            var build = new CiBuild()
                            {
                                CiProjectId    = project.Id,
                                CiBuildId      = ++previousBuildId,
                                CommitHash     = data.PullRequest.Head.Sha,
                                RemoteRef      = headRef,
                                Branch         = GitRunHelpers.ParseRefBranch(headRef),
                                IsSafe         = false,
                                PreviousCommit = data.PullRequest.Base.Sha,
                                CommitMessage  = $"Pull request #{data.PullRequest.Number}",

                                // TODO: commits would need to be retrieved from data.PullRequest.CommitsUrl
                                Commits = null,
                            };

                            await database.CiBuilds.AddAsync(build);

                            await database.SaveChangesAsync();

                            jobClient.Enqueue <CheckAndStartCIBuild>(x =>
                                                                     x.Execute(build.CiProjectId, build.CiBuildId, CancellationToken.None));
                        }

                        // TODO: could run some special actions on PR open
                        if (data.Action == "opened")
                        {
                        }
                    }
                }

                if (!matched)
                {
                    logger.LogWarning("Pull request event didn't match any repos: {Fullname}",
                                      data.Repository.FullName);
                }
            }

            // TODO: should this always be updated. Github might send us quite a few events if we subscribe to them all
            hook.LastUsed = DateTime.UtcNow;
            await database.SaveChangesAsync();

            return(Ok());
        }