Call stage from function in Jenkinsfile - groovy

I have in my Jenkinsfile:
def foo = ["1", "2", "3"]
def parallelStagesFromMap = foo.collectEntries {
["Build ${it}" : generateStage(it)]
}
def generateStage(bar) {
return {
stage("Build ${bar}") {
echo "Building for ${bar}"
}
}
}
I can then use them with parallel parallel parallelStagesFromMap but now I'm trying to call one in particular, for example:
generateStage("a") and it is just skipped... Am I missing anything?

You are missing closure invocation. Your generateStage(name) method returns a closure, and this closure is not invoked implicitly. (It works with parallel stages, because parallel method expects a map where each entry value is a closure, so it iterates over all map entries and invokes collected closures).
Here is what your example should look like to add a non-parallel stage to the pipeline using generateStage(name) method:
def foo = ["1", "2", "3"]
def parallelStagesFromMap = foo.collectEntries {
["Build ${it}" : generateStage(it)]
}
def generateStage(bar) {
return {
stage("Build ${bar}") {
echo "Building for ${bar}"
}
}
}
node {
parallel parallelStagesFromMap
generateStage("skipped") // no invocation, stage is skipped
generateStage("nonparallel").call()
}
And here is what the Blue Ocean UI looks like after running this exemplary pipeline:

Related

Groovy method as variable

