diff --git a/tools/notification-configuration/notification-configuration.sln b/tools/notification-configuration/notification-configuration.sln index 53cdbea96e9..85293118f18 100644 --- a/tools/notification-configuration/notification-configuration.sln +++ b/tools/notification-configuration/notification-configuration.sln @@ -5,10 +5,18 @@ VisualStudioVersion = 17.5.33103.201 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Sdk.Tools.NotificationConfiguration", "notification-creator\Azure.Sdk.Tools.NotificationConfiguration.csproj", "{5759063D-A7B3-4D36-ACF4-5595C2789D27}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Sdk.Tools.NotificationConfiguration.Tests", "notification-creator.Tests\Azure.Sdk.Tools.NotificationConfiguration.Tests.csproj", "{3097CBB4-ED3C-4273-AC67-F5D189CB94BA}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Sdk.Tools.CodeOwnersParser", "..\code-owners-parser\CodeOwnersParser\Azure.Sdk.Tools.CodeOwnersParser.csproj", "{A9826C8B-85DF-48DB-8A05-40FB04833C42}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Sdk.Tools.CodeOwnersParser.Tests", "..\code-owners-parser\Azure.Sdk.Tools.CodeOwnersParser.Tests\Azure.Sdk.Tools.CodeOwnersParser.Tests.csproj", "{2146E1FF-04D1-4B19-9767-C011A73CB40D}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "identity-resolution", "..\identity-resolution\identity-resolution.csproj", "{9805B503-5469-412C-9A0C-F09F504F0ED8}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Sdk.Tools.RetrieveCodeOwners", "..\code-owners-parser\Azure.Sdk.Tools.RetrieveCodeOwners\Azure.Sdk.Tools.RetrieveCodeOwners.csproj", "{3E5237F2-6536-4329-A9CF-92E42B040612}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Sdk.Tools.RetrieveCodeOwners.Tests", "..\code-owners-parser\Azure.Sdk.Tools.RetrieveCodeOwners.Tests\Azure.Sdk.Tools.RetrieveCodeOwners.Tests.csproj", "{8DAEC12F-8390-4122-9959-9CF3391F18CC}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{EBC153AF-0244-4DFB-8084-E6C0ACAA5CF3}" ProjectSection(SolutionItems) = preProject ci.yml = ci.yml @@ -33,6 +41,22 @@ Global {9805B503-5469-412C-9A0C-F09F504F0ED8}.Debug|Any CPU.Build.0 = Debug|Any CPU {9805B503-5469-412C-9A0C-F09F504F0ED8}.Release|Any CPU.ActiveCfg = Release|Any CPU {9805B503-5469-412C-9A0C-F09F504F0ED8}.Release|Any CPU.Build.0 = Release|Any CPU + {8DAEC12F-8390-4122-9959-9CF3391F18CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8DAEC12F-8390-4122-9959-9CF3391F18CC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8DAEC12F-8390-4122-9959-9CF3391F18CC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8DAEC12F-8390-4122-9959-9CF3391F18CC}.Release|Any CPU.Build.0 = Release|Any CPU + {2146E1FF-04D1-4B19-9767-C011A73CB40D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2146E1FF-04D1-4B19-9767-C011A73CB40D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2146E1FF-04D1-4B19-9767-C011A73CB40D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2146E1FF-04D1-4B19-9767-C011A73CB40D}.Release|Any CPU.Build.0 = Release|Any CPU + {3E5237F2-6536-4329-A9CF-92E42B040612}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3E5237F2-6536-4329-A9CF-92E42B040612}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3E5237F2-6536-4329-A9CF-92E42B040612}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3E5237F2-6536-4329-A9CF-92E42B040612}.Release|Any CPU.Build.0 = Release|Any CPU + {3097CBB4-ED3C-4273-AC67-F5D189CB94BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3097CBB4-ED3C-4273-AC67-F5D189CB94BA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3097CBB4-ED3C-4273-AC67-F5D189CB94BA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3097CBB4-ED3C-4273-AC67-F5D189CB94BA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/tools/notification-configuration/notification-creator.Tests/Azure.Sdk.Tools.NotificationConfiguration.Tests.csproj b/tools/notification-configuration/notification-creator.Tests/Azure.Sdk.Tools.NotificationConfiguration.Tests.csproj new file mode 100644 index 00000000000..c6c64882654 --- /dev/null +++ b/tools/notification-configuration/notification-creator.Tests/Azure.Sdk.Tools.NotificationConfiguration.Tests.csproj @@ -0,0 +1,22 @@ + + + + net6.0 + enable + Nullable + false + + + + + + + + + + + + + + + diff --git a/tools/notification-configuration/notification-creator.Tests/ProgramTests.cs b/tools/notification-configuration/notification-creator.Tests/ProgramTests.cs new file mode 100644 index 00000000000..e7a92d50636 --- /dev/null +++ b/tools/notification-configuration/notification-creator.Tests/ProgramTests.cs @@ -0,0 +1,30 @@ +using System; +using NUnit.Framework; + +namespace Azure.Sdk.Tools.NotificationConfiguration.Tests; + +[TestFixture] +public class ProgramTests +{ + [Test] + public void ThrowsVssUnauthorizedException() + { + Environment.SetEnvironmentVariable("aadAppIdVar", "aadAppIdVarValue"); + Environment.SetEnvironmentVariable("aadAppSecretVar", "aadAppSecretVarValue"); + Environment.SetEnvironmentVariable("aadTenantVar", "aadTenantVarValue"); + Assert.ThrowsAsync( + async () => + // Act + await Program.Main( + organization: "fooOrg", + project: "barProj", + pathPrefix: "qux", + tokenVariableName: "token", + aadAppIdVar: "aadAppIdVar", + aadAppSecretVar: "aadAppSecretVar", + aadTenantVar: "aadTenantVar", + selectionStrategy: PipelineSelectionStrategy.Scheduled, + dryRun: true) + ); + } +} diff --git a/tools/notification-configuration/notification-creator/Program.cs b/tools/notification-configuration/notification-creator/Program.cs index bee812834f4..dbc343717f6 100644 --- a/tools/notification-configuration/notification-creator/Program.cs +++ b/tools/notification-configuration/notification-creator/Program.cs @@ -7,64 +7,113 @@ using Azure.Sdk.Tools.NotificationConfiguration.Helpers; using Azure.Identity; -namespace Azure.Sdk.Tools.NotificationConfiguration +namespace Azure.Sdk.Tools.NotificationConfiguration; + +/// +/// A tool for creating and configuring Azure DevOps groups for sending email notifications +/// on build failures to owners of relevant build definitions. The recipients are determined +/// based on the build definition .yml file paths as given by the CODEOWNERS of given build definition +/// source repository. +/// +public static class Program { - class Program + /// + /// Create notification groups for failures in scheduled builds + /// + /// Azure DevOps Organization + /// Name of the DevOps project + /// Path prefix to include pipelines (e.g. "\net") + /// Environment variable token name (e.g. "SYSTEM_ACCESSTOKEN") + /// AAD App ID environment variable name (OpensourceAPI access) + /// AAD App Secret environment variable name (OpensourceAPI access) + /// AAD Tenant environment variable name (OpensourceAPI access) + /// Pipeline selection strategy + /// Prints changes but does not alter any objects + /// + public static async Task Main( + string organization, + string project, + string pathPrefix, + string tokenVariableName, + string aadAppIdVar, + string aadAppSecretVar, + string aadTenantVar, + PipelineSelectionStrategy selectionStrategy = PipelineSelectionStrategy.Scheduled, + bool dryRun = false) { - /// - /// Create notification groups for failures in scheduled builds - /// - /// Azure DevOps Organization - /// Name of the DevOps project - /// Path prefix to include pipelines (e.g. "\net") - /// Environment variable token name (e.g. "SYSTEM_ACCESSTOKEN") - /// AAD App ID environment variable name (OpensourceAPI access) - /// AAD App Secret environment variable name (OpensourceAPI access) - /// AAD Tenant environment variable name (OpensourceAPI access) - /// Pipeline selection strategy - /// Prints changes but does not alter any objects - /// - static async Task Main( - string organization, - string project, - string pathPrefix, - string tokenVariableName, - string aadAppIdVar, - string aadAppSecretVar, - string aadTenantVar, - PipelineSelectionStrategy selectionStrategy = PipelineSelectionStrategy.Scheduled, - bool dryRun = false) + var loggerFactory = LoggerFactory.Create(builder => { - var devOpsToken = Environment.GetEnvironmentVariable(tokenVariableName); - var devOpsCreds = new VssBasicCredential("nobody", devOpsToken); - var devOpsConnection = new VssConnection(new Uri($"https://dev.azure.com/{organization}/"), devOpsCreds); + builder.AddSimpleConsole(config => { config.IncludeScopes = true; }); + }); + var logger = loggerFactory.CreateLogger(nameof(Program)); + logger.LogInformation( + "Executing Azure.Sdk.Tools.NotificationConfiguration.Program.Main with following arguments: " + + "organization: '{organization}' " + + "project: '{project}' " + + "pathPrefix: '{pathPrefix}' " + + "tokenVariableName: '{tokenVariableName}' " + + "aadAppIdVar: '{aadAppIdVar}' " + + "aadAppSecretVar: '{aadAppSecretVar}' " + + "aadTenantVar: '{aadTenantVar}' " + + "selectionStrategy: '{selectionStrategy}' " + + "dryRun: '{dryRun}' " + , organization + , project + , pathPrefix + , tokenVariableName + , aadAppIdVar + , aadAppSecretVar + , aadTenantVar + , selectionStrategy + , dryRun); + + var notificationConfigurator = new NotificationConfigurator( + AzureDevOpsService(organization, tokenVariableName, loggerFactory), + GitHubService(loggerFactory), + loggerFactory.CreateLogger()); + + await notificationConfigurator.ConfigureNotifications( + project, + pathPrefix, + GitHubToAADConverter(aadTenantVar, aadAppIdVar, aadAppSecretVar, loggerFactory), + persistChanges: !dryRun, + strategy: selectionStrategy); + } - var loggerFactory = LoggerFactory.Create(builder => - { - builder.AddSimpleConsole(config => { config.IncludeScopes = true; }); - }); - var devOpsServiceLogger = loggerFactory.CreateLogger(); - var notificationConfiguratorLogger = loggerFactory.CreateLogger(); + private static AzureDevOpsService AzureDevOpsService( + string organization, + string tokenVariableName, + ILoggerFactory loggerFactory) + { + var devOpsToken = Environment.GetEnvironmentVariable(tokenVariableName); + var devOpsCreds = new VssBasicCredential("nobody", devOpsToken); + var devOpsConnection = new VssConnection( + new Uri($"https://dev.azure.com/{organization}/"), + devOpsCreds); + var devOpsService = new AzureDevOpsService( + devOpsConnection, + loggerFactory.CreateLogger()); + return devOpsService; + } - var devOpsService = new AzureDevOpsService(devOpsConnection, devOpsServiceLogger); - var gitHubService = new GitHubService(loggerFactory.CreateLogger()); - var credential = new ClientSecretCredential( - Environment.GetEnvironmentVariable(aadTenantVar), - Environment.GetEnvironmentVariable(aadAppIdVar), - Environment.GetEnvironmentVariable(aadAppSecretVar)); - var githubToAadResolver = new GitHubToAADConverter( - credential, - loggerFactory.CreateLogger() - ); - var configurator = new NotificationConfigurator(devOpsService, - gitHubService, notificationConfiguratorLogger); - await configurator.ConfigureNotifications( - project, - pathPrefix, - githubToAadResolver, - persistChanges: !dryRun, - strategy: selectionStrategy); + private static GitHubService GitHubService(ILoggerFactory loggerFactory) + => new GitHubService(loggerFactory.CreateLogger()); + + private static GitHubToAADConverter GitHubToAADConverter( + string aadTenantVar, + string aadAppIdVar, + string aadAppSecretVar, + ILoggerFactory loggerFactory) + { + var credential = new ClientSecretCredential( + Environment.GetEnvironmentVariable(aadTenantVar), + Environment.GetEnvironmentVariable(aadAppIdVar), + Environment.GetEnvironmentVariable(aadAppSecretVar)); - } + var githubToAadResolver = new GitHubToAADConverter( + credential, + loggerFactory.CreateLogger() + ); + return githubToAadResolver; } }