Example #1
0
        public async Task <IRemote> GetRemoteAsync(string repoUrl, ILogger logger)
        {
            using (logger.BeginScope($"Getting remote for repo {repoUrl}."))
            {
                // Normalize the url with the AzDO client prior to attempting to
                // get a token. When we do coherency updates we build a repo graph and
                // may end up traversing links to classic azdo uris.
                string normalizedUrl     = AzureDevOpsClient.NormalizeUrl(repoUrl);
                Uri    normalizedRepoUri = new Uri(normalizedUrl);
                // Look up the setting for where the repo root should be held.  Default to empty,
                // which will use the temp directory.
                string temporaryRepositoryRoot = Configuration.GetValue <string>("DarcTemporaryRepoRoot", null);
                if (string.IsNullOrEmpty(temporaryRepositoryRoot))
                {
                    temporaryRepositoryRoot = _tempFiles.GetFilePath("repos");
                }
                IGitRepo gitClient;

                long installationId = await Context.GetInstallationId(normalizedUrl);

                await ExponentialRetry.RetryAsync(
                    async() => await EnsureLocalGit(logger),
                    ex => logger.LogError(ex, $"Failed to install git to local temporary directory."),
                    ex => true);

                switch (normalizedRepoUri.Host)
                {
                case "github.com":
                    if (installationId == default)
                    {
                        throw new GithubApplicationInstallationException($"No installation is avaliable for repository '{normalizedUrl}'");
                    }

                    gitClient = new GitHubClient(_gitExecutable, await GitHubTokenProvider.GetTokenForInstallationAsync(installationId),
                                                 logger, temporaryRepositoryRoot, Cache.Cache);
                    break;

                case "dev.azure.com":
                    gitClient = new AzureDevOpsClient(_gitExecutable, await AzureDevOpsTokenProvider.GetTokenForRepository(normalizedUrl),
                                                      logger, temporaryRepositoryRoot);
                    break;

                default:
                    throw new NotImplementedException($"Unknown repo url type {normalizedUrl}");
                }
                ;

                return(new Remote(gitClient, new MaestroBarClient(Context), logger));
            }
        }
        public async Task <IRemote> GetRemoteAsync(string repoUrl, ILogger logger)
        {
            // Normalize the url with the AzDO client prior to attempting to
            // get a token. When we do coherency updates we build a repo graph and
            // may end up traversing links to classic azdo uris.
            string normalizedUrl     = AzureDevOpsClient.NormalizeUrl(repoUrl);
            Uri    normalizedRepoUri = new Uri(normalizedUrl);
            // Look up the setting for where the repo root should be held.  Default to empty,
            // which will use the temp directory.
            string temporaryRepositoryRoot = Configuration.GetValue <string>("DarcTemporaryRepoRoot", null);

            if (string.IsNullOrEmpty(temporaryRepositoryRoot))
            {
                temporaryRepositoryRoot = Path.GetTempPath();
            }
            IGitRepo gitClient;

            long installationId = await Context.GetInstallationId(normalizedUrl);

            switch (normalizedRepoUri.Host)
            {
            case "github.com":
                if (installationId == default)
                {
                    throw new SubscriptionException($"No installation is avaliable for repository '{normalizedUrl}'");
                }

                gitClient = new GitHubClient(await GitHubTokenProvider.GetTokenForInstallation(installationId),
                                             logger, temporaryRepositoryRoot);
                break;

            case "dev.azure.com":
                gitClient = new AzureDevOpsClient(await AzureDevOpsTokenProvider.GetTokenForRepository(normalizedUrl),
                                                  logger, temporaryRepositoryRoot);
                break;

            default:
                throw new NotImplementedException($"Unknown repo url type {normalizedUrl}");
            }
            ;

            return(new Remote(gitClient, new MaestroBarClient(Context), logger));
        }
Example #3
0
        public async Task <IRemote> GetRemoteAsync(string repoUrl, ILogger logger)
        {
            using (logger.BeginScope($"Getting remote for repo {repoUrl}."))
            {
                // Normalize the url with the AzDO client prior to attempting to
                // get a token. When we do coherency updates we build a repo graph and
                // may end up traversing links to classic azdo uris.
                string normalizedUrl     = AzureDevOpsClient.NormalizeUrl(repoUrl);
                Uri    normalizedRepoUri = new Uri(normalizedUrl);

                IGitRepo gitClient;

                long installationId = await Context.GetInstallationId(normalizedUrl);

                switch (normalizedRepoUri.Host)
                {
                case "github.com":
                    if (installationId == default)
                    {
                        throw new GithubApplicationInstallationException($"No installation is avaliable for repository '{normalizedUrl}'");
                    }
                    gitClient = new GitHubClient(null, await GitHubTokenProvider.GetTokenForInstallationAsync(installationId),
                                                 logger, null, Cache.Cache);
                    break;

                case "dev.azure.com":
                    gitClient = new AzureDevOpsClient(null, await AzureDevOpsTokenProvider.GetTokenForRepository(normalizedUrl),
                                                      logger, null);
                    break;

                default:
                    throw new NotImplementedException($"Unknown repo url type {normalizedUrl}");
                }
                ;

                return(new Remote(gitClient, new MaestroBarClient(Context, KustoClientProvider), logger));
            }
        }
        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.