예제 #1
0
        private async Task EnsureLabelsAsync()
        {
            GitHubConnectionOptions options = _githubOptions.Value;
            IGitHubClient           client  = await _gitHubApplicationClientFactory.CreateGitHubClientAsync(options.Organization, options.Repository);

            await GitHubModifications.TryCreateAsync(
                () => client.Issue.Labels.Create(
                    options.Organization,
                    options.Repository,
                    new NewLabel(_githubOptions.Value.RcaLabel, "009999")),
                _logger
                );
        }
예제 #2
0
        /// <summary>
        /// Creates/updates the github issue.
        /// </summary>
        /// <param name="updateHistoryError">Error info for which github issue has to be created</param>
        /// <param name="issueRepo">Repository where the github issue is created</param>
        /// <param name="shouldReplaceDescription">Func that carries info the description has to be replaced </param>
        /// <param name="description">Description for the issue body / comment body</param>
        /// <returns></returns>
        private async Task CreateOrUpdateGithubIssueAsync(
            RepositoryBranchUpdateHistoryEntry updateHistoryError,
            string issueRepo,
            Func <string, string, bool> shouldReplaceDescription,
            string description)
        {
            _logger.LogInformation($"Error Message : '{updateHistoryError.ErrorMessage}' in repository :  '{updateHistoryError.Repository}'");
            IReliableDictionary <(string repository, string branch), int> gitHubIssueEvaluator =
                await _stateManager.GetOrAddAsync <IReliableDictionary <(string repository, string branch), int> >("gitHubIssueEvaluator");

            var           parseRepoUri = ParseRepoUri(issueRepo);
            IGitHubClient client       = await _authenticateGitHubApplicationClient.CreateGitHubClientAsync(parseRepoUri.owner, parseRepoUri.repo);

            Octokit.Repository repo = await client.Repository.Get(
                parseRepoUri.owner,
                parseRepoUri.repo);

            var issueNumber = new ConditionalValue <int>();

            using (ITransaction tx = _stateManager.CreateTransaction())
            {
                issueNumber = await gitHubIssueEvaluator.TryGetValueAsync(
                    tx,
                    (updateHistoryError.Repository,
                     updateHistoryError.Branch));

                await tx.CommitAsync();
            }
            if (issueNumber.HasValue)
            {
                Issue issue = await client.Issue.Get(repo.Id, issueNumber.Value);

                // check if the issue is open only then update it else create a new issue and update the dictionary.
                if (issue.State.Equals("Open"))
                {
                    _logger.LogInformation($@"Updating a gitHub issue number : '{issueNumber}' for the error : '{updateHistoryError.ErrorMessage}' for the repository : '{updateHistoryError.Repository}'");
                    await UpdateIssueAsync(
                        client,
                        updateHistoryError,
                        shouldReplaceDescription,
                        description,
                        issue,
                        repo.Id);

                    return;
                }
            }
            // Create a new issue for the error if the issue is already closed or the issue does not exists.
            _logger.LogInformation($@"Creating a new gitHub issue for dependency Update Error, for the error message : '{updateHistoryError.ErrorMessage} for the repository : '{updateHistoryError.Repository}'");
            await CreateIssueAsync(
                client,
                updateHistoryError,
                gitHubIssueEvaluator,
                description,
                repo.Id,
                issueRepo);
        }
        private async Task ProcessBuildNotificationsAsync(Build build)
        {
            const string fullBranchPrefix = "refs/heads/";

            foreach (var monitor in _options.Value.Monitor.Builds)
            {
                if (!string.Equals(build.Project.Name, monitor.Project, StringComparison.OrdinalIgnoreCase))
                {
                    continue;
                }

                if (!string.Equals(monitor.DefinitionPath, $"{build.Definition.Path}\\{build.Definition.Name}", StringComparison.OrdinalIgnoreCase))
                {
                    continue;
                }

                if (monitor.Branches.All(mb => !string.Equals($"{fullBranchPrefix}{mb}",
                                                              build.SourceBranch,
                                                              StringComparison.OrdinalIgnoreCase)))
                {
                    continue;
                }

                string prettyBranch = build.SourceBranch;
                if (prettyBranch.StartsWith(fullBranchPrefix))
                {
                    prettyBranch = prettyBranch.Substring(fullBranchPrefix.Length);
                }

                _logger.LogInformation(
                    "Build '{buildNumber}' in project '{projectName}' with definition '{definitionPath}' and branch '{branch}' matches monitoring criteria, sending notification",
                    build.BuildNumber,
                    build.Project.Name,
                    build.Definition.Path,
                    build.SourceBranch);

                _logger.LogInformation("Fetching timeline messages...");
                string timelineMessage = await BuildTimelineMessage(build);

                _logger.LogInformation("Fetching changes messages...");
                string changesMessage = await BuildChangesMessage(build);

                BuildMonitorOptions.IssuesOptions repo = _options.Value.Issues;
                IGitHubClient github = await _gitHubApplicationClientFactory.CreateGitHubClientAsync(repo.Owner, repo.Name);

                DateTimeOffset?finishTime = DateTimeOffset.TryParse(build.FinishTime, out var parsedFinishTime) ?parsedFinishTime: (DateTimeOffset?)null;
                DateTimeOffset?startTime  = DateTimeOffset.TryParse(build.StartTime, out var parsedStartTime) ? parsedStartTime:(DateTimeOffset?)null;

                string timeString     = "";
                string durationString = "";
                if (finishTime.HasValue)
                {
                    timeString = finishTime.Value.ToString("R");
                    if (startTime.HasValue)
                    {
                        durationString = ((int)(finishTime.Value - startTime.Value).TotalMinutes) + " minutes";
                    }
                }

                string icon = build.Result == "failed" ? ":x:" : ":warning:";

                string body = @$ "Build [#{build.BuildNumber}]({build.Links.Web.Href}) {build.Result}

## {icon} : {build.Project.Name} / {build.Definition.Name} {build.Result}

### Summary
**Finished** - {timeString}
**Duration** - {durationString}
**Requested for** - {build.RequestedFor.DisplayName}
**Reason** - {build.Reason}

### Details

{timelineMessage}

### Changes

{changesMessage}
예제 #4
0
        /// <summary>
        /// Creates/updates the github issue.
        /// </summary>
        /// <param name="updateHistoryError">Error info for which github issue has to be created</param>
        /// <param name="issueRepo">Repository where the github issue is created</param>
        /// <param name="shouldReplaceDescription">Func that carries info the description has to be replaced </param>
        /// <param name="description">Description for the issue body / comment body</param>
        /// <returns></returns>
        private async Task CreateOrUpdateGithubIssueAsync(
            UpdateHistoryEntry updateHistoryError,
            string issueRepo,
            Func <string, string, bool> shouldReplaceDescription,
            string description)
        {
            var           parsedRepoUri = ParseRepoUri(issueRepo);
            IGitHubClient client        = await _authenticateGitHubApplicationClient.CreateGitHubClientAsync(parsedRepoUri.owner, parsedRepoUri.repo);

            Repository repo = await client.Repository.Get(
                parsedRepoUri.owner,
                parsedRepoUri.repo);

            var   issueNumber = new ConditionalValue <int>();
            Issue issue       = null;

            switch (updateHistoryError)
            {
            case RepositoryBranchUpdateHistoryEntry repoBranchUpdateHistoryError:
            {
                _logger.LogInformation($"Error Message : '{repoBranchUpdateHistoryError.ErrorMessage}' in repository :  '{repoBranchUpdateHistoryError.Repository}'");

                IReliableDictionary <(string repository, string branch), int> gitHubIssueEvaluator =
                    await _stateManager.GetOrAddAsync <IReliableDictionary <(string repository, string branch), int> >("gitHubIssueEvaluator");

                using (ITransaction tx = _stateManager.CreateTransaction())
                {
                    issueNumber = await gitHubIssueEvaluator.TryGetValueAsync(
                        tx,
                        (repoBranchUpdateHistoryError.Repository,
                         repoBranchUpdateHistoryError.Branch));

                    await tx.CommitAsync();
                }

                if (issueNumber.HasValue)
                {
                    _logger.LogInformation($"Found a matching issue for {repoBranchUpdateHistoryError.Repository}:{repoBranchUpdateHistoryError.Branch}. Issue number: {issueNumber}");
                    issue = await client.Issue.Get(repo.Id, issueNumber.Value);

                    // check if the issue is open only then update it else create a new issue and update the dictionary.
                    if (issue.State.Equals("Open"))
                    {
                        // Found an existing issue, fall through to update.
                        break;
                    }

                    _logger.LogInformation($"Matching issue {issueNumber} for {repoBranchUpdateHistoryError.Repository}:{repoBranchUpdateHistoryError.Branch} was closed. Creating a new issue.");
                }
                // Create a new issue for the error if the issue is already closed or the issue does not exist.
                _logger.LogInformation($@"Creating a new gitHub issue for dependency Update Error, for the error message : '{repoBranchUpdateHistoryError.ErrorMessage} for the repository : '{repoBranchUpdateHistoryError.Repository}'");
                await CreateDependencyUpdateErrorIssueAsync(
                    client,
                    repoBranchUpdateHistoryError,
                    gitHubIssueEvaluator,
                    description,
                    repo.Id,
                    issueRepo);

                break;
            }

            case SubscriptionUpdateHistoryEntry subscriptionUpdateHistoryError:
            {
                _logger.LogInformation($"Error Message : '{subscriptionUpdateHistoryError.ErrorMessage}' in subscription :  '{subscriptionUpdateHistoryError.SubscriptionId}'");

                IReliableDictionary <Guid, int> gitHubIssueEvaluator =
                    await _stateManager.GetOrAddAsync <IReliableDictionary <Guid, int> >("gitHubSubscriptionIssueEvaluator");

                using (ITransaction tx = _stateManager.CreateTransaction())
                {
                    issueNumber = await gitHubIssueEvaluator.TryGetValueAsync(
                        tx,
                        subscriptionUpdateHistoryError.SubscriptionId);

                    await tx.CommitAsync();
                }
                if (issueNumber.HasValue)
                {
                    _logger.LogInformation($"Found a matching issue for subscription {subscriptionUpdateHistoryError.SubscriptionId}. Issue number: {issueNumber}");
                    issue = await client.Issue.Get(repo.Id, issueNumber.Value);

                    // check if the issue is open only then update it else create a new issue and update the dictionary.
                    if (issue.State.Equals("Open"))
                    {
                        // Found an existing issue, fall through to update.
                        break;
                    }

                    _logger.LogInformation($"Matching issue {issueNumber} for subscription {subscriptionUpdateHistoryError.SubscriptionId} was closed. Creating a new issue.");
                }
                // Create a new issue for the error if the issue is already closed or the issue does not exist.
                _logger.LogInformation($@"Creating a new gitHub issue for Subscription Update Error, for the error message : '{subscriptionUpdateHistoryError.ErrorMessage} for subscription : '{subscriptionUpdateHistoryError.SubscriptionId}'");
                await CreateSubscriptionUpdateErrorIssueAsync(
                    client,
                    subscriptionUpdateHistoryError,
                    gitHubIssueEvaluator,
                    description,
                    repo.Id,
                    issueRepo);

                break;
            }

            default:
                throw new InvalidOperationException($"Unknown update history entry type: {updateHistoryError.GetType()}");
            }

            // Updating an existing issue; can use same codepath.
            if (issueNumber.HasValue)
            {
                // check if the issue is open only then update it. Otherwise, we already created the new issue, so do nothing.
                if (issue.State.Equals("Open"))
                {
                    _logger.LogInformation($@"Updating a gitHub issue number : '{issueNumber}' for the error : '{updateHistoryError.ErrorMessage}' for {GetPrintableDescription(updateHistoryError)}");
                    await UpdateIssueAsync(client, updateHistoryError, shouldReplaceDescription, description, issue, repo.Id);

                    return;
                }
            }
        }
예제 #5
0
        private async Task ProcessBuildNotificationsAsync(Build build)
        {
            const string fullBranchPrefix = "refs/heads/";

            foreach (var monitor in _options.Value.Monitor.Builds)
            {
                if (!string.Equals(build.Project.Name, monitor.Project, StringComparison.OrdinalIgnoreCase))
                {
                    continue;
                }

                if (!string.Equals(monitor.DefinitionPath, $"{build.Definition.Path}\\{build.Definition.Name}", StringComparison.OrdinalIgnoreCase))
                {
                    continue;
                }

                if (monitor.Branches.All(mb => !string.Equals($"{fullBranchPrefix}{mb}",
                                                              build.SourceBranch,
                                                              StringComparison.OrdinalIgnoreCase)))
                {
                    continue;
                }

                if (monitor.Tags != null && monitor.Tags.Any() && !(monitor.Tags.Intersect(build.Tags).Any()))
                {
                    // We should only skip processing if tags were specified in the monitor, and none of those tags were found in the build
                    continue;
                }

                string prettyBranch = build.SourceBranch;
                if (prettyBranch.StartsWith(fullBranchPrefix))
                {
                    prettyBranch = prettyBranch.Substring(fullBranchPrefix.Length);
                }

                string prettyTags = (monitor.Tags != null && monitor.Tags.Any()) ? $"{string.Join(", ", build.Tags)}" : "";

                _logger.LogInformation(
                    "Build '{buildNumber}' in project '{projectName}' with definition '{definitionPath}', tags '{prettyTags}', and branch '{branch}' matches monitoring criteria, sending notification",
                    build.BuildNumber,
                    build.Project.Name,
                    build.Definition.Path,
                    prettyTags,
                    build.SourceBranch);

                _logger.LogInformation("Fetching timeline messages...");
                string timelineMessage = await BuildTimelineMessage(build);

                _logger.LogInformation("Fetching changes messages...");
                string changesMessage = await BuildChangesMessage(build);

                BuildMonitorOptions.IssuesOptions repo = _options.Value.Issues.SingleOrDefault(i => string.Equals(monitor.IssuesId, i.Id, StringComparison.OrdinalIgnoreCase));

                if (repo != null)
                {
                    IGitHubClient github = await _gitHubApplicationClientFactory.CreateGitHubClientAsync(repo.Owner, repo.Name);

                    DateTimeOffset?finishTime = DateTimeOffset.TryParse(build.FinishTime, out var parsedFinishTime) ?parsedFinishTime: (DateTimeOffset?)null;
                    DateTimeOffset?startTime  = DateTimeOffset.TryParse(build.StartTime, out var parsedStartTime) ? parsedStartTime:(DateTimeOffset?)null;

                    string timeString     = "";
                    string durationString = "";
                    if (finishTime.HasValue)
                    {
                        timeString = finishTime.Value.ToString("R");
                        if (startTime.HasValue)
                        {
                            durationString = ((int)(finishTime.Value - startTime.Value).TotalMinutes) + " minutes";
                        }
                    }

                    string icon = build.Result == "failed" ? ":x:" : ":warning:";

                    string body             = @$ "Build [#{build.BuildNumber}]({build.Links.Web.Href}) {build.Result}

## {icon} : {build.Project.Name} / {build.Definition.Name} {build.Result}

### Summary
**Finished** - {timeString}
**Duration** - {durationString}
**Requested for** - {build.RequestedFor.DisplayName}
**Reason** - {build.Reason}

### Details

{timelineMessage}

### Changes

{changesMessage}
";
                    string issueTitlePrefix = $"Build failed: {build.Definition.Name}/{prettyBranch} {prettyTags}";

                    if (repo.UpdateExisting)
                    {
                        // There is no way to get the username of our bot directly from the GithubApp with the C# api.
                        // Issue opened in Octokit: https://github.com/octokit/octokit.net/issues/2335
                        // We do, however, have access to the HtmlUrl, which ends with the name of the bot.
                        // Additionally, when the bot opens issues, the username used ends with [bot], which isn't strictly
                        // part of the name anywhere else. So, to get the correct creator name, get the HtmlUrl, grab
                        // the bot's name from it, and append [bot] to that string.
                        var    githubAppClient = _gitHubApplicationClientFactory.CreateGitHubAppClient();
                        string creator         = (await githubAppClient.GitHubApps.GetCurrent()).HtmlUrl.Split("/").Last();

                        RepositoryIssueRequest issueRequest = new RepositoryIssueRequest {
                            Creator       = $"{creator}[bot]",
                            State         = ItemStateFilter.Open,
                            SortProperty  = IssueSort.Created,
                            SortDirection = SortDirection.Descending
                        };

                        foreach (string label in repo.Labels.OrEmpty())
                        {
                            issueRequest.Labels.Add(label);
                        }

                        foreach (string label in monitor.Labels.OrEmpty())
                        {
                            issueRequest.Labels.Add(label);
                        }

                        List <Issue> matchingIssues = (await github.Issue.GetAllForRepository(repo.Owner, repo.Name, issueRequest)).ToList();
                        Issue        matchingIssue  = matchingIssues.FirstOrDefault(i => i.Title.StartsWith(issueTitlePrefix));

                        if (matchingIssue != null)
                        {
                            _logger.LogInformation("Found matching issue {issueNumber} in {owner}/{repo}. Will attempt to add a new comment.", matchingIssue.Number, repo.Owner, repo.Name);
                            // Add a new comment to the issue with the body
                            IssueComment newComment = await github.Issue.Comment.Create(repo.Owner, repo.Name, matchingIssue.Number, body);

                            _logger.LogInformation("Logged comment in {owner}/{repo}#{issueNumber} for build failure", repo.Owner, repo.Name, matchingIssue.Number);

                            return;
                        }
                        else
                        {
                            _logger.LogInformation("Matching issues for {issueTitlePrefix} not found. Creating a new issue.", issueTitlePrefix);
                        }
                    }

                    // Create new issue if repo.UpdateExisting is false or there were no matching issues
                    var newIssue =
                        new NewIssue($"{issueTitlePrefix} #{build.BuildNumber}")
                    {
                        Body = body,
                    };

                    if (!string.IsNullOrEmpty(monitor.Assignee))
                    {
                        newIssue.Assignees.Add(monitor.Assignee);
                    }

                    foreach (string label in repo.Labels.OrEmpty())
                    {
                        newIssue.Labels.Add(label);
                    }

                    foreach (string label in monitor.Labels.OrEmpty())
                    {
                        newIssue.Labels.Add(label);
                    }

                    Issue issue = await github.Issue.Create(repo.Owner, repo.Name, newIssue);

                    _logger.LogInformation("Logged issue {owner}/{repo}#{issueNumber} for build failure", repo.Owner, repo.Name, issue.Number);
                }