Ejemplo n.º 1
0
        private async Task <ActionResult <bool?> > SynchronizePullRequestAsync(string prUrl)
        {
            ConditionalValue <InProgressPullRequest> maybePr =
                await StateManager.TryGetStateAsync <InProgressPullRequest>(PullRequest);

            if (!maybePr.HasValue || maybePr.Value.Url != prUrl)
            {
                return(ActionResult.Create(
                           (bool?)null,
                           $"Not Applicable: Pull Request '{prUrl}' is not tracked by maestro anymore."));
            }

            (string targetRepository, string targetBranch) = await GetTargetAsync();

            IRemote darc = await DarcRemoteFactory.GetRemoteAsync(targetRepository, Logger);

            InProgressPullRequest pr = maybePr.Value;
            PrStatus status          = await darc.GetPullRequestStatusAsync(prUrl);

            ActionResult <bool?> checkPolicyResult = null;

            switch (status)
            {
            case PrStatus.Open:
                checkPolicyResult = await CheckMergePolicyAsync(prUrl, darc);

                if (checkPolicyResult.Result == true)
                {
                    goto case PrStatus.Merged;
                }

                if (checkPolicyResult.Result == false)
                {
                    return(ActionResult.Create((bool?)true, checkPolicyResult.Message));
                }

                return(ActionResult.Create((bool?)false, checkPolicyResult.Message));

            case PrStatus.Merged:
                await UpdateSubscriptionsForMergedPRAsync(pr.ContainedSubscriptions);

                goto case PrStatus.Closed;

            case PrStatus.Closed:
                await StateManager.RemoveStateAsync(PullRequest);

                break;

            default:
                Logger.LogError("Unknown pr status '{status}'", status);
                break;
            }

            if (checkPolicyResult != null)
            {
                return(ActionResult.Create((bool?)null, checkPolicyResult.Message));
            }

            return(ActionResult.Create((bool?)null, $"PR Has been manually {status}"));
        }
        public async Task TagSourceRepositoryGitHubContactsAsync(InProgressPullRequest pr)
        {
            // We'll try to notify the source repo if the subscription provided a list of aliases to tag.
            // The API checks when creating / updating subscriptions that any resolve-able logins are in the
            // "Microsoft" Github org, so we can safely use them in any comment.
            if (pr.SourceRepoNotified == true)
            {
                Logger.LogInformation($"Skipped notifying source repository for {pr.Url}'s failed policies, as it has already been tagged");
                return;
            }

            var subscriptionFromPr = pr.ContainedSubscriptions.FirstOrDefault();

            if (subscriptionFromPr == null)
            {
                Logger.LogWarning("Unable to get any contained subscriptions from this PR for notification; skipping attempts to notify.");
                pr.SourceRepoNotified = true;
                return;
            }

            // In practice these all contain the same subscription id, the property is more like "containedBuildsAndTheirSubscriptions"
            Logger.LogInformation($"PR contains {pr.ContainedSubscriptions.Count} builds.  Using first ({subscriptionFromPr.SubscriptionId}) for notification tagging.");

            (string owner, string repo, int prIssueId) = GitHubClient.ParsePullRequestUri(pr.Url);
            if (owner == null || repo == null || prIssueId == 0)
            {
                Logger.LogInformation($"Unable to parse pull request URI '{pr.Url}' (typically due to Azure DevOps pull requests), will not notify on this PR.");
                pr.SourceRepoNotified = true;
                return;
            }

            var darcRemote = await DarcRemoteFactory.GetRemoteAsync($"https://github.com/{owner}/{repo}", Logger);

            var darcSubscriptionObject = await darcRemote.GetSubscriptionAsync(subscriptionFromPr.SubscriptionId.ToString());

            string sourceRepository = darcSubscriptionObject.SourceRepository;
            string targetRepository = darcSubscriptionObject.TargetRepository;

            // If we're here, there are failing checks, but if the only checks that failed were Maestro Merge Policy checks, we'll skip informing until something else fails too.
            var prChecks = await darcRemote.GetPullRequestChecksAsync(pr.Url);

            var failedPrChecks = prChecks.Where(p => !p.IsMaestroMergePolicy && (p.Status == CheckState.Failure || p.Status == CheckState.Error)).AsEnumerable();

            if (failedPrChecks.Count() == 0)
            {
                Logger.LogInformation($"All failing or error state checks are 'Maestro Merge Policy'-type checks, not notifying subscribed users.");
                return;
            }

            List <string> tagsToNotify = new List <string>();

            if (!string.IsNullOrEmpty(darcSubscriptionObject.PullRequestFailureNotificationTags))
            {
                tagsToNotify.AddRange(darcSubscriptionObject.PullRequestFailureNotificationTags.Split(';', StringSplitOptions.RemoveEmptyEntries));
            }

            if (tagsToNotify.Count == 0)
            {
                Logger.LogInformation("Found no matching tags for source '{sourceRepo}' to target '{targetRepo}' on channel '{channel}'. ", sourceRepository, targetRepository, darcSubscriptionObject.Channel);
                return;
            }

            // At this point we definitely have notifications to make, so do it.
            Logger.LogInformation("Found {count} matching tags for source '{sourceRepo}' to target '{targetRepo}' on channel '{channel}'. ", tagsToNotify.Count, sourceRepository, targetRepository, darcSubscriptionObject.Channel);

            // To ensure GitHub notifies the people / teams on the list, forcibly check they are inserted with a preceding '@'
            for (int i = 0; i < tagsToNotify.Count; i++)
            {
                if (!tagsToNotify[i].StartsWith('@'))
                {
                    tagsToNotify[i] = $"@{tagsToNotify[i]}";
                }
            }

            string githubToken = await GitHubTokenProvider.GetTokenForRepository(targetRepository);

            var gitHubClient = GitHubClientFactory.CreateGitHubClient(githubToken);

            string sourceRepoNotificationComment = @$ "
#### Notification for subscribed users from {sourceRepository}:

{string.Join($", { Environment.NewLine } ", tagsToNotify)}

#### Action requested: Please take a look at this failing automated dependency-flow pull request's checks; failures may be related to changes which originated in your repo.

- This pull request contains changes from your source repo ({sourceRepository}) and seems to have failed checks in this PR.  Please take a peek at the failures and comment if they seem relevant to your changes.
Ejemplo n.º 3
0
        /// <summary>
        ///     Creates a pull request from the given updates.
        /// </summary>
        /// <param name="updates"></param>
        /// <returns>The pull request url when a pr was created; <see langref="null" /> if no PR is necessary</returns>
        private async Task <string> CreatePullRequestAsync(List <UpdateAssetsParameters> updates)
        {
            (string targetRepository, string targetBranch) = await GetTargetAsync();

            IRemote darcRemote = await DarcRemoteFactory.GetRemoteAsync(targetRepository, Logger);

            List <(UpdateAssetsParameters update, List <DependencyDetail> deps)> requiredUpdates =
                await GetRequiredUpdates(updates, DarcRemoteFactory, targetRepository, targetBranch);

            if (requiredUpdates.Count < 1)
            {
                return(null);
            }

            string newBranchName = $"darc-{targetBranch}-{Guid.NewGuid()}";
            await darcRemote.CreateNewBranchAsync(targetRepository, targetBranch, newBranchName);

            try
            {
                using (var description = new StringWriter())
                {
                    description.WriteLine("This pull request updates the following dependencies");
                    description.WriteLine();

                    await CommitUpdatesAsync(requiredUpdates, description, darcRemote, targetRepository, newBranchName);

                    var inProgressPr = new InProgressPullRequest
                    {
                        // Calculate the subscriptions contained within the
                        // update. Coherency updates do not have subscription info.
                        ContainedSubscriptions = requiredUpdates
                                                 .Where(u => !u.update.IsCoherencyUpdate)
                                                 .Select(
                            u => new SubscriptionPullRequestUpdate
                        {
                            SubscriptionId = u.update.SubscriptionId,
                            BuildId        = u.update.BuildId
                        })
                                                 .ToList()
                    };

                    string prUrl = await darcRemote.CreatePullRequestAsync(
                        targetRepository,
                        new PullRequest
                    {
                        Title       = await ComputePullRequestTitleAsync(inProgressPr, targetBranch),
                        Description = description.ToString(),
                        BaseBranch  = targetBranch,
                        HeadBranch  = newBranchName
                    });

                    if (!string.IsNullOrEmpty(prUrl))
                    {
                        inProgressPr.Url = prUrl;

                        await StateManager.SetStateAsync(PullRequest, inProgressPr);

                        await StateManager.SaveStateAsync();

                        await Reminders.TryRegisterReminderAsync(
                            PullRequestCheck,
                            null,
                            TimeSpan.FromMinutes(5),
                            TimeSpan.FromMinutes(5));

                        return(prUrl);
                    }

                    // Something wrong happened when trying to create the PR but didn't throw an exception (probably there was no diff).
                    // We need to delete the branch also in this case.
                    await darcRemote.DeleteBranchAsync(targetRepository, newBranchName);

                    return(null);
                }
            }
            catch
            {
                await darcRemote.DeleteBranchAsync(targetRepository, newBranchName);

                throw;
            }
        }