jenkinsgroovyjenkins-groovyjenkins-job-dslgroovyshell

How to parse Jenkinsfile from Groovy code


I have a Jenkinsfile with some properties, for example

trace = false

userNotifications = [
    build_master :                 [name : 'name',
                                    email : 'email',
                                    slackid: 'slack id',
                                    slackchannel: 'slack channel']
]
env.aProperty = "aValue"
node('COMPILE')
{
...
}

I want to parse the above Jenkinsfile inside groovy code in order to access some of the property values.

When I use GroovyShell like this

        Binding binding = new Binding()
        GroovyShell shell = new GroovyShell(binding)
        Object groovyDsl = shell.evaluate(clean)

I get this error

groovy.lang.MissingMethodException: No signature of method: Script1.node() is applicable for argument types: (String, Script1$_run_closure1) values: [COMPILE, Script1$_run_closure1@7d804e7]

I might be able to get around the particular error with some Groovy meta-programming, however, I am unsure if this is the right direction. My question is what is the best way to parse a Jenkinsfile in Groovy code? This is Groovy DSL at the end of the day and I would expect it to be more straightforward.


Solution

  • Here is how I eventually did what I wanted:

    import org.codehaus.groovy.control.CompilerConfiguration
    
    class JenkinsfileParser {
        static void main(String[] args) {
            LinkedHashMap vars = JenkinsfileParser.parse('Jenkinsfile' as File)
            println vars.toString()
        }
    
        static LinkedHashMap parse(File jenkinsfile) {
            CompilerConfiguration config = new CompilerConfiguration()
            config.scriptBaseClass = JenkinsfileBaseClass.class.name
            Binding binding = new Binding()
            GroovyShell shell = new GroovyShell(this.class.classLoader, binding, config)
            String clean = jenkinsfile.text.replace('import hudson.model.*', '')
                    .replace('import hudson.EnvVars', '')
            Script script = shell.parse(clean)
            script.run()
            return binding.variables
        }
    
        static abstract class JenkinsfileBaseClass extends Script {
            LinkedHashMap<String, Closure> nodeClosures = new LinkedHashMap<>()
            LinkedHashMap env = new LinkedHashMap()
            CurrentBuild currentBuild = new CurrentBuild()
            LinkedHashMap parallelClosures = new LinkedHashMap<>()
    
            void node(String name, Closure closure) {
                nodeClosures.put(name, closure)
            }
    
            void parallel(LinkedHashMap closures) {
                parallelClosures.putAll(closures)
            }
    
            class CurrentBuild {
                List<String> expectedResults = new ArrayList<>()
    
                boolean resultIsBetterOrEqualTo(String expected) {
                    expectedResults << expected
                    return true
                }
            }
        }
    }
    

    The JenkinsfileBaseClass is specific to my Jenkinsfile and would need adaptation for a different Jenkinsfile.

    Relevant documentation can be found here: https://groovy-lang.org/integrating.html

    I tried using the implementation group: 'org.jenkins-ci.main', name: 'jenkins-core', version: '2.9' package from the maven repository http://repo.jenkins-ci.org/public/, however, there doesn't seem to be anything in there useful for my case.

    Let me know if there's a better or more elegant way of doing it.