Groovy is supported in Jenkinsfile for quick scripting.
However, lots of features in the Groovy language is not supported and simple works in Groovy can be really tricky in Jenkinsfile.
Different ways to process XML file
In summary, if it is possible, use another script language (e.g., Python) for file manipulation in Jenkinsfile.
It is time consuming to navigate all tricky stuffs of Groovy implementaiton in Jenkins:
In-process Script Approval: you have to approve every single class and method one by one .
Some features of Groovy is not supported and it takes time to figure out what is not supported and how to work around. When in doubt, use @NonCPS
.
Groovy method in Jenkinsfile
Jenkinsfile 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import groovy.xml.StreamingMarkupBuilder
import groovy.xml.XmlUtil
def settingsFile = 'temp.xml'
@NonCPS
def xmlTransform ( txt , username , password ) {
def xmlRoot = new XmlSlurper ( false , false ). parseText ( txt )
echo 'Start tranforming XML'
xmlRoot . servers . server . each { node ->
node . username = username
node . password = password
}
// TRICKY: FileWriter does NOT work
def outWriter = new StringWriter ()
XmlUtil . serialize ( xmlRoot , outWriter )
return outWriter . toString ()
}
pipeline {
agent { node { label 'test-agent' } }
stages {
stage ( "compile" ) {
steps {
checkout scm
withCredentials ([
[ $class : 'StringBinding' , credentialsId: 'nexusUsername' , variable: 'nexusUsername' ],
[ $class : 'StringBinding' , credentialsId: 'nexusPassword' , variable: 'nexusPassword' ]
]) {
script {
def xmlTemplate = readFile ( 'jenkins/settings.xml' )
def xmlFile = xmlTransform ( xmlTemplate , env . nexusUsername , env . nexusPassword )
writeFile file: settingsFile , text: xmlFile
sh "mvn -B -s ${settingsFile} clean compile"
}
}
}
post {
failure {
echo "Sending email for compile failed (TBD)"
}
}
}
}
}
Some notes:
import
statements must be at the top, right after the shebang and before anything else.
The Groovy methods must be annotated with @NonCPS
or Jenkins will report the error “java.io.NotSerializableException”.
The Groovy methods can not be defined inside a step
block. It must be defined at the top.
@NonCPS
is required since the Groovy method uses several non-serializble objects.
Groovy method in separate script
Jenkinsfile 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
def myScript
pipeline {
agent { node { label 'test-agent' } }
stages {
stage ( "compile" ) {
steps {
checkout scm
withCredentials ([
[ $class : 'StringBinding' , credentialsId: 'nexusUsername' , variable: 'nexusUsername' ],
[ $class : 'StringBinding' , credentialsId: 'nexusPassword' , variable: 'nexusPassword' ]
]) {
script {
myScript = load 'jenkins/xml.groovy'
def xmlTemplate = readFile ( 'jenkins/settings.xml' )
String xmlFile = myScript . transformXml ( xmlTemplate , env . nexusUsername , env . nexusPassword )
String myPath = 'temp.xml'
def mCommand = "cat >${myPath} <<EOF"
mCommand += "\n${xmlFile}\nEOF"
sh mCommand
sh "mvn -B clean compile -s ${myPath}"
sh "rm ${myPath}"
}
}
}
post {
failure {
echo "Sending email for compile failed (TBD)"
}
}
}
}
}
xml.groovy 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import groovy.xml.StreamingMarkupBuilder
import groovy.xml.XmlUtil
@NonCPS
def transformXml ( String xmlContent , String username , String password ) {
def xml = new XmlSlurper ( false , false ). parseText ( xmlContent )
echo 'Start tranforming XML'
xml . servers . server . each { node ->
node . username = username
node . password = password
}
def outWriter = new StringWriter ()
XmlUtil . serialize ( xml , outWriter )
return outWriter . toString ()
}
return this
Groovy method in shared library
The above Nexus authentication code is likely repeated across multiple Maven builds.
Therefore, it is worth converting it into a DSL into a Shared Library in Jenkins.
The DSL takes two parameters:
templateFile: settings.xml template with Nexus credentials info redacted.
command: Maven command with settings file NOT specified (i.e., NO “-s” option in the command).
The example usage is as follows:
Jenkinsfile 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pipeline {
agent { node { label 'test-agent' } }
stages {
stage ( "compile" ) {
steps {
checkout scm
script {
withNexusMaven {
templateFile = 'jenkins/settings.xml'
command = "mvn -B clean compile"
}
}
}
post {
failure {
echo "Sending email for compile failed (TBD)"
}
}
}
}
}
The Jenksinfile is much cleaner since most of implementation details have been moved inside the DSL:
withNexusMaven.groovy 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#!groovy
import groovy.xml.XmlUtil
@NonCPS
def transformXml ( String xmlContent , String username , String password ) {
def xml = new XmlSlurper ( false , false ). parseText ( xmlContent )
echo 'Start tranforming XML'
xml . servers . server . each { node ->
node . username = username
node . password = password
}
def outWriter = new StringWriter ()
XmlUtil . serialize ( xml , outWriter )
return outWriter . toString ()
}
def call ( Closure body ) {
def config = [:]
if ( body != null ) {
body . resolveStrategy = Closure . DELEGATE_FIRST
body . delegate = config
body ()
}
def templateFile = config . templateFile ?: '/home/data/settings.xml'
def command = config . command ?: "mvn -B clean compile"
withCredentials ([
[ $class : 'StringBinding' , credentialsId: 'nexusUsername' , variable: 'nexusUsername' ],
[ $class : 'StringBinding' , credentialsId: 'nexusPassword' , variable: 'nexusPassword' ]
]) {
def xmlTemplate = readFile templateFile
String xmlFile = transformXml ( xmlTemplate , env . nexusUsername , env . nexusPassword )
String tempFile = 'temp.xml'
writeFile file: tempFile , text: xmlFile
sh "${command} -s ${tempFile}"
// Clean up
sh "rm ${tempFile}"
}
}