/// <summary> /// Create notification groups for failures in scheduled builds /// </summary> /// <param name="organization">Azure DevOps Organization</param> /// <param name="project">Name of the DevOps project</param> /// <param name="pathPrefix">Path prefix to include pipelines (e.g. "\net")</param> /// <param name="tokenVariableName">Environment variable token name (e.g. "SYSTEM_ACCESSTOKEN")</param> /// <param name="selectionStrategy">Pipeline selection strategy</param> /// <param name="dryRun">Prints changes but does not alter any objects</param> /// <returns></returns> static async Task Main( string organization, string project, string pathPrefix, string tokenVariableName, PipelineSelectionStrategy selectionStrategy = PipelineSelectionStrategy.Scheduled, bool dryRun = false) { var devOpsToken = Environment.GetEnvironmentVariable(tokenVariableName); var devOpsCreds = new VssBasicCredential("nobody", devOpsToken); var devOpsConnection = new VssConnection(new Uri($"https://dev.azure.com/{organization}/"), devOpsCreds); #pragma warning disable CS0618 // Type or member is obsolete var loggerFactory = LoggerFactory.Create(builder => { builder.AddConsole(config => { config.IncludeScopes = true; }); }); #pragma warning restore CS0618 // Type or member is obsolete var devOpsServiceLogger = loggerFactory.CreateLogger <AzureDevOpsService>(); var notificationConfiguratorLogger = loggerFactory.CreateLogger <NotificationConfigurator>(); var devOpsService = new AzureDevOpsService(devOpsConnection, devOpsServiceLogger); var configurator = new NotificationConfigurator(devOpsService, notificationConfiguratorLogger); await configurator.ConfigureNotifications( project, pathPrefix, persistChanges : !dryRun, strategy : selectionStrategy); }
public void CreatePullRequest() { if (this.Solution == null) { return; } if (this.WorkItem == null) { MessageBox.ErrorQuery(50, 7, "VSTS", "You must select a work item first", "Ok"); return; } if (!EnsureHasNoChanges()) { return; } WindowManager.ShowLog(); GitService.CheckoutBranch(this.Solution, this.WorkItem.TaskID); GitService.Pull(this.Solution); GitService.Push(this.Solution); GitService.UpdateStatus(this.Solution, true); if (!EnsureHasNoChanges()) { WindowManager.CloseLog(); return; } GitService.Push(this.Solution); foreach (RepositoryVO repository in this.Solution.Repositories) { if ((!repository.Selected)) { continue; } if (!GitService.HasCommits(repository, this.WorkItem, this.BranchBase)) { continue; } bool isPullRequestAlreadyCreated = false; List <PullRequestVO> pullRequests = AzureDevOpsService.ListPullRequests(repository.Path); foreach (PullRequestVO pullRequest in pullRequests) { if (isPullRequestAlreadyCreated = ((pullRequest.Repository == repository.Name) && (pullRequest.Title.StartsWith(this.WorkItem.TaskID)))) { break; } } if (isPullRequestAlreadyCreated) { continue; } //Create PR AzureDevOpsService.CreatePullRequest(repository, this.WorkItem); } GitService.UpdateStatus(this.Solution, true); WindowManager.CloseLog(); this.RefreshUI(); }
public void AddReviewerToPullRequest() { if (this.Solution == null) { return; } if (this.WorkItem == null) { MessageBox.ErrorQuery(50, 7, "VSTS", "You must select a work item first", "Ok"); return; } string reviewerEmail = "*****@*****.**"; reviewerEmail = WindowManager.ShowDialogText("Choose the Reviewer", "E-mail:", reviewerEmail); if (string.IsNullOrEmpty(reviewerEmail)) { return; } WindowManager.ShowLog(); foreach (RepositoryVO repository in this.Solution.Repositories) { if ((!repository.Selected)) { continue; } List <PullRequestVO> pullRequests = AzureDevOpsService.ListPullRequests(repository.Path); PullRequestVO pullRequestWorkItem = null; foreach (PullRequestVO pullRequest in pullRequests) { if (((pullRequest.Repository != repository.Name) || (!pullRequest.Title.StartsWith(this.WorkItem.TaskID)))) { continue; } pullRequestWorkItem = pullRequest; break; } if (pullRequestWorkItem == null) { continue; } List <string> reviewers = AzureDevOpsService.ListPullRequestReviewers(repository, pullRequestWorkItem); if (reviewers.Contains(reviewerEmail)) { continue; } AzureDevOpsService.AddPullRequestReviewer(repository, pullRequestWorkItem, reviewerEmail); } WindowManager.CloseLog(); this.RefreshUI(); }
public async Task <RocketChatUserDto[]> GetUsersAsync(Uri azureDevOpsBaseUri, Guid[] identityIds) { var identities = await AzureDevOpsService.GetAndIterateIdentitiesAsync( azureDevOpsBaseUri, identityIds ); var mails = identities .Select(x => x.Properties.GetOrDefault(AzureDevOpsRocketChatConsts.Identity.MailPropertyName)?.ToString()) .Where(x => !x.IsNullOrWhiteSpace()) .ToArray(); return(await RocketChatClient.GetUsersByEmailAsync(new GetRocketChatUsersByEmailInputDto { Emails = mails })); }
public override async Task OnWillAppear(StreamDeckEventPayload args) { this.azureDevOpsService = new AzureDevOpsService(async(message) => { // This is only used for debugging. No-op in release builds. #if DEBUG await Manager.LogMessageAsync(args.context, message); #else await Task.CompletedTask; #endif }); await base.OnWillAppear(args); timer = new Timer(5 * 60 * 1000); timer.Elapsed += async(sender, e) => { await Manager.ShowOkAsync(args.context); await FetchLatestInfo(args); }; timer.Start(); await FetchLatestInfo(args); }
public void ShowWorkItems() { if (this.Solution == null) { return; } if (!AzureDevOpsService.IsConfigurated()) { MessageBox.ErrorQuery(50, 7, "VSTS", "You must configure VSTS client first", "Ok"); return; } string path = Directory.GetParent(this.Solution.Path).FullName; List <WorkItemVO> workItems = AzureDevOpsService.ListWorkItems(path); Dictionary <WorkItemVO, string> objects = new Dictionary <WorkItemVO, string>(); foreach (WorkItemVO workItem in workItems) { objects.Add(workItem, string.Format("{0} - {1}", workItem.Code, workItem.Name)); } WorkItemVO workitemSelected = WindowManager.ShowDialogObjects <WorkItemVO>("WorkItems", objects); this.WorkItem = workitemSelected; this.RefreshUI(); }
/// <summary> /// Synchronizes CODEOWNERS contacts to appropriate DevOps groups /// </summary> /// <param name="organization">Azure DevOps organization name</param> /// <param name="project">Azure DevOps project name</param> /// <param name="devOpsTokenVar">Personal Access Token environment variable name</param> /// <param name="aadAppIdVar">AAD App ID environment variable name (Kusto access)</param> /// <param name="aadAppSecretVar">AAD App Secret environment variable name (Kusto access)</param> /// <param name="aadTenantVar">AAD Tenant environment variable name (Kusto access)</param> /// <param name="kustoUrlVar">Kusto URL environment variable name</param> /// <param name="kustoDatabaseVar">Kusto DB environment variable name</param> /// <param name="kustoTableVar">Kusto Table environment variable name</param> /// <param name="pathPrefix">Azure DevOps path prefix (e.g. "\net")</param> /// <param name="dryRun">Do not persist changes</param> /// <returns></returns> public static async Task Main( string organization, string project, string devOpsTokenVar, string aadAppIdVar, string aadAppSecretVar, string aadTenantVar, string kustoUrlVar, string kustoDatabaseVar, string kustoTableVar, string pathPrefix = "", bool dryRun = false ) { #pragma warning disable CS0618 // Type or member is obsolete using (var loggerFactory = LoggerFactory.Create(builder => { builder.AddConsole(config => { config.IncludeScopes = true; }); })) #pragma warning restore CS0618 // Type or member is obsolete { var devOpsService = AzureDevOpsService.CreateAzureDevOpsService( Environment.GetEnvironmentVariable(devOpsTokenVar), $"https://dev.azure.com/{organization}/", loggerFactory.CreateLogger <AzureDevOpsService>() ); var gitHubServiceLogger = loggerFactory.CreateLogger <GitHubService>(); var gitHubService = new GitHubService(gitHubServiceLogger); var githubNameResolver = new GitHubNameResolver( Environment.GetEnvironmentVariable(aadAppIdVar), Environment.GetEnvironmentVariable(aadAppSecretVar), Environment.GetEnvironmentVariable(aadTenantVar), Environment.GetEnvironmentVariable(kustoUrlVar), Environment.GetEnvironmentVariable(kustoDatabaseVar), Environment.GetEnvironmentVariable(kustoTableVar), loggerFactory.CreateLogger <GitHubNameResolver>() ); var logger = loggerFactory.CreateLogger <Program>(); var pipelineGroupTasks = (await devOpsService.GetAllTeamsAsync(project)) .Where(team => YamlHelper.Deserialize <TeamMetadata>(team.Description, swallowExceptions: true)?.Purpose == TeamPurpose.SynchronizedNotificationTeam ).Select(async team => { var pipelineId = YamlHelper.Deserialize <TeamMetadata>(team.Description).PipelineId; return(new { Pipeline = await devOpsService.GetPipelineAsync(project, pipelineId), Team = team }); }); var pipelineGroups = await Task.WhenAll(pipelineGroupTasks); var filteredGroups = pipelineGroups.Where(group => group.Pipeline != default && group.Pipeline.Path.StartsWith(pathPrefix)); foreach (var group in filteredGroups) { using (logger.BeginScope("Team Name = {0}", group.Team.Name)) { if (group.Pipeline.Process.Type != PipelineYamlProcessType) { continue; } // Get contents of CODEOWNERS logger.LogInformation("Fetching CODEOWNERS file"); var managementUrl = new Uri(group.Pipeline.Repository.Properties["manageUrl"]); var codeownersContent = await gitHubService.GetCodeownersFile(managementUrl); if (codeownersContent == default) { logger.LogInformation("CODEOWNERS file not found, skipping sync"); continue; } var process = group.Pipeline.Process as YamlProcess; // Find matching contacts for the YAML file's path var parser = new CodeOwnersParser(codeownersContent); var matchPath = PathWithoutFilename(process.YamlFilename); var searchPath = $"/{matchPath}/"; logger.LogInformation("Searching CODEOWNERS for matching path Path = {0}", searchPath); var contacts = parser.GetContactsForPath(searchPath); logger.LogInformation("Matching Contacts Path = {0}, NumContacts = {1}", searchPath, contacts.Count); // Get set of team members in the CODEOWNERS file var contactResolutionTasks = contacts .Where(contact => contact.StartsWith("@")) .Select(contact => githubNameResolver.GetInternalUserPrincipal(contact.Substring(1))); var codeownerPrincipals = await Task.WhenAll(contactResolutionTasks); var codeownersDescriptorsTasks = codeownerPrincipals .Where(userPrincipal => !string.IsNullOrEmpty(userPrincipal)) .Select(userPrincipal => devOpsService.GetDescriptorForPrincipal(userPrincipal)); var codeownersDescriptors = await Task.WhenAll(codeownersDescriptorsTasks); var codeownersSet = new HashSet <string>(codeownersDescriptors); // Get set of existing team members var teamMembers = await devOpsService.GetMembersAsync(group.Team); var teamContactTasks = teamMembers .Select(async member => await devOpsService.GetUserFromId(new Guid(member.Identity.Id))); var teamContacts = await Task.WhenAll(teamContactTasks); var teamDescriptors = teamContacts.Select(contact => contact.SubjectDescriptor.ToString()); var teamSet = new HashSet <string>(teamDescriptors); // Synchronize contacts var contactsToRemove = teamSet.Except(codeownersSet); var contactsToAdd = codeownersSet.Except(teamSet); var teamDescriptor = await devOpsService.GetDescriptorAsync(group.Team.Id); foreach (var descriptor in contactsToRemove) { logger.LogInformation("Delete Contact TeamDescriptor = {0}, ContactDescriptor = {1}", teamDescriptor, descriptor); if (!dryRun) { await devOpsService.RemoveMember(teamDescriptor, descriptor); } } foreach (var descriptor in contactsToAdd) { logger.LogInformation("Add Contact TeamDescriptor = {0}, ContactDescriptor = {1}", teamDescriptor, descriptor); if (!dryRun) { await devOpsService.AddToTeamAsync(teamDescriptor, descriptor); } } } } } }
/// <summary> /// Synchronizes CODEOWNERS contacts to appropriate DevOps groups /// </summary> /// <param name="organization">Azure DevOps organization name</param> /// <param name="project">Azure DevOps project name</param> /// <param name="devOpsTokenVar">Personal Access Token environment variable name</param> /// <param name="aadAppIdVar">AAD App ID environment variable name (OpensourceAPI access)</param> /// <param name="aadAppSecretVar">AAD App Secret environment variable name (OpensourceAPI access)</param> /// <param name="aadTenantVar">AAD Tenant environment variable name (OpensourceAPI access)</param> /// <param name="pathPrefix">Azure DevOps path prefix (e.g. "\net")</param> /// <param name="dryRun">Do not persist changes</param> /// <returns></returns> public static async Task Main( string organization, string project, string devOpsTokenVar, string aadAppIdVar, string aadAppSecretVar, string aadTenantVar, string pathPrefix = "", bool dryRun = false ) { #pragma warning disable CS0618 // Type or member is obsolete using (var loggerFactory = LoggerFactory.Create(builder => { builder.AddConsole(config => { config.IncludeScopes = true; }); })) #pragma warning restore CS0618 // Type or member is obsolete { var devOpsService = AzureDevOpsService.CreateAzureDevOpsService( Environment.GetEnvironmentVariable(devOpsTokenVar), $"https://dev.azure.com/{organization}/", loggerFactory.CreateLogger <AzureDevOpsService>() ); var gitHubServiceLogger = loggerFactory.CreateLogger <GitHubService>(); var gitHubService = new GitHubService(gitHubServiceLogger); var credential = new ClientSecretCredential( Environment.GetEnvironmentVariable(aadTenantVar), Environment.GetEnvironmentVariable(aadAppIdVar), Environment.GetEnvironmentVariable(aadAppSecretVar)); var githubToAadResolver = new GitHubToAADConverter( credential, loggerFactory.CreateLogger <GitHubToAADConverter>() ); var logger = loggerFactory.CreateLogger <Program>(); var pipelines = (await devOpsService.GetPipelinesAsync(project)).ToDictionary(p => p.Id); var pipelineGroupTasks = (await devOpsService.GetAllTeamsAsync(project)) .Where(team => YamlHelper.Deserialize <TeamMetadata>(team.Description, swallowExceptions: true)?.Purpose == TeamPurpose.SynchronizedNotificationTeam ).Select(async team => { BuildDefinition pipeline; var pipelineId = YamlHelper.Deserialize <TeamMetadata>(team.Description).PipelineId; if (!pipelines.ContainsKey(pipelineId)) { pipeline = await devOpsService.GetPipelineAsync(project, pipelineId); } else { pipeline = pipelines[pipelineId]; } if (pipeline == default) { logger.LogWarning($"Could not find pipeline with id {pipelineId} referenced by team {team.Name}."); } return(new { Pipeline = pipeline, Team = team }); }); var pipelineGroups = await Task.WhenAll(pipelineGroupTasks); var filteredGroups = pipelineGroups.Where(group => group.Pipeline != default && group.Pipeline.Path.StartsWith(pathPrefix)); foreach (var group in filteredGroups) { using (logger.BeginScope("Team Name = {0}", group.Team.Name)) { if (group.Pipeline.Process.Type != PipelineYamlProcessType) { continue; } // Get contents of CODEOWNERS logger.LogInformation("Fetching CODEOWNERS file"); var managementUrl = new Uri(group.Pipeline.Repository.Properties["manageUrl"]); var codeOwnerEntries = await gitHubService.GetCodeownersFile(managementUrl); if (codeOwnerEntries == default) { logger.LogInformation("CODEOWNERS file not found, skipping sync"); continue; } var process = group.Pipeline.Process as YamlProcess; logger.LogInformation("Searching CODEOWNERS for matching path for {0}", process.YamlFilename); var codeOwnerEntry = CodeOwnersFile.FindOwnersForClosestMatch(codeOwnerEntries, process.YamlFilename); codeOwnerEntry.FilterOutNonUserAliases(); logger.LogInformation("Matching Contacts Path = {0}, NumContacts = {1}", process.YamlFilename, codeOwnerEntry.Owners.Count); // Get set of team members in the CODEOWNERS file var codeownerPrincipals = codeOwnerEntry.Owners .Select(contact => githubToAadResolver.GetUserPrincipalNameFromGithub(contact)); var codeownersDescriptorsTasks = codeownerPrincipals .Where(userPrincipal => !string.IsNullOrEmpty(userPrincipal)) .Select(userPrincipal => devOpsService.GetDescriptorForPrincipal(userPrincipal)); var codeownersDescriptors = await Task.WhenAll(codeownersDescriptorsTasks); var codeownersSet = new HashSet <string>(codeownersDescriptors); // Get set of existing team members var teamMembers = await devOpsService.GetMembersAsync(group.Team); var teamContactTasks = teamMembers .Select(async member => await devOpsService.GetUserFromId(new Guid(member.Identity.Id))); var teamContacts = await Task.WhenAll(teamContactTasks); var teamDescriptors = teamContacts.Select(contact => contact.SubjectDescriptor.ToString()); var teamSet = new HashSet <string>(teamDescriptors); // Synchronize contacts var contactsToRemove = teamSet.Except(codeownersSet); var contactsToAdd = codeownersSet.Except(teamSet); var teamDescriptor = await devOpsService.GetDescriptorAsync(group.Team.Id); foreach (var descriptor in contactsToRemove) { logger.LogInformation("Delete Contact TeamDescriptor = {0}, ContactDescriptor = {1}", teamDescriptor, descriptor); if (!dryRun) { await devOpsService.RemoveMember(teamDescriptor, descriptor); } } foreach (var descriptor in contactsToAdd) { logger.LogInformation("Add Contact TeamDescriptor = {0}, ContactDescriptor = {1}", teamDescriptor, descriptor); if (!dryRun) { await devOpsService.AddToTeamAsync(teamDescriptor, descriptor); } } } } } }
public NotificationConfigurator(AzureDevOpsService service, ILogger <NotificationConfigurator> logger) { this.service = service; this.logger = logger; }
static void Main(string[] args) { var builder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile("secrets.json", optional: false, reloadOnChange: true); IConfigurationRoot configuration = builder.Build(); Console.WriteLine("Retrieving token from key vault..."); var devOpsToken = configuration["AzureDevOpsInstallerToken"]; var devOpsSettingSection = configuration.GetSection("DevOpsEnvironmentSettings"); var organizationUri = GetSetting(devOpsSettingSection, "OrganizationUri"); var projectName = GetSetting(devOpsSettingSection, "ProjecName"); var projectGuid = GetSetting(devOpsSettingSection, "ProjectGuid"); var username = GetSetting(devOpsSettingSection, "Username"); Console.WriteLine("Connecting Dev Ops organization..."); var devOpsService = new AzureDevOpsService(organizationUri, username, devOpsToken, new Guid(projectGuid)); Console.WriteLine("Retrieving build data..."); var lastBuild = devOpsService.GetProjectBuilds() .Result .OrderByDescending(k => k.LastChangedDate) .FirstOrDefault(); Console.WriteLine("Downloading zip from artifactor..."); if (File.Exists(FilePath)) { File.Delete(FilePath); } CreateNewDirectory(ArtifactorExtractionFolder); //CreateNewDirectory(AssemblyCliExtractionFolder); Stream zipStream = devOpsService.GetArtifact(lastBuild.Id, ArtifactName).Result; using (FileStream zipFile = new FileStream(FilePath, FileMode.Append)) { zipStream.CopyTo(zipFile); } Console.WriteLine("Unzipping artifactor..."); ZipFile.ExtractToDirectory(FilePath, ArtifactorExtractionFolder); var assemblyCliZipFileName = $@"{ArtifactorExtractionFolder}\Assembly\DDCli\{lastBuild.Id}.zip"; var assemblyCliDynamicsZipFileName = $@"{ArtifactorExtractionFolder}\Assembly\DDCli.Dynamics\{lastBuild.Id}.zip"; Console.WriteLine("Unzipping assemblies..."); DeleteDirectory(AssemblyCliExtractionFolder); DeleteDirectory(AssemblyCliDynamicsExtractionFolder); ZipFile.ExtractToDirectory(assemblyCliZipFileName, AssemblyCliExtractionFolder); ZipFile.ExtractToDirectory(assemblyCliDynamicsZipFileName, AssemblyCliDynamicsExtractionFolder); Console.WriteLine("Installing files in folder..."); if (Directory.Exists(InstallCliFolder)) { Directory.Delete(InstallCliFolder, true); } if (Directory.Exists(InstallCliDynamicsFolder)) { Directory.Delete(InstallCliDynamicsFolder, true); } Directory.Move(AssemblyCliExtractionFolder, InstallCliFolder); Directory.Move(AssemblyCliDynamicsExtractionFolder, InstallCliDynamicsFolder); Console.WriteLine("Installation complete"); }