diff --git a/src/MigrationTools.Clients.AzureDevops.Rest/Processors/AzureDevOpsPipelineProcessor.cs b/src/MigrationTools.Clients.AzureDevops.Rest/Processors/AzureDevOpsPipelineProcessor.cs index ea80a25fe..284f12602 100644 --- a/src/MigrationTools.Clients.AzureDevops.Rest/Processors/AzureDevOpsPipelineProcessor.cs +++ b/src/MigrationTools.Clients.AzureDevops.Rest/Processors/AzureDevOpsPipelineProcessor.cs @@ -79,7 +79,7 @@ private async System.Threading.Tasks.Task MigratePipelinesAsync() } if (Options.MigrateTaskGroups) { - taskGroupMappings = await CreateTaskGroupDefinitionsAsync(); + taskGroupMappings = await CreateTaskGroupDefinitionsAsync(serviceConnectionMappings); } if (Options.MigrateBuildPipelines) { @@ -200,8 +200,9 @@ private IEnumerable FilterOutExistingTaskGroups(IEnumerable /// /// + /// /// List of filtered Definitions - private IEnumerable FilterOutIncompatibleTaskGroups(IEnumerable filteredTaskGroups, IEnumerable availableTasks) + private IEnumerable FilterOutIncompatibleTaskGroupsWithMappings(IEnumerable filteredTaskGroups, IEnumerable availableTasks, IEnumerable taskGroupMappings) { var objectsToMigrate = filteredTaskGroups.Where(g => { @@ -212,6 +213,15 @@ private IEnumerable FilterOutIncompatibleTaskGroups(IEnumerable m.SourceId == t.Task.Id)) + { + return true; + } + } + missingTasksNames.Add(t.DisplayName); return false; }); @@ -277,6 +287,11 @@ not null when typeof(DefinitionType) == typeof(ReleaseDefinition) => definitionN .ToList(); } + private static bool IsMetaTask(string definitionType) + { + return string.Equals(definitionType, "metaTask", StringComparison.OrdinalIgnoreCase); + } + private async Task> CreateBuildPipelinesAsync(IEnumerable TaskGroupMapping = null, IEnumerable VariableGroupMapping = null, IEnumerable serviceConnectionMappings = null) { Log.LogInformation("Processing Build Pipelines.."); @@ -311,7 +326,7 @@ private async Task> CreateBuildPipelinesAsync(IEnumerable> CreateServiceConnectionsAsync() return mappings; } - private async Task> CreateTaskGroupDefinitionsAsync() + private async Task> CreateTaskGroupDefinitionsAsync(IEnumerable serviceConnectionMappings) { Log.LogInformation($"Processing Taskgroups.."); @@ -623,11 +638,90 @@ private async Task> CreateTaskGroupDefinitionsAsync() var targetDefinitions = await Target.GetApiDefinitionsAsync(queryForDetails: false); var availableTasks = await Target.GetApiDefinitionsAsync(queryForDetails: false); var filteredTaskGroups = FilterOutExistingTaskGroups(sourceDefinitions, targetDefinitions); - filteredTaskGroups = FilterOutIncompatibleTaskGroups(filteredTaskGroups, availableTasks).ToList(); + filteredTaskGroups = FilterOutIncompatibleTaskGroupsWithMappings(filteredTaskGroups, availableTasks, null).ToList(); + + var existingMappings = FindExistingMappings(sourceDefinitions, targetDefinitions, new List()); + + Log.LogInformation($"Phase 1 - Unnested Taskgroups"); + var unnestedTaskGroups = filteredTaskGroups.Where(g => g.Tasks.All(t => !IsMetaTask(t.Task.DefinitionType))); + existingMappings = await CreateTaskGroupsAsync(serviceConnectionMappings, targetDefinitions, availableTasks, unnestedTaskGroups, existingMappings); + + Log.LogInformation($"Phase 2 - Nested Taskgroups"); + var nestedTaskGroups = filteredTaskGroups.Where(g => g.Tasks.Any(t => IsMetaTask(t.Task.DefinitionType))).ToList(); + var taskGroupsToMigrate = new List(); + + do + { + // We need to process the nested taskgroups in a loop, because they can contain other nested taskgroups. + taskGroupsToMigrate.Clear(); + foreach (var taskGroup in nestedTaskGroups) + { + var nestedTaskGroup = taskGroup.Tasks.Where(t => IsMetaTask(t.Task.DefinitionType)).Select(t => t.Task).ToList(); + if (nestedTaskGroup.All(t => existingMappings.Any(m => t.Id == m.SourceId))) + { + taskGroupsToMigrate.Add(taskGroup); + } + } + + nestedTaskGroups = nestedTaskGroups.Where(g => !taskGroupsToMigrate.Any(t => t.Id == g.Id)).ToList(); + existingMappings = await CreateTaskGroupsAsync(serviceConnectionMappings, targetDefinitions, availableTasks, taskGroupsToMigrate, existingMappings); + } while (nestedTaskGroups.Any() && taskGroupsToMigrate.Any()); + + return existingMappings; + } + + private async Task> CreateTaskGroupsAsync(IEnumerable serviceConnectionMappings, IEnumerable targetDefinitions, IEnumerable availableTasks, IEnumerable filteredTaskGroups, IEnumerable existingMappings) + { + filteredTaskGroups = FilterOutIncompatibleTaskGroupsWithMappings(filteredTaskGroups, availableTasks, existingMappings).ToList(); var rootSourceDefinitions = SortDefinitionsByVersion(filteredTaskGroups).First(); var updatedSourceDefinitions = SortDefinitionsByVersion(filteredTaskGroups).Last(); + foreach (var definitionToBeMigrated in rootSourceDefinitions) + { + if (serviceConnectionMappings is not null) + { + foreach (var task in definitionToBeMigrated.Tasks) + { + var newInputs = new Dictionary(); + foreach (var input in (IDictionary)task.Inputs) + { + var mapping = serviceConnectionMappings.FirstOrDefault(d => d.SourceId == input.Value.ToString()); + if (mapping != null) + { + newInputs.Add(input.Key, mapping.TargetId); + } + } + + foreach (var input in newInputs) + { + ((IDictionary)task.Inputs).Remove(input.Key); + ((IDictionary)task.Inputs).Add(input.Key, input.Value); + } + } + } + + if (existingMappings is not null) + { + foreach (var task in definitionToBeMigrated.Tasks) + { + if (!IsMetaTask(task.Task.DefinitionType)) + { + continue; + } + var mapping = existingMappings.FirstOrDefault(d => d.SourceId == task.Task.Id); + if (mapping == null) + { + Log.LogWarning("Can't find taskgroup {MissingTaskGroupId} in the target collection.", task.Task.Id); + } + else + { + task.Task.Id = mapping.TargetId; + } + } + } + } + var mappings = await Target.CreateApiDefinitionsAsync(rootSourceDefinitions); targetDefinitions = await Target.GetApiDefinitionsAsync(queryForDetails: false); @@ -635,7 +729,8 @@ private async Task> CreateTaskGroupDefinitionsAsync() await Target.UpdateTaskGroupsAsync(rootTargetDefinitions, updatedSourceDefinitions); targetDefinitions = await Target.GetApiDefinitionsAsync(queryForDetails: false); - mappings.AddRange(FindExistingMappings(sourceDefinitions, targetDefinitions.Where(d => d.Name != null), mappings)); + mappings.AddRange(FindExistingMappings(rootSourceDefinitions, targetDefinitions.Where(d => d.Name != null), mappings)); + mappings.AddRange(existingMappings); return mappings; }