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)); }
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.