//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);
        }
Example #2
0
        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);
        }
Example #3
0
        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);
        }
Example #5
0
        /// <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
            });
        }
Example #6
0
        /// <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);
        }
Example #7
0
        /// <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
            });
        }
Example #8
0
        /// <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
            });
        }