//Search GitHub object for all environment variables public List <string> SearchForVariablesV2(GitHubActionsRoot gitHubActions) { List <string> variables = new List <string>(); if (gitHubActions.env != null) { foreach (KeyValuePair <string, string> env in gitHubActions.env) { variables.Add(env.Key); } } if (gitHubActions.jobs != null) { foreach (KeyValuePair <string, GitHubActions.Job> job in gitHubActions.jobs) { if (job.Value.env != null) { foreach (KeyValuePair <string, string> env in job.Value.env) { variables.Add(env.Key); } } } } return(variables); }
public static string Serialize(GitHubActionsRoot gitHubActions, List <string> variableList = null, string matrixVariableName = null) { string yaml = GenericObjectSerialization.SerializeYaml <GitHubActionsRoot>(gitHubActions); yaml = ProcessGitHubActionYAML(yaml, variableList, matrixVariableName); return(yaml); }
public void GitHubDeserializationTest() { //Arrange string yaml = @" on: push: branches: - master jobs: build: runs-on: ubuntu-latest name: Build 1 runs-on: windows-latest steps: - uses: actions/checkout@v2 - run: Write-Host ""Hello world!"" shell: powershell "; //Act GitHubActionsRoot gitHubAction = GitHubActionsSerialization.Deserialize(yaml); //Assert Assert.AreNotEqual(null, gitHubAction); Assert.AreEqual(null, gitHubAction.env); //environment variables are null //Test for messages and name Assert.AreEqual(0, gitHubAction.messages.Count); Assert.AreEqual(null, gitHubAction.name); //Test the trigger Assert.AreNotEqual(null, gitHubAction.on); Assert.AreEqual(null, gitHubAction.on.pull_request); Assert.AreNotEqual(null, gitHubAction.on.push); Assert.AreEqual(1, gitHubAction.on.push.branches.Length); Assert.AreEqual("master", gitHubAction.on.push.branches[0]); Assert.AreEqual(null, gitHubAction.on.push.branches_ignore); Assert.AreEqual(null, gitHubAction.on.push.paths); Assert.AreEqual(null, gitHubAction.on.push.paths_ignore); Assert.AreEqual(null, gitHubAction.on.push.tags); Assert.AreEqual(null, gitHubAction.on.push.tags_ignore); Assert.AreEqual(null, gitHubAction.on.schedule); //Test that jobs exist Assert.AreNotEqual(null, gitHubAction.jobs); Assert.AreEqual(1, gitHubAction.jobs.Count); Assert.AreEqual(true, gitHubAction.jobs.ContainsKey("build")); gitHubAction.jobs.TryGetValue("build", out Job gitHubJob); Assert.AreEqual(null, gitHubJob._if); Assert.AreEqual("Build 1", gitHubJob.name); Assert.AreEqual("windows-latest", gitHubJob.runs_on); //Test that steps exist Assert.AreNotEqual(null, gitHubJob.steps); Assert.AreEqual(2, gitHubJob.steps.Length); }
/// <summary> /// Process an Azure DevOps Pipeline, converting it to a GitHub Action /// </summary> /// <param name="azurePipeline">Azure DevOps Pipeline object</param> /// <param name="simpleTrigger">When the YAML has a simple trigger, (String[]). Can be null</param> /// <param name="complexTrigger">When the YAML has a complex trigger. Can be null</param> /// <returns>GitHub Actions object</returns> public GitHubActionsRoot ProcessPipeline(AzurePipelinesRoot <TTriggers, TVariables> azurePipeline, string[] simpleTrigger, AzurePipelines.Trigger complexTrigger, Dictionary <string, string> simpleVariables, AzurePipelines.Variable[] complexVariables) { VariableList = new List <string>(); GeneralProcessing generalProcessing = new GeneralProcessing(_verbose); GitHubActionsRoot gitHubActions = new GitHubActionsRoot(); //Name if (azurePipeline.name != null) { gitHubActions.name = azurePipeline.name; } //Container if (azurePipeline.container != null) { gitHubActions.messages.Add("TODO: Container conversion not yet done, we need help!: https://github.com/samsmithnz/AzurePipelinesToGitHubActionsConverter/issues/39"); } //Triggers for pushs TriggerProcessing tp = new TriggerProcessing(_verbose); if (azurePipeline.trigger != null) { if (complexTrigger != null) { gitHubActions.on = tp.ProcessComplexTrigger(complexTrigger); } else if (simpleTrigger != null) { gitHubActions.on = tp.ProcessSimpleTrigger(simpleTrigger); } } //Triggers for pull requests if (azurePipeline.pr != null) { GitHubActions.Trigger pr = tp.ProcessPullRequest(azurePipeline.pr); if (gitHubActions.on == null) { gitHubActions.on = pr; } else { gitHubActions.on.pull_request = pr.pull_request; } } //pool/demands if (azurePipeline.pool != null && azurePipeline.pool.demands != null) { gitHubActions.messages.Add("Note: GitHub Actions does not have a 'demands' command on 'runs-on' yet"); } //schedules if (azurePipeline.schedules != null) { string[] schedules = tp.ProcessSchedules(azurePipeline.schedules); if (gitHubActions.on == null) { gitHubActions.on = new GitHubActions.Trigger(); } gitHubActions.on.schedule = schedules; } //Resources if (azurePipeline.resources != null) { //Note: Containers is in the jobs - this note should be removed once pipeliens and repositories is moved too //TODO: There is currently no conversion path for pipelines if (azurePipeline.resources.pipelines != null) { gitHubActions.messages.Add("TODO: Resource pipelines conversion not yet done: https://github.com/samsmithnz/AzurePipelinesToGitHubActionsConverter/issues/8"); if (azurePipeline.resources.pipelines.Length > 0) { if (azurePipeline.resources.pipelines[0].pipeline != null) { ConversionUtility.WriteLine("pipeline: " + azurePipeline.resources.pipelines[0].pipeline, _verbose); } if (azurePipeline.resources.pipelines[0].project != null) { ConversionUtility.WriteLine("project: " + azurePipeline.resources.pipelines[0].project, _verbose); } if (azurePipeline.resources.pipelines[0].source != null) { ConversionUtility.WriteLine("source: " + azurePipeline.resources.pipelines[0].source, _verbose); } if (azurePipeline.resources.pipelines[0].branch != null) { ConversionUtility.WriteLine("branch: " + azurePipeline.resources.pipelines[0].branch, _verbose); } if (azurePipeline.resources.pipelines[0].version != null) { ConversionUtility.WriteLine("version: " + azurePipeline.resources.pipelines[0].version, _verbose); } if (azurePipeline.resources.pipelines[0].trigger != null) { if (azurePipeline.resources.pipelines[0].trigger.autoCancel) { ConversionUtility.WriteLine("autoCancel: " + azurePipeline.resources.pipelines[0].trigger.autoCancel, _verbose); } if (azurePipeline.resources.pipelines[0].trigger.batch) { ConversionUtility.WriteLine("batch: " + azurePipeline.resources.pipelines[0].trigger.batch, _verbose); } } } } //TODO: There is currently no conversion path for repositories if (azurePipeline.resources.repositories != null) { gitHubActions.messages.Add("TODO: Resource repositories conversion not yet done: https://github.com/samsmithnz/AzurePipelinesToGitHubActionsConverter/issues/8"); if (azurePipeline.resources.repositories.Length > 0) { if (azurePipeline.resources.repositories[0].repository != null) { ConversionUtility.WriteLine("repository: " + azurePipeline.resources.repositories[0].repository, _verbose); } if (azurePipeline.resources.repositories[0].type != null) { ConversionUtility.WriteLine("type: " + azurePipeline.resources.repositories[0].type, _verbose); } if (azurePipeline.resources.repositories[0].name != null) { ConversionUtility.WriteLine("name: " + azurePipeline.resources.repositories[0].name, _verbose); } if (azurePipeline.resources.repositories[0]._ref != null) { ConversionUtility.WriteLine("ref: " + azurePipeline.resources.repositories[0]._ref, _verbose); } if (azurePipeline.resources.repositories[0].endpoint != null) { ConversionUtility.WriteLine("endpoint: " + azurePipeline.resources.repositories[0].endpoint, _verbose); } if (azurePipeline.resources.repositories[0].connection != null) { ConversionUtility.WriteLine("connection: " + azurePipeline.resources.repositories[0].connection, _verbose); } if (azurePipeline.resources.repositories[0].source != null) { ConversionUtility.WriteLine("source: " + azurePipeline.resources.repositories[0].source, _verbose); } } } } //Stages (Note: stages are not yet present in actions, we are merging them into one giant list of jobs, appending the stage name to jobs to keep names unique) if (azurePipeline.stages != null) { //Count the number of jobs and initialize the jobs array with that number int jobCounter = 0; foreach (Stage stage in azurePipeline.stages) { if (stage.jobs != null) { jobCounter += stage.jobs.Length; } } azurePipeline.jobs = new AzurePipelines.Job[jobCounter]; //We are going to take each stage and assign it a set of jobs int currentIndex = 0; foreach (Stage stage in azurePipeline.stages) { if (stage.jobs != null) { int j = 0; for (int i = 0; i < stage.jobs.Length; i++) { //Get the job name string jobName = ConversionUtility.GenerateJobName(stage.jobs[i], currentIndex); //Rename the job, using the stage name as prefix, so that we keep the job names unique stage.jobs[j].job = stage.stage + "_Stage_" + jobName; ConversionUtility.WriteLine("This variable is not needed in actions: " + stage.displayName, _verbose); azurePipeline.jobs[currentIndex] = stage.jobs[j]; azurePipeline.jobs[currentIndex].condition = stage.condition; //Move over the variables, the stage variables will need to be applied to each job if (stage.variables != null && stage.variables.Count > 0) { azurePipeline.jobs[currentIndex].variables = new Dictionary <string, string>(); foreach (KeyValuePair <string, string> stageVariable in stage.variables) { azurePipeline.jobs[currentIndex].variables.Add(stageVariable.Key, stageVariable.Value); } } j++; currentIndex++; } } } } //Jobs (when no stages are defined) if (azurePipeline.jobs != null) { //If there is a parent strategy, and no child strategy, load in the parent //This is not perfect... if (azurePipeline.strategy != null) { foreach (AzurePipelines.Job item in azurePipeline.jobs) { if (item.strategy == null) { item.strategy = azurePipeline.strategy; } } } gitHubActions.jobs = ProcessJobs(azurePipeline.jobs, azurePipeline.resources); if (gitHubActions.jobs.Count == 0) { gitHubActions.messages.Add("Note that although having no jobs is valid YAML, it is not a valid GitHub Action."); } } //Pool + Steps (When there are no jobs defined) if ((azurePipeline.pool != null && azurePipeline.jobs == null) || (azurePipeline.steps != null && azurePipeline.steps.Length > 0)) { //Steps only have one job, so we just create it here StepsProcessing sp = new StepsProcessing(); gitHubActions.jobs = new Dictionary <string, GitHubActions.Job> { { "build", new GitHubActions.Job { runs_on = generalProcessing.ProcessPool(azurePipeline.pool), strategy = generalProcessing.ProcessStrategy(azurePipeline.strategy), container = generalProcessing.ProcessContainer(azurePipeline.resources), //resources = ProcessResources(azurePipeline.resources), steps = sp.AddSupportingSteps(azurePipeline.steps) } } }; MatrixVariableName = generalProcessing.MatrixVariableName; } //Variables VariablesProcessing vp = new VariablesProcessing(_verbose); if (azurePipeline.variables != null) { if (complexVariables != null) { gitHubActions.env = vp.ProcessComplexVariables(complexVariables); VariableList.AddRange(vp.VariableList); } else if (simpleVariables != null) { gitHubActions.env = vp.ProcessSimpleVariables(simpleVariables); VariableList.AddRange(vp.VariableList); } } else if (azurePipeline.parameters != null) { //For now, convert the parameters to variables gitHubActions.env = vp.ProcessSimpleVariables(azurePipeline.parameters); } return(gitHubActions); }
/// <summary> /// Convert an entire Azure DevOps Pipeline to a GitHub Actions /// </summary> /// <param name="input">Yaml to convert</param> /// <returns>Converion object, with original yaml, processed yaml, and comments on the conversion</returns> public ConversionResponse ConvertAzurePipelineToGitHubAction(string input) { List <string> variableList = new List <string>(); string yaml; GitHubActionsRoot gitHubActions = null; //Triggers and variables are hard, as there are two data types for each that can exist, so we need to go with the most common type and handle the less common type with exceptions. //There are 4 combinations here, simple/simple, simple/complex, complex/simple, and complex/complex AzurePipelinesRoot <string[], Dictionary <string, string> > azurePipelineWithSimpleTriggerAndSimpleVariables = null; AzurePipelinesRoot <string[], AzurePipelines.Variable[]> azurePipelineWithSimpleTriggerAndComplexVariables = null; AzurePipelinesRoot <AzurePipelines.Trigger, Dictionary <string, string> > azurePipelineWithComplexTriggerAndSimpleVariables = null; AzurePipelinesRoot <AzurePipelines.Trigger, AzurePipelines.Variable[]> azurePipelineWithComplexTriggerAndComplexVariables = null; try { azurePipelineWithSimpleTriggerAndSimpleVariables = AzurePipelinesSerialization <string[], Dictionary <string, string> > .DeserializeSimpleTriggerAndSimpleVariables(input); } catch { try { azurePipelineWithComplexTriggerAndSimpleVariables = AzurePipelinesSerialization <AzurePipelines.Trigger, Dictionary <string, string> > .DeserializeComplexTriggerAndSimpleVariables(input); } catch { try { azurePipelineWithSimpleTriggerAndComplexVariables = AzurePipelinesSerialization <string[], AzurePipelines.Variable[]> .DeserializeSimpleTriggerAndComplexVariables(input); } catch { azurePipelineWithComplexTriggerAndComplexVariables = AzurePipelinesSerialization <AzurePipelines.Trigger, AzurePipelines.Variable[]> .DeserializeComplexTriggerAndComplexVariables(input); } } } //Generate the github actions if (azurePipelineWithSimpleTriggerAndSimpleVariables != null) { PipelineProcessing <string[], Dictionary <string, string> > processing = new PipelineProcessing <string[], Dictionary <string, string> >(); gitHubActions = processing.ProcessPipeline(azurePipelineWithSimpleTriggerAndSimpleVariables, azurePipelineWithSimpleTriggerAndSimpleVariables.trigger, null, azurePipelineWithSimpleTriggerAndSimpleVariables.variables, null); if (processing.MatrixVariableName != null) { _matrixVariableName = processing.MatrixVariableName; } variableList.AddRange(processing.VariableList); } else if (azurePipelineWithSimpleTriggerAndComplexVariables != null) { PipelineProcessing <string[], AzurePipelines.Variable[]> processing = new PipelineProcessing <string[], AzurePipelines.Variable[]>(); gitHubActions = processing.ProcessPipeline(azurePipelineWithSimpleTriggerAndComplexVariables, azurePipelineWithSimpleTriggerAndComplexVariables.trigger, null, null, azurePipelineWithSimpleTriggerAndComplexVariables.variables); if (processing.MatrixVariableName != null) { _matrixVariableName = processing.MatrixVariableName; } variableList.AddRange(processing.VariableList); } else if (azurePipelineWithComplexTriggerAndSimpleVariables != null) { PipelineProcessing <AzurePipelines.Trigger, Dictionary <string, string> > processing = new PipelineProcessing <AzurePipelines.Trigger, Dictionary <string, string> >(); gitHubActions = processing.ProcessPipeline(azurePipelineWithComplexTriggerAndSimpleVariables, null, azurePipelineWithComplexTriggerAndSimpleVariables.trigger, azurePipelineWithComplexTriggerAndSimpleVariables.variables, null); if (processing.MatrixVariableName != null) { _matrixVariableName = processing.MatrixVariableName; } variableList.AddRange(processing.VariableList); } else if (azurePipelineWithComplexTriggerAndComplexVariables != null) { PipelineProcessing <AzurePipelines.Trigger, AzurePipelines.Variable[]> processing = new PipelineProcessing <AzurePipelines.Trigger, AzurePipelines.Variable[]>(); gitHubActions = processing.ProcessPipeline(azurePipelineWithComplexTriggerAndComplexVariables, null, azurePipelineWithComplexTriggerAndComplexVariables.trigger, null, azurePipelineWithComplexTriggerAndComplexVariables.variables); if (processing.MatrixVariableName != null) { _matrixVariableName = processing.MatrixVariableName; } variableList.AddRange(processing.VariableList); } //Commented out the new solution, as it doesn't process failed/invalid documents. //var success = false; //if (!success) //{ // var azurePipelineWithSimpleTriggerAndSimpleVariables = AzurePipelinesSerialization<string[], Dictionary<string, string>>.DeserializeSimpleTriggerAndSimpleVariables(input); // if (azurePipelineWithSimpleTriggerAndSimpleVariables != null) // { // success = true; // PipelineProcessing<string[], Dictionary<string, string>> processing = new PipelineProcessing<string[], Dictionary<string, string>>(); // gitHubActions = processing.ProcessPipeline(azurePipelineWithSimpleTriggerAndSimpleVariables, azurePipelineWithSimpleTriggerAndSimpleVariables.trigger, null, azurePipelineWithSimpleTriggerAndSimpleVariables.variables, null); // if (processing.MatrixVariableName != null) // { // _matrixVariableName = processing.MatrixVariableName; // } // variableList.AddRange(processing.VariableList); // } //} //if (!success) //{ // var azurePipelineWithSimpleTriggerAndComplexVariables = AzurePipelinesSerialization<string[], AzurePipelines.Variable[]>.DeserializeSimpleTriggerAndComplexVariables(input); // if (azurePipelineWithSimpleTriggerAndComplexVariables != null) // { // success = true; // PipelineProcessing<string[], AzurePipelines.Variable[]> processing = new PipelineProcessing<string[], AzurePipelines.Variable[]>(); // gitHubActions = processing.ProcessPipeline(azurePipelineWithSimpleTriggerAndComplexVariables, azurePipelineWithSimpleTriggerAndComplexVariables.trigger, null, null, azurePipelineWithSimpleTriggerAndComplexVariables.variables); // if (processing.MatrixVariableName != null) // { // _matrixVariableName = processing.MatrixVariableName; // } // variableList.AddRange(processing.VariableList); // } //} //if (!success) //{ // var azurePipelineWithComplexTriggerAndSimpleVariables = AzurePipelinesSerialization<AzurePipelines.Trigger, Dictionary<string, string>>.DeserializeComplexTriggerAndSimpleVariables(input); // if (azurePipelineWithComplexTriggerAndSimpleVariables != null) // { // success = true; // PipelineProcessing<AzurePipelines.Trigger, Dictionary<string, string>> processing = new PipelineProcessing<AzurePipelines.Trigger, Dictionary<string, string>>(); // gitHubActions = processing.ProcessPipeline(azurePipelineWithComplexTriggerAndSimpleVariables, null, azurePipelineWithComplexTriggerAndSimpleVariables.trigger, azurePipelineWithComplexTriggerAndSimpleVariables.variables, null); // if (processing.MatrixVariableName != null) // { // _matrixVariableName = processing.MatrixVariableName; // } // variableList.AddRange(processing.VariableList); // } //} //if (!success) //{ // var azurePipelineWithComplexTriggerAndComplexVariables = AzurePipelinesSerialization<AzurePipelines.Trigger, AzurePipelines.Variable[]>.DeserializeComplexTriggerAndComplexVariables(input); // if (azurePipelineWithComplexTriggerAndComplexVariables != null) // { // PipelineProcessing<AzurePipelines.Trigger, AzurePipelines.Variable[]> processing = new PipelineProcessing<AzurePipelines.Trigger, AzurePipelines.Variable[]>(); // gitHubActions = processing.ProcessPipeline(azurePipelineWithComplexTriggerAndComplexVariables, null, azurePipelineWithComplexTriggerAndComplexVariables.trigger, null, azurePipelineWithComplexTriggerAndComplexVariables.variables); // if (processing.MatrixVariableName != null) // { // _matrixVariableName = processing.MatrixVariableName; // } // variableList.AddRange(processing.VariableList); // } //} //if (!success) //{ // throw new NotSupportedException("All deserialisation methods failed... oops! Please create a GitHub issue so we can fix this"); //} //Search for any other variables. Duplicates are ok, they are processed the same variableList.AddRange(SearchForVariables(input)); //Create the YAML and apply some adjustments if (gitHubActions != null) { yaml = GitHubActionsSerialization.Serialize(gitHubActions, variableList, _matrixVariableName); } else { yaml = ""; } //Load failed task comments for processing List <string> stepComments = new List <string>(); if (gitHubActions != null) { //Add any header messages if (gitHubActions.messages != null) { foreach (string message in gitHubActions.messages) { stepComments.Add(ConvertMessageToYamlComment(message)); } } if (gitHubActions.jobs != null) { //Add each individual step comments foreach (KeyValuePair <string, GitHubActions.Job> job in gitHubActions.jobs) { if (job.Value.steps != null) { if (job.Value.job_message != null) { stepComments.Add(ConvertMessageToYamlComment(job.Value.job_message)); } foreach (GitHubActions.Step step in job.Value.steps) { if (step != null && string.IsNullOrEmpty(step.step_message) == false) { stepComments.Add(ConvertMessageToYamlComment(step.step_message)); } } } } } } //Append all of the comments to the top of the file foreach (string item in stepComments) { yaml = item + Environment.NewLine + yaml; } //Return the final conversion result, with the original (pipeline) yaml, processed (actions) yaml, and any comments return(new ConversionResponse { pipelinesYaml = input, actionsYaml = yaml, comments = stepComments }); }
/// <summary> /// Process an Azure DevOps Pipeline, converting it to a GitHub Action /// </summary> /// <param name="azurePipeline">Azure DevOps Pipeline object</param> /// <param name="simpleTrigger">When the YAML has a simple trigger, (String[]). Can be null</param> /// <param name="complexTrigger">When the YAML has a complex trigger. Can be null</param> /// <returns>GitHub Actions object</returns> public GitHubActionsRoot ProcessPipeline(AzurePipelinesRoot <T, T2> azurePipeline, string[] simpleTrigger, AzurePipelines.Trigger complexTrigger, Dictionary <string, string> simpleVariables, AzurePipelines.Variable[] complexVariables) { VariableList = new List <string>(); GitHubActionsRoot gitHubActions = new GitHubActionsRoot(); //Name if (azurePipeline.name != null) { gitHubActions.name = azurePipeline.name; } //Container if (azurePipeline.container != null) { gitHubActions.messages.Add("TODO: Container conversion not yet done: https://github.com/samsmithnz/AzurePipelinesToGitHubActionsConverter/issues/39"); } //Triggers for pushs if (azurePipeline.trigger != null) { if (complexTrigger != null) { gitHubActions.on = ProcessComplexTrigger(complexTrigger); } else if (simpleTrigger != null) { gitHubActions.on = ProcessSimpleTrigger(simpleTrigger); } } //Triggers for pull requests if (azurePipeline.pr != null) { GitHubActions.Trigger pr = ProcessPullRequest(azurePipeline.pr); if (gitHubActions.on == null) { gitHubActions.on = pr; } else { gitHubActions.on.pull_request = pr.pull_request; } } //Container if (azurePipeline.pool != null && azurePipeline.pool.demands != null) { gitHubActions.messages.Add("Note: GitHub Actions does not have a 'demands' command on 'runs-on' yet"); } //schedules if (azurePipeline.schedules != null) { string[] schedules = ProcessSchedules(azurePipeline.schedules); if (gitHubActions.on == null) { gitHubActions.on = new GitHubActions.Trigger(); } gitHubActions.on.schedule = schedules; } //Resources if (azurePipeline.resources != null) { //Note: Containers is in the jobs - this note should be removed once pipeliens and repositories is moved too //TODO: Add code for pipelines if (azurePipeline.resources.pipelines != null) { gitHubActions.messages.Add("TODO: Resource pipelines conversion not yet done: https://github.com/samsmithnz/AzurePipelinesToGitHubActionsConverter/issues/8"); if (azurePipeline.resources.pipelines.Length > 0) { if (azurePipeline.resources.pipelines[0].pipeline != null) { Console.WriteLine("pipeline: " + azurePipeline.resources.pipelines[0].pipeline); } if (azurePipeline.resources.pipelines[0].project != null) { Console.WriteLine("project: " + azurePipeline.resources.pipelines[0].project); } if (azurePipeline.resources.pipelines[0].source != null) { Console.WriteLine("source: " + azurePipeline.resources.pipelines[0].source); } if (azurePipeline.resources.pipelines[0].branch != null) { Console.WriteLine("branch: " + azurePipeline.resources.pipelines[0].branch); } if (azurePipeline.resources.pipelines[0].version != null) { Console.WriteLine("version: " + azurePipeline.resources.pipelines[0].version); } if (azurePipeline.resources.pipelines[0].trigger != null) { if (azurePipeline.resources.pipelines[0].trigger.autoCancel) { Console.WriteLine("autoCancel: " + azurePipeline.resources.pipelines[0].trigger.autoCancel); } if (azurePipeline.resources.pipelines[0].trigger.batch) { Console.WriteLine("batch: " + azurePipeline.resources.pipelines[0].trigger.batch); } } } } //TODO: Add code for repositories if (azurePipeline.resources.repositories != null) { gitHubActions.messages.Add("TODO: Resource repositories conversion not yet done: https://github.com/samsmithnz/AzurePipelinesToGitHubActionsConverter/issues/8"); if (azurePipeline.resources.repositories.Length > 0) { if (azurePipeline.resources.repositories[0].repository != null) { Console.WriteLine("repository: " + azurePipeline.resources.repositories[0].repository); } if (azurePipeline.resources.repositories[0].type != null) { Console.WriteLine("type: " + azurePipeline.resources.repositories[0].type); } if (azurePipeline.resources.repositories[0].name != null) { Console.WriteLine("name: " + azurePipeline.resources.repositories[0].name); } if (azurePipeline.resources.repositories[0]._ref != null) { Console.WriteLine("ref: " + azurePipeline.resources.repositories[0]._ref); } if (azurePipeline.resources.repositories[0].endpoint != null) { Console.WriteLine("endpoint: " + azurePipeline.resources.repositories[0].endpoint); } if (azurePipeline.resources.repositories[0].connection != null) { Console.WriteLine("connection: " + azurePipeline.resources.repositories[0].connection); } if (azurePipeline.resources.repositories[0].source != null) { Console.WriteLine("source: " + azurePipeline.resources.repositories[0].source); } } } } //Stages (Note: stages are not yet present in actions, we are merging them into one giant list of jobs, appending the stage name to jobs to keep names unique) if (azurePipeline.stages != null) { //Count the number of jobs and initialize the jobs array with that number int jobCounter = 0; foreach (Stage stage in azurePipeline.stages) { jobCounter += stage.jobs.Length; } azurePipeline.jobs = new AzurePipelines.Job[jobCounter]; //We are going to take each stage and assign it a set of jobs int currentIndex = 0; foreach (Stage stage in azurePipeline.stages) { int j = 0; for (int i = currentIndex; i < currentIndex + stage.jobs.Length; i++) { //Get the job name string jobName = stage.jobs[j].job; if (jobName == null && stage.jobs[j].template != null) { jobName = "Template"; } //Rename the job, using the stage name as prefix, so that we keep the job names unique stage.jobs[j].job = stage.stage + "_Stage_" + jobName; Console.WriteLine("This variable is not needed in actions: " + stage.displayName); azurePipeline.jobs[i] = stage.jobs[j]; azurePipeline.jobs[i].condition = stage.condition; j++; } currentIndex += stage.jobs.Length; } } //Jobs (when no stages are defined) if (azurePipeline.jobs != null) { //If there is a parent strategy, and no child strategy, load in the parent //This is not perfect... if (azurePipeline.strategy != null) { foreach (AzurePipelines.Job item in azurePipeline.jobs) { if (item.strategy == null) { item.strategy = azurePipeline.strategy; } } } gitHubActions.jobs = ProcessJobs(azurePipeline.jobs, azurePipeline.resources); } //Pool + Steps (When there are no jobs defined) if (azurePipeline.pool != null || (azurePipeline.steps != null && azurePipeline.steps.Length > 0)) { //Steps only have one job, so we just create it here gitHubActions.jobs = new Dictionary <string, GitHubActions.Job> { { "build", new GitHubActions.Job { runs_on = ProcessPool(azurePipeline.pool), strategy = ProcessStrategy(azurePipeline.strategy), container = ProcessContainer(azurePipeline.resources), //resources = ProcessResources(azurePipeline.resources), steps = ProcessSteps(azurePipeline.steps) } } }; } //Variables if (azurePipeline.variables != null) { if (complexVariables != null) { gitHubActions.env = ProcessComplexVariables(complexVariables); } else if (simpleVariables != null) { gitHubActions.env = ProcessSimpleVariables(simpleVariables); } } else if (azurePipeline.parameters != null) { //For now, convert the parameters to variables gitHubActions.env = ProcessSimpleVariables(azurePipeline.parameters); } return(gitHubActions); }
/// <summary> /// V1 plan: /// 1. get the yaml /// 2. try to deserialize the entire doc on a few common combinations /// 3. convert the azure pipelines objects into a github action /// </summary> /// <param name="yaml"></param> /// <returns></returns> private ConversionResponse ConvertAzurePipelineToGitHubActionV1(string yaml) { string gitHubYaml; List <string> variableList = new List <string>(); List <string> stepComments = new List <string>(); GitHubActionsRoot gitHubActions = null; //Run some processing to convert simple pools and demands to the complex editions, to avoid adding to the combinations below. //Also clean and remove variables with reserved words that get into trouble during deserialization. HACK alert... :( string processedInput = ConversionUtility.CleanYamlBeforeDeserialization(yaml); //Start the main deserialization methods bool success = false; if (success == false) { var azurePipelineWithSimpleTriggerAndSimpleVariables = AzurePipelinesSerialization <string[], Dictionary <string, string> > .DeserializeSimpleTriggerAndSimpleVariables(processedInput); if (azurePipelineWithSimpleTriggerAndSimpleVariables != null) { success = true; var pp = new PipelineProcessing <string[], Dictionary <string, string> >(_verbose); gitHubActions = pp.ProcessPipeline(azurePipelineWithSimpleTriggerAndSimpleVariables, azurePipelineWithSimpleTriggerAndSimpleVariables.trigger, null, azurePipelineWithSimpleTriggerAndSimpleVariables.variables, null); if (pp.MatrixVariableName != null) { _matrixVariableName = pp.MatrixVariableName; } variableList.AddRange(pp.VariableList); } } if (success == false) { var azurePipelineWithSimpleTriggerAndComplexVariables = AzurePipelinesSerialization <string[], AzurePipelines.Variable[]> .DeserializeSimpleTriggerAndComplexVariables(processedInput); if (azurePipelineWithSimpleTriggerAndComplexVariables != null) { success = true; var pp = new PipelineProcessing <string[], AzurePipelines.Variable[]>(_verbose); gitHubActions = pp.ProcessPipeline(azurePipelineWithSimpleTriggerAndComplexVariables, azurePipelineWithSimpleTriggerAndComplexVariables.trigger, null, null, azurePipelineWithSimpleTriggerAndComplexVariables.variables); if (pp.MatrixVariableName != null) { _matrixVariableName = pp.MatrixVariableName; } variableList.AddRange(pp.VariableList); } } if (success == false) { var azurePipelineWithComplexTriggerAndSimpleVariables = AzurePipelinesSerialization <AzurePipelines.Trigger, Dictionary <string, string> > .DeserializeComplexTriggerAndSimpleVariables(processedInput); if (azurePipelineWithComplexTriggerAndSimpleVariables != null) { success = true; var pp = new PipelineProcessing <AzurePipelines.Trigger, Dictionary <string, string> >(_verbose); gitHubActions = pp.ProcessPipeline(azurePipelineWithComplexTriggerAndSimpleVariables, null, azurePipelineWithComplexTriggerAndSimpleVariables.trigger, azurePipelineWithComplexTriggerAndSimpleVariables.variables, null); if (pp.MatrixVariableName != null) { _matrixVariableName = pp.MatrixVariableName; } variableList.AddRange(pp.VariableList); } } if (success == false) { var azurePipelineWithComplexTriggerAndComplexVariables = AzurePipelinesSerialization <AzurePipelines.Trigger, AzurePipelines.Variable[]> .DeserializeComplexTriggerAndComplexVariables(processedInput); if (azurePipelineWithComplexTriggerAndComplexVariables != null) { success = true; var pp = new PipelineProcessing <AzurePipelines.Trigger, AzurePipelines.Variable[]>(_verbose); gitHubActions = pp.ProcessPipeline(azurePipelineWithComplexTriggerAndComplexVariables, null, azurePipelineWithComplexTriggerAndComplexVariables.trigger, null, azurePipelineWithComplexTriggerAndComplexVariables.variables); if (pp.MatrixVariableName != null) { _matrixVariableName = pp.MatrixVariableName; } variableList.AddRange(pp.VariableList); } } if (success == false && string.IsNullOrEmpty(processedInput?.Trim()) == false) { throw new NotSupportedException("All deserialisation methods failed... oops! Please create a GitHub issue so we can fix this"); } //Search for any other variables. Duplicates are ok, they are processed the same VariablesProcessing vp = new VariablesProcessing(_verbose); variableList.AddRange(vp.SearchForVariables(processedInput)); //Create the GitHub YAML and apply some adjustments if (gitHubActions != null) { gitHubYaml = GitHubActionsSerialization.Serialize(gitHubActions, variableList, _matrixVariableName); } else { gitHubYaml = ""; } //Load failed task comments for processing if (gitHubActions != null) { //Add any header messages if (gitHubActions.messages != null) { foreach (string message in gitHubActions.messages) { stepComments.Add(ConversionUtility.ConvertMessageToYamlComment(message)); } } if (gitHubActions.jobs != null) { //Add each individual step comments foreach (KeyValuePair <string, GitHubActions.Job> job in gitHubActions.jobs) { if (job.Value.steps != null) { if (job.Value.job_message != null) { stepComments.Add(ConversionUtility.ConvertMessageToYamlComment(job.Value.job_message)); } foreach (GitHubActions.Step step in job.Value.steps) { if (step != null && string.IsNullOrEmpty(step.step_message) == false) { stepComments.Add(ConversionUtility.ConvertMessageToYamlComment(step.step_message)); } } } } } } //Append all of the comments to the top of the file foreach (string item in stepComments) { gitHubYaml = item + System.Environment.NewLine + gitHubYaml; } //Return the final conversion result, with the original (pipeline) yaml, processed (actions) yaml, and any comments return(new ConversionResponse { pipelinesYaml = yaml, actionsYaml = gitHubYaml, comments = stepComments, v2ConversionSuccessful = false }); }
/// <summary> /// V2 plan: /// 1. get the yaml /// 2. converting the yaml into a json document /// 3. parse the json document into azure pipelines sub-objects /// 4. put it together into one azure pipelines object /// 5. convert the azure pipelines object to github action /// </summary> /// <param name="yaml"></param> /// <returns></returns> private ConversionResponse ConvertAzurePipelineToGitHubActionV2(string yaml) { string gitHubYaml = ""; List <string> variableList = new List <string>(); List <string> stepComments = new List <string>(); //convert the yaml into json, it's easier to parse JObject json = null; if (yaml != null) { //Clean up the YAML to remove conditional insert statements string processedYaml = ConversionUtility.CleanYamlBeforeDeserializationV2(yaml); json = JSONSerialization.DeserializeStringToObject(processedYaml); } //Build up the GitHub object piece by piece GitHubActionsRoot gitHubActions = new GitHubActionsRoot(); GeneralProcessing gp = new GeneralProcessing(_verbose); if (json != null) { //Name if (json["name"] != null) { string nameYaml = json["name"].ToString(); gitHubActions.name = gp.ProcessNameV2(nameYaml); } //Trigger/PR/Schedules TriggerProcessing tp = new TriggerProcessing(_verbose); if (json["trigger"] != null) { string triggerYaml = json["trigger"].ToString(); triggerYaml = ConversionUtility.ProcessNoneJsonElement(triggerYaml); gitHubActions.on = tp.ProcessTriggerV2(triggerYaml); } if (json["pr"] != null) { string prYaml = json["pr"].ToString(); prYaml = ConversionUtility.ProcessNoneJsonElement(prYaml); GitHubActions.Trigger prTrigger = tp.ProcessPullRequestV2(prYaml); if (gitHubActions.on == null) { gitHubActions.on = prTrigger; } else { gitHubActions.on.pull_request = prTrigger.pull_request; } } if (json["schedules"] != null) { string schedulesYaml = json["schedules"].ToString(); GitHubActions.Trigger schedules = tp.ProcessSchedulesV2(schedulesYaml); if (gitHubActions.on == null) { gitHubActions.on = schedules; } else { gitHubActions.on.schedule = schedules.schedule; } } //Parameters & Variables string parametersYaml = json["parameters"]?.ToString(); string variablesYaml = json["variables"]?.ToString(); VariablesProcessing vp = new VariablesProcessing(_verbose); gitHubActions.env = vp.ProcessParametersAndVariablesV2(parametersYaml, variablesYaml); //Resources string resourcesYaml = json["resources"]?.ToString(); //Resource Pipelines if (resourcesYaml?.IndexOf("\"pipelines\"") >= 0) { gitHubActions.messages.Add("TODO: Resource pipelines conversion not yet done: https://github.com/samsmithnz/AzurePipelinesToGitHubActionsConverter/issues/8"); } //Resource Repositories if (resourcesYaml?.IndexOf("\"repositories\"") >= 0) { gitHubActions.messages.Add("TODO: Resource repositories conversion not yet done: https://github.com/samsmithnz/AzurePipelinesToGitHubActionsConverter/issues/8"); } //Resource Container if (resourcesYaml?.IndexOf("\"containers\"") >= 0) { gitHubActions.messages.Add("TODO: Container conversion not yet done, we need help!: https://github.com/samsmithnz/AzurePipelinesToGitHubActionsConverter/issues/39"); } //Strategy string strategyYaml = json["strategy"]?.ToString(); //If we have stages, convert them into jobs first: if (json["stages"] != null) { StagesProcessing sp = new StagesProcessing(_verbose); gitHubActions.jobs = sp.ProcessStagesV2(json["stages"], strategyYaml); } //If we don't have stages, but have jobs: else if (json["stages"] == null && json["jobs"] != null) { JobProcessing jp = new JobProcessing(_verbose); gitHubActions.jobs = jp.ProcessJobsV2(jp.ExtractAzurePipelinesJobsV2(json["jobs"], strategyYaml), gp.ExtractResourcesV2(resourcesYaml)); _matrixVariableName = jp.MatrixVariableName; } //Otherwise, if we don't have stages or jobs, we just have steps, and need to load them into a new job else if (json["stages"] == null && json["jobs"] == null) { //Pool string poolYaml = json["pool"]?.ToString(); //pool/demands if (poolYaml?.IndexOf("\"demands\":") >= 0) { gitHubActions.messages.Add("Note: GitHub Actions does not have a 'demands' command on 'runs-on' yet"); } //Steps string stepsYaml = json["steps"]?.ToString(); JobProcessing jp = new JobProcessing(_verbose); AzurePipelines.Job[] pipelineJobs = jp.ProcessJobFromPipelineRootV2(poolYaml, strategyYaml, stepsYaml); Resources resources = gp.ExtractResourcesV2(resourcesYaml); gitHubActions.jobs = jp.ProcessJobsV2(pipelineJobs, resources); _matrixVariableName = jp.MatrixVariableName; } if (gitHubActions.jobs != null && gitHubActions.jobs.Count == 0) { gitHubActions.messages.Add("Note that although having no jobs is valid YAML, it is not a valid GitHub Action."); } //Load in all variables. Duplicates are ok, they are processed the same variableList.AddRange(vp.SearchForVariables(yaml)); variableList.AddRange(vp.SearchForVariablesV2(gitHubActions)); //Create the GitHub YAML and apply some adjustments if (gitHubActions != null) { gitHubYaml = GitHubActionsSerialization.Serialize(gitHubActions, variableList, _matrixVariableName); } else { gitHubYaml = ""; } //Load failed task comments for processing //Add any header messages if (gitHubActions?.messages != null) { foreach (string message in gitHubActions.messages) { stepComments.Add(ConversionUtility.ConvertMessageToYamlComment(message)); } } if (gitHubActions?.jobs != null) { //Add each individual step comments foreach (KeyValuePair <string, GitHubActions.Job> job in gitHubActions.jobs) { if (job.Value.steps != null) { if (job.Value.job_message != null) { stepComments.Add(ConversionUtility.ConvertMessageToYamlComment(job.Value.job_message)); } foreach (GitHubActions.Step step in job.Value.steps) { if (step != null && string.IsNullOrEmpty(step.step_message) == false) { stepComments.Add(ConversionUtility.ConvertMessageToYamlComment(step.step_message)); } } } } } } //Append all of the comments to the top of the file foreach (string item in stepComments) { gitHubYaml = item + System.Environment.NewLine + gitHubYaml; } //Return the final conversion result, with the original (pipeline) yaml, processed (actions) yaml, and any comments return(new ConversionResponse { pipelinesYaml = yaml, actionsYaml = gitHubYaml, comments = stepComments, v2ConversionSuccessful = true }); }