-
Notifications
You must be signed in to change notification settings - Fork 828
The Configure Block
configure is a method inside the Job DSL to give direct access to underlying XML of the Jenkins config.xml. The method is provided a closure to manipulate the groovy.util.Node that is passed in. All standard documentation for Node applies here, the object passed in represents the Jenkins root object .
Transforming XML via Node is no fun, and quite ugly. The general groovy use-cases are to consume this structure or build it up via NodeBuilder. Use the samples below to help navigate the Jenkins XML, but keep in mind that the actual syntax is Groovy syntax: http://groovy.codehaus.org/Updating+XML+with+XmlParser. Things to keep in mind:
- All queries (methodMissing or find) assume that a NodeList is being returned, so if you need a single Node get the first element, e.g. [0]
- plus() adds siblings, so once we have one node, you can keep adding siblings, but will need an initial peer to add to.
- Children can be easily accessed if they exist, if they don't exist you have to append them. This means accessing deep trees is laborious.
To ease navigation, two key operators have been overridden. Try to use them as much as possible:
- div() - finds a child node by name, always returning the first child. If no child exists, one will be created. E.g. project/description will find the description node, creating it if it doesn't exist. (n.b. "div" stands for the division sign ("/") not the HTML element so named). And it has a very low precedence in the order of operation, so you need to wrap parenthesis around some operations.
- leftshift() - appends as a child. If a Node is provided, it is directly added. A string is created as a node. A closure is processed like a NodeBuilder, allowing many nodes to be appended.
- configure can be stated multiple times
- execution order is maintained
- Closure (fragments) can be passed in
- If no template is specified via using() then a simple free-style project structure is provided.
- configure blocks are run in the order they are provided with DSL commands.
Here is an inelegant configure block which may explain what Groovy sees, e.g.
configure {
// "it" is a groovy.util.Node
// representing the job's config.xml's root "project" element.
// anotherNode is also groovy.util.Node
// obtained with the overloaded "/" operator
// on which we can call "setValue(...)"
def aNode = it
def anotherNode = aNode / 'blockBuildWhenDownstreamBuilding'
anotherNode.setValue("true")
// You can chain these steps,
// but must add wrapping parenthesis
// because the "/" has a very low precedence (lower than the ".")
(it / 'blockBuildWhenUpstreamBuilding').setValue("true")
}
All other samples are given in the context of a configure block, e.g.
job {
name = "Job-Name"
configure { project ->
// Put Sample here
}
}
All DSL commands started as samples here. So, hint, hint, if you want a DSL command, add a working sample here. If a DSL sample is provided, that means we've written it directly into the plugin. It's highly recommended to use a DSL command is possible.
configure:
def matrix = project / 'properties' / 'hudson.security.AuthorizationMatrixProperty'
matrix << {
permission('hudson.model.Item.Configure:jill')
permission('hudson.model.Item.Configure:jack')
}
matrix.appendNode('permission', 'hudson.model.Run.Delete:jryan')
Result:
<project>
<properties>
<hudson.security.AuthorizationMatrixProperty>
<permission>hudson.model.Item.Configure:jill</permission>
<permission>hudson.model.Item.Configure:jack</permission>
<permission>hudson.model.Run.Delete:jryan</permission>
</hudson.security.AuthorizationMatrixProperty>
</properties>
</project>
DSL:
job {
authorization {
permission('hudson.model.Item.Configure:jill')
permission('hudson.model.Item.Configure:jack')
}
}
Configure:
// Doesn't take into account existing node
project << logRotator {
daysToKeep(-1)
numToKeep(10)
artifactDaysToKeep(-1)
artifactNumToKeep(-1)
}
// Alters existing value
(project / logRotator / daysToKeep) = -2
Result:
<project>
<logRotator>
<daysToKeep>-2</daysToKeep>
<numToKeep>10</numToKeep>
<artifactDaysToKeep>-1</artifactDaysToKeep>
<artifactNumToKeep>-1</artifactNumToKeep>
</logRotator>
</project>
configure:
def publisher = project/publisher/'hudson.plugins.emailext.ExtendedEmailPublisher'
Closure email() {
trigger {
email {
recipientList = '$PROJECT_DEFAULT_RECIPIENTS'
subject = '$PROJECT_DEFAULT_SUBJECT'
body = '$PROJECT_DEFAULT_CONTENT'
sendToDevelopers = true
sendToRequester = false
includeCulprits = false
sendToRecipientList = true
}
}
}
publisher {
recipientList = '[email protected]'
configuredTriggers {
'hudson.plugins.emailext.plugins.trigger.FailureTrigger' email()
'hudson.plugins.emailext.plugins.trigger.FixedTrigger' email()
}
contentType = 'default'
defaultSubject = '$DEFAULT_SUBJECT'
defaultContent = '$DEFAULT_CONTENT'
}
Result:
<publisher>
<hudson.plugins.emailext.ExtendedEmailPublisher>
<recipientList>[email protected]</recipientList>
<configuredTriggers>
<hudson.plugins.emailext.plugins.trigger.FailureTrigger>
<email>
<recipientList>$PROJECT_DEFAULT_RECIPIENTS</recipientList>
<subject>$PROJECT_DEFAULT_SUBJECT</subject>
<body>$PROJECT_DEFAULT_CONTENT</body>
<sendToDevelopers>true</sendToDevelopers>
<sendToRequester>false</sendToRequester>
<includeCulprits>false</includeCulprits>
<sendToRecipientList>true</sendToRecipientList>
</email>
</hudson.plugins.emailext.plugins.trigger.FailureTrigger>
<hudson.plugins.emailext.plugins.trigger.FixedTrigger>
<email>
<recipientList>$PROJECT_DEFAULT_RECIPIENTS</recipientList>
<subject>$PROJECT_DEFAULT_SUBJECT</subject>
<body>$PROJECT_DEFAULT_CONTENT</body>
<sendToDevelopers>true</sendToDevelopers>
<sendToRequester>false</sendToRequester>
<includeCulprits>true</includeCulprits>
<sendToRecipientList>true</sendToRecipientList>
</email>
</hudson.plugins.emailext.plugins.trigger.FixedTrigger>
</configuredTriggers>
<contentType>default</contentType>
<defaultSubject>$DEFAULT_SUBJECT</defaultSubject>
<defaultContent>$DEFAULT_CONTENT</defaultContent>
</hudson.plugins.emailext.ExtendedEmailPublisher>
</publishers>
configure:
Result:
configure:
Result:
configure:
project / builders / 'hudson.tasks.Shell' {
command = 'curl "http://artifacts/gradle/oss.gradle" > ${WORKSPACE}/oss.gradle'
}
Result:
<builders>
<hudson.tasks.Shell>
<command>curl "http://artifacts.netflix.com/build-gradle/netflix-oss.gradle" > ${WORKSPACE}/netflix-oss.gradle</command>
</hudson.tasks.Shell>
</builders>
DSL:
job {
steps {
shell 'curl "http://artifacts/gradle/oss.gradle" > ${WORKSPACE}/oss.gradle'
}
}
configure:
project / builders / 'hudson.plugins.gradle.Gradle' {
description ''
switches '-Dtiming-multiple=5'
tasks 'test'
rootBuildScriptDir ''
buildFile ''
useWrapper 'true'
wrapperScript 'gradlew'
}
Result:
<builders>
<hudson.plugins.gradle.Gradle>
<description/>
<switches>-Dtiming-multiple=5</switches>
<tasks>clean${Task}</tasks>
<rootBuildScriptDir/>
<buildFile/>
<useWrapper>true</useWrapper>
<wrapperScript>gradlew</wrapperScript>
</hudson.plugins.gradle.Gradle>
</builders>
DSL:
steps {
gradle('test', '-Dtiming-multiple-5', true) {
wrapperScript 'gradlew'
}
}
## Configure SVN - TBC
_configure_:
```groovy
project / scm('hudson.scm.SubversionSCM') {
locations {
'hudson.scm.SubversionSCM_-ModuleLocation' {
remote 'http://svn.apache.org/repos/asf/tomcat/maven-plugin/trunk'
local '.'
}
excludedRegions ''
includedRegions ''
excludedUsers ''
excludedRevprop ''
excludedCommitMessages ''
workspaceUpdater(class: "hudson.scm.subversion.UpdateUpdater")
}
Result:
<scm class="hudson.scm.SubversionSCM">
<locations>
<hudson.scm.SubversionSCM_-ModuleLocation>
<remote>http://svn.apache.org/repos/asf/tomcat/maven-plugin/trunk</remote>
<local>.</local>
</hudson.scm.SubversionSCM_-ModuleLocation>
</locations>
<excludedRegions/>
<includedRegions/>
<excludedUsers/>
<excludedRevprop/>
<excludedCommitMessages/>
<workspaceUpdater class="hudson.scm.subversion.UpdateUpdater"/>
</scm>
configure:
def gitConfigWithSubdirRemote(node, subdir, remote) {
// use remote name given
(node / 'userRemoteConfigs' / 'hudson.plugins.git.UserRemoteConfig').appendNode('name', remote)
// use local dir given
(node / 'extensions' / 'hudson.plugins.git.extensions.impl.RelativeTargetDirectory').appendNode('relativeTargetDir', subdir)
// clean after checkout
node / 'extensions' / 'hudson.plugins.git.extensions.impl.CleanCheckout' << {}
}
job {
multiscm {
git('git@server:account/repo1.git', 'remoteB/master') { node -> gitConfigWithSubdir(node, 'repo1', 'remoteB') }
git('git@server:account/repo2.git', 'remoteB/master') { node -> gitConfigWithSubdir(node, 'repo2', 'remoteB') }
...
Result:
...
<scm class='hudson.plugins.git.GitSCM'>
...
<userRemoteConfigs>
<hudson.plugins.git.UserRemoteConfig>
<url>git@server:account/repo1.git</url>
<name>remoteB</name>
</hudson.plugins.git.UserRemoteConfig>
</userRemoteConfigs>
<branches>
<hudson.plugins.git.BranchSpec>
<name>remoteB/master</name>
</hudson.plugins.git.BranchSpec>
</branches>
<extensions>
<hudson.plugins.git.extensions.impl.RelativeTargetDirectory>
<relativeTargetDir>repo1</relativeTargetDir>
</hudson.plugins.git.extensions.impl.RelativeTargetDirectory>
<hudson.plugins.git.extensions.impl.CleanCheckout></hudson.plugins.git.extensions.impl.CleanCheckout>
</extensions>
</scm>
...
configure:
project.name = 'matrix-project'
project / axes / 'hudson.matrix.LabelAxis' {
name 'label'
values {
string 'linux'
string 'windows'
}
}
project / executionStrategy(class: 'hudson.matrix.DefaultMatrixExecutionStrategyImpl') {
runSequentially false
}
Result:
<matrix-project>
<actions/>
<description/>
<keepDependencies>false</keepDependencies>
<properties/>
<scm class="hudson.scm.NullSCM"/>
<canRoam>true</canRoam>
<disabled>false</disabled>
<blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
<blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
<triggers class="vector"/>
<concurrentBuild>false</concurrentBuild>
<builders/>
<publishers/>
<buildWrappers/>
<axes>
<hudson.matrix.LabelAxis>
<name>label</name>
<values>
<string>linux</string>
<string>windows</string>
</values>
</hudson.matrix.LabelAxis>
</axes>
<executionStrategy class="hudson.matrix.DefaultMatrixExecutionStrategyImpl">
<runSequentially>false</runSequentially>
</executionStrategy>
</matrix-project>
configure:
configure { project ->
project / publishers / 'hudson.tasks.test.AggregatedTestResultPublisher' << {
jobs('some-downstream-test')
includeFailedBuilds('false')
}
}
Result:
...
<publishers>
...
<hudson.tasks.test.AggregatedTestResultPublisher>
<jobs>some-downstream-test</jobs>
<includeFailedBuilds>false</includeFailedBuilds>
</hudson.tasks.test.AggregatedTestResultPublisher>
...
</publishers>
...
Uses Prerequisite build step plugin: https://wiki.jenkins-ci.org/display/JENKINS/Prerequisite+build+step+plugin
configure:
configure { project ->
project / builders / 'dk.hlyh.ciplugins.prereqbuildstep.PrereqBuilder'(plugin: '[email protected]') {
projects('project-A,project-B') // Important that there are no spaces for comma delimited values, plugin doesn't handle by trimming
warningOnly(false)
}
}
Result:
...
<builders>
...
<dk.hlyh.ciplugins.prereqbuildstep.PrereqBuilder plugin="[email protected]">
<projects>project-A,project-B</projects>
<warningOnly>false</warningOnly>
</dk.hlyh.ciplugins.prereqbuildstep.PrereqBuilder>
...
</builders>
...
To reuse an configure block for many jobs, the configure block can be moved to a helper class.
The following example shows how to refactor the Matrix job sample above into a reusable helper class.
package helpers
class MatrixProjectHelper {
static Closure matrixProject(Iterable<String> labels) {
return { project ->
project.name = 'matrix-project'
project / axes / 'hudson.matrix.LabelAxis' {
name 'label'
values {
labels.each { string it }
}
}
project / executionStrategy(class: 'hudson.matrix.DefaultMatrixExecutionStrategyImpl') {
runSequentially false
}
}
}
}
In the job script you can then import the helper method and use it create several matrix jobs.
import static helpers.MatrixProjectHelper.matrixProject
job {
name 'matrix-job-A'
configure matrixProject(['label-1', 'label-2'])
}
job {
name 'matrix-job-B'
configure matrixProject(['label-3', 'label-4'])
}
Stack Overflow | Mailing List | API Reference | Issue Tracker | Playground | Plugin | Wiki | GitHub
Home
Release Notes
Migration
Talks and Blog Posts
Documentation
Tutorial
Dynamic DSL
Configure Blocks
Job DSL Commands
Script Security
Handling Credentials
Configuration as Code
FAQ
Real World Examples
User Power Moves
IDE Support
Testing DSL Scripts
For Developers
Extending the DSL
Job DSL Architecture