public async Task TestGetLatestBuildAsyncWithNoBuild() { this.httpTest.RespondWithJson( new { message = "No completed builds were found for pipeline 31845.", typeName = "Microsoft.TeamFoundation.Build.WebApi.BuildNotFoundException, Microsoft.TeamFoundation.Build2.WebApi", typeKey = "BuildNotFoundException", errorCode = 0, eventId = 3000 }); VstsBuild latestBuild = await this.buildManagement.GetLatestBuildAsync(BuildDefinitionIds.CI, "master").ConfigureAwait(false); this.httpTest.ShouldHaveCalled($"https://dev.azure.com/{DevOpsAccessSetting.AzureOrganization}/{DevOpsAccessSetting.AzureProject}/_apis/build/latest/{BuildDefinitionIds.CI}?api-version=5.1-preview.1&branchName=master") .WithVerb(HttpMethod.Get) .WithBasicAuth(string.Empty, PersonalAccessToken) .Times(1); Assert.NotNull(latestBuild); Assert.IsNull(latestBuild.BuildNumber); Assert.AreEqual(VstsBuildStatus.None, latestBuild.Status); Assert.AreEqual(VstsBuildResult.None, latestBuild.Result); Assert.AreEqual("01/01/0001 00:00:00", latestBuild.QueueTime.ToString(CultureInfo.InvariantCulture)); Assert.AreEqual("01/01/0001 00:00:00", latestBuild.StartTime.ToString(CultureInfo.InvariantCulture)); Assert.AreEqual("01/01/0001 00:00:00", latestBuild.FinishTime.ToString(CultureInfo.InvariantCulture)); }
void UpsertVstsBuildToDb(SqlConnection sqlConnection, VstsBuild build) { var cmd = new SqlCommand { Connection = sqlConnection, CommandType = System.Data.CommandType.StoredProcedure, CommandText = "UpsertVstsBuild" }; cmd.Parameters.Add(new SqlParameter("@BuildNumber", build.BuildNumber)); cmd.Parameters.Add(new SqlParameter("@DefinitionId", build.DefinitionId)); cmd.Parameters.Add(new SqlParameter("@DefinitionName", build.DefinitionId.DisplayName())); cmd.Parameters.Add(new SqlParameter("@SourceBranch", build.SourceBranch)); cmd.Parameters.Add(new SqlParameter("@SourceVersionDisplayUri", build.SourceVersionDisplayUri.AbsoluteUri)); cmd.Parameters.Add(new SqlParameter("@WebUri", build.WebUri.AbsoluteUri)); cmd.Parameters.Add(new SqlParameter("@Status", build.Status.ToString())); cmd.Parameters.Add(new SqlParameter("@Result", build.Result.ToString())); cmd.Parameters.Add(new SqlParameter("@QueueTime", SqlDbType.DateTime2) { Value = build.QueueTime }); cmd.Parameters.Add(new SqlParameter("@StartTime", SqlDbType.DateTime2) { Value = build.StartTime }); cmd.Parameters.Add(new SqlParameter("@FinishTime", SqlDbType.DateTime2) { Value = build.FinishTime }); cmd.ExecuteNonQuery(); }
/// <summary> /// This method is used to get latest build result of given build definition Ids and branch name. /// The results should always contain same number of vsts build entity of given build definitions. /// If result is not found for a build definition Id, it will return vsts build entity with no result. /// Note: no validation of build definition ids is taken. /// Reference: https://docs.microsoft.com/en-us/rest/api/azure/devops/wit/work%20items/list?view=azure-devops-rest-5.1 /// </summary> /// <param name="buildDefinitionIds">build definition Ids</param> /// <param name="branchName">github repository branch name</param> /// <returns>List of vsts build entities</returns> public async Task <IList <VstsBuild> > GetLatestBuildsAsync(HashSet <BuildDefinitionId> buildDefinitionIds, string branchName) { ValidationUtil.ThrowIfNulOrEmptySet(buildDefinitionIds, nameof(buildDefinitionIds)); ValidationUtil.ThrowIfNullOrWhiteSpace(branchName, nameof(branchName)); // TODO: need to think about how to handle unexpected exception during REST API call string requestPath = string.Format(LatestBuildPathSegmentFormat, this.accessSetting.Organization, this.accessSetting.Project); IFlurlRequest latestBuildRequest = DevOpsAccessSetting.BaseUrl .AppendPathSegment(requestPath) .SetQueryParam("definitions", string.Join(",", buildDefinitionIds.Select(b => b.IdString()))) .SetQueryParam("queryOrder", "finishTimeDescending") .SetQueryParam("maxBuildsPerDefinition", "1") .SetQueryParam("api-version", "5.1") .SetQueryParam("branchName", branchName) .WithBasicAuth(string.Empty, this.accessSetting.PersonalAccessToken); string resultJson = await latestBuildRequest.GetStringAsync().ConfigureAwait(false); JObject result = JObject.Parse(resultJson); if (!result.ContainsKey("count") || (int)result["count"] <= 0) { return(buildDefinitionIds.Select(i => VstsBuild.GetBuildWithNoResult(i, branchName)).ToList()); } Dictionary <BuildDefinitionId, VstsBuild> latestBuilds = JsonConvert.DeserializeObject <VstsBuild[]>(result["value"].ToString()).ToDictionary(b => b.DefinitionId, b => b); return(buildDefinitionIds.Select(i => latestBuilds.ContainsKey(i) ? latestBuilds[i] : VstsBuild.GetBuildWithNoResult(i, branchName)).ToList()); }
public async Task TestGetLatestBuildAsync() { this.httpTest.RespondWithJson( new { buildNumber = "20191019.2", _links = new { web = new { href = "https://dev.azure.com/msazure/b32aa71e-8ed2-41b2-9d77-5bc261222004/_build/results?buildId=25980152" } }, status = "completed", result = "succeeded", queueTime = "2019-10-19T16:57:09.3224324Z", startTime = "2019-10-19T16:58:37.7494889Z", finishTime = "2019-10-19T17:42:44.0001429Z" }); VstsBuild latestBuild = await this.buildManagement.GetLatestBuildAsync(BuildDefinitionIds.CI, "master").ConfigureAwait(false); this.httpTest.ShouldHaveCalled($"https://dev.azure.com/{DevOpsAccessSetting.AzureOrganization}/{DevOpsAccessSetting.AzureProject}/_apis/build/latest/{BuildDefinitionIds.CI}?api-version=5.1-preview.1&branchName=master") .WithVerb(HttpMethod.Get) .WithBasicAuth(string.Empty, PersonalAccessToken) .Times(1); Assert.NotNull(latestBuild); Assert.AreEqual("20191019.2", latestBuild.BuildNumber); Assert.AreEqual("https://dev.azure.com/msazure/b32aa71e-8ed2-41b2-9d77-5bc261222004/_build/results?buildId=25980152", latestBuild.WebUri.AbsoluteUri); Assert.AreEqual(VstsBuildStatus.Completed, latestBuild.Status); Assert.AreEqual(VstsBuildResult.Succeeded, latestBuild.Result); Assert.AreEqual("10/19/2019 16:57:09", latestBuild.QueueTime.ToString(CultureInfo.InvariantCulture)); Assert.AreEqual("10/19/2019 16:58:37", latestBuild.StartTime.ToString(CultureInfo.InvariantCulture)); Assert.AreEqual("10/19/2019 17:42:44", latestBuild.FinishTime.ToString(CultureInfo.InvariantCulture)); }
public async Task <IActionResult> Index([FromQuery(Name = "branch")] string branch = "master") { var buildManagement = new BuildManagement(new DevOpsAccessSetting(this._appConfig.PersonalAccessToken)); VstsBuild ciBuild = await buildManagement.GetLatestBuildAsync(BuildDefinitionIds.CI, branch).ConfigureAwait(false); return(this.View( new DashboardViewModel { CIBuild = ciBuild })); }
static void VerifyEmptyBuildResult(VstsBuild build) { Assert.AreEqual(string.Empty, build.BuildNumber); Assert.AreEqual("https://dev.azure.com/msazure/One/_build", build.WebUri.AbsoluteUri); Assert.AreEqual("https://dev.azure.com/msazure/One/_build", build.WebUri.AbsoluteUri); Assert.AreEqual(VstsBuildStatus.None, build.Status); Assert.AreEqual(VstsBuildResult.None, build.Result); Assert.AreEqual(DateTime.MinValue, build.QueueTime); Assert.AreEqual(DateTime.MinValue, build.StartTime); Assert.AreEqual(DateTime.MinValue, build.FinishTime); }
public async Task <IList <VstsBuild> > GetBuildsAsync(HashSet <BuildDefinitionId> buildDefinitionIds, string branchName, DateTime?minTime = null, int?maxBuildsPerDefinition = null) { ValidationUtil.ThrowIfNullOrEmptySet(buildDefinitionIds, nameof(buildDefinitionIds)); ValidationUtil.ThrowIfNullOrWhiteSpace(branchName, nameof(branchName)); // TODO: need to think about how to handle unexpected exception during REST API call string requestPath = string.Format(LatestBuildPathSegmentFormat, this.accessSetting.Organization, this.accessSetting.Project); IFlurlRequest latestBuildRequest = GetBuildsRequestUri(buildDefinitionIds, branchName, requestPath, minTime, maxBuildsPerDefinition) .WithBasicAuth(string.Empty, this.accessSetting.PersonalAccessToken); string resultJson = await latestBuildRequest.GetStringAsync().ConfigureAwait(false); JObject result = JObject.Parse(resultJson); if (!result.ContainsKey("count") || (int)result["count"] <= 0) { return(buildDefinitionIds.Select(i => VstsBuild.CreateBuildWithNoResult(i, branchName)).ToList()); } return(JsonConvert.DeserializeObject <VstsBuild[]>(result["value"].ToString()).ToList()); }
/// <summary> /// This method is used to get latest build result of given build definition Ids and branch name. /// The results should always contain same number of vsts build entity of given build definitions. /// If result is not found for a build definition Id, it will return vsts build entity with no result. /// Note: no validation of build definition ids is taken. /// Reference: https://docs.microsoft.com/en-us/rest/api/azure/devops/build/builds/list?view=azure-devops-rest-5.1 /// </summary> /// <param name="buildDefinitionIds">build definition Ids</param> /// <param name="branchName">github repository branch name</param> /// <returns>List of vsts build entities</returns> public async Task <IList <VstsBuild> > GetLatestBuildsAsync(HashSet <BuildDefinitionId> buildDefinitionIds, string branchName) { ValidationUtil.ThrowIfNullOrEmptySet(buildDefinitionIds, nameof(buildDefinitionIds)); ValidationUtil.ThrowIfNullOrWhiteSpace(branchName, nameof(branchName)); // TODO: need to think about how to handle unexpected exception during REST API call string requestPath = string.Format(LatestBuildPathSegmentFormat, DevOpsAccessSetting.AzureOrganization, DevOpsAccessSetting.AzureProject); IFlurlRequest latestBuildRequest = GetBuildsRequestUri(buildDefinitionIds, branchName, requestPath, null, 1) .WithBasicAuth(string.Empty, this.accessSetting.MsazurePAT); string resultJson = await latestBuildRequest.GetStringAsync().ConfigureAwait(false); JObject result = JObject.Parse(resultJson); if (!result.ContainsKey("count") || (int)result["count"] <= 0) { return(buildDefinitionIds.Select(i => VstsBuild.CreateBuildWithNoResult(i, branchName)).ToList()); } Dictionary <BuildDefinitionId, VstsBuild> latestBuilds = JsonConvert.DeserializeObject <VstsBuild[]>(result["value"].ToString()).ToDictionary(b => b.DefinitionId, b => b); return(buildDefinitionIds.Select(i => latestBuilds.ContainsKey(i) ? latestBuilds[i] : VstsBuild.CreateBuildWithNoResult(i, branchName)).ToList()); }
/// <summary> /// This method is used to create a bug in Azure Dev Ops. /// If it cannot create the bug it will rethrow the exception from the DevOps api. /// Reference: https://docs.microsoft.com/en-us/rest/api/azure/devops/wit/work%20items/create?view=azure-devops-rest-6.0 /// </summary> /// <param name="branch">Branch for which the bug is being created</param> /// <param name="build">Build for which the bug is being created</param> /// <returns>Work item id for the created bug.</returns> public async Task <string> CreateBugAsync(string branch, VstsBuild build) { string requestPath = string.Format(WorkItemPathSegmentFormat, DevOpsAccessSetting.BaseUrl, this.accessSetting.Organization, this.accessSetting.Project); IFlurlRequest workItemQueryRequest = ((Url)requestPath) .WithBasicAuth(string.Empty, this.accessSetting.PersonalAccessToken) .WithHeader("Content-Type", "application/json-patch+json") .SetQueryParam("api-version", "6.0"); var jsonBody = new object[] { new { op = "add", path = "/fields/System.Title", from = string.Empty, value = $"Test failure on {branch}: {build.DefinitionId.ToString()} {build.BuildNumber}" }, new { op = "add", path = "/fields/Microsoft.VSTS.TCM.ReproSteps", from = string.Empty, value = $"This bug is autogenerated by the vsts-pipeline-sync service. Link to failing build:<div> {build.WebUri}" }, new { op = "add", path = "/fields/Microsoft.VSTS.Common.Priority", from = string.Empty, value = "0" }, new { op = "add", path = "/fields/System.AreaPath", from = string.Empty, value = "One\\IoT\\Platform\\IoTEdge" }, new { op = "add", path = "/relations/-", value = new { rel = "Hyperlink", url = $"{build.WebUri}" } } }; JObject result; try { IFlurlResponse response = await workItemQueryRequest .PostJsonAsync(jsonBody); result = await response.GetJsonAsync <JObject>(); } catch (FlurlHttpException e) { string message = $"Failed making call to vsts work item api: {e.Message}"; Console.WriteLine(message); Console.WriteLine(e.Call.RequestBody); Console.WriteLine(e.Call.Response.StatusCode); Console.WriteLine(e.Call.Response.ResponseMessage); throw new Exception(message); } return(result["id"].ToString()); }
/// <summary> /// This method is used to create a bug in Azure Dev Ops. /// If it cannot create the bug it will rethrow the exception from the DevOps api. /// Reference: https://docs.microsoft.com/en-us/rest/api/azure/devops/wit/work%20items/create?view=azure-devops-rest-6.0 /// </summary> /// <param name="branch">Branch for which the bug is being created</param> /// <param name="build">Build for which the bug is being created</param> /// <returns>Work item id for the created bug.</returns> public async Task <string> CreateBugAsync(string branch, VstsBuild build) { string requestPath = string.Format(WorkItemPathSegmentFormat, DevOpsAccessSetting.BaseUrl, DevOpsAccessSetting.AzureOrganization, DevOpsAccessSetting.AzureProject); IFlurlRequest workItemQueryRequest = ((Url)requestPath) .WithBasicAuth(string.Empty, this.accessSetting.MsazurePAT) .WithHeader("Content-Type", "application/json-patch+json") .SetQueryParam("api-version", "6.0"); (string bugOwnerFullName, string bugOwnerEmail) = await this.GetBugOwnerInfoAsync(build.SourceVersion); string bugDescription = GenerateBugDescription(bugOwnerFullName, bugOwnerEmail, build); var jsonBody = new object[] { new { op = "add", path = "/fields/System.Title", from = string.Empty, value = $"Test failure on {branch}: {build.DefinitionId.ToString()} {build.BuildNumber}" }, new { op = "add", path = "/fields/Microsoft.VSTS.TCM.ReproSteps", from = string.Empty, value = bugDescription }, new { op = "add", path = "/fields/Microsoft.VSTS.Common.Priority", from = string.Empty, value = "0" }, new { op = "add", path = "/fields/System.AreaPath", from = string.Empty, value = "One\\IoT\\Platform and Devices\\IoTEdge" }, new { op = "add", path = "/relations/-", value = new { rel = "Hyperlink", url = $"{build.WebUri}" } }, new { op = "add", path = "/fields/System.AssignedTo", value = bugOwnerEmail }, new { op = "add", path = "/fields/System.Tags", value = "auto-pipeline-failed" } }; JObject result; try { IFlurlResponse response = await workItemQueryRequest .PostJsonAsync(jsonBody); result = await response.GetJsonAsync <JObject>(); } catch (FlurlHttpException e) { string message = $"Failed making call to vsts work item api: {e.Message}"; Console.WriteLine(message); Console.WriteLine(e.Call.RequestBody); Console.WriteLine(e.Call.Response.StatusCode); Console.WriteLine(e.Call.Response.ResponseMessage); throw new Exception(message); } return(result["id"].ToString()); }
static string GenerateBugDescription(string bugOwnerFullName, string bugOwnerEmail, VstsBuild build) { string bugDescription = "This bug is autogenerated and assigned by the vsts-pipeline-sync service. "; if (bugOwnerEmail.Equals(string.Empty)) { bugDescription = $"Attempted to assign to {bugOwnerFullName}, but failed to do so. Either this person is not a team member or they are not in the iotedge devops organization."; } else { bugDescription = $"Assigned to {bugOwnerFullName}."; } bugDescription += $"<div>`<div> Please address if the failure was caused by your changes. Otherwise please help to triage appropriately. "; bugDescription += $"Reference the backup on-call report to match to an existing bug. If the bug does not exist yet, please create a new bug and coordinate with backup on-call to confirm the bug gets added to the most recent backup on-call report. After this is complete you can close this bug. Link to resource: <div> <a href=\"{BackupOnCallReportLink}\">Backup On-Call Reports</a> <div>`<div>"; bugDescription += $"Link to failing build:<div> <a href=\"{build.WebUri}\">Failing Build</a>"; return(bugDescription); }