private static async Task <IEnumerable <string> > GetInProgressBuildsAsync(IBuildHttpClient client, int pipelineId, Guid projectId) { IPagedList <WebApi.Build> builds = await client.GetBuildsAsync( projectId, definitions : new int[] { pipelineId }, statusFilter : WebApi.BuildStatus.InProgress); return(builds.Select(build => build.GetWebLink())); }
private async Task <bool> HasInProgressBuildAsync(IBuildHttpClient client, int pipelineId, Guid projectId) { IPagedList <Build> builds = await client.GetBuildsAsync( projectId, definitions : new int[] { pipelineId }, statusFilter : BuildStatus.InProgress); return(builds.Any()); }
private async Task QueueBuildForStaleImages(Subscription subscription, IEnumerable <string> pathsToRebuild) { if (!pathsToRebuild.Any()) { this.loggerService.WriteMessage($"All images for subscription '{subscription}' are using up-to-date base images. No rebuild necessary."); return; } string formattedPathsToRebuild = pathsToRebuild .Select(path => $"{ManifestFilterOptions.FormattedPathOption} '{path}'") .Aggregate((p1, p2) => $"{p1} {p2}"); string parameters = "{\"" + subscription.PipelineTrigger.PathVariable + "\": \"" + formattedPathsToRebuild + "\"}"; this.loggerService.WriteMessage($"Queueing build for subscription {subscription} with parameters {parameters}."); if (Options.IsDryRun) { return; } using (IVssConnection connection = this.connectionFactory.Create( new Uri($"https://dev.azure.com/{Options.BuildOrganization}"), new VssBasicCredential(String.Empty, Options.BuildPersonalAccessToken))) using (IProjectHttpClient projectHttpClient = connection.GetProjectHttpClient()) using (IBuildHttpClient client = connection.GetBuildHttpClient()) { TeamProject project = await projectHttpClient.GetProjectAsync(Options.BuildProject); Build build = new Build { Project = new TeamProjectReference { Id = project.Id }, Definition = new BuildDefinitionReference { Id = subscription.PipelineTrigger.Id }, SourceBranch = subscription.RepoInfo.Branch, Parameters = parameters }; if (await HasInProgressBuildAsync(client, subscription.PipelineTrigger.Id, project.Id)) { this.loggerService.WriteMessage( $"An in-progress build was detected on the pipeline for subscription '{subscription.ToString()}'. Queueing the build will be skipped."); return; } await client.QueueBuildAsync(build); } }
private async Task QueueBuildForStaleImages(Subscription subscription, IEnumerable <string> pathsToRebuild) { if (!pathsToRebuild.Any()) { _loggerService.WriteMessage($"All images for subscription '{subscription}' are using up-to-date base images. No rebuild necessary."); return; } string formattedPathsToRebuild = pathsToRebuild .Select(path => $"{ManifestFilterOptions.FormattedPathOption} '{path}'") .Aggregate((p1, p2) => $"{p1} {p2}"); string parameters = "{\"" + subscription.PipelineTrigger.PathVariable + "\": \"" + formattedPathsToRebuild + "\"}"; _loggerService.WriteMessage($"Queueing build for subscription {subscription} with parameters {parameters}."); if (Options.IsDryRun) { return; } (Uri baseUrl, VssCredentials credentials) = Options.AzdoOptions.GetConnectionDetails(); using (IVssConnection connection = _connectionFactory.Create(baseUrl, credentials)) using (IProjectHttpClient projectHttpClient = connection.GetProjectHttpClient()) using (IBuildHttpClient client = connection.GetBuildHttpClient()) { TeamProject project = await projectHttpClient.GetProjectAsync(Options.AzdoOptions.Project); Build build = new Build { Project = new TeamProjectReference { Id = project.Id }, Definition = new BuildDefinitionReference { Id = subscription.PipelineTrigger.Id }, SourceBranch = subscription.Manifest.Branch, Parameters = parameters }; if (await HasInProgressBuildAsync(client, subscription.PipelineTrigger.Id, project.Id)) { _loggerService.WriteMessage( $"An in-progress build was detected on the pipeline for subscription '{subscription}'. Queueing the build will be skipped."); return; } await client.QueueBuildAsync(build); } }
private static async Task <(bool ShouldSkipBuild, IEnumerable <string> RecentFailedBuilds)> ShouldDisallowBuildDueToRecentFailuresAsync( IBuildHttpClient client, int pipelineId, Guid projectId) { List <WebApi.Build> autoBuilderBuilds = (await client.GetBuildsAsync(projectId, definitions: new int[] { pipelineId })) .Where(build => build.Tags.Contains(AzdoTags.AutoBuilder)) .OrderByDescending(build => build.QueueTime) .Take(BuildFailureLimit) .ToList(); if (autoBuilderBuilds.Count == BuildFailureLimit && autoBuilderBuilds.All(build => build.Status == WebApi.BuildStatus.Completed && build.Result == WebApi.BuildResult.Failed)) { return(true, autoBuilderBuilds.Select(build => build.GetWebLink())); } return(false, Enumerable.Empty <string>()); }
private async Task QueueBuildForStaleImages(Subscription subscription, IEnumerable <string> pathsToRebuild) { if (!pathsToRebuild.Any()) { _loggerService.WriteMessage($"All images for subscription '{subscription}' are using up-to-date base images. No rebuild necessary."); return; } string formattedPathsToRebuild = pathsToRebuild .Select(path => $"{CliHelper.FormatAlias(ManifestFilterOptionsBuilder.PathOptionName)} '{path}'") .Aggregate((p1, p2) => $"{p1} {p2}"); string parameters = "{\"" + subscription.PipelineTrigger.PathVariable + "\": \"" + formattedPathsToRebuild + "\"}"; _loggerService.WriteMessage($"Queueing build for subscription {subscription} with parameters {parameters}."); if (Options.IsDryRun) { return; } WebApi.Build? queuedBuild = null; Exception? exception = null; IEnumerable <string>?inProgressBuilds = null; IEnumerable <string>?recentFailedBuilds = null; try { (Uri baseUrl, VssCredentials credentials) = Options.AzdoOptions.GetConnectionDetails(); using (IVssConnection connection = _connectionFactory.Create(baseUrl, credentials)) using (IProjectHttpClient projectHttpClient = connection.GetProjectHttpClient()) using (IBuildHttpClient client = connection.GetBuildHttpClient()) { TeamProject project = await projectHttpClient.GetProjectAsync(Options.AzdoOptions.Project); WebApi.Build build = new() { Project = new TeamProjectReference { Id = project.Id }, Definition = new WebApi.BuildDefinitionReference { Id = subscription.PipelineTrigger.Id }, SourceBranch = subscription.Manifest.Branch, Parameters = parameters }; inProgressBuilds = await GetInProgressBuildsAsync(client, subscription.PipelineTrigger.Id, project.Id); if (!inProgressBuilds.Any()) { (bool shouldDisallowBuild, IEnumerable <string> recentFailedBuildsLocal) = await ShouldDisallowBuildDueToRecentFailuresAsync(client, subscription.PipelineTrigger.Id, project.Id); recentFailedBuilds = recentFailedBuildsLocal; if (shouldDisallowBuild) { _loggerService.WriteMessage( PipelineHelper.FormatErrorCommand("Unable to queue build due to too many recent build failures.")); } else { queuedBuild = await client.QueueBuildAsync(build); await client.AddBuildTagAsync(project.Id, queuedBuild.Id, AzdoTags.AutoBuilder); } } } } catch (Exception ex) { exception = ex; throw; } finally { await LogAndNotifyResultsAsync( subscription, pathsToRebuild, queuedBuild, exception, inProgressBuilds, recentFailedBuilds); } }
public override async Task ExecuteAsync() { StringBuilder notificationMarkdown = new(); string buildUrl = string.Empty; Dictionary <string, TaskResult?> taskResults = Options.TaskNames .ToDictionary(name => name, name => (TaskResult?)null); Dictionary <string, string> buildParameters = new(); BuildResult overallResult = BuildResult.Succeeded; BuildReason buildReason = BuildReason.None; string? correlatedQueueNotificationUrl = null; if (!Options.IsDryRun) { (Uri baseUrl, VssCredentials credentials) = Options.AzdoOptions.GetConnectionDetails(); using (IVssConnection connection = _connectionFactory.Create(baseUrl, credentials)) using (IProjectHttpClient projectHttpClient = connection.GetProjectHttpClient()) using (IBuildHttpClient buildClient = connection.GetBuildHttpClient()) { TeamProject project = await projectHttpClient.GetProjectAsync(Options.AzdoOptions.Project); TeamFoundation.Build.WebApi.Build build = await buildClient.GetBuildAsync(project.Id, Options.BuildId); buildUrl = build.GetWebLink(); buildReason = build.Reason; // Get the build's queue-time parameters if (build.Parameters is not null) { JObject parametersJson = JsonConvert.DeserializeObject <JObject>(build.Parameters); foreach (KeyValuePair <string, JToken?> pair in parametersJson) { buildParameters.Add(pair.Key, pair.Value?.ToString() ?? string.Empty); } } overallResult = await GetBuildTaskResultsAsync(taskResults, buildClient, project); correlatedQueueNotificationUrl = await GetCorrelatedQueueNotificationUrlAsync(); } } notificationMarkdown.AppendLine($"# Publish Results"); notificationMarkdown.AppendLine(); WriteSummaryMarkdown(notificationMarkdown, buildUrl, overallResult, buildReason, correlatedQueueNotificationUrl); notificationMarkdown.AppendLine(); WriteTaskStatusesMarkdown(taskResults, notificationMarkdown); notificationMarkdown.AppendLine(); WriteBuildParameters(buildParameters, notificationMarkdown); notificationMarkdown.AppendLine(); WriteImagesMarkdown(notificationMarkdown); await _notificationService.PostAsync( $"Publish Result - {Options.SourceRepo}/{Options.SourceBranch}", notificationMarkdown.ToString(), new string[] { NotificationLabels.Publish, NotificationLabels.GetRepoLocationLabel(Options.SourceRepo, Options.SourceBranch) }.AppendIf(NotificationLabels.Failure, () => overallResult == BuildResult.Failed), Options.GitOptions.GetRepoUrl().ToString(), Options.GitOptions.AuthToken, Options.IsDryRun); }
private async Task <BuildResult> GetBuildTaskResultsAsync(Dictionary <string, TaskResult?> taskResults, IBuildHttpClient buildClient, TeamProject project) { BuildResult overallResult = BuildResult.None; Timeline timeline = await buildClient.GetBuildTimelineAsync(project.Id, Options.BuildId); foreach (string task in Options.TaskNames) { TimelineRecord?record = timeline.Records.FirstOrDefault(rec => rec.Name == task); if (record is null) { throw new InvalidOperationException( $"Build task with name '{task}' could not be found in the build timeline."); } taskResults[task] = record.Result; if (record.Result is not null) { switch (record.Result.Value) { case TaskResult.Succeeded: if (overallResult == BuildResult.None) { overallResult = BuildResult.Succeeded; } break; case TaskResult.SucceededWithIssues: if (overallResult == BuildResult.None || overallResult == BuildResult.Succeeded) { overallResult = BuildResult.PartiallySucceeded; } break; case TaskResult.Failed: overallResult = BuildResult.Failed; break; case TaskResult.Canceled: overallResult = BuildResult.Canceled; break; } } } return(overallResult); }