Exemple #1
0
        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();
        }
Exemple #3
0
        /// <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());
        }
Exemple #4
0
        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));
        }
Exemple #5
0
        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
            }));
        }
Exemple #6
0
 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);
 }
Exemple #7
0
        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());
        }
Exemple #8
0
        /// <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());
        }
Exemple #9
0
        /// <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());
        }
Exemple #10
0
        /// <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());
        }
Exemple #11
0
        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);
        }