I have the following Groovy script:
def Deploy() {
if (App == "TEST"){
def book = load "book.groovy"
book.buildList.each {
a lot of actions
}
else {
book.each {
the same a lot of actions
}
}
So the difference only in execution methods (properties): book.buildList.each or book.each. How to avoid to repeat those a lot of actions and keep code cleaner. Probably there is a way to put book.buildList.each or book.each into the variable?
File Book.groovy contain few map:
buildList = [
'key1':'value1',
'key2':'value2',
'key3':'value3',
]
anotherList = [
'key11':'value11',
'key22':'value22',
'key33':'value33',
]
return this
But if App not "TEST" I have book map:
[
'key1':'value1',
'key2':'value2',
'key3':'value3',
]
If both each invocations do the exact same thing, you can extract the body of the closure you pass to each method to a variable and share it between both invocations. Consider the following (simplified) example:
def closure = { k, v ->
println "k = $k, v = $v"
}
def a = [
'key1': 'value1',
'key2': 'value2',
'key3': 'value3',
]
def b = [
'key11': 'value11',
'key22': 'value22',
'key33': 'value33',
]
println "Calling common closure on the map a:"
a.each closure
println "Calling common closure on the map b:"
b.each closure
In this example, I common closure to the variable, and then I was able to re-use it in both iterations. Also, keep in mind that in your example you are using maps, not lists. Calling each on map is absolutely correct, and in this case, you can use a closure with k and v parameters to map key and value accurately.

How to append build jobs into a map?

I'm trying to implement the code inspired from here with << operator in the following way:
builder = { name, param1, param2 ->
[job: name, parameters: [string(name: 'Param1', value: param1), string(name: 'Param2', value: param2)], quietPeriod: 2, wait: false]
}
node {
stage('Tests') {
def testBuilds = [:]
testBuilds << build *builder('Test', 'Foo', 'Bar')
testBuilds << build *builder('Test', 'Foo2', 'Bar2')
parallel testBuilds
}
}
where I expect to append two jobs into testBuilds map in order to run them in parallel.
However when running the job, I've got the following exception error:
groovy.lang.MissingPropertyException: No such property: build for class: groovy.lang.Binding
at groovy.lang.Binding.getVariable(Binding.java:63)
at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxInterceptor.onGetProperty(SandboxInterceptor.java:224)
at org.kohsuke.groovy.sandbox.impl.Checker$4.call(Checker.java:241)
at org.kohsuke.groovy.sandbox.impl.Checker.checkedGetProperty(Checker.java:238)
at org.kohsuke.groovy.sandbox.impl.Checker.checkedGetProperty(Checker.java:221)
at org.kohsuke.groovy.sandbox.impl.Checker.checkedGetProperty(Checker.java:221)
at com.cloudbees.groovy.cps.sandbox.SandboxInvoker.getProperty(SandboxInvoker.java:24)
...
What would be the correct syntax using the above approach?
You've got few problems in your code:
Branches description testBuild is a map (key: value) object, not a list. So you cannot left shift (<<) values into it the way you did, such operation is not supported for map
Jenkins pipeline parallel operation expects map with closure as its values.
build is part of pipeline DSL, not a common Groovy method. It appears it cannot accept arguments this way. While the expression build *builder(foo, bar) passes syntax check at first, omitting round brackets is just groovy syntactic sugar. Rewriting this line as build(*builder(foo, bar)) generates syntax error exception.
All in all, you can rewrite your code in a way like:
def builder(name, param1, param2) {
return build(job: name, parameters: [string(name: 'Param1', value: param1)], [string(name: 'Param2', value: param2)], quietPeriod: 2, wait: false)
}
node {
stage('Tests') {
def testBuilds = [:]
testBuilds['test1'] = { builder('Test', 'Foo', 'Bar') }
testBuilds['test2'] = { builder('Test', 'Foo2', 'Bar2') }
parallel testBuilds
}
}

How can I retrieve the build parameters from a queued job?

I would like to write a system groovy script which inspects the queued jobs in Jenkins, and extracts the build parameters (and build cause as a bonus) supplied as the job was scheduled. Ideas?
Specifically:
def q = Jenkins.instance.queue
q.items.each { println it.task.name }
retrieves the queued items. I can't for the life of me figure out where the build parameters live.
The closest I am getting is this:
def q = Jenkins.instance.queue
q.items.each {
println("${it.task.name}:")
it.task.properties.each { key, val ->
println(" ${key}=${val}")
}
}
This gets me this:
4.1.next-build-launcher:
com.sonyericsson.jenkins.plugins.bfa.model.ScannerJobProperty$ScannerJobPropertyDescriptor#b299407=com.sonyericsson.jenkins.plugins.bfa.model.ScannerJobProperty#5e04bfd7
com.chikli.hudson.plugin.naginator.NaginatorOptOutProperty$DescriptorImpl#40d04eaa=com.chikli.hudson.plugin.naginator.NaginatorOptOutProperty#16b308db
hudson.model.ParametersDefinitionProperty$DescriptorImpl#b744c43=hudson.mod el.ParametersDefinitionProperty#440a6d81
...
The params property of the queue element itself contains a string with the parameters in a property file format -- key=value with multiple parameters separated by newlines.
def q = Jenkins.instance.queue
q.items.each {
println("${it.task.name}:")
println("Parameters: ${it.params}")
}
yields:
dbacher params:
Parameters:
MyParameter=Hello world
BoolParameter=true
I'm no Groovy expert, but when exploring the Jenkins scripting interface, I've found the following functions to be very helpful:
def showProps(inst, prefix="Properties:") {
println prefix
for (prop in inst.properties) {
def pc = ""
if (prop.value != null) {
pc = prop.value.class
}
println(" $prop.key : $prop.value ($pc)")
}
}
def showMethods(inst, prefix="Methods:") {
println prefix
inst.metaClass.methods.name.unique().each {
println " $it"
}
}
The showProps function reveals that the queue element has another property named causes that you'll need to do some more decoding on:
causes : [hudson.model.Cause$UserIdCause#56af8f1c] (class java.util.Collections$UnmodifiableRandomAccessList)

Adding optional clauses to a Groovy DSL

I am developing a DSL with Groovy and I have run into the following problem. I have a method which performs some action on an object with the given parameters.
def run(x) {
[with:{ y -> foo(x,y) }]
}
run "thing" with "param" // evaluates to foo("thing","param")
Now, assume I want to add a default functionality to my DSL:
def runDefault(x) {
foo(x)
}
runDefault "thing" // evaluates to foo("thing")
Is there a way to combine the two into a single function, such that the with "param" part becomes an optional clause? I want to be able to use the DSL as shown below:
run "thing" with "param" // should do foo("thing","param")
run "thing" // should do foo("thing")
if you are able to distinguish both calls in the run-method, you could do something like this:
def run(x) {
switch (x) {
case 'foo':
println "foo($x)"; break;
case 'bar':
[with:{ y -> println "bar($x,$y)" }]; break;
}
}
run "bar" with "param"
run "foo"

Groovy Spock unit tests with closures

How can I do this in Spock/groovy?
package org.jenkinsci.plugins
import hudson.matrix.*
import spock.lang.*
import org.junit.Rule
import org.jvnet.hudson.test.JenkinsRule
class xxxx extends Specification {
#Rule JenkinsRule rule = new JenkinsRule()
def 'matrix'() {
given:
def matrixProject = rule.createMatrixProject()
AxisList axl = new AxisList();
def axis = new TextAxis('TEST', "1", "2", "3")
axl.add(axis)
matrixProject.setAxes(axl)
expect: matrixProject.scheduleBuild2(0).get().logFile.text.contains("Some String!")
matrixProject.scheduleBuild2(0).get().getRuns().each(){
expect: it.logFile.text.contains("Another String")
}
}
}
specifically, how can I run a closure with a nested test? The "Another String" test doesn't work
Does this work?
def 'matrix'() {
given:
def matrixProject = rule.createMatrixProject()
def axis = new TextAxis('TEST', "1", "2", "3")
matrixProject.axes.add(axis)
expect:
with( matrixProject.scheduleBuild2(0).get() ) {
logFile.text.contains("Some String!")
runs.every { it.logFile.text.contains("Another String") }
}
}
}
Either use every instead of each, or use a nested assert.
I'm not sure if I understand your question well. However if by nested test you mean evaluating statement inside of each closure, why not just use assert
expect:
matrixProject.scheduleBuild2(0).get().logFile.text.contains("Some String!")
matrixProject.scheduleBuild2(0).get().getRuns().each() {
assert it.logFile.text.contains("Another String")
}
#tim_yates's approach also seems fine and it's more like Spock's way. I haven't tested it though.
EDIT
If you want be sure that all logFiles contain test string then use 'every' method as Peter suggested.
expect:
...
matrixProject.scheduleBuild2(0).get().getRuns().every {
it.text.contains('Another String')
}
Other approach, if you prefer to know how many logFiles don't contain test string on test fail count them and compare result size to zero:
expect:
...
matrixProject.scheduleBuild2(0).get().getRuns().count {
!it.text.contains('Another String')
} == 0
Yet another, if you like to know which files caused test to fail, get names of those which don't contain test string and compare that to an empty list:
expect:
...
matrixProject.scheduleBuild2(0).get().getRuns().findAll {
!it.text.contains('Another String')
}*.name == []

Resources