diff --git a/.gitignore b/.gitignore index 120d8b6..bce3b4c 100644 --- a/.gitignore +++ b/.gitignore @@ -151,3 +151,4 @@ UpgradeLog.htm /src/AzureDevOpsDemoBuilder/log /src/AzureDevOpsDemoBuilder/Appsettings.Development.json /src/AzureDevOpsDemoBuilder/appsettings.json +/src/ADOGenerator/ExtractedTemplate diff --git a/src/ADOGenerator/ADOGenerator.csproj b/src/ADOGenerator/ADOGenerator.csproj index 34dac0a..e1fc806 100644 --- a/src/ADOGenerator/ADOGenerator.csproj +++ b/src/ADOGenerator/ADOGenerator.csproj @@ -9,6 +9,7 @@ + diff --git a/src/ADOGenerator/IServices/IExtractorService.cs b/src/ADOGenerator/IServices/IExtractorService.cs new file mode 100644 index 0000000..f52425b --- /dev/null +++ b/src/ADOGenerator/IServices/IExtractorService.cs @@ -0,0 +1,32 @@ +using ADOGenerator.Models; +using RestAPI; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ADOGenerator.IServices +{ + public interface IExtractorService + { + ProjectConfigurations ProjectConfiguration(Project model); + int GetTeamsCount(ProjectConfigurations appConfig); + int GetIterationsCount(ProjectConfigurations appConfig); + int GetBuildDefinitionCount(ProjectConfigurations appConfig); + int GetReleaseDefinitionCount(ProjectConfigurations appConfig); + string[] GenerateTemplateArifacts(Project model); + Dictionary GetWorkItemsCount(ProjectConfigurations appConfig); + List GetInstalledExtensions(ProjectConfigurations appConfig, string extractedFolderName); + void ExportQuries(ProjectConfigurations appConfig, string extractedFolderName); + bool ExportTeams(RestAPI.ADOConfiguration con, Project model, string extractedFolderName); + bool ExportIterations(ProjectConfigurations appConfig, string extractedFolderName); + bool ExportWorkItems(ProjectConfigurations appConfig, string extractedFolderName); + bool ExportRepositoryList(ProjectConfigurations appConfig, string extractedFolderName); + int GetBuildDefinitions(ProjectConfigurations appConfig, string extractedFolderName); + int GeneralizingGetReleaseDefinitions(ProjectConfigurations appConfig, string extractedFolderName); + void GetServiceEndpoints(ProjectConfigurations appConfig, string extractedFolderName); + bool ExportDeliveryPlans(ProjectConfigurations appConfig, string extractedFolderName); + bool IsTemplateExists(string templateName); + } +} diff --git a/src/ADOGenerator/IServices/IProjectService.cs b/src/ADOGenerator/IServices/IProjectService.cs index 7982db5..c21caad 100644 --- a/src/ADOGenerator/IServices/IProjectService.cs +++ b/src/ADOGenerator/IServices/IProjectService.cs @@ -17,6 +17,9 @@ public interface IProjectService public bool InstallExtensions(Project model, string accountName, string PAT); public bool WhereDoseTemplateBelongTo(string templatName); + public HttpResponseMessage GetProjects(string accname, string pat, string authScheme); + + public Task> SelectProject(string accessToken, HttpResponseMessage projectsData); } } diff --git a/src/ADOGenerator/IServices/ITemplateService.cs b/src/ADOGenerator/IServices/ITemplateService.cs index 419c9f7..4e497d7 100644 --- a/src/ADOGenerator/IServices/ITemplateService.cs +++ b/src/ADOGenerator/IServices/ITemplateService.cs @@ -1,6 +1,11 @@ -namespace ADOGenerator.IServices +using ADOGenerator.Models; + +namespace ADOGenerator.IServices { public interface ITemplateService { + bool AnalyzeProject(Project model); + bool CheckTemplateExists(Project model); + (bool,string,string) GenerateTemplateArtifacts(Project model); } } diff --git a/src/ADOGenerator/Models/ExtractorAnalysis.cs b/src/ADOGenerator/Models/ExtractorAnalysis.cs new file mode 100644 index 0000000..3d88cc5 --- /dev/null +++ b/src/ADOGenerator/Models/ExtractorAnalysis.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ADOGenerator.Models +{ + internal class ExtractorAnalysis + { + public int teamCount { get; set; } + public int IterationCount { get; set; } + public int BuildDefCount { get; set; } + public int ReleaseDefCount { get; set; } + public Dictionary WorkItemCounts { get; set; } + public List ErrorMessages { get; set; } + } +} diff --git a/src/ADOGenerator/Program.cs b/src/ADOGenerator/Program.cs index 2bf1bec..0f1f594 100644 --- a/src/ADOGenerator/Program.cs +++ b/src/ADOGenerator/Program.cs @@ -4,7 +4,11 @@ using ADOGenerator.Services; using Microsoft.Extensions.Configuration; using Microsoft.Identity.Client; +using Microsoft.VisualStudio.Services.DelegatedAuthorization; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using RestAPI; +using System.Text; using System.Text.RegularExpressions; var configuration = new ConfigurationBuilder() @@ -13,211 +17,529 @@ .Build(); Console.WriteLine("Welcome to Azure DevOps Demo Generator! This tool will help you generate a demo environment for Azure DevOps."); - -try +(string accessToken, string organizationName, string authScheme)? authenticationDetails = null; +string authChoice = string.Empty; +string currentPath = Directory.GetCurrentDirectory() + .Replace("bin\\Debug\\net8.0", "") + .Replace("bin\\Release\\net8.0", "") + .Replace("bin\\Debug", "") + .Replace("bin\\Release", ""); + +do { - do + string id = Guid.NewGuid().ToString().Split('-')[0]; + Console.ForegroundColor = ConsoleColor.Cyan; + Console.WriteLine("Do you want to create a new template or create a new project using the demo generator project template?"); + Console.WriteLine("1. Create a new project using the demo generator project template"); + Console.WriteLine("2. Generate new artifacts using an existing project."); + Console.ResetColor(); + id.AddMessage(Environment.NewLine+"Enter the option number from the list of options above:"); + var userChoiceTemplate = Console.ReadLine(); + + switch (userChoiceTemplate) { - string id = Guid.NewGuid().ToString().Split('-')[0]; - Init init = new Init(); - id.AddMessage("Template Details"); - var templatePath = Path.Combine(Directory.GetCurrentDirectory(), "Templates", "TemplateSetting.json"); + case "1": + HandleNewProjectCreation(configuration, id); + break; - if (!File.Exists(templatePath)) - { - id.ErrorId().AddMessage("TemplateSettings.json file not found."); + case "2": + var (isArtifactsGenerated, template, model) = HandleArtifactGeneration(configuration, id); + if (isArtifactsGenerated) + { + HandleTemplateAndArtifactsUpdate(template, id, model, currentPath); + } + else + { + if(!template.Equals("skipped", StringComparison.OrdinalIgnoreCase)) + { + id.ErrorId().AddMessage(Environment.NewLine + "Artifacts generation failed."); + } + } break; - } - var templateSettings = File.ReadAllText(templatePath); - var json = JObject.Parse(templateSettings); - var groupwiseTemplates = json["GroupwiseTemplates"]; + default: + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine("Invalid choice. Please select either 1 or 2."); + Console.ResetColor(); + continue; + } - if (groupwiseTemplates == null) + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine(Environment.NewLine+"Do you want to create another project? (yes/no): press enter to confirm"); + Console.ResetColor(); + var createAnotherProject = Console.ReadLine(); + if (string.IsNullOrWhiteSpace(createAnotherProject) || createAnotherProject.Equals("yes", StringComparison.OrdinalIgnoreCase) || createAnotherProject.Equals("y", StringComparison.OrdinalIgnoreCase)) + { + if (authChoice == "2") { - id.ErrorId().AddMessage("No templates found."); - break; + authenticationDetails = null; } - - int templateIndex = 1; - var templateDictionary = new Dictionary(); - - foreach (var group in groupwiseTemplates) + else { - var groupName = group["Groups"]?.ToString(); - Console.WriteLine(groupName); - - var templates = group["Template"]; - if (templates != null) + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine("Do you want to use the existing authentication details? (yes/no): press enter to confirm"); + Console.ResetColor(); + var useExistingAuth = Console.ReadLine(); + if (string.IsNullOrWhiteSpace(useExistingAuth) || useExistingAuth.Equals("yes", StringComparison.OrdinalIgnoreCase) || useExistingAuth.Equals("y", StringComparison.OrdinalIgnoreCase)) { - foreach (var template in templates) + if (authenticationDetails != null) { - var templateName = template["Name"]?.ToString(); - Console.WriteLine($" {templateIndex}. {templateName}"); - templateDictionary.Add(templateIndex, templateName); - templateIndex++; + authenticationDetails = (authenticationDetails.Value.accessToken, null, authenticationDetails.Value.authScheme); } } + else + { + authenticationDetails = null; + } + } + continue; + } + + id.AddMessage("Exiting the application."); + Environment.Exit(0); + +} while (true); +return 0; + +void HandleTemplateAndArtifactsUpdate(string template, string id, Project model, string currentPath) +{ + id.AddMessage(Environment.NewLine+"Do you want to update the template settings and move the artifacts to the executable directory? (yes/no): press enter to confirm"); + var updateTemplateSettings = Console.ReadLine(); + + if (string.IsNullOrWhiteSpace(updateTemplateSettings) || updateTemplateSettings.Equals("yes", StringComparison.OrdinalIgnoreCase) || updateTemplateSettings.Equals("y", StringComparison.OrdinalIgnoreCase)) + { + id.AddMessage(Environment.NewLine + "Updating template settings..."); + var templatePathBin = Path.Combine(Directory.GetCurrentDirectory(), "Templates", "TemplateSetting.json"); + var templatePathOriginal = Path.Combine(currentPath, "Templates", "TemplateSetting.json"); + + if (UpdateTemplateSettings(template, id, templatePathOriginal)) + { + id.AddMessage(Environment.NewLine+"Template settings updated successfully at " + templatePathOriginal); + } + else + { + id.ErrorId().AddMessage(Environment.NewLine+"Template settings update failed at " + templatePathOriginal); } - id.AddMessage("Enter the template number from the list of templates above:"); - if (!int.TryParse(Console.ReadLine(), out var selectedTemplateNumber) || !templateDictionary.TryGetValue(selectedTemplateNumber, out var selectedTemplateName)) + CopyFileIfExists(id,templatePathOriginal, templatePathBin); + + id.AddMessage(Environment.NewLine+"Template settings copied to the current directory and updated successfully."); + id.AddMessage(Environment.NewLine + "Moving artifacts to the current directory..."); + + var artifactsPathOriginal = Path.Combine(currentPath, "Templates", $"CT-{model.ProjectName.Replace(" ", "-")}"); + var artifactsPath = Path.Combine(Directory.GetCurrentDirectory(), "Templates", $"CT-{model.ProjectName.Replace(" ", "-")}"); + + MoveArtifacts(artifactsPathOriginal, artifactsPath, id); + } + else + { + SkipTemplateAndArtifactsUpdate(id, currentPath, model, template); + } +} + +void CopyFileIfExists(string id,string sourcePath, string destinationPath) +{ + if (File.Exists(sourcePath)) + { + File.Copy(sourcePath, destinationPath, true); + } + else + { + Console.ForegroundColor = ConsoleColor.Green; + id.AddMessage(Environment.NewLine+$"Source file '{sourcePath}' does not exist. Creating a new file at the destination."); + string fileContents = File.ReadAllText(sourcePath); + File.WriteAllText(destinationPath, fileContents); + id.AddMessage(Environment.NewLine + $"New file created at '{destinationPath}'."); + Console.ResetColor(); + } +} + +void MoveArtifacts(string sourcePath, string destinationPath, string id) +{ + if (Directory.Exists(sourcePath)) + { + if (Directory.Exists(destinationPath)) { - id.AddMessage("Invalid template number entered."); - continue; + Directory.Delete(destinationPath, true); } - selectedTemplateName = selectedTemplateName.Trim(); - if (!TryGetTemplateDetails(groupwiseTemplates, selectedTemplateName, out var templateFolder, out var confirmedExtension, id)) + Directory.CreateDirectory(destinationPath); + + foreach (var directory in Directory.GetDirectories(sourcePath, "*", SearchOption.AllDirectories)) { - id.AddMessage($"Template '{selectedTemplateName}' not found in the list."); - id.AddMessage("Would you like to try again or exit? (type 'retry' to try again or 'exit' to quit):"); - var userChoice = Console.ReadLine(); - if (userChoice?.Equals("exit", StringComparison.OrdinalIgnoreCase) == true) + var relativePath = Path.GetRelativePath(sourcePath, directory); + var destinationDirectory = Path.Combine(destinationPath, relativePath); + Directory.CreateDirectory(destinationDirectory); + } + + foreach (var file in Directory.GetFiles(sourcePath, "*", SearchOption.AllDirectories)) + { + var relativePath = Path.GetRelativePath(sourcePath, file); + var destinationFile = Path.Combine(destinationPath, relativePath); + File.Copy(file, destinationFile, true); + } + + id.AddMessage(Environment.NewLine+"Artifacts moved to the current directory."); + } + else + { + id.ErrorId().AddMessage(Environment.NewLine+"Artifacts directory not found at " + sourcePath); + } +} + +void SkipTemplateAndArtifactsUpdate(string id, string currentPath, Project model, string template) +{ + var templatePathBin = Path.Combine(Directory.GetCurrentDirectory(), "Templates", "TemplateSetting.json"); + var artifactsPathOriginal = Path.Combine(currentPath, "Templates", $"CT-{model.ProjectName.Replace(" ", "-")}"); + var artifactsPath = Path.Combine(Directory.GetCurrentDirectory(), "Templates", $"CT-{model.ProjectName.Replace(" ", "-")}"); + id.WarningId().AddMessage(Environment.NewLine+template); + id.WarningId().AddMessage(Environment.NewLine+"Copy the generated template JSON and update the template settings manually in the following file location: " + templatePathBin); + id.WarningId().AddMessage(Environment.NewLine+"Template settings update and copying artifacts skipped."); + id.WarningId().AddMessage(Environment.NewLine+"Copy the generated artifacts directory from " + artifactsPathOriginal + " and update the artifacts manually in the following directory location: " + artifactsPath); +} + + +void HandleNewProjectCreation(IConfiguration configuration, string id) +{ + Init init = new Init(); + id.AddMessage(Environment.NewLine+"Template Details"); + + var templatePath = Path.Combine(Directory.GetCurrentDirectory(), "Templates", "TemplateSetting.json"); + if (!File.Exists(templatePath)) + { + id.ErrorId().AddMessage("TemplateSettings.json file not found."); + return; + } + + var groupwiseTemplates = LoadTemplates(templatePath, id); + if (groupwiseTemplates == null) return; + + var selectedTemplateName = SelectTemplate(groupwiseTemplates, id); + if (string.IsNullOrEmpty(selectedTemplateName)) return; + + var templateFolder = string.Empty; + var confirmedExtension = false; + if (!TryGetTemplateDetails(groupwiseTemplates, selectedTemplateName, out templateFolder, out confirmedExtension, id)) + { + return; + } + + ValidateExtensions(templateFolder, id); + + var (accessToken, organizationName, authScheme) = AuthenticateUser(init, id); + if (string.IsNullOrWhiteSpace(accessToken) || string.IsNullOrWhiteSpace(organizationName)) return; + + var projectName = GetValidProjectName(init, id); + if (string.IsNullOrWhiteSpace(projectName)) return; + + var project = new Project + { + id = id, + accessToken = accessToken, + accountName = organizationName, + ProjectName = projectName, + TemplateName = selectedTemplateName, + selectedTemplateFolder = templateFolder, + SelectedTemplate = templateFolder, + isExtensionNeeded = confirmedExtension, + isAgreeTerms = confirmedExtension, + adoAuthScheme = authScheme + }; + + CreateProjectEnvironment(project); +} + +(bool,string,Project) HandleArtifactGeneration(IConfiguration configuration, string id) +{ + Init init = new Init(); + var (accessToken, organizationName, authScheme) = AuthenticateUser(init, id); + if (string.IsNullOrWhiteSpace(accessToken) || string.IsNullOrWhiteSpace(organizationName)) return (false, string.Empty,null); + + IProjectService projService = new ProjectService(configuration); + var projects = projService.GetProjects(organizationName, accessToken, authScheme); + var projectDetails = projService.SelectProject(accessToken, projects).Result; + + var projectName = projectDetails[1]; + if (string.IsNullOrWhiteSpace(projectName)) return (false, string.Empty, null); + + var model = new Project + { + accountName = organizationName, + ProjectName = projectName, + ProjectId = projectDetails[0], + accessToken = accessToken, + adoAuthScheme = authScheme, + id = id + }; + + ITemplateService templateService = new TemplateService(configuration); + bool isTemplateExists = templateService.CheckTemplateExists(model); + string template = string.Empty; + if (isTemplateExists) + { + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine(Environment.NewLine + "Do you still need to update the existing template?(yes/no)"); + Console.ResetColor(); + var updateTemplate = Console.ReadLine(); + if (updateTemplate?.Equals("yes", StringComparison.OrdinalIgnoreCase) == true) + { + (bool isArtifactsGenerated, template, Project project) = AnalyzeAndGenerateArtifacts(model, templateService); + if (isArtifactsGenerated) { - id.AddMessage("Exiting the application."); - return 0; + return (true, template, project); } - continue; } else { - id.AddMessage($"Selected template: {selectedTemplateName}"); - ValidateExtensions(templateFolder, id); + id.AddMessage(Environment.NewLine + "Template update skipped."); + return (false, "skipped", null); } + } + else + { + (bool isArtifactsGenerated, template, Project project) = AnalyzeAndGenerateArtifacts(model, templateService); + if (isArtifactsGenerated) + { + return (true, template, project); + } + } + return (false, template, null); +} +(bool, string, Project) AnalyzeAndGenerateArtifacts(Project model, ITemplateService templateService) +{ + var analyzed = templateService.AnalyzeProject(model); + if (analyzed) + { + model.id.AddMessage("Artifacts analyzed successfully."); + } + else + { + model.id.ErrorId().AddMessage("Artifacts analysis failed."); + return (false, string.Empty, null); + } + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine(Environment.NewLine + "Do you want to create artifacts yes/no:"); + Console.ResetColor(); + string response = Console.ReadLine(); + if (response == "yes") + { + (bool isArtifactsGenerated, string template, string templateLocation) = templateService.GenerateTemplateArtifacts(model); + if (isArtifactsGenerated) + { + model.id.AddMessage(Environment.NewLine + "Artifacts has been generated sccessfully at the location: " + templateLocation); + return (true, template, model); + } + else + { + model.id.ErrorId().AddMessage(Environment.NewLine + "Artifacts generation failed."); + } + } + else + { + model.id.AddMessage(Environment.NewLine + "Artifacts generation skipped."); + return (false, "skipped", null); + } + return (false, string.Empty, null); +} +JToken LoadTemplates(string templatePath, string id) +{ + var templateSettings = File.ReadAllText(templatePath); + var json = JObject.Parse(templateSettings); + var groupwiseTemplates = json["GroupwiseTemplates"]; - id.AddMessage("Choose authentication method: 1. Device Login using AD auth 2. Personal Access Token (PAT)"); - var authChoice = Console.ReadLine(); + if (groupwiseTemplates == null) + { + id.ErrorId().AddMessage(Environment.NewLine + "No templates found."); + return null; + } + + return groupwiseTemplates; +} - string accessToken = string.Empty; - string organizationName = string.Empty; - string authScheme = string.Empty; +string SelectTemplate(JToken groupwiseTemplates, string id) +{ + int templateIndex = 1; + var templateDictionary = new Dictionary(); - if (authChoice == "1") + foreach (var group in groupwiseTemplates) + { + var groupName = group["Groups"]?.ToString(); + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine(groupName); + Console.ResetColor(); + var templates = group["Template"]; + if (templates != null) { - try + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine(new string('-', 100)); // Top border of the table + Console.WriteLine($"| {"Index",-10} | {"Template Name",-30} | {"Description",-50} |"); + Console.WriteLine(new string('-', 100)); // Separator line + foreach (var template in templates) { - var app = PublicClientApplicationBuilder.Create(AuthService.clientId) - .WithAuthority(AuthService.authority) - .WithDefaultRedirectUri() - .Build(); - AuthService authService = new AuthService(); + var templateName = template["Name"]?.ToString(); + var templateDescription = template["Description"]?.ToString(); + + // Wrap content to fit console width + var wrappedTemplateName = WrapText(templateName, 30); + var wrappedTemplateDescription = WrapText(templateDescription, 50); + + var templateNameLines = wrappedTemplateName.Split(Environment.NewLine); + var templateDescriptionLines = wrappedTemplateDescription.Split(Environment.NewLine); + var maxLines = Math.Max(templateNameLines.Length, templateDescriptionLines.Length); - var accounts = await app.GetAccountsAsync(); - if (accounts.Any()) + for (int i = 0; i < maxLines; i++) { - try + var nameLine = i < templateNameLines.Length ? templateNameLines[i] : string.Empty; + var descriptionLine = i < templateDescriptionLines.Length ? templateDescriptionLines[i] : string.Empty; + + if (i == 0) { - var result = await app.AcquireTokenSilent(AuthService.scopes, accounts.FirstOrDefault()) - .WithForceRefresh(true) - .ExecuteAsync(); - accessToken = result.AccessToken; + Console.WriteLine($"| {templateIndex,-10} | {nameLine,-30} | {descriptionLine,-50} |"); } - catch (Exception ex) + else { - var result = await authService.AcquireTokenAsync(app); - accessToken = result.AccessToken; - id.ErrorId().AddMessage($"Error: {ex.Message}"); + Console.WriteLine($"| {"",-10} | {nameLine,-30} | {descriptionLine,-50} |"); } } - else - { - var result = await authService.AcquireTokenAsync(app); - accessToken = result.AccessToken; - } - var memberId = await authService.GetProfileInfoAsync(accessToken); - var organizations = await authService.GetOrganizationsAsync(accessToken, memberId); - organizationName = await authService.SelectOrganization(accessToken, organizations); - authScheme = "Bearer"; - } - catch (Exception ex) - { - id.ErrorId().AddMessage($"Error: {ex.Message}"); - id.AddMessage("Exiting the application."); - Environment.Exit(1); + Console.WriteLine(new string('-', 100)); // Divider after each row + templateDictionary.Add(templateIndex, templateName); + templateIndex++; } + Console.ResetColor(); } - else if (authChoice == "2") - { - id.AddMessage("Enter your Azure DevOps organization name:"); - organizationName = Console.ReadLine(); - if (string.IsNullOrWhiteSpace(organizationName)) - { - id.ErrorId().AddMessage("Organization name cannot be empty."); - continue; - } + } + id.AddMessage(Environment.NewLine+"Enter the template number from the list of templates above:"); + if (!int.TryParse(Console.ReadLine(), out var selectedTemplateNumber) || !templateDictionary.TryGetValue(selectedTemplateNumber, out var selectedTemplateName)) + { + id.AddMessage(Environment.NewLine+"Invalid template number entered."); + return null; + } + + return selectedTemplateName.Trim(); +} +string WrapText(string text, int maxWidth) +{ + if (string.IsNullOrEmpty(text)) return string.Empty; + + var wrappedText = new StringBuilder(); + int currentIndex = 0; + + while (currentIndex < text.Length) + { + int remainingLength = text.Length - currentIndex; + int lineLength = Math.Min(maxWidth, remainingLength); + + // Split at the exact maxWidth if no whitespace or special character is found + wrappedText.AppendLine(text.Substring(currentIndex, lineLength).Trim()); + currentIndex += lineLength; + } + + return wrappedText.ToString().TrimEnd(); +} - if (Uri.IsWellFormedUriString(organizationName, UriKind.Absolute)) +(string accessToken, string organizationName, string authScheme) AuthenticateUser(Init init, string id) +{ + string organizationName = string.Empty; + if (authenticationDetails.HasValue && + (!string.IsNullOrWhiteSpace(authenticationDetails.Value.organizationName) || + !string.IsNullOrWhiteSpace(authenticationDetails.Value.accessToken) || + !string.IsNullOrWhiteSpace(authenticationDetails.Value.authScheme))) + { + if (string.IsNullOrWhiteSpace(authenticationDetails.Value.organizationName)) + { + if (authChoice == "1") { - id.ErrorId().AddMessage("Please enter only the last part of the Azure DevOps URL (e.g., {ORGANIZATION_NAME} from https://dev.azure.com/{ORGANIZATION_NAME})."); - continue; + AuthService authService = new AuthService(); + var memberId = authService.GetProfileInfoAsync(authenticationDetails.Value.accessToken).Result; + var organizations = authService.GetOrganizationsAsync(authenticationDetails.Value.accessToken, memberId).Result; + organizationName = authService.SelectOrganization(authenticationDetails.Value.accessToken, organizations).Result; } - - id.AddMessage("Enter your Azure DevOps personal access token:"); - accessToken = init.ReadSecret(); - if (string.IsNullOrWhiteSpace(accessToken)) + else if (authChoice == "2") { - id.ErrorId().AddMessage("Personal access token cannot be empty."); - continue; + id.AddMessage(Environment.NewLine + "Enter your Azure DevOps organization name:"); + organizationName = Console.ReadLine(); } - - authScheme = "Basic"; + authenticationDetails = (authenticationDetails.Value.accessToken, organizationName, authenticationDetails.Value.authScheme); } + return authenticationDetails.Value; + } + id.AddMessage(Environment.NewLine + "Choose authentication method: 1. Device Login using AD auth 2. Personal Access Token (PAT)"); + authChoice = Console.ReadLine(); - string projectName = ""; - do + string accessToken = string.Empty; + string authScheme = string.Empty; + + if (authChoice == "1") + { + var app = PublicClientApplicationBuilder.Create(AuthService.clientId) + .WithAuthority(AuthService.authority) + .WithDefaultRedirectUri() + .Build(); + AuthService authService = new AuthService(); + + var accounts = app.GetAccountsAsync().Result; + if (accounts.Any()) { - id.AddMessage("Enter the new project name:"); - projectName = Console.ReadLine(); - if (string.IsNullOrWhiteSpace(projectName)) + try { - id.ErrorId().AddMessage("Project name cannot be empty."); - continue; + var result = app.AcquireTokenSilent(AuthService.scopes, accounts.FirstOrDefault()) + .WithForceRefresh(true) + .ExecuteAsync().Result; + accessToken = result.AccessToken; } - if (!init.CheckProjectName(projectName)) + catch (MsalUiRequiredException) { - id.ErrorId().AddMessage("Validation error: Project name is not valid."); - id.AddMessage("Do you want to try with a valid project name or exit? (type 'retry' to try again or 'exit' to quit):"); - var userChoice = Console.ReadLine(); - if (userChoice?.Equals("exit", StringComparison.OrdinalIgnoreCase) == true) - { - id.AddMessage("Exiting the application."); - Environment.Exit(1); - } - projectName = ""; - continue; + var result = authService.AcquireTokenAsync(app).Result; + accessToken = result.AccessToken; } - } while (string.IsNullOrWhiteSpace(projectName)); - - if (string.IsNullOrWhiteSpace(organizationName) || string.IsNullOrWhiteSpace(accessToken) || string.IsNullOrWhiteSpace(projectName)) + } + else { - id.ErrorId().AddMessage("Validation error: All inputs must be provided. Exiting.."); - Environment.Exit(1); + var result = authService.AcquireTokenAsync(app).Result; + accessToken = result.AccessToken; } - var project = new Project - { - id = id, - accessToken = accessToken, - accountName = organizationName, - ProjectName = projectName, - TemplateName = selectedTemplateName, - selectedTemplateFolder = templateFolder, - SelectedTemplate = templateFolder, - isExtensionNeeded = confirmedExtension, - isAgreeTerms = confirmedExtension, - adoAuthScheme = authScheme - }; - - CreateProjectEnvironment(project); - - } while (true); + var memberId = authService.GetProfileInfoAsync(accessToken).Result; + var organizations = authService.GetOrganizationsAsync(accessToken, memberId).Result; + organizationName = authService.SelectOrganization(accessToken, organizations).Result; + authScheme = "Bearer"; + } + else if (authChoice == "2") + { + id.AddMessage(Environment.NewLine + "Enter your Azure DevOps organization name:"); + organizationName = Console.ReadLine(); + + id.AddMessage(Environment.NewLine + "Enter your Azure DevOps personal access token:"); + accessToken = init.ReadSecret(); + + authScheme = "Basic"; + } + + authenticationDetails = (accessToken, organizationName, authScheme); + return authenticationDetails.Value; } -catch (Exception ex) + +string GetValidProjectName(Init init, string id) { - Console.WriteLine($"An error occurred: {ex.Message}"); - Console.WriteLine("Exiting the application."); - Environment.Exit(1); + string projectName = ""; + do + { + id.AddMessage(Environment.NewLine + "Enter the new project name:"); + projectName = Console.ReadLine(); + if (!init.CheckProjectName(projectName)) + { + id.ErrorId().AddMessage(Environment.NewLine+"Validation error: Project name is not valid."); + id.AddMessage(Environment.NewLine + "Do you want to try with a valid project name or exit? (type 'retry' to try again or 'exit' to quit):"); + var userChoice = Console.ReadLine(); + if (userChoice?.Equals("exit", StringComparison.OrdinalIgnoreCase) == true) + { + id.AddMessage(Environment.NewLine + "Exiting the application."); + Environment.Exit(1); + } + projectName = ""; + } + } while (string.IsNullOrWhiteSpace(projectName)); + return projectName; } bool TryGetTemplateDetails(JToken groupwiseTemplates, string selectedTemplateName, out string templateFolder, out bool confirmedExtension, string id) @@ -292,23 +614,23 @@ bool ValidateExtensions(string templateFolderPath, string id) if (extensions.HasValues) { - id.AddMessage("Do you want to proceed with this extension? (yes/No): press enter to confirm"); + id.AddMessage(Environment.NewLine + "Do you want to proceed with this extension? (yes/No): press enter to confirm"); var userConfirmation = Console.ReadLine(); if (string.IsNullOrWhiteSpace(userConfirmation) || (userConfirmation.Equals("yes", StringComparison.OrdinalIgnoreCase) || userConfirmation.Equals("y", StringComparison.OrdinalIgnoreCase))) { Console.ForegroundColor = ConsoleColor.Yellow; - id.AddMessage("Agreed for license? (yes/no): press enter to confirm"); + id.AddMessage(Environment.NewLine + "Agreed for license? (yes/no): press enter to confirm"); Console.ResetColor(); var licenseConfirmation = Console.ReadLine(); if (string.IsNullOrWhiteSpace(licenseConfirmation) || (licenseConfirmation.Equals("yes", StringComparison.OrdinalIgnoreCase) || licenseConfirmation.Equals("y", StringComparison.OrdinalIgnoreCase))) { - id.AddMessage("Confirmed Extension installation"); + id.AddMessage(Environment.NewLine + "Confirmed Extension installation"); return true; } } else { - id.AddMessage("Extension installation is not confirmed."); + id.AddMessage(Environment.NewLine + "Extension installation is not confirmed."); } } @@ -317,42 +639,103 @@ bool ValidateExtensions(string templateFolderPath, string id) void CreateProjectEnvironment(Project model) { - try + Console.WriteLine($"Creating project '{model.ProjectName}' in organization '{model.accountName}' using template from '{model.TemplateName}'..."); + var projectService = new ProjectService(configuration); + var result = projectService.CreateProjectEnvironment(model); + if (result) { - Console.WriteLine($"Creating project '{model.ProjectName}' in organization '{model.accountName}' using template from '{model.TemplateName}'..."); - var projectService = new ProjectService(configuration); - var result = projectService.CreateProjectEnvironment(model); - if (result) - { - Console.WriteLine("Project created successfully."); - } - else - { - Console.WriteLine("Project creation failed."); - } - Console.WriteLine("Do you want to create another project? (yes/no): press enter to confirm"); - var createAnotherProject = Console.ReadLine(); - if (string.IsNullOrWhiteSpace(createAnotherProject) || createAnotherProject.Equals("yes", StringComparison.OrdinalIgnoreCase) || createAnotherProject.Equals("y", StringComparison.OrdinalIgnoreCase)) - { - createAnotherProject = "yes"; - } - else - { - createAnotherProject = "no"; - } - Console.WriteLine(); - if (createAnotherProject.Equals("no", StringComparison.OrdinalIgnoreCase)) + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine("Project created successfully."); + Console.ResetColor(); + } + else + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine("Project creation failed."); + Console.ResetColor(); + } +} +bool UpdateTemplateSettings(string template, string id, string templatePath) +{ + if (!File.Exists(templatePath)) + { + id.ErrorId().AddMessage(Environment.NewLine+"TemplateSettings.json file not found at " + templatePath); + return false; + } + + var templateSettings = File.ReadAllText(templatePath); + var json = JObject.Parse(templateSettings); + + UpdateGroups(json, id); + UpdateGroupwiseTemplates(json, template, id); + + File.WriteAllText(templatePath, JsonConvert.SerializeObject(json, Formatting.Indented)); + return true; +} + +void UpdateGroups(JObject json, string id) +{ + var groups = json["Groups"] as JArray ?? new JArray(json["Groups"]); + if (!groups.Any(g => g.ToString().Equals("Custom Templates", StringComparison.OrdinalIgnoreCase))) + { + var customTemplates = new JArray { "Custom Templates" }; + foreach (var item in customTemplates) { - model.id.AddMessage("Exiting the application."); - Environment.Exit(0); + groups.Add(item); } + json["Groups"] = groups; } - catch (Exception ex) + else { - Console.WriteLine($"An error occurred while creating the project: {ex.Message}"); - model.id.AddMessage("Exiting the application."); - Environment.Exit(1); + id.AddMessage("Custom Templates group already exists."); } } -return 0; +void UpdateGroupwiseTemplates(JObject json, string template, string id) +{ + var groupwiseTemplates = json["GroupwiseTemplates"] as JArray ?? new JArray(json["GroupwiseTemplates"]); + var newCustomTemplate = JObject.Parse(template); + var groupwiseCustomTemplates = newCustomTemplate["GroupwiseTemplates"]; + + if (groupwiseTemplates != null && groupwiseCustomTemplates != null) + { + foreach (var group in groupwiseCustomTemplates) + { + var groupName = group["Groups"]?.ToString(); + var existingGroup = groupwiseTemplates.FirstOrDefault(g => g["Groups"]?.ToString().Equals(groupName, StringComparison.OrdinalIgnoreCase) == true); + + if (existingGroup != null) + { + MergeTemplates(existingGroup, group, id); + } + else + { + groupwiseTemplates.Add(group); + } + } + } +} + +void MergeTemplates(JToken existingGroup, JToken newGroup, string id) +{ + var existingTemplates = existingGroup["Template"] as JArray ?? new JArray(existingGroup["Template"]); + var newTemplates = newGroup["Template"] as JArray ?? new JArray(newGroup["Template"]); + + var existingTemplateNames = existingTemplates.Select(t => t["Name"]?.ToString()).ToList(); + var newTemplateNames = newTemplates.Select(t => t["Name"]?.ToString()).ToList(); + + var duplicateTemplates = newTemplateNames.Where(t => existingTemplateNames.Contains(t)).ToList(); + if (duplicateTemplates.Any()) + { + id.AddMessage($"Duplicate templates found: {string.Join(", ", duplicateTemplates)}. Skipping these templates."); + newTemplates = new JArray(newTemplates.Where(t => !duplicateTemplates.Contains(t["Name"]?.ToString()))); + } + else + { + id.AddMessage($"No duplicate templates found. Adding new templates."); + } + foreach (var templateCustom in newTemplates) + { + existingTemplates.Add(templateCustom); + } +} \ No newline at end of file diff --git a/src/ADOGenerator/ServiceExtensions.cs b/src/ADOGenerator/ServiceExtensions.cs index 64033ad..c64d97b 100644 --- a/src/ADOGenerator/ServiceExtensions.cs +++ b/src/ADOGenerator/ServiceExtensions.cs @@ -26,6 +26,11 @@ public static string ErrorId(this string str) str = str + "_Errors"; return str; } + public static string WarningId(this string str) + { + str = str + "_Warning"; + return str; + } public static void AddMessage(this string id, string message) { @@ -40,8 +45,7 @@ public static void AddMessage(this string id, string message) string fileName = $"{DateTime.Now.ToString("yyyy-MM-dd")}-{id}.txt"; if (id.EndsWith("_Errors")) { - // Create Errors Log Directory if not exists - if (!Directory.Exists(Path.Combine(logFilePath, "Errors"))) + if(!Directory.Exists(Path.Combine(logFilePath, "Errors"))) { Directory.CreateDirectory(Path.Combine(logFilePath, "Errors")); } @@ -63,7 +67,7 @@ public static void AddMessage(this string id, string message) } File.AppendAllLines(Path.Combine(logFilePath, fileName), new string[] { message }); // Create Log file - Console.ForegroundColor = ConsoleColor.Green; + Console.ForegroundColor = id.EndsWith("_Warning") ? ConsoleColor.Yellow : ConsoleColor.Green; Console.WriteLine(message); Console.ResetColor(); } diff --git a/src/ADOGenerator/Services/AuthService.cs b/src/ADOGenerator/Services/AuthService.cs index bccf493..0c0ae0b 100644 --- a/src/ADOGenerator/Services/AuthService.cs +++ b/src/ADOGenerator/Services/AuthService.cs @@ -69,17 +69,47 @@ public async Task SelectOrganization(string accessToken, JObject account { if (accountsJson["count"].Value() > 0) { - Console.WriteLine("Select an organization:"); + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine(Environment.NewLine + "Select an organization:"); + Console.ResetColor(); var accounts = accountsJson["value"]; + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine("+-----+-------------------------------+--------------------------------------+"); + Console.WriteLine("| No | Organization Name | Organization ID |"); + Console.WriteLine("+-----+-------------------------------+--------------------------------------+"); for (int i = 0; i < accounts.Count(); i++) { - Console.WriteLine($"{i + 1}. {accounts[i]["accountName"]} (ID: {accounts[i]["accountId"]})"); + string serialNo = (i + 1).ToString(); + string accountName = accounts[i]["accountName"].ToString(); + string accountId = accounts[i]["accountId"].ToString(); + + // Ensure proper wrapping and alignment + accountName = accountName.Length > 25 ? accountName.Substring(0, 22) + "..." : accountName.PadRight(25); + + Console.WriteLine($"| {serialNo.PadRight(3)} | {accountName} | {accountId} |"); + + // Wrap to the next line if content exceeds the table width + if (accounts[i]["accountName"].ToString().Length > 25 || accounts[i]["accountId"].ToString().Length > 15) + { + string wrappedAccountName = accounts[i]["accountName"].ToString().Length > 25 + ? accounts[i]["accountName"].ToString().Substring(25) + : string.Empty; + + if (!string.IsNullOrEmpty(wrappedAccountName)) + { + Console.WriteLine($"| | {wrappedAccountName.PadRight(25)} | |"); + } + } } + Console.WriteLine("+-----+-------------------------------+--------------------------------------+"); + Console.ResetColor(); int selectedIndex; do { - Console.Write("Enter the number of the organization: "); + Console.ForegroundColor = ConsoleColor.Green; + Console.Write(Environment.NewLine + "Enter the number of the organization: "); + Console.ResetColor(); } while (!int.TryParse(Console.ReadLine(), out selectedIndex) || selectedIndex < 1 || selectedIndex > accounts.Count()); var selectedAccountId = accounts[selectedIndex - 1]["accountId"].ToString(); diff --git a/src/ADOGenerator/Services/ExtractorService.cs b/src/ADOGenerator/Services/ExtractorService.cs new file mode 100644 index 0000000..6a431e8 --- /dev/null +++ b/src/ADOGenerator/Services/ExtractorService.cs @@ -0,0 +1,1660 @@ +using ADOGenerator.IServices; +using ADOGenerator.Models; +using log4net; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json; +using RestAPI.DeliveryPlans; +using RestAPI.ExtensionManagement; +using RestAPI.Extractor; +using RestAPI.ProjectsAndTeams; +using RestAPI.Viewmodel.Extractor; +using RestAPI.Viewmodel.GitHub; +using RestAPI.Viewmodel.Plans; +using RestAPI.Viewmodel.ProjectAndTeams; +using RestAPI; +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using static RestAPI.Viewmodel.Extractor.GetServiceEndpoints; +using static RestAPI.Viewmodel.Plans.DeliveryPlans; +using Configuration = RestAPI.ADOConfiguration; +using RestAPI.QueriesAndWidgets; +using Microsoft.Extensions.Configuration; +using static ADOGenerator.Models.TemplateSelection; +//using ADOProjectConfigurations = ADOGenerator.Models.ADOProjectConfigurations; + +namespace ADOGenerator.Services +{ + public class ExtractorService : IExtractorService + { + #region STATIC DECLARATIONS + private readonly IConfiguration _config; + public static ILog logger = LogManager.GetLogger("ErrorLog"); + public static readonly object objLock = new object(); + public static Dictionary statusMessages; + public List errorMessages = new List(); + public static string extractedTemplatePath = string.Empty; + private ProjectProperties.Properties projectProperties = new ProjectProperties.Properties(); + public static string currentPath = Directory.GetCurrentDirectory().Replace("bin\\Debug\\net8.0", "").Replace("bin\\Release\\net8.0", "").Replace("bin\\Debug", "").Replace("bin\\Release", ""); + + public ExtractorService(IConfiguration config) + { + _config = config; + } + #endregion STATIC DECLARATIONS + + #region ANALYSIS - GET COUNTS + public ProjectConfigurations ProjectConfiguration(Project model) + { + string repoVersion = _config["AppSettings:RepoVersion"]; + string buildVersion = _config["AppSettings:BuildVersion"]; + string releaseVersion = _config["AppSettings:ReleaseVersion"]; + string wikiVersion = _config["AppSettings:WikiVersion"]; + string boardVersion = _config["AppSettings:BoardVersion"]; + string workItemsVersion = _config["AppSettings:WorkItemsVersion"]; + string releaseHost = _config["AppSettings:ReleaseHost"]; + string defaultHost = _config["AppSettings:DefaultHost"]; + string extensionHost = _config["AppSettings:ExtensionHost"]; + string getReleaseVersion = _config["AppSettings:GetRelease"]; + string agentQueueVersion = _config["AppSettings:AgentQueueVersion"]; + string extensionVersion = _config["AppSettings:ExtensionVersion"]; + string endpointVersion = _config["AppSettings:EndPointVersion"]; + string queriesVersion = _config["AppSettings:QueriesVersion"]; + string variableGroupsApiVersion = _config["AppSettings:VariableGroupsApiVersion"]; + ProjectConfigurations projectConfig = new ProjectConfigurations(); + + projectConfig.AgentQueueConfig = new Configuration() { UriString = defaultHost + model.accountName + "/", PersonalAccessToken = model.accessToken, Project = model.ProjectName, AccountName = model.accountName, Id = model.id, VersionNumber = wikiVersion, _adoAuthScheme = model.adoAuthScheme }; + projectConfig.WorkItemConfig = new Configuration() { UriString = defaultHost + model.accountName + "/", PersonalAccessToken = model.accessToken, Project = model.ProjectName, AccountName = model.accountName, Id = model.id, VersionNumber = wikiVersion, _adoAuthScheme = model.adoAuthScheme }; + projectConfig.BuildDefinitionConfig = new Configuration() { UriString = defaultHost + model.accountName + "/", PersonalAccessToken = model.accessToken, Project = model.ProjectName, AccountName = model.accountName, Id = model.id, VersionNumber = buildVersion, _adoAuthScheme = model.adoAuthScheme }; + projectConfig.ReleaseDefinitionConfig = new Configuration() { UriString = releaseHost + model.accountName + "/", PersonalAccessToken = model.accessToken, Project = model.ProjectName, AccountName = model.accountName, Id = model.id, VersionNumber = releaseVersion, _adoAuthScheme = model.adoAuthScheme }; + projectConfig.RepoConfig = new Configuration() { UriString = defaultHost + model.accountName + "/", PersonalAccessToken = model.accessToken, Project = model.ProjectName, AccountName = model.accountName, Id = model.id, VersionNumber = repoVersion, _adoAuthScheme = model.adoAuthScheme }; + projectConfig.BoardConfig = new Configuration() { UriString = defaultHost + model.accountName + "/", PersonalAccessToken = model.accessToken, Project = model.ProjectName, AccountName = model.accountName, Id = model.id, VersionNumber = boardVersion, _adoAuthScheme = model.adoAuthScheme }; + projectConfig.Config = new Configuration() { UriString = defaultHost + model.accountName + "/", PersonalAccessToken = model.accessToken, Project = model.ProjectName, AccountName = model.accountName, Id = model.id, _adoAuthScheme = model.adoAuthScheme }; + projectConfig.GetReleaseConfig = new Configuration() { UriString = releaseHost + model.accountName + "/", PersonalAccessToken = model.accessToken, Project = model.ProjectName, AccountName = model.accountName, Id = model.id, VersionNumber = getReleaseVersion, _adoAuthScheme = model.adoAuthScheme }; + projectConfig.ExtensionConfig = new Configuration() { UriString = extensionHost + model.accountName + "/", PersonalAccessToken = model.accessToken, Project = model.ProjectName, AccountName = model.accountName, Id = model.id, VersionNumber = extensionVersion, _adoAuthScheme = model.adoAuthScheme }; + projectConfig.EndpointConfig = new Configuration() { UriString = defaultHost + model.accountName + "/", PersonalAccessToken = model.accessToken, Project = model.ProjectName, AccountName = model.accountName, Id = model.id, VersionNumber = endpointVersion, _adoAuthScheme = model.adoAuthScheme }; + projectConfig.QueriesConfig = new Configuration() { UriString = defaultHost + model.accountName + "/", PersonalAccessToken = model.accessToken, Project = model.ProjectName, AccountName = model.accountName, Id = model.id, VersionNumber = queriesVersion, _adoAuthScheme = model.adoAuthScheme }; + projectConfig.VariableGroupConfig = new Configuration() { UriString = defaultHost + model.accountName + "/", PersonalAccessToken = model.accessToken, Project = model.ProjectName, AccountName = model.accountName, Id = model.id, VersionNumber = variableGroupsApiVersion, _adoAuthScheme = model.adoAuthScheme }; + + return projectConfig; + } + public bool IsTemplateExists(string templateName) + { + string templatesDirectory = currentPath + @"Templates\"; + string templateSettingsPath = templatesDirectory + "TemplateSetting.json"; + if (!File.Exists(templateSettingsPath)) + { + return false; + } + string templateSettingsContent = File.ReadAllText(templateSettingsPath); + JObject templateSettings = JObject.Parse(templateSettingsContent); + // Check if the template name exists in the settings by looping the GroupwiseTemplates + bool templateExists = false; + string templateFolder = string.Empty; + foreach (var group in templateSettings["GroupwiseTemplates"]) + { + var templates = group["Template"]; + foreach (var template in templates) + { + if (template["Name"].ToString() == templateName) + { + templateExists = true; + templateFolder = template["TemplateFolder"].ToString(); + break; + } + } + if (templateExists) + { + break; + } + } + if(!templateExists) + { + return false; + } + string templatePath = templatesDirectory + templateFolder; + if (Directory.Exists(templatePath)) + { + return true; + } + else + { + return false; + } + } + public int GetTeamsCount(ProjectConfigurations appConfig) + { + RestAPI.Extractor.ClassificationNodes nodes = new RestAPI.Extractor.ClassificationNodes(appConfig.BoardConfig); + TeamList teamList = nodes.ExportTeamList(""); + int count = 0; + if (teamList.value != null) + { + count = teamList.value.Count; + } + return count; + } + public int GetIterationsCount(ProjectConfigurations appConfig) + { + RestAPI.Extractor.ClassificationNodes nodes = new RestAPI.Extractor.ClassificationNodes(appConfig.BoardConfig); + GetINumIteration.Iterations iterations = new GetINumIteration.Iterations(); + iterations = nodes.GetiterationCount(); + if (iterations.count > 0) + { + return iterations.count; + } + else + { + if (!(string.IsNullOrEmpty(nodes.LastFailureMessage))) + { + errorMessages.Add("Error while fetching iteration(s) count: " + nodes.LastFailureMessage); + } + return 0; + } + } + public int GetBuildDefinitionCount(ProjectConfigurations appConfig) + { + int BuildDefCount = 0; + BuildandReleaseDefs buildandReleaseDefs = new BuildandReleaseDefs(appConfig.BuildDefinitionConfig); + GetBuildDefResponse.BuildDef buildDef = new GetBuildDefResponse.BuildDef(); + buildDef = buildandReleaseDefs.GetBuildDefCount(); + if (buildDef.count > 0) + { + BuildDefCount = buildDef.count; + } + else if (!string.IsNullOrEmpty(buildandReleaseDefs.LastFailureMessage)) + { + errorMessages.Add("Error while fetching build definition count: " + buildandReleaseDefs.LastFailureMessage); + } + return BuildDefCount; + } + public int GetReleaseDefinitionCount(ProjectConfigurations appConfig) + { + int ReleaseDefCount = 0; + BuildandReleaseDefs buildandReleaseDefs = new BuildandReleaseDefs(appConfig.ReleaseDefinitionConfig); + GetReleaseDefResponse.ReleaseDef releaseDef = new GetReleaseDefResponse.ReleaseDef(); + releaseDef = buildandReleaseDefs.GetReleaseDefCount(); + if (releaseDef.count > 0) + { + ReleaseDefCount = releaseDef.count; + } + else if (!string.IsNullOrEmpty(buildandReleaseDefs.LastFailureMessage)) + { + errorMessages.Add("Error while fetching release definition count: " + buildandReleaseDefs.LastFailureMessage); + } + return ReleaseDefCount; + } + public Dictionary GetWorkItemsCount(ProjectConfigurations appConfig) + { + string[] workItemtypes = GetAllWorkItemsName(appConfig);//{ "Epic", "Feature", "Product Backlog Item", "Task", "Test Case", "Bug", "User Story", "Test Suite", "Test Plan", "Issue" }; + GetWorkItemsCount itemsCount = new GetWorkItemsCount(appConfig.WorkItemConfig); + Dictionary fetchedWorkItemsCount = new Dictionary(); + if (workItemtypes.Length > 0) + { + foreach (var workItem in workItemtypes) + { + itemsCount.LastFailureMessage = ""; + WorkItemFetchResponse.WorkItems WITCount = itemsCount.GetWorkItemsfromSource(workItem); + if (WITCount.count > 0) + { + fetchedWorkItemsCount.Add(workItem, WITCount.count); + } + else if (!string.IsNullOrEmpty(itemsCount.LastFailureMessage)) + { + errorMessages.Add(string.Format("Error while querying work item - {0}: {1}", workItem, itemsCount.LastFailureMessage)); + } + } + } + + return fetchedWorkItemsCount; + } + #endregion ANALYSIS - GET COUNTS + + #region GENERATE ARTIFACTS + public string[] GenerateTemplateArifacts(Project model) + { + model.id.AddMessage(Environment.NewLine+"Template Generation Started"); + extractedTemplatePath = currentPath + @"Templates\"; + string extractedFolderName = extractedTemplatePath + $"CT-{model.ProjectName.Replace(" ", "-")}"; + if (Directory.Exists(extractedFolderName)) + { + Directory.Delete(extractedFolderName, true); + } + ProjectConfigurations appConfig = ProjectConfiguration(model); + + GetInstalledExtensions(appConfig, extractedFolderName); + + ExportQuries(appConfig, extractedFolderName); + ExportTeams(appConfig.BoardConfig, model, extractedFolderName); + + if (ExportIterations(appConfig, extractedFolderName)) + { + model.id.AddMessage("Iterations Definition Exported"); + } + string filePathToRead = currentPath + @"\\PreSetting"; + + string projectSetting = ""; + projectSetting = filePathToRead + "\\ProjectSettings.json"; + projectSetting = File.ReadAllText(projectSetting); + projectSetting = projectSetting.Replace("$type$", model.ProcessTemplate).Replace("$id$", projectProperties.value.Where(x => x.name == "System.ProcessTemplateType").FirstOrDefault().value); + File.WriteAllText(extractedFolderName + "\\ProjectSettings.json", projectSetting); + + string projectTemplate = ""; + projectTemplate = filePathToRead + "\\ProjectTemplate.json"; + projectTemplate = File.ReadAllText(projectTemplate); + File.WriteAllText(extractedFolderName + "\\ProjectTemplate.json", projectTemplate); + + string teamArea = ""; + teamArea = filePathToRead + "\\TeamArea.json"; + teamArea = File.ReadAllText(teamArea); + File.WriteAllText(extractedFolderName + "\\TeamArea.json", teamArea); + model.id.AddMessage("Team Areas Exported"); + + if(ExportWorkItems(appConfig, extractedFolderName)) + model.id.AddMessage("Work Items Exported"); + + if(ExportDeliveryPlans(appConfig, extractedFolderName)) + model.id.AddMessage("Delivery Plans Exported"); + + if(ExportRepositoryList(appConfig, extractedFolderName)) + model.id.AddMessage("Repository Exported"); + + GetServiceEndpoints(appConfig, extractedFolderName); + int count = GetBuildDefinitions(appConfig, extractedFolderName); + if (count >= 1) + { + model.id.AddMessage("Build Definition Exported"); + } + + int relCount = GeneralizingGetReleaseDefinitions(appConfig, extractedFolderName); + if (relCount >= 1) + { + model.id.AddMessage("Release Definition Exported"); + } + + // Generate custom template JSON + var customTemplateJson = new + { + Groups = new[] + { + "Custom Templates" + }, + GroupwiseTemplates = new[] + { + new + { + Groups = "Custom Templates", + Template = new[] + { + new + { + Name = model.ProjectName, + TemplateFolder = $"CT-{model.ProjectName.Replace(" ", "-")}", + Description = $"This is a custom template for the project '{model.ProjectName}'.", + } + } + } + } + }; + return new string[] { model.id, JsonConvert.SerializeObject(customTemplateJson, Formatting.Indented) , extractedFolderName }; + } + public List GetInstalledExtensions(ProjectConfigurations appConfig,string extractedFolderName) + { + try + { + GetListExtenison listExtenison = new GetListExtenison(appConfig.ExtensionConfig); + List extensionList = new List(); + GetExtensions.ExtensionsList returnExtensionsList = listExtenison.GetInstalledExtensions(); + if (returnExtensionsList != null && returnExtensionsList.count > 0) + { + List builtInExtensions = returnExtensionsList.value.Where(x => x.flags == null).ToList(); + List trustedExtensions = returnExtensionsList.value.Where(x => x.flags != null && x.flags.ToString() == "trusted").ToList(); + builtInExtensions.AddRange(trustedExtensions); + returnExtensionsList.value = builtInExtensions; + + foreach (GetExtensions.Value data in returnExtensionsList.value) + { + RequiredExtensions.ExtensionWithLink extension = new RequiredExtensions.ExtensionWithLink(); + if (data.extensionName.ToLower() != "analytics") + { + extension.extensionId = data.extensionId; + extension.extensionName = data.extensionName; + extension.publisherId = data.publisherId; + extension.publisherName = data.publisherName; + extension.link = "" + data.extensionName + ""; + extension.License = "License Terms"; + extensionList.Add(extension); + } + } + RequiredExtensions.listExtension listExtension = new RequiredExtensions.listExtension(); + if (extensionList.Count > 0) + { + listExtension.Extensions = extensionList; + if (!Directory.Exists(extractedFolderName)) + { + Directory.CreateDirectory(extractedFolderName); + } + string fetchedJson = JsonConvert.SerializeObject(listExtension, Formatting.Indented); + + File.WriteAllText(extractedFolderName + "\\Extensions.json", JsonConvert.SerializeObject(listExtension, Formatting.Indented)); + appConfig.ExtensionConfig.Id.AddMessage("Extensions Exported"); + } + } + else if (!string.IsNullOrEmpty(listExtenison.LastFailureMessage)) + { + appConfig.ExtensionConfig.Id.ErrorId().AddMessage("Some error occured while fetching extensions"+Environment.NewLine+listExtenison.LastFailureMessage); + } + return extensionList; + } + catch (Exception ex) + { + appConfig.ExtensionConfig.Id.ErrorId().AddMessage(DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss") + "\t" + ex.Message + "\n" + ex.StackTrace + "\n"); + } + return new List(); + } + public void ExportQuries(ProjectConfigurations appConfig, string extractedFolderName) + { + try + { + Queries queries = new Queries(appConfig.QueriesConfig); + GetQueries.Queries listQueries = queries.GetQueriesWiql(); + if (listQueries.count > 0) + { + foreach (var _queries in listQueries.value) + { + if (_queries.hasChildren) + { + foreach (var query in _queries.children) + { + if (!query.hasChildren) + { + if (query.wiql != null) + { + query.wiql = query.wiql.Replace(appConfig.QueriesConfig.Project, "$projectId$"); + JObject jobj = new JObject(); + jobj["name"] = query.name; + jobj["wiql"] = query.wiql; + if (!Directory.Exists(extractedFolderName + "\\Dashboard\\Queries")) + { + Directory.CreateDirectory(extractedFolderName + "\\Dashboard"); + File.WriteAllText(extractedFolderName + "\\Dashboard\\Dashboard.json", JsonConvert.SerializeObject("text", Formatting.Indented)); + } + if (!Directory.Exists(extractedFolderName + "\\Dashboard\\Queries")) + { + Directory.CreateDirectory(extractedFolderName + "\\Dashboard\\Queries"); + File.WriteAllText(extractedFolderName + "\\Dashboard\\Queries\\" + query.name + ".json", JsonConvert.SerializeObject(jobj, Formatting.Indented)); + } + else + { + File.WriteAllText(extractedFolderName + "\\Dashboard\\Queries\\" + query.name + ".json", JsonConvert.SerializeObject(jobj, Formatting.Indented)); + } + } + } + else + { + foreach (var child1 in query.children) + { + if (child1.wiql != null) + { + child1.wiql = child1.wiql.Replace(appConfig.QueriesConfig.Project, "$projectId$"); + JObject jobj = new JObject(); + jobj["name"] = child1.name; + jobj["wiql"] = child1.wiql; + if (!Directory.Exists(extractedFolderName + "\\Dashboard\\Queries")) + { + Directory.CreateDirectory(extractedFolderName + "\\Dashboard\\Queries"); + + File.WriteAllText(extractedFolderName + "\\Dashboard\\Queries\\" + child1.name + ".json", JsonConvert.SerializeObject(jobj, Formatting.Indented)); + } + else + { + File.WriteAllText(extractedFolderName + "\\Dashboard\\Queries\\" + child1.name + ".json", JsonConvert.SerializeObject(jobj, Formatting.Indented)); + } + } + } + } + } + } + } + appConfig.QueriesConfig.Id.AddMessage("Queries Exported"); + } + else if (!string.IsNullOrEmpty(queries.LastFailureMessage)) + { + appConfig.QueriesConfig.Id.ErrorId().AddMessage("Error while fetching queries"); + } + } + catch (Exception ex) + { + appConfig.QueriesConfig.Id.ErrorId().AddMessage(DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss") + "\t" + ex.Message + "\n" + ex.StackTrace + "\n"); + } + + } + public bool ExportTeams(Configuration con, Project model,string extractedFolderName) + { + try + { + string defaultTeamID = string.Empty; + RestAPI.Extractor.ClassificationNodes nodes = new RestAPI.Extractor.ClassificationNodes(con); + TeamList _team = new TeamList(); + + string defaultHost = _config["AppSettings:DefaultHost"]; + string ProjectPropertyVersion = _config["AppSettings:ProjectPropertyVersion"]; + ADOConfiguration config = new ADOConfiguration() { AccountName = model.accountName, PersonalAccessToken = model.accessToken, UriString = defaultHost + model.accountName, VersionNumber = ProjectPropertyVersion, ProjectId = model.ProjectId, _adoAuthScheme = model.adoAuthScheme }; + + Projects projects = new Projects(config); + projectProperties = projects.GetProjectProperties(); + + if (projectProperties.count > 0) + { + defaultTeamID = projectProperties.value.Where(x => x.name == "System.Microsoft.TeamFoundation.Team.Default").FirstOrDefault().value; + } + _team = nodes.ExportTeamList(defaultTeamID); + if (_team.value != null) + { + con.Id.AddMessage("Teams"); + + string fetchedJson = JsonConvert.SerializeObject(_team.value, Formatting.Indented); + if (fetchedJson != "") + { + if (!Directory.Exists(extractedFolderName + "\\Teams")) + { + Directory.CreateDirectory(extractedFolderName + "\\Teams"); + } + File.WriteAllText(extractedFolderName + "\\Teams\\Teams.json", fetchedJson); + + List boardTypes = new List(); + boardTypes.Add("Epics"); + if (model.ProcessTemplate.ToLower() == "agile") + { + boardTypes.Add("Features"); + boardTypes.Add("Stories"); + } + else if (model.ProcessTemplate.ToLower() == "basic") + { + boardTypes.Add("Issues"); + } + else if (model.ProcessTemplate.ToLower() == "scrum") + { + boardTypes.Add("Features"); + boardTypes.Add("Backlog Items"); + } + + foreach (var team in _team.value) + { + List columnResponsesScrum = new List(); + List columnResponsesAgile = new List(); + List columnResponsesBasic = new List(); + List boardRows = new List(); + + ExportTeamSetting.Setting listTeamSetting = new ExportTeamSetting.Setting(); + + List jObjCardFieldList = new List(); + List jObjcardStyleList = new List(); + string teamFolderPath = extractedFolderName + "\\Teams\\" + team.name; + if (!Directory.Exists(teamFolderPath)) + { + Directory.CreateDirectory(teamFolderPath); + } + //Export Board Colums for each team + con.Team = team.name; + + ClassificationNodes teamNodes = new ClassificationNodes(con); + foreach (var boardType in boardTypes) + { + var response = teamNodes.ExportBoardColums(boardType); + if (response.IsSuccessStatusCode && response.StatusCode == System.Net.HttpStatusCode.OK) + { + if (model.ProcessTemplate.ToLower() == "scrum") + { + string res = response.Content.ReadAsStringAsync().Result; + BoardColumnResponseScrum.ColumnResponse scrumColumns = JsonConvert.DeserializeObject(res); + scrumColumns.BoardName = boardType; + columnResponsesScrum.Add(scrumColumns); + } + else if (model.ProcessTemplate.ToLower() == "agile") + { + string res = response.Content.ReadAsStringAsync().Result; + BoardColumnResponseAgile.ColumnResponse agileColumns = JsonConvert.DeserializeObject(res); + agileColumns.BoardName = boardType; + columnResponsesAgile.Add(agileColumns); + } + else if (model.ProcessTemplate.ToLower() == "basic") + { + string res = response.Content.ReadAsStringAsync().Result; + BoardColumnResponseBasic.ColumnResponse basicColumns = JsonConvert.DeserializeObject(res); + basicColumns.BoardName = boardType; + columnResponsesBasic.Add(basicColumns); + } + con.Id.AddMessage("Board Columns Exported for "+ boardType); + Thread.Sleep(2000); + } + else + { + var errorMessage = response.Content.ReadAsStringAsync(); + string error = RestAPI.Utility.GeterroMessage(errorMessage.Result.ToString()); + teamNodes.LastFailureMessage = error; + con.Id.ErrorId().AddMessage("Error occured while exporting Board Columns: " + teamNodes.LastFailureMessage); + } + + //Export board rows for each team + ExportBoardRows.Rows rows = teamNodes.ExportBoardRows(boardType); + if (rows.value != null && rows.value.Count > 0) + { + rows.BoardName = boardType; + boardRows.Add(rows); + con.Id.AddMessage("Board Rows Exported for "+boardType); + Thread.Sleep(2000); + } + else if (!string.IsNullOrEmpty(teamNodes.LastFailureMessage)) + { + con.Id.ErrorId().AddMessage("Error occured while exporting Board Rows: " + teamNodes.LastFailureMessage); + } + + + //Export Card Fields for each team + var cardFieldResponse = teamNodes.ExportCardFields(boardType); + if (cardFieldResponse.IsSuccessStatusCode && cardFieldResponse.StatusCode == System.Net.HttpStatusCode.OK) + { + string res = cardFieldResponse.Content.ReadAsStringAsync().Result; + JObject jObj = JsonConvert.DeserializeObject(res); + jObj["BoardName"] = boardType; + jObjCardFieldList.Add(jObj); + con.Id.AddMessage("Card fields Definition Exported for "+ boardType); + + } + else + { + var errorMessage = cardFieldResponse.Content.ReadAsStringAsync(); + string error = RestAPI.Utility.GeterroMessage(errorMessage.Result.ToString()); + teamNodes.LastFailureMessage = error; + con.Id.ErrorId().AddMessage("Error occured while exporting Card Fields: " + teamNodes.LastFailureMessage); + } + + // Export card styles for each team + var cardStyleResponse = teamNodes.ExportCardStyle(boardType); + if (cardStyleResponse.IsSuccessStatusCode && cardStyleResponse.StatusCode == System.Net.HttpStatusCode.OK) + { + string res = cardStyleResponse.Content.ReadAsStringAsync().Result; + res = res.Replace(con.Project, "$ProjectName$"); + JObject jObj = JsonConvert.DeserializeObject(res); + jObj["BoardName"] = boardType; + var style = jObj; + style["url"] = ""; + style["_links"] = "{}"; + var tagStyle = style["rules"]["tagStyle"]; + if (tagStyle == null) + { + style["rules"]["tagStyle"] = new JArray(); + } + jObjcardStyleList.Add(jObj); + con.Id.AddMessage("Card style exported for "+ boardType); + + } + else + { + var errorMessage = cardStyleResponse.Content.ReadAsStringAsync(); + string error = RestAPI.Utility.GeterroMessage(errorMessage.Result.ToString()); + teamNodes.LastFailureMessage = error; + con.Id.ErrorId().AddMessage("Error occured while exporting Card Styles: " + teamNodes.LastFailureMessage); + } + } + //Export Team Setting for each team + if (model.ProcessTemplate.ToLower() != "basic") + { + ExportTeamSetting.Setting teamSetting = teamNodes.ExportTeamSetting(); + if (teamSetting.backlogVisibilities != null) + { + listTeamSetting = teamSetting; + con.Id.AddMessage("Team Settings Definition Exported"); + } + } + else if (!string.IsNullOrEmpty(teamNodes.LastFailureMessage)) + { + con.Id.ErrorId().AddMessage("Error occured while exporting Team Setting: " + teamNodes.LastFailureMessage); + } + + if (columnResponsesAgile.Count > 0) + { + File.WriteAllText(teamFolderPath + "\\BoardColumns.json", JsonConvert.SerializeObject(columnResponsesAgile, Formatting.Indented, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore })); + } + if (columnResponsesScrum.Count > 0) + { + File.WriteAllText(teamFolderPath + "\\BoardColumns.json", JsonConvert.SerializeObject(columnResponsesScrum, Formatting.Indented, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore })); + } + if (columnResponsesBasic.Count > 0) + { + File.WriteAllText(teamFolderPath + "\\BoardColumns.json", JsonConvert.SerializeObject(columnResponsesBasic, Formatting.Indented, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore })); + } + if (boardRows.Count > 0) + { + File.WriteAllText(teamFolderPath + "\\BoardRows.json", JsonConvert.SerializeObject(boardRows, Formatting.Indented)); + } + if (!string.IsNullOrEmpty(listTeamSetting.bugsBehavior)) + { + File.WriteAllText(teamFolderPath + "\\TeamSetting.json", JsonConvert.SerializeObject(listTeamSetting, Formatting.Indented)); + } + if (jObjCardFieldList.Count > 0) + { + File.WriteAllText(teamFolderPath + "\\CardFields.json", JsonConvert.SerializeObject(jObjCardFieldList, Formatting.Indented)); + } + if (jObjcardStyleList.Count > 0) + { + File.WriteAllText(teamFolderPath + "\\CardStyles.json", JsonConvert.SerializeObject(jObjcardStyleList, Formatting.Indented)); + } + } + + return true; + } + else if (!string.IsNullOrEmpty(nodes.LastFailureMessage)) + { + con.Id.ErrorId().AddMessage(nodes.LastFailureMessage); + string error = nodes.LastFailureMessage; + return false; + } + else + { + return false; + } + } + else + { + con.Id.ErrorId().AddMessage(nodes.LastFailureMessage); + return false; + } + } + catch (Exception ex) + { + con.Id.ErrorId().AddMessage(DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss") + "\t" + ex.Message + "\n" + ex.StackTrace + "\n"); + } + return false; + } + public bool ExportIterations(ProjectConfigurations appConfig,string extractedFolderName) + { + try + { + ClassificationNodes nodes = new RestAPI.Extractor.ClassificationNodes(appConfig.BoardConfig); + ExportedIterations.Iterations viewModel = nodes.ExportIterationsToSave(); + string fetchedJson = JsonConvert.SerializeObject(viewModel, Formatting.Indented); + if (fetchedJson != "") + { + if (!Directory.Exists(extractedFolderName)) + { + Directory.CreateDirectory(extractedFolderName); + } + File.WriteAllText(extractedFolderName + "\\Iterations.json", fetchedJson); + return true; + } + else + { + string error = nodes.LastFailureMessage; + appConfig.BoardConfig.Id.ErrorId().AddMessage(error); + return false; + } + } + catch (Exception ex) + { + appConfig.BoardConfig.Id.ErrorId().AddMessage(DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss") + "\t" + ex.Message + "\n" + ex.StackTrace + "\n"); + } + return false; + } + public bool ExportWorkItems(ProjectConfigurations appConfig, string extractedFolderName) + { + bool isWorkItemExported = false; + try + { + string[] workItemtypes = GetAllWorkItemsName(appConfig);//{ "Epic", "Feature", "Product Backlog Item", "Task", "Test Case", "Bug", "User Story", "Test Suite", "Test Plan", "Issue" }; + if (!Directory.Exists(extractedFolderName)) + { + Directory.CreateDirectory(extractedFolderName); + } + + if (workItemtypes.Length > 0) + { + foreach (var WIT in workItemtypes) + { + GetWorkItemsCount WorkitemsCount = new GetWorkItemsCount(appConfig.WorkItemConfig); + WorkItemFetchResponse.WorkItems fetchedWorkItem = WorkitemsCount.GetWorkItemsfromSource(WIT); + string workItemJson = JsonConvert.SerializeObject(fetchedWorkItem, Formatting.Indented); + if (fetchedWorkItem.count > 0) + { + workItemJson = workItemJson.Replace(appConfig.WorkItemConfig.Project + "\\", "$ProjectName$\\"); + string item = WIT; + if (!Directory.Exists(extractedFolderName + "\\WorkItems")) + { + Directory.CreateDirectory(extractedFolderName + "\\WorkItems"); + } + File.WriteAllText(extractedFolderName + "\\WorkItems\\" + item + ".json", workItemJson); + } + else if (!string.IsNullOrEmpty(WorkitemsCount.LastFailureMessage)) + { + appConfig.WorkItemConfig.Id.ErrorId().AddMessage(WorkitemsCount.LastFailureMessage); + } + } + isWorkItemExported = true; + } + } + catch (Exception ex) + { + appConfig.WorkItemConfig.Id.ErrorId().AddMessage(DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss") + "\t" + ex.Message + "\n" + ex.StackTrace + "\n"); + } + return isWorkItemExported; + } + public bool ExportRepositoryList(ProjectConfigurations appConfig, string extractedFolderName) + { + bool isRepoExported = false; + try + { + BuildandReleaseDefs repolist = new BuildandReleaseDefs(appConfig.RepoConfig); + RepositoryList.Repository repos = repolist.GetRepoList(); + if (repos.count > 0) + { + foreach (var repo in repos.value) + { + string preSettingPath = currentPath + @"\PreSetting"; + string host = appConfig.RepoConfig.UriString + appConfig.RepoConfig.Project; + string sourceCodeJson = File.ReadAllText(preSettingPath + "\\ImportSourceCode.json"); + sourceCodeJson = sourceCodeJson.Replace("$Host$", host).Replace("$Repo$", repo.name); + string endPointJson = File.ReadAllText(preSettingPath + "\\ServiceEndPoint.json"); + endPointJson = endPointJson.Replace("$Host$", host).Replace("$Repo$", repo.name); + if (!Directory.Exists(extractedFolderName + "\\ImportSourceCode")) + { + Directory.CreateDirectory(extractedFolderName + "\\ImportSourceCode"); + File.WriteAllText(extractedFolderName + "\\ImportSourceCode\\" + repo.name + ".json", sourceCodeJson); + } + else + { + File.WriteAllText(extractedFolderName + "\\ImportSourceCode\\" + repo.name + ".json", sourceCodeJson); + } + if (!Directory.Exists(extractedFolderName + "\\ServiceEndpoints")) + { + Directory.CreateDirectory(extractedFolderName + "\\ServiceEndpoints"); + File.WriteAllText(extractedFolderName + "\\ServiceEndpoints\\" + repo.name + "-code.json", endPointJson); + } + else + { + File.WriteAllText(extractedFolderName + "\\ServiceEndpoints\\" + repo.name + "-code.json", endPointJson); + } + } + isRepoExported = true; + } + } + catch (Exception ex) + { + appConfig.RepoConfig.Id.ErrorId().AddMessage(DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss") + "\t" + ex.Message + "\n" + ex.StackTrace + "\n"); + } + return isRepoExported; + } + /// + /// Get the Build definitions to write into file + /// + /// + /// + public int GetBuildDefinitions(ProjectConfigurations appConfig, string extractedFolderName) + { + try + { + BuildandReleaseDefs buildandReleaseDefs = new BuildandReleaseDefs(appConfig.BuildDefinitionConfig); + List builds = buildandReleaseDefs.ExportBuildDefinitions(); + BuildandReleaseDefs repoDefs = new BuildandReleaseDefs(appConfig.RepoConfig); + Dictionary variableGroupNameId = GetVariableGroups(appConfig, extractedFolderName); + RepositoryList.Repository repo = repoDefs.GetRepoList(); + if (builds.Count > 0) + { + int count = 1; + //creating ImportCode Json file + foreach (JObject def in builds) + { + string repoID = ""; + var buildName = def["name"]; + string fileName = buildName.ToString().Replace(".", "") + ".json"; + var repoName = def["repository"]["name"]; + var type = def["repository"]["type"]; + foreach (var re in repo.value) + { + if (re.name == repoName.ToString()) + { + repoID = re.id; + } + } + def["authoredBy"] = "{}"; + def["project"] = "{}"; + def["url"] = ""; + def["uri"] = ""; + def["id"] = ""; + if (def["queue"]["pool"].HasValues) + { + def["queue"]["pool"]["id"] = ""; + } + def["_links"] = "{}"; + def["createdDate"] = ""; + if (def["variableGroups"] != null) + { + var variableGroup = def["variableGroups"].HasValues ? def["variableGroups"].ToArray() : new JToken[0]; + if (variableGroup.Length > 0) + { + foreach (var groupId in variableGroup) + { + groupId["id"] = "$" + variableGroupNameId.Where(x => x.Key == groupId["id"].ToString()).FirstOrDefault().Value + "$"; + } + } + } + var yamalfilename = def["process"]["yamlFilename"]; + + #region YML PIPELINES OF TYPE AZURE REPOS + if (yamalfilename != null && type.ToString().ToLower() == "tfsgit") + { + count = YmlWithAzureRepos(appConfig, count, extractedFolderName, def, fileName, type); + } + #endregion + + #region YML PIPELINE WITH GITHUB + else if (yamalfilename != null && type.ToString().ToLower() == "github") + { + count = YmlWithGitHub(appConfig, count, extractedFolderName, def, fileName, type); + } + #endregion + + #region OTHER + else if (yamalfilename == null) + { + count = NormalPipeline(appConfig, count, extractedFolderName, def, fileName, repoName, type); + } + #endregion + } + return count; + } + } + catch (Exception ex) + { + logger.Info(DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss") + "\t" + ex.Message + "\n" + ex.StackTrace + "\n"); + } + return 0; + } + /// + /// Normal Build pipeline, which could be either pointing from Azure Repos or GitHub + /// + /// + /// + /// + /// + /// + /// + /// + /// + private static int NormalPipeline(ProjectConfigurations appConfig, int count, string templatePath, JObject def, string fileName, JToken repoName, JToken type) + { + try + { + def["queue"]["id"] = ""; + def["queue"]["url"] = ""; + def["queue"]["_links"] = "{}"; + def["queue"]["pool"]["id"] = ""; + def["_links"] = "{}"; + def["createdDate"] = ""; + + var process = def["process"]; + if (process != null) + { + var phases = process["phases"]; + if (phases != null) + { + foreach (var phase in phases) + { + phase["target"]["queue"] = "{}"; + var steps = phase["steps"]; + if (steps != null) + { + foreach (var step in steps) + { + string keyConfig = File.ReadAllText(currentPath + @"\\Templates\EndpointKeyConfig.json"); + KeyConfig.Keys keyC = new KeyConfig.Keys(); + keyC = JsonConvert.DeserializeObject(keyConfig); + foreach (var key in keyC.keys) + { + string keyVal = step[key] != null ? step[key].ToString() : ""; + if (!string.IsNullOrEmpty(keyVal)) + { + step[key] = ""; + } + } + foreach (var key in keyC.keys) + { + string keyVal = step["inputs"][key] != null ? step["inputs"][key].ToString() : ""; + if (!string.IsNullOrEmpty(keyVal)) + { + step["inputs"][key] = ""; + } + } + } + } + } + } + } + + if (type.ToString().ToLower() == "github") + { + + Guid g = Guid.NewGuid(); + string randStr = g.ToString().Substring(0, 8); + def["repository"]["type"] = "Git"; + def["repository"]["properties"]["fullName"] = "repository"; + def["repository"]["properties"]["connectedServiceId"] = "$GitHub_" + randStr + "$"; + def["repository"]["name"] = "repository"; + string url = def["repository"]["url"].ToString(); + if (url != "") + { + string endPointString = File.ReadAllText(currentPath + @"PreSetting\\GitHubEndPoint.json"); + endPointString = endPointString.Replace("$GitHubURL$", url).Replace("$Name$", "GitHub_" + randStr); + + if (!Directory.Exists(extractedTemplatePath + appConfig.RepoConfig.Project + "\\ServiceEndpoints")) + { + Directory.CreateDirectory(extractedTemplatePath + appConfig.RepoConfig.Project + "\\ServiceEndpoints"); + File.WriteAllText(extractedTemplatePath + appConfig.RepoConfig.Project + "\\ServiceEndpoints\\GitHub" + randStr + "-EndPoint.json", endPointString); + } + else + { + File.WriteAllText(extractedTemplatePath + appConfig.RepoConfig.Project + "\\ServiceEndpoints\\GitHub" + randStr + "-EndPoint.json", endPointString); + } + } + } + else if (type.ToString().ToLower() == "git") + { + Guid g = Guid.NewGuid(); + string randStr = g.ToString().Substring(0, 8); + string url = def["repository"]["url"].ToString(); + string endPointString = File.ReadAllText(currentPath + @"PreSetting\\GitHubEndPoint.json"); + endPointString = endPointString.Replace("$GitHubURL$", url).Replace("$Name$", "GitHub_" + randStr); + + if (!Directory.Exists(extractedTemplatePath + appConfig.RepoConfig.Project + "\\ServiceEndpoints")) + { + Directory.CreateDirectory(extractedTemplatePath + appConfig.RepoConfig.Project + "\\ServiceEndpoints"); + File.WriteAllText(extractedTemplatePath + appConfig.RepoConfig.Project + "\\ServiceEndpoints\\GitHub_" + randStr + "-EndPoint.json", endPointString); + } + else + { + File.WriteAllText(extractedTemplatePath + appConfig.RepoConfig.Project + "\\ServiceEndpoints\\GitHub_" + randStr + "-EndPoint.json", endPointString); + } + def["repository"]["properties"]["connectedServiceId"] = "$GitHub_" + randStr + "$"; + } + else + { + def["repository"]["id"] = "$" + repoName + "$"; + def["repository"]["url"] = ""; + def["repository"]["properties"]["connectedServiceId"] = ""; + } + var input = def["processParameters"]["inputs"]; + if (input != null) + { + if (input.HasValues) + { + foreach (var i in input) + { + i["defaultValue"] = ""; + + } + } + } + var build = def["build"]; + if (build != null) + { + if (build.HasValues) + { + foreach (var b in build) + { + b["inputs"]["serverEndpoint"] = ""; + } + } + } + count++; + if (!Directory.Exists(templatePath + "\\BuildDefinitions")) + { + Directory.CreateDirectory(templatePath + "\\BuildDefinitions"); + File.WriteAllText(templatePath + "\\BuildDefinitions\\" + fileName, JsonConvert.SerializeObject(def, Formatting.Indented)); + } + else + { + File.WriteAllText(templatePath + "\\BuildDefinitions\\" + fileName, JsonConvert.SerializeObject(def, Formatting.Indented)); + } + + return count; + } + catch (Exception ex) + { + logger.Info(DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss") + "Exporting normalPipeline \t" + ex.Message + "\n" + ex.StackTrace + "\n"); + } + return count; + } + /// + /// YAML pipeline which is pointing to GitHub + /// + /// + /// + /// + /// + /// + /// + /// + private static int YmlWithGitHub(ProjectConfigurations appConfig, int count, string templatePath, JObject def, string fileName, JToken type) + { + try + { + Guid g = Guid.NewGuid(); + string randStr = g.ToString().Substring(0, 8); + var ymlRepoUrl = def["repository"]["url"].ToString(); + if (!Directory.Exists(templatePath + "\\ImportSourceCode")) + { + Directory.CreateDirectory(templatePath + "\\ImportSourceCode"); + } + if (type.ToString().ToLower() == "github") + { + string gitHubRepo = def["repository"]["id"].ToString(); + string[] gitHubIdSplit = gitHubRepo.Split('/'); + gitHubIdSplit[0] = "$username$"; + gitHubRepo = string.Join("/", gitHubIdSplit); + + ForkRepos.Fork gitHubRepoList = new ForkRepos.Fork(); + gitHubRepoList.repositories = new List(); + if (File.Exists(templatePath + "\\ImportSourceCode\\GitRepository.json")) + { + string readrepo = File.ReadAllText(templatePath + "\\ImportSourceCode\\GitRepository.json"); + gitHubRepoList = JsonConvert.DeserializeObject(readrepo); + } + ForkRepos.Repository repoName = new ForkRepos.Repository + { + fullName = def["repository"]["id"].ToString(), + endPointName = "GitHub_" + randStr + }; + gitHubRepoList.repositories.Add(repoName); + + File.WriteAllText(templatePath + "\\ImportSourceCode\\GitRepository.json", JsonConvert.SerializeObject(gitHubRepoList, Formatting.Indented)); + + def["repository"]["properties"]["apiUrl"] = "https://api.github.com/repos/" + gitHubRepo; + def["repository"]["properties"]["branchesUrl"] = "https://api.github.com/repos/" + gitHubRepo + "/branches"; + def["repository"]["properties"]["cloneUrl"] = "https://github.com/" + gitHubRepo + ".git"; + def["repository"]["properties"]["fullName"] = "repository"; + def["repository"]["properties"]["manageUrl"] = "https://github.com/" + gitHubRepo; + def["repository"]["properties"]["connectedServiceId"] = "$GitHub_" + randStr + "$"; + def["repository"]["name"] = gitHubRepo; + def["repository"]["url"] = "https://github.com/" + gitHubRepo + ".git"; + def["repository"]["id"] = gitHubRepo; + } + if (ymlRepoUrl != "") + { + string endPointString = File.ReadAllText(currentPath + @"PreSetting\\GitHubEndPoint.json"); + endPointString = endPointString.Replace("$GitHubURL$", ymlRepoUrl).Replace("$Name$", "GitHub_" + randStr); + + if (!Directory.Exists(templatePath + "\\ServiceEndpoints")) + { + Directory.CreateDirectory(templatePath + "\\ServiceEndpoints"); + File.WriteAllText(templatePath + "\\ServiceEndpoints\\GitHub_" + randStr + "-EndPoint.json", endPointString); + } + else + { + File.WriteAllText(templatePath + "\\ServiceEndpoints\\GitHub_" + randStr + "-EndPoint.json", endPointString); + } + } + count = count + 1; + if (!Directory.Exists(templatePath + "\\BuildDefinitionGitHub")) + { + Directory.CreateDirectory(templatePath + "\\BuildDefinitionGitHub"); + File.WriteAllText(templatePath + "\\BuildDefinitionGitHub\\" + fileName, JsonConvert.SerializeObject(def, Formatting.Indented)); + } + else + { + File.WriteAllText(templatePath + "\\BuildDefinitionGitHub\\" + fileName, JsonConvert.SerializeObject(def, Formatting.Indented)); + } + + return count; + } + catch (Exception ex) + { + logger.Info(DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss") + "Exporting ymlWithGitHub \t" + ex.Message + "\n" + ex.StackTrace + "\n"); + } + return count; + } + /// + /// YAML pipeline which is pointing to Azure Repos + /// + /// + /// + /// + /// + /// + /// + /// + private static int YmlWithAzureRepos(ProjectConfigurations appConfig, int count, string templatePath, JObject def, string fileName, JToken type) + { + try + { + Guid g = Guid.NewGuid(); + string randStr = g.ToString().Substring(0, 8); + // def["triggers"] = new JArray(); + if (type.ToString().ToLower() == "github") + { + def["repository"]["properties"]["fullName"] = "repository"; + def["repository"]["properties"]["connectedServiceId"] = "$GitHub_" + randStr + "$"; + def["repository"]["name"] = "repository"; + } + var ymlRepoUrl = def["repository"]["url"].ToString(); + if (ymlRepoUrl != "") + { + string endPointString = File.ReadAllText(currentPath + @"PreSetting\\GitHubEndPoint.json"); + endPointString = endPointString.Replace("$GitHubURL$", ymlRepoUrl).Replace("$Name$", "GitHub_" + randStr); + if (!Directory.Exists(templatePath + "\\ServiceEndpoints")) + { + Directory.CreateDirectory(templatePath + "\\ServiceEndpoints"); + File.WriteAllText(templatePath + "\\ServiceEndpoints\\GitHub-" + randStr + "-EndPoint.json", endPointString); + } + else + { + File.WriteAllText(templatePath + "\\ServiceEndpoints\\GitHub-" + randStr + "-EndPoint.json", endPointString); + } + } + string[] splitYmlRepoUrl = ymlRepoUrl.Split('/'); + if (splitYmlRepoUrl.Length > 0) + { + splitYmlRepoUrl[2] = "$Organization$@dev.azure.com"; + splitYmlRepoUrl[3] = "$Organization$"; + splitYmlRepoUrl[4] = "$ProjectName$"; + ymlRepoUrl = string.Join("/", splitYmlRepoUrl); + def["repository"]["url"] = ymlRepoUrl; + def["repository"]["properties"]["cloneUrl"] = ymlRepoUrl; + } + def["repository"]["properties"]["safeRepository"] = string.Format("${0}$", def["repository"]["name"].ToString()); + def["repository"]["id"] = string.Format("${0}$", def["repository"]["name"].ToString()); + var queueHref = def["queue"]["_links"]["self"]["href"].ToString(); + if (queueHref != "") + { + string[] splitQhref = queueHref.Split('/'); + if (splitQhref.Length > 0) + { + splitQhref[3] = "$Organization$"; + splitQhref[splitQhref.Length - 1] = "$" + def["queue"]["name"].ToString() + "$"; + def["queue"]["_links"]["self"]["href"] = string.Join("/", splitQhref); + } + def["queue"]["id"] = "$" + def["queue"]["name"] + "$"; + def["queue"]["url"] = string.Join("/", splitQhref); + } + count = count + 1; + if (!Directory.Exists(templatePath + "\\BuildDefinitions")) + { + Directory.CreateDirectory(templatePath + "\\BuildDefinitions"); + File.WriteAllText(templatePath + "\\BuildDefinitions\\" + fileName, JsonConvert.SerializeObject(def, Formatting.Indented)); + } + else + { + File.WriteAllText(templatePath + "\\BuildDefinitions\\" + fileName, JsonConvert.SerializeObject(def, Formatting.Indented)); + } + + return count; + } + catch (Exception ex) + { + logger.Info(DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss") + "Exporting ymlWithAzureRepos \t" + ex.Message + "\n" + ex.StackTrace + "\n"); + } + return count; + } + /// + /// Generalizing the release definition method to make it work for All kind of Release definition + /// + /// + /// + public int GeneralizingGetReleaseDefinitions(ProjectConfigurations appConfig, string extractedFolderName) + { + try + { + BuildandReleaseDefs releaseDefs = new BuildandReleaseDefs(appConfig.ReleaseDefinitionConfig); + List releases = releaseDefs.GetReleaseDefs(); + string rells = JsonConvert.SerializeObject(releases); + BuildandReleaseDefs agent = new BuildandReleaseDefs(appConfig.AgentQueueConfig); + Dictionary variableGroupNameId = GetVariableGroups(appConfig, extractedFolderName); + Dictionary queue = agent.GetQueues(); + int releasecount = 1; + if (releases.Count > 0) + { + foreach (JObject rel in releases) + { + var name = rel["name"]; + rel["id"] = ""; + rel["url"] = ""; + rel["_links"] = "{}"; + rel["createdBy"] = "{}"; + rel["createdOn"] = ""; + rel["modifiedBy"] = "{}"; + rel["modifiedOn"] = ""; + + var variableGroup = rel["variableGroups"].HasValues ? rel["variableGroups"].ToArray() : new JToken[0]; + if (variableGroup.Length > 0) + { + foreach (var groupId in variableGroup) + { + rel["variableGroups"] = new JArray("$" + variableGroupNameId.Where(x => x.Key == groupId.ToString()).FirstOrDefault().Value + "$"); + } + } + else + { + rel["variableGroups"] = new JArray(); + } + var env = rel["environments"]; + foreach (var e in env) + { + e["badgeUrl"] = ""; + var envVariableGroup = e["variableGroups"].HasValues ? e["variableGroups"].ToArray() : new JToken[0]; + if (envVariableGroup.Length > 0) + { + foreach (var envgroupId in envVariableGroup) + { + e["variableGroups"] = new JArray("$" + variableGroupNameId.Where(x => x.Key == envgroupId.ToString()).FirstOrDefault().Value + "$"); + } + } + else + { + e["variableGroups"] = new JArray(); + } + var owner = e["owner"]; + owner["id"] = "$OwnerId$"; + owner["displayName"] = "$OwnerDisplayName$"; + owner["uniqueName"] = "$OwnerUniqueName$"; + owner["url"] = ""; + owner["_links"] = "{}"; + owner["imageUrl"] = ""; + owner["descriptor"] = ""; + + var deployPhases = e["deployPhases"]; + if (deployPhases.HasValues) + { + foreach (var dep in deployPhases) + { + + var deploymentInput = dep["deploymentInput"]; + var queueID = deploymentInput["queueId"]; + string queueName = ""; + if (queue != null) + { + if (queue.Count > 0) + { + var q = queue.ContainsValue(Convert.ToInt32(queueID)); + if (q) + { + var agenetName = queue.Where(x => x.Value.ToString() == queueID.ToString()).FirstOrDefault(); + if (agenetName.Key != null) + { + queueName = agenetName.Key.ToString(); + } + else + { + queueName = ""; + } + } + } + } + if (queueName != "") + { + deploymentInput["queueId"] = "$" + queueName + "$"; + } + else + { + deploymentInput["queueId"] = ""; + } + + var workflow = dep["workflowTasks"]; + if (workflow.HasValues) + { + foreach (var flow in workflow) + { + var input = flow["inputs"]; + string keyConfig = File.ReadAllText(currentPath + @"\\Templates\EndpointKeyConfig.json"); + KeyConfig.Keys keyC = new KeyConfig.Keys(); + keyC = JsonConvert.DeserializeObject(keyConfig); + foreach (var key in keyC.keys) + { + string keyVal = input[key] != null ? input[key].ToString() : ""; + if (!string.IsNullOrEmpty(keyVal)) + { + input[key] = ""; + } + } + } + } + } + } + } + + var artifact = rel["artifacts"]; + if (artifact.HasValues) + { + foreach (var art in artifact) + { + string buildName = art["definitionReference"]["definition"]["name"].ToString(); + string type = art["type"].ToString(); + if (type.ToLower() == "build") + { + art["sourceId"] = "$ProjectId$:" + "$" + buildName + "-id$"; + art["definitionReference"]["definition"]["id"] = "$" + buildName + "-id$"; + art["definitionReference"]["project"]["id"] = "$ProjectId$"; + art["definitionReference"]["project"]["name"] = "$ProjectName$"; + art["definitionReference"]["artifactSourceDefinitionUrl"] = "{}"; + } + if (type.ToLower() == "azurecontainerrepository") + { + art["sourceId"] = "$ProjectId$:" + "$" + buildName + "-id$"; + art["definitionReference"]["connection"]["id"] = ""; + art["definitionReference"]["definition"]["id"] = ""; + art["definitionReference"]["definition"]["name"] = ""; + art["definitionReference"]["registryurl"]["id"] = ""; + art["definitionReference"]["registryurl"]["name"] = ""; + art["definitionReference"]["resourcegroup"]["id"] = ""; + art["definitionReference"]["resourcegroup"]["name"] = ""; + } + } + } + if (!(Directory.Exists(extractedFolderName + "\\ReleaseDefinitions"))) + { + Directory.CreateDirectory(extractedFolderName + "\\ReleaseDefinitions"); + File.WriteAllText(extractedFolderName + "\\ReleaseDefinitions\\" + name + ".json", JsonConvert.SerializeObject(rel, Formatting.Indented)); + } + else + { + File.WriteAllText(extractedFolderName + "\\ReleaseDefinitions\\" + name + ".json", JsonConvert.SerializeObject(rel, Formatting.Indented)); + } + releasecount++; + } + } + return releasecount; + } + catch (Exception ex) + { + logger.Info(DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss") + "\t" + ex.Message + "\n" + ex.StackTrace + "\n"); + appConfig.ReleaseDefinitionConfig.Id.ErrorId().AddMessage(ex.Message + Environment.NewLine + ex.StackTrace); + } + return 0; + } + /// + /// Get different kinds of service endpoints and format it into POST json format + /// + /// + public void GetServiceEndpoints(ProjectConfigurations appConfig, string extractedFolderName) + { + try + { + RestAPI.Service.ServiceEndPoint serviceEndPoint = new RestAPI.Service.ServiceEndPoint(appConfig.EndpointConfig); + ServiceEndPoint getServiceEndPoint = serviceEndPoint.GetServiceEndPoints(); + if (getServiceEndPoint.count > 0) + { + foreach (RestAPI.Viewmodel.Extractor.GetServiceEndpoints.Value endpoint in getServiceEndPoint.value) + { + switch (endpoint.authorization.scheme) + { + case "OAuth": + case "InstallationToken": + switch (endpoint.type) + { + case "github": + case "GitHub": + if (endpoint.authorization.parameters == null) + { + endpoint.authorization.parameters = new RestAPI.Viewmodel.Extractor.GetServiceEndpoints.Parameters + { + AccessToken = "AccessToken" + }; + } + else + { + endpoint.authorization.parameters.AccessToken = endpoint.authorization.parameters.AccessToken ?? "AccessToken"; + } + break; + } + break; + case "UsernamePassword": + endpoint.authorization.parameters.username = endpoint.authorization.parameters.username ?? "username"; + endpoint.authorization.parameters.password = endpoint.authorization.parameters.password ?? "password"; + break; + case "ManagedServiceIdentity": + if (endpoint.authorization.parameters == null) + { + endpoint.authorization.parameters = new RestAPI.Viewmodel.Extractor.GetServiceEndpoints.Parameters + { + tenantId = Guid.NewGuid().ToString() + }; + } + else + { + endpoint.authorization.parameters.tenantId = endpoint.authorization.parameters.tenantId ?? Guid.NewGuid().ToString(); + } + break; + case "ServicePrincipal": + switch (endpoint.type) + { + case "devCenter": + endpoint.authorization.parameters.servicePrincipalKey = endpoint.authorization.parameters.servicePrincipalKey ?? "P2ssw0rd@123"; + break; + case "azurerm": + endpoint.authorization.parameters.url = null; + endpoint.authorization.parameters.servicePrincipalId = endpoint.authorization.parameters.servicePrincipalId ?? Guid.NewGuid().ToString(); + endpoint.authorization.parameters.authenticationType = endpoint.authorization.parameters.authenticationType ?? "spnKey"; + endpoint.authorization.parameters.tenantId = endpoint.authorization.parameters.tenantId ?? Guid.NewGuid().ToString(); + endpoint.authorization.parameters.servicePrincipalKey = endpoint.authorization.parameters.servicePrincipalKey ?? "spnKey"; + switch (endpoint.data.scopeLevel) + { + case "ManagementGroup": + endpoint.data.managementGroupId = endpoint.data.managementGroupId ?? "managedgroup"; + endpoint.data.managementGroupName = endpoint.data.managementGroupName ?? "groupname"; + break; + } + break; + } + break; + case "Certificate": + switch (endpoint.type) + { + case "dockerhost": + if (endpoint.authorization.parameters == null) + { + endpoint.authorization.parameters = new RestAPI.Viewmodel.Extractor.GetServiceEndpoints.Parameters(); + endpoint.authorization.parameters.cacert = endpoint.authorization.parameters.cacert ?? "cacert"; + endpoint.authorization.parameters.cert = endpoint.authorization.parameters.cert ?? "cert"; + endpoint.authorization.parameters.key = endpoint.authorization.parameters.key ?? "key"; + } + else + { + endpoint.authorization.parameters.cacert = endpoint.authorization.parameters.cacert ?? "cacert"; + endpoint.authorization.parameters.cert = endpoint.authorization.parameters.cert ?? "cert"; + endpoint.authorization.parameters.key = endpoint.authorization.parameters.key ?? "key"; + } + break; + + case "azure": + if (endpoint.authorization.parameters == null) + { + endpoint.authorization.parameters = new RestAPI.Viewmodel.Extractor.GetServiceEndpoints.Parameters + { + certificate = "certificate" + }; + } + else + { + endpoint.authorization.parameters.certificate = endpoint.authorization.parameters.certificate ?? "certificate"; + } + break; + } + break; + case "Token": + if (endpoint.authorization.parameters == null) + { + endpoint.authorization.parameters = new RestAPI.Viewmodel.Extractor.GetServiceEndpoints.Parameters + { + apitoken = "apitoken" + }; + } + else + { + endpoint.authorization.parameters.apitoken = endpoint.authorization.parameters.apitoken ?? "apitoken"; + } + break; + case "None": + switch (endpoint.type) + { + case "AzureServiceBus": + if (endpoint.authorization.parameters == null) + { + endpoint.authorization.parameters = new RestAPI.Viewmodel.Extractor.GetServiceEndpoints.Parameters + { + serviceBusConnectionString = "connectionstring" + }; + } + else + { + endpoint.authorization.parameters.serviceBusConnectionString = endpoint.authorization.parameters.serviceBusConnectionString ?? "connectionstring"; + } + break; + case "externalnugetfeed": + if (endpoint.authorization.parameters == null) + { + endpoint.authorization.parameters = new RestAPI.Viewmodel.Extractor.GetServiceEndpoints.Parameters + { + nugetkey = "nugetkey" + }; + } + else + { + endpoint.authorization.parameters.nugetkey = endpoint.authorization.parameters.nugetkey ?? "nugetkey"; + } + break; + } + break; + + } + string endpointString = JsonConvert.SerializeObject(endpoint); + if (!Directory.Exists(extractedFolderName + "\\ServiceEndpoints")) + { + Directory.CreateDirectory(extractedFolderName + "\\ServiceEndpoints"); + File.WriteAllText(extractedFolderName + "\\ServiceEndpoints\\", JsonConvert.SerializeObject(endpoint, Formatting.Indented, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore })); + } + else + { + File.WriteAllText(extractedFolderName + "\\ServiceEndpoints\\" + endpoint.name + ".json", JsonConvert.SerializeObject(endpoint, Formatting.Indented, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore })); + } + } + appConfig.EndpointConfig.Id.AddMessage("Service endpoints exported"); + } + else if (!string.IsNullOrEmpty(serviceEndPoint.LastFailureMessage)) + { + appConfig.EndpointConfig.Id.ErrorId().AddMessage("Error occured while fetching service endpoints"); + } + } + catch (Exception ex) + { + appConfig.EndpointConfig.Id.ErrorId().AddMessage(DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss") + "\t" + ex.Message + "\n" + ex.StackTrace + "\n"); + } + } + /// + /// Get All work item names + /// + /// + /// + private string[] GetAllWorkItemsName(ProjectConfigurations appConfig) + { + GetWorkItemsCount getWorkItems = new GetWorkItemsCount(appConfig.WorkItemConfig); + WorkItemNames.Names workItems = getWorkItems.GetAllWorkItemNames(); + List workItemNames = new List(); + if (workItems.count > 0) + { + foreach (var workItem in workItems.value) + { + workItemNames.Add(workItem.name); + } + } + return workItemNames.ToArray(); + } + private Dictionary GetVariableGroups(ProjectConfigurations appConfig, string extractedFolderName) + { + VariableGroups variableGroups = new VariableGroups(appConfig.VariableGroupConfig); + GetVariableGroups.Groups groups = variableGroups.GetVariableGroups(); + Dictionary varibaleGroupDictionary = new Dictionary(); + if (groups.count > 0) + { + if (!(Directory.Exists(extractedFolderName + "\\VariableGroups"))) + { + Directory.CreateDirectory(extractedFolderName + "\\VariableGroups"); + File.WriteAllText(extractedFolderName + "\\VariableGroups\\VariableGroup.json", JsonConvert.SerializeObject(groups, Formatting.Indented)); + } + else + { + File.WriteAllText(extractedFolderName + "\\VariableGroups\\VariableGroup.json", JsonConvert.SerializeObject(groups, Formatting.Indented)); + } + foreach (var vg in groups.value) + { + if (!varibaleGroupDictionary.ContainsKey(vg.id)) + { + varibaleGroupDictionary.Add(vg.id, vg.name); + } + } + } + return varibaleGroupDictionary; + } + public bool ExportDeliveryPlans(ProjectConfigurations appConfig, string extractedFolderName) + { + bool isDeliveryPlanExported = false; + try + { + Plans plans = new Plans(appConfig.WorkItemConfig); + GetPlans.Root plansList = plans.GetDeliveryPlans(appConfig.WorkItemConfig.AccountName, appConfig.WorkItemConfig.Project); + if (plansList.count > 0) + { + RestAPI.Extractor.ClassificationNodes nodes = new RestAPI.Extractor.ClassificationNodes(appConfig.BoardConfig); + string defaultTeamID = string.Empty; + + var teamsRes = nodes.GetTeams(); + RootTeams rootTeams = new RootTeams(); + if (teamsRes != null && teamsRes.IsSuccessStatusCode) + { + rootTeams = JsonConvert.DeserializeObject(teamsRes.Content.ReadAsStringAsync().Result); + } + + foreach (var plan in plansList.value) + { + APlan.Root aplan = plans.GetAPlan(appConfig.WorkItemConfig.AccountName, appConfig.WorkItemConfig.Project, plan.id); + if (aplan.properties?.teamBacklogMappings != null) + { + foreach (var team in aplan.properties?.teamBacklogMappings) + { + if (rootTeams.count > 0) + { + foreach (var teams in rootTeams.value) + { + if (team.teamId == teams.id) + { + team.teamId = $"${teams.name}$"; + break; + } + } + } + } + } + if (!string.IsNullOrEmpty(aplan.id)) + { + if (!(Directory.Exists(extractedFolderName + "\\DeliveryPlans"))) + { + Directory.CreateDirectory(extractedFolderName + "\\DeliveryPlans"); + File.WriteAllText(extractedFolderName + $"\\DeliveryPlans\\{aplan.name}.json", JsonConvert.SerializeObject(aplan, Formatting.Indented)); + } + else + { + File.WriteAllText(extractedFolderName + $"\\DeliveryPlans\\{aplan.name}.json", JsonConvert.SerializeObject(aplan, Formatting.Indented)); + } + } + } + isDeliveryPlanExported = true; + } + } + catch (Exception ex) + { + appConfig.WorkItemConfig.Id.ErrorId().AddMessage(DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss") + "\t" + ex.Message + "\n" + ex.StackTrace + "\n"); + } + return isDeliveryPlanExported; + } + #endregion END GENERATE ARTIFACTS + } +} diff --git a/src/ADOGenerator/Services/Init.cs b/src/ADOGenerator/Services/Init.cs index f922eeb..adef919 100644 --- a/src/ADOGenerator/Services/Init.cs +++ b/src/ADOGenerator/Services/Init.cs @@ -52,6 +52,10 @@ public bool CheckProjectName(string name) public string ExtractHref(string link) { + if(string.IsNullOrEmpty(link)) + { + return string.Empty; + } var startIndex = link.IndexOf("href='") + 6; var endIndex = link.IndexOf("'", startIndex); return link.Substring(startIndex, endIndex - startIndex); diff --git a/src/ADOGenerator/Services/ProjectService.cs b/src/ADOGenerator/Services/ProjectService.cs index c0f9c41..2db13d3 100644 --- a/src/ADOGenerator/Services/ProjectService.cs +++ b/src/ADOGenerator/Services/ProjectService.cs @@ -72,6 +72,108 @@ public HttpResponseMessage GetprojectList(string accname, string pat) HttpResponseMessage response = projects.GetListOfProjects(); return response; } + + public HttpResponseMessage GetProjects(string accname, string pat, string authScheme) + { + string defaultHost = _configuration["AppSettings:DefaultHost"]; + if (string.IsNullOrEmpty(defaultHost)) + { + throw new InvalidOperationException("DefaultHost configuration is missing."); + } + + string ProjectCreationVersion = _configuration["AppSettings:ProjectCreationVersion"]; + if (ProjectCreationVersion == null) + { + throw new InvalidOperationException("ProjectCreationVersion configuration is missing."); + } + + ADOConfiguration config = new ADOConfiguration() { AccountName = accname, PersonalAccessToken = pat, UriString = defaultHost + accname, VersionNumber = ProjectCreationVersion, _adoAuthScheme = authScheme }; + Projects projects = new Projects(config); + HttpResponseMessage response = projects.GetListOfProjects(); + return response; + } + + + public async Task> SelectProject(string accessToken, HttpResponseMessage projectsData) + { + var projectsJson = JObject.Parse(await projectsData.Content.ReadAsStringAsync()); + List projectDetails = new List(); + return await Task.Run(() => + { + if (projectsJson["count"].Value() > 0) + { + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine(Environment.NewLine + "Select an Project:"); + Console.ResetColor(); + var projects = projectsJson["value"]; + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine("+-----+--------------------------------+--------------------------------------+"); + Console.WriteLine("| No | Project Name | Project ID |"); + Console.WriteLine("+-----+--------------------------------+--------------------------------------+"); + for (int i = 0; i < projects.Count(); i++) + { + string projectName = projects[i]["name"].ToString(); + string projectId = projects[i]["id"].ToString(); + + // Wrap text if needed for Project Name + if (projectName.Length > 30) + { + string wrappedName = projectName.Substring(0, 30); + Console.WriteLine($"| {i + 1,-3} | {wrappedName.PadRight(30)} | {projectId.PadRight(36)} |"); + projectName = projectName.Substring(30); + while (projectName.Length > 0) + { + wrappedName = projectName.Length > 30 ? projectName.Substring(0, 30) : projectName; + Console.WriteLine($"| | {wrappedName.PadRight(30)} | {"".PadRight(36)} |"); + projectName = projectName.Length > 30 ? projectName.Substring(30) : string.Empty; + } + } + else + { + Console.WriteLine($"| {i + 1,-3} | {projectName.PadRight(30)} | {projectId.PadRight(36)} |"); + } + + // Wrap text if needed for Project ID + if (projectId.Length > 36) + { + string wrappedId = projectId.Substring(0, 36); + Console.WriteLine($"| | {"".PadRight(30)} | {wrappedId.PadRight(36)} |"); + projectId = projectId.Substring(36); + while (projectId.Length > 0) + { + wrappedId = projectId.Length > 36 ? projectId.Substring(0, 36) : projectId; + Console.WriteLine($"| | {"".PadRight(30)} | {wrappedId.PadRight(36)} |"); + projectId = projectId.Length > 36 ? projectId.Substring(36) : string.Empty; + } + } + } + Console.WriteLine("+-----+--------------------------------+--------------------------------------+"); + Console.ResetColor(); + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine("Please select a project that uses the standard Scrum or Agile process template"); + Console.ResetColor(); + int selectedIndex; + do + { + Console.ForegroundColor= ConsoleColor.Green; + Console.Write(Environment.NewLine+"Enter the number of the project: "); + Console.ResetColor(); + } while (!int.TryParse(Console.ReadLine(), out selectedIndex) || selectedIndex < 1 || selectedIndex > projects.Count()); + + projectDetails.Add(projects[selectedIndex - 1]["id"].ToString()); + projectDetails.Add(projects[selectedIndex - 1]["name"].ToString()); + return projectDetails; + } + else + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine("No organizations found."); + Console.ResetColor(); + } + return null; + }); + } + /// /// Get the path where we can file template related json files for selected template /// diff --git a/src/ADOGenerator/Services/TemplateService.cs b/src/ADOGenerator/Services/TemplateService.cs index c10927b..608c8b3 100644 --- a/src/ADOGenerator/Services/TemplateService.cs +++ b/src/ADOGenerator/Services/TemplateService.cs @@ -1,9 +1,140 @@ using ADOGenerator.IServices; +using ADOGenerator.Models; +using ADOGenerator.Services; +using Microsoft.Extensions.Configuration; +using RestAPI.Extractor; +using RestAPI.ProjectsAndTeams; +using RestAPI; +using ADOGenerator; -namespace ADOGenerator.Services +public class TemplateService : ITemplateService { - public class TemplateService : ITemplateService + private readonly IConfiguration _config; + private readonly IExtractorService extractorService; + + public TemplateService(IConfiguration config) { - public TemplateService() { } + _config = config; + extractorService = new ExtractorService(_config); + } + + public bool AnalyzeProject(Project model) + { + try + { + string defaultHost = _config["AppSettings:DefaultHost"]; + string projectPropertyVersion = _config["AppSettings:ProjectPropertyVersion"]; + + ADOConfiguration config = new ADOConfiguration + { + AccountName = model.accountName, + PersonalAccessToken = model.accessToken, + UriString = defaultHost + model.accountName, + VersionNumber = projectPropertyVersion, + ProjectId = model.ProjectId, + _adoAuthScheme = model.adoAuthScheme + }; + + Projects projects = new Projects(config); + ProjectProperties.Properties load = projects.GetProjectProperties(); + model.ProcessTemplate = load.value[4].value; + ExtractorService es = new ExtractorService(_config); + ExtractorAnalysis analysis = new ExtractorAnalysis(); + ProjectConfigurations appConfig = extractorService.ProjectConfiguration(model); + analysis.teamCount = extractorService.GetTeamsCount(appConfig); + analysis.IterationCount = extractorService.GetIterationsCount(appConfig); + analysis.WorkItemCounts = extractorService.GetWorkItemsCount(appConfig); + analysis.BuildDefCount = extractorService.GetBuildDefinitionCount(appConfig); + analysis.ReleaseDefCount = extractorService.GetReleaseDefinitionCount(appConfig); + analysis.ErrorMessages = es.errorMessages; + + LogAnalysisResults(model, analysis); + + return true; + } + catch (Exception ex) + { + model.id.ErrorId().AddMessage("Error during project analysis: " + ex.Message); + return false; + } + } + public bool CheckTemplateExists(Project model) + { + try + { + if (extractorService.IsTemplateExists(model.ProjectName)) + { + model.id.AddMessage("Template already exists."); + return true; // Template exists + } + else + { + model.id.AddMessage("Template does not exist."); + return false; // Template does not exist + } + } + catch (Exception ex) + { + model.id.ErrorId().AddMessage("Error checking template existence: " + ex.Message); + return false; // Error occurred while checking template existence + } + } + + public (bool,string,string) GenerateTemplateArtifacts(Project model) + { + try + { + string[] createdTemplate = extractorService.GenerateTemplateArifacts(model); + if (createdTemplate == null || createdTemplate.Length == 0) + { + model.id.AddMessage("No artifacts were generated."); + return (false, string.Empty,string.Empty); // No artifacts generated + } + string template = createdTemplate[1]; + string templateLocation = createdTemplate[2]; + return (true,template, templateLocation); // Artifact generation completed successfully + } + catch (Exception ex) + { + model.id.ErrorId().AddMessage("Error during artifact generation: " + ex.Message); + return (false, string.Empty, string.Empty); // Artifact generation failed + } + } + + private void LogAnalysisResults(Project model, ExtractorAnalysis analysis) + { + model.id.AddMessage(Environment.NewLine + "-------------------------------------------------------------------"); + model.id.AddMessage("| Analysis of the project |"); + model.id.AddMessage("|-----------------------------------------------------------------|"); + model.id.AddMessage($"| {"Property",-30} | {"Value",-30} |"); + model.id.AddMessage("|-----------------------------------------------------------------|"); + model.id.AddMessage($"| {"Project Name",-30} | {model.ProjectName.PadRight(30)} |"); + model.id.AddMessage($"| {"Process Template Type",-30} | {model.ProcessTemplate.PadRight(30)} |"); + model.id.AddMessage($"| {"Teams Count",-30} | {analysis.teamCount.ToString().PadRight(30)} |"); + model.id.AddMessage($"| {"Iterations Count",-30} | {analysis.IterationCount.ToString().PadRight(30)} |"); + model.id.AddMessage($"| {"Build Definitions Count",-30} | {analysis.BuildDefCount.ToString().PadRight(30)} |"); + model.id.AddMessage($"| {"Release Definitions Count",-30} | {analysis.ReleaseDefCount.ToString().PadRight(30)} |"); + + if (analysis.WorkItemCounts.Count > 0) + { + model.id.AddMessage("|-----------------------------------------------------------------|"); + model.id.AddMessage("| Work Items Count: |"); + model.id.AddMessage("|-----------------------------------------------------------------|"); + foreach (var item in analysis.WorkItemCounts) + { + model.id.AddMessage($"| {item.Key.PadRight(30)} | {item.Value.ToString().PadRight(30)} |"); + } + } + if (analysis.ErrorMessages.Count > 0) + { + model.id.AddMessage("|-----------------------------------------------------------------|"); + model.id.AddMessage("| Errors: |"); + model.id.AddMessage("|-----------------------------------------------------------------|"); + foreach (var item in analysis.ErrorMessages) + { + model.id.AddMessage($"| {item.PadRight(60)} |"); + } + } + model.id.AddMessage("-------------------------------------------------------------------"); } } diff --git a/src/API/Extractor/GetWorkItemsCount.cs b/src/API/Extractor/GetWorkItemsCount.cs index a16b0fc..2afaf0b 100644 --- a/src/API/Extractor/GetWorkItemsCount.cs +++ b/src/API/Extractor/GetWorkItemsCount.cs @@ -99,7 +99,7 @@ public WorkItemFetchResponse.WorkItems GetWorkItemsDetailInBatch(string workitem HttpResponseMessage response = client.GetAsync(_configuration.UriString + "/_apis/wit/workitems?api-version=" + _configuration.VersionNumber + "&ids=" + workitemstoFetch + "&$expand=relations").Result; if (response.IsSuccessStatusCode && response.StatusCode == System.Net.HttpStatusCode.OK) { - viewModel = response.Content.ReadFromJsonAsync().Result; + viewModel = JsonConvert.DeserializeObject(response.Content.ReadAsStringAsync().Result); } else {