Merged PR 791180: Refactor and fix capture build properties for org and codebase for Reliability dashboard

Refactor and fix capture build properties for org and codebase for Reliability dashboard.
Most of the URL's in Reliability dashboard are of the format - dev.azure.com and visualstudio.com
So I reused the existing code to normalize gitRemoteRepoUrl's/
Added few test cases to test these scenarios.

Related work items: #2188136
This commit is contained in:
Sahiti Chandramouli 2024-06-20 20:43:23 +00:00
Родитель 58b1da4eea
Коммит 59e70b0f97
3 изменённых файлов: 108 добавлений и 44 удалений

Просмотреть файл

@ -2078,14 +2078,7 @@ namespace BuildXL
// Using a map to capture all the traceInfoProperties, which can be later used for removal of duplicates. // Using a map to capture all the traceInfoProperties, which can be later used for removal of duplicates.
// If the user has passed a buildProperty through traceInfo flag,then that value is used to override the value to be set by the build properties methods. // If the user has passed a buildProperty through traceInfo flag,then that value is used to override the value to be set by the build properties methods.
Dictionary<string, string> traceInfoProperties = CaptureBuildInfo.CaptureTelemetryEnvProperties(configuration); Dictionary<string, string> traceInfoProperties = CaptureBuildInfo.CaptureTelemetryEnvProperties(configuration, gitRemoteRepoUrl);
// Codebase represents the code or product being built.
// We use GitRemoteRepoUrl as codebase property value when we fail to obtain it from ADO predefined env variable or if not set by the user.
if (!traceInfoProperties.ContainsKey(CaptureBuildProperties.CodeBaseKey) && !string.IsNullOrEmpty(gitRemoteRepoUrl))
{
traceInfoProperties.Add(CaptureBuildProperties.CodeBaseKey, gitRemoteRepoUrl);
}
foreach (KeyValuePair<string, string> traceInfo in traceInfoProperties.OrderBy(kvp => kvp.Key, StringComparer.InvariantCultureIgnoreCase)) foreach (KeyValuePair<string, string> traceInfo in traceInfoProperties.OrderBy(kvp => kvp.Key, StringComparer.InvariantCultureIgnoreCase))
{ {

Просмотреть файл

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using BuildXL.ToolSupport; using BuildXL.ToolSupport;
using BuildXL.Utilities.Configuration; using BuildXL.Utilities.Configuration;
@ -69,20 +70,25 @@ namespace BuildXL
/// </summary> /// </summary>
public const string AdoPreDefinedVariableForJobId = "SYSTEM_JOBID"; public const string AdoPreDefinedVariableForJobId = "SYSTEM_JOBID";
private const string ADONewUrlFormatRegex = @"(?<=dev\.azure\.com/)(.*?)(?=/)";
private const string ADOLegacyUrlFormatRegex = @"(?<=https://)(.*?)(?=\.visualstudio\.com)";
/// <summary> /// <summary>
/// This is the primary method in the class which is called by ComputeEnvironment(), to capture the build properties. /// This is the primary method in the class which is called by ComputeEnvironment(), to capture the build properties.
/// </summary> /// </summary>
/// <param name="configuration">This configuration object contains computed telemetry env properties and traceInfo flag fields.</param> /// <param name="configuration">This configuration object contains computed telemetry env properties and traceInfo flag fields.</param>
/// <param name="gitRemoteRepoUrl">This value is used to extract the codebase and org value when the required env vars are not available. </param>
/// <returns>The traceInfo Dictionary with build properties for is returned </returns> /// <returns>The traceInfo Dictionary with build properties for is returned </returns>
public static Dictionary<string, string> CaptureTelemetryEnvProperties(IConfiguration configuration) public static Dictionary<string, string> CaptureTelemetryEnvProperties(IConfiguration configuration, string gitRemoteRepoUrl = null)
{ {
var traceInfoProperties = new Dictionary<string, string>(configuration.Logging.TraceInfo, StringComparer.InvariantCultureIgnoreCase); var traceInfoProperties = new Dictionary<string, string>(configuration.Logging.TraceInfo, StringComparer.InvariantCultureIgnoreCase);
// The organization name // The organization name
CaptureNewProperty(traceInfoProperties, CaptureBuildProperties.OrgKey, GetOrg); CaptureNewProperty(traceInfoProperties, CaptureBuildProperties.OrgKey, getOrg);
// The name of the triggering repository. // The name of the triggering repository.
CaptureNewPropertyFromEnvironment(traceInfoProperties, CaptureBuildProperties.CodeBaseKey, AdoPreDefinedVariableForCodebase); CaptureNewProperty(traceInfoProperties, CaptureBuildProperties.CodeBaseKey, getCodebase);
// The id of the pipeline that is used to build the codebase. // The id of the pipeline that is used to build the codebase.
CaptureNewPropertyFromEnvironment(traceInfoProperties, CaptureBuildProperties.PipelineIdKey, AdoPreDefinedVariableForPipelineId); CaptureNewPropertyFromEnvironment(traceInfoProperties, CaptureBuildProperties.PipelineIdKey, AdoPreDefinedVariableForPipelineId);
@ -104,6 +110,46 @@ namespace BuildXL
CaptureNewProperty(traceInfoProperties, CaptureBuildProperties.InfraKey, () => GetInfra(configuration)); CaptureNewProperty(traceInfoProperties, CaptureBuildProperties.InfraKey, () => GetInfra(configuration));
return traceInfoProperties; return traceInfoProperties;
// This method is used to set the 'org' property in the EnvString for telemetry purposes.
// This method first attempts to capture the org name from the environment variable.
// If the environment variable is not set or is invalid, and if the gitRemoteRepoUrl is available,
// the method extracts the org value from the gitRemoteRepoUrl.
string getOrg()
{
var orgFromEnvVar = ExtractOrgFromUrl(Environment.GetEnvironmentVariable(EnvVariableForOrg));
return !string.IsNullOrEmpty(orgFromEnvVar) ? orgFromEnvVar : ExtractOrgFromUrl(gitRemoteRepoUrl);
}
// This method is used to set the 'codebase' property in the EnvString for telemetry purposes.
// This method first attempts to capture the codebase value from the env var.
// If the env var is not set, and if the gitRemoteRepoUrl is available,
// the method extracts the codebase value from it.
string getCodebase()
{
var codeBase = Environment.GetEnvironmentVariable(AdoPreDefinedVariableForCodebase);
if (!string.IsNullOrEmpty(codeBase))
{
return codeBase;
}
if (!string.IsNullOrEmpty(gitRemoteRepoUrl))
{
#pragma warning disable ERP022 // Unobserved exception in generic exception handler
try
{
var uri = new Uri(gitRemoteRepoUrl);
return uri.Segments.Last();
}
catch (Exception)
{
}
#pragma warning restore ERP022 // Unobserved exception in generic exception handler
}
return null;
}
} }
/// <summary> /// <summary>
@ -165,20 +211,20 @@ namespace BuildXL
} }
/// <summary> /// <summary>
/// This method is used to set a property called org in the EnvString for telemetry purpose. The method parses the URL and capture the organization name. /// Extracts the organization name from the given URL based on predefined domain patterns.
/// </summary> /// </summary>
private static string GetOrg() private static string ExtractOrgFromUrl(string url)
{ {
string orgUnParsedURL = Environment.GetEnvironmentVariable(EnvVariableForOrg); if (!string.IsNullOrEmpty(url))
if (!string.IsNullOrEmpty(orgUnParsedURL))
{ {
// According to the AzureDevOps documentation, there are two kinds of ADO URL's // According to the AzureDevOps documentation and telemetry observations, there are two primary formats for Azure DevOps URLs:
// New format(https://dev.azure.com/{organization}) & legacy format(https://{organization}.visualstudio.com). // - New format: https://dev.azure.com/{organization}
// Based on this information, the name of the organization is extracted using the below logic. // - Legacy format: https://{organization}.visualstudio.com
var match = Regex.Match(orgUnParsedURL, "(?<=dev\\.azure\\.com/)(.*?)(?=/)"); // These patterns are used to extract the organization info as required.
var match = Regex.Match(url, ADONewUrlFormatRegex);
if (!match.Success) if (!match.Success)
{ {
match = Regex.Match(orgUnParsedURL, "(?<=https://)(.*?)(?=\\.visualstudio\\.com)"); match = Regex.Match(url, ADOLegacyUrlFormatRegex);
if (!match.Success) if (!match.Success)
{ {
return null; return null;

Просмотреть файл

@ -5,9 +5,9 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using BuildXL; using BuildXL;
using BuildXL.Utilities.Core;
using BuildXL.Utilities.Configuration; using BuildXL.Utilities.Configuration;
using BuildXL.Utilities.Configuration.Mutable; using BuildXL.Utilities.Configuration.Mutable;
using BuildXL.Utilities.Core;
using BuildXL.Utilities.Tracing; using BuildXL.Utilities.Tracing;
using Test.BuildXL.TestUtilities.Xunit; using Test.BuildXL.TestUtilities.Xunit;
using Xunit; using Xunit;
@ -25,6 +25,10 @@ namespace Test.BuildXL
private const string OrgURLFormatTestValue = "https://bxlTestCheck.visualstudio.com"; private const string OrgURLFormatTestValue = "https://bxlTestCheck.visualstudio.com";
private const string GitRemoteRepoUrlTestValue1 = "https://mseng.visualstudio.com/mseng/Domino/_git/BuildXL.Internal";
private const string GitRemoteRepoUrlTestValue2 = "https://dev.azure.com/mseng/Domino/_git/BuildXL.Internal";
/// <summary> /// <summary>
/// This test is to check if the "infra" property is set to "ado" when "Build_DefinitionName" is present as an environment variable. /// This test is to check if the "infra" property is set to "ado" when "Build_DefinitionName" is present as an environment variable.
/// </summary> /// </summary>
@ -91,17 +95,19 @@ namespace Test.BuildXL
} }
/// <summary> /// <summary>
/// This test is to check if the "org" property is set to the organization name extracted from the URL for both ADO and CB env. /// This test is to check if the "org" property is set to the organization name extracted from the URL for both ADO and GitRemoteRepoUrl.
/// </summary> /// </summary>
[Theory] [Theory]
[InlineData(CaptureBuildInfo.EnvVariableForOrg, OrgURLNewFormatTestValue, "bxlTestCheck")] [InlineData(CaptureBuildInfo.EnvVariableForOrg, OrgURLNewFormatTestValue, "bxlTestCheck", GitRemoteRepoUrlTestValue1)] // Case 1: The "org" property is set to ADO predefined var value in ADO if it is available, the URL is in the new format and if GitRemoteRepoUrl is available.
[InlineData(CaptureBuildInfo.EnvVariableForOrg, "https://dev.azure.com123/bxlTestCheck/check/newformat/URL//", null)] [InlineData(CaptureBuildInfo.EnvVariableForOrg, OrgURLFormatTestValue, "bxlTestCheck", GitRemoteRepoUrlTestValue1)] //Case 2: The "org" property is set to ADO predefined var value in ADO if it is available, the URL is in the old format and if GitRemoteRepoUrl is available.
[InlineData(CaptureBuildInfo.EnvVariableForOrg, OrgURLFormatTestValue, "bxlTestCheck")] [InlineData(CaptureBuildInfo.EnvVariableForOrg, null, "mseng", GitRemoteRepoUrlTestValue1)] // Case 3: The "org" value is set to the GitRemoteRepoUrl old format, if it is present, when the ADO predefined variable is unavailable.
[InlineData(CaptureBuildInfo.EnvVariableForOrg, "notAURI_JustaString?//", null)] [InlineData(CaptureBuildInfo.EnvVariableForOrg, null, "mseng", GitRemoteRepoUrlTestValue2)] // Case 4: The "org" value is set to the GitRemoteRepoUrl new format, if it is present, when the ADO predefined variable is unavailable.
[InlineData("NotAnEnvVariable", "notAURI_JustaString?//", null)] [InlineData(CaptureBuildInfo.EnvVariableForOrg, "https://dev.azure.com123/bxlTestCheck/check/newformat/URL//", "mseng", GitRemoteRepoUrlTestValue2)] // Case 5: The "org" value is set to the GitRemoteRepoUrl format, if it is present, when the "org" value extracted from the ADO predefined variable is invalid.
public static void TestOrgProperty(string adoPreDefinedEnvVar, string adoPreDefinedEnvVarTestValue, string expectedValueInEnvString) [InlineData(CaptureBuildInfo.EnvVariableForOrg, "https://dev.azure.com123/bxlTestCheck/check/newformat/URL//", null, null)] // Case 6: The "org" value is unset, when the "org" value extracted from the ADO predefined variable is invalid and GitRemoteRepoUrl is absent.
[InlineData("NotAnEnvVariable", null, null, null)] // Case 7: The "org" value remains unset if both environment variable and GitRemoteRepoUrl are absent.
public static void TestOrgProperty(string adoPreDefinedEnvVar, string adoPreDefinedEnvVarTestValue, string expectedValueInEnvString, string gitRemoteRepoUrl)
{ {
string[] envString = ComputeEnvBlockForTesting(null, adoPreDefinedEnvVar, adoPreDefinedEnvVarTestValue); string[] envString = ComputeEnvBlockForTesting(null, adoPreDefinedEnvVar, adoPreDefinedEnvVarTestValue, gitRemoteRepoUrl);
if (expectedValueInEnvString != null) if (expectedValueInEnvString != null)
{ {
XAssert.IsTrue(AssertEnvStringContainsTelemetryEnvProperty("org=" + expectedValueInEnvString, envString)); XAssert.IsTrue(AssertEnvStringContainsTelemetryEnvProperty("org=" + expectedValueInEnvString, envString));
@ -119,33 +125,52 @@ namespace Test.BuildXL
public void TestOrgPropertyForTraceInfoValue() public void TestOrgPropertyForTraceInfoValue()
{ {
string traceInfoArgs = "/traceInfo:org=test"; string traceInfoArgs = "/traceInfo:org=test";
string[] envString = ComputeEnvBlockForTesting(traceInfoArgs, CaptureBuildInfo.EnvVariableForOrg, OrgURLNewFormatTestValue); string[] envString = ComputeEnvBlockForTesting(traceInfoArgs, CaptureBuildInfo.EnvVariableForOrg, OrgURLNewFormatTestValue, GitRemoteRepoUrlTestValue1);
XAssert.IsTrue(AssertEnvStringContainsTelemetryEnvProperty("org=test", envString)); XAssert.IsTrue(AssertEnvStringContainsTelemetryEnvProperty("org=test", envString));
XAssert.IsFalse(AssertEnvStringContainsTelemetryEnvProperty("org=bxlTestCheck", envString)); XAssert.IsFalse(AssertEnvStringContainsTelemetryEnvProperty("org=bxlTestCheck", envString));
} XAssert.IsFalse(AssertEnvStringContainsTelemetryEnvProperty("org=BuildXL.Internal", envString));
/// <summary>
/// This test is to check if the "codebase" property is set to the branch name when "BUILD_REPOSITORY_NAME" is present as an environment variable.
/// </summary>
[Fact]
public static void TestCodebasePropertyADO()
{
string[] envString = ComputeEnvBlockForTesting(null, CaptureBuildInfo.AdoPreDefinedVariableForCodebase, EnvVarExpectedValue);
XAssert.IsTrue(AssertEnvStringContainsTelemetryEnvProperty("codebase=TestADO", envString));
} }
/// <summary> /// <summary>
/// This test is to check if the "codebase" property has been to set the branch name passed via traceInfo in the CB environment. /// This test is to check if the "codebase" property has been to set the branch name passed via traceInfo in the CB environment.
/// This test also tests the scenario when the codebase property has been passed via traceInfo in the "CloudBuild" environment and the presence of the environment variable "BUILD_REPOSITORY_NAME". /// This test also tests the scenario when the codebase property has been passed via traceInfo in the "CloudBuild" environment,
/// the presence of the environment variable "BUILD_REPOSITORY_NAME" and GitRemoteRepoUrl.
/// In this case the codebase property value obtained from the traceInfo argument should be set in the envString for codebase. /// In this case the codebase property value obtained from the traceInfo argument should be set in the envString for codebase.
/// </summary> /// </summary>
[Fact] [Fact]
public void TestCodebasePropertyCloudBuild() public void TestCodebasePropertyCloudBuild()
{ {
string traceInfoArgs = "/traceInfo:codebase=TestCB"; string traceInfoArgs = "/traceInfo:codebase=TestCB";
string[] envString = ComputeEnvBlockForTesting(traceInfoArgs, CaptureBuildInfo.AdoPreDefinedVariableForCodebase, EnvVarExpectedValue); string[] envString = ComputeEnvBlockForTesting(traceInfoArgs, CaptureBuildInfo.AdoPreDefinedVariableForCodebase, EnvVarExpectedValue, GitRemoteRepoUrlTestValue1);
XAssert.IsTrue(AssertEnvStringContainsTelemetryEnvProperty("codebase=TestCB", envString)); XAssert.IsTrue(AssertEnvStringContainsTelemetryEnvProperty("codebase=TestCB", envString));
XAssert.IsFalse(AssertEnvStringContainsTelemetryEnvProperty("codebase=TestADO", envString)); XAssert.IsFalse(AssertEnvStringContainsTelemetryEnvProperty("codebase=TestADO", envString));
XAssert.IsFalse(AssertEnvStringContainsTelemetryEnvProperty("codebase=BuildXL.Internal", envString));
}
/// <summary>
/// This test validates the following cases:
/// Case 1: The "codebase" property is set to the repository name when "BUILD_REPOSITORY_NAME" is present as an environment variable in ADO.
/// Case 2: When the codebase value cannot be obtained from the env var.
/// The test checks that the codebase value is correctly extracted from the GitRemoteRepoUrl if it is present.
/// Case 3: If the environment variable is not present and the GitRemoteRepoUrl is not valid (e.g., ends with a slash or is null/empty), the codebase value remains unset.
/// Case 4: If both the environment variable and GitRemoteRepoUrl are not present, the codebase value remains unset.
[Theory]
[InlineData(GitRemoteRepoUrlTestValue1, EnvVarExpectedValue, EnvVarExpectedValue)]
[InlineData(GitRemoteRepoUrlTestValue1, null, "BuildXL.Internal")]
[InlineData("https://mseng.visualstudio.com/DefaultCollection/Domino/_git/BuildXL.Internal//", null, null)]
[InlineData(null, null, null)]
[InlineData("https://DEV.AZURE.COM/mseng/Domino/_git/BuildXL.Internal", null, "BuildXL.Internal")]
public void ValidateExtractCodebasePropertyFromGitRemoteRepoUrl(string gitRemoteRepoUrl, string adoPredefinedValueForCodebase, string expectedRepoName)
{
string[] envString = ComputeEnvBlockForTesting(null, CaptureBuildInfo.AdoPreDefinedVariableForCodebase, adoPredefinedValueForCodebase, gitRemoteRepoUrl);
if (expectedRepoName != null)
{
XAssert.IsTrue(AssertEnvStringContainsTelemetryEnvProperty("codebase=" + expectedRepoName, envString));
}
else
{
XAssert.IsFalse(AssertEnvStringContainsTelemetryEnvProperty("codebase=", envString));
}
} }
/// <summary> /// <summary>
@ -275,7 +300,7 @@ namespace Test.BuildXL
/// The environment property which is used to add the appropriate properties of build. /// The environment property which is used to add the appropriate properties of build.
/// Ex: The presence of envProperty "Build_DefinitionName" adds a property called "infra=ado" to the envString. /// Ex: The presence of envProperty "Build_DefinitionName" adds a property called "infra=ado" to the envString.
/// </param> /// </param>
public static string[] ComputeEnvBlockForTesting(string argument, string envProperty, string envPropertyTestValue) public static string[] ComputeEnvBlockForTesting(string argument, string envProperty, string envPropertyTestValue, string gitRemoteRepoUrl = null)
{ {
string envPropertyOriginalValue = Environment.GetEnvironmentVariable(envProperty); string envPropertyOriginalValue = Environment.GetEnvironmentVariable(envProperty);
try try
@ -284,7 +309,7 @@ namespace Test.BuildXL
// We determine infra during the command line args parsing, so the config object should be constructed // We determine infra during the command line args parsing, so the config object should be constructed
// after we set an env var that we are testing. // after we set an env var that we are testing.
var configuration = AddTraceInfoOrEnvironmentArguments(argument); var configuration = AddTraceInfoOrEnvironmentArguments(argument);
string env = BuildXLApp.ComputeEnvironment(configuration); string env = BuildXLApp.ComputeEnvironment(configuration, gitRemoteRepoUrl);
string[] envString = env.Split(';'); string[] envString = env.Split(';');
// Adding this test condition to make sure that there are no duplicates. // Adding this test condition to make sure that there are no duplicates.
AssertNoDuplicates(envString); AssertNoDuplicates(envString);