why is my for-each loop not working properly in parallel stages in jenkins scripted syntax? [duplicate] - jenkins

In the context of Jenkins pipelines, I have some Groovy code that's enumerating a list, creating closures, and then using that value in the closure as a key to lookup another value in a map. This appears to be rife with some sort of anomaly or race condition almost every time.
This is a simplification of the code:
def tasks = [:]
for (platformName in platforms) {
// ...
tasks[platformName] = {
def componentUploadPath = componentUploadPaths[platformName]
echo "Uploading for platform [${platformName}] to [${componentUploadPath}]."
// ...
}
tasks.failFast = true
parallel(tasks)
platforms has two values. I will usually see two iterations and two tasks registered and the keys in tasks will be correct, but the echo statement inside the closure indicates that we're just running one of the platforms twice:
14:20:02 [platform2] Uploading for platform [platform1] to [some_path/platform1].
14:20:02 [platform1] Uploading for platform [platform1] to [some_path/platform1].
It's ridiculous.
What do I need to add or do differently?

It's the same issue as you'd see in Javascript.
When you generate the closures in a for loop, they are bound to a variable, not the value of the variable.
When the loop exits, and the closures are run, they will all be using the same value...that is -- the last value in the for loop before it exited
For example, you'd expect the following to print 1 2 3 4, but it doesn't
def closures = []
for (i in 1..4) {
closures << { -> println i }
}
closures.each { it() }
It prints 4 4 4 4
To fix this, you need to do one of two things... First, you could capture the value in a locally scoped variable, then close over this variable:
for (i in 1..4) {
def n = i
closures << { -> println n }
}
The second thing you could do is use groovy's each or collect as each time they are called, the variable is a different instance, so it works again:
(1..4).each { i ->
closures << { -> println i }
}
For your case, you can loop over platforms and collect into a map at the same time by using collectEntries:
def tasks = platforms.collectEntries { platformName ->
[
platformName,
{ ->
def componentUploadPath = componentUploadPaths[platformName]
echo "Uploading for platform [${platformName}] to [${componentUploadPath}]."
}
]
}
Hope this helps!

Related

Test if there is a match when using regular expression in jenkins pipeline

I am using regex to grab a number from a string in my pipeline
it works ok as long that I have a match, but when there is no match I get an error
java.lang.IndexOutOfBoundsException: index is out of range 0..-1 (index = 0)
There error happens when i try to capture the group on following line
env.ChangeNr = chngnr[0][1]
How can i test if there isn't a match from my capture group ?
This is the pipeline
pipeline {
agent {
node {
label 'myApplicationNode'
}
}
environment {
GIT_MESSAGE = "${bat(script: "git log --no-walk --format=format:%%s ${GIT_COMMIT}", returnStdout: true)}".readLines().drop(2).join(" ")
}
stages {
stage('get_commit_msg'){
steps {
script {
def gitmsg=env.GIT_MESSAGE
def chngnr = gitmsg =~/([0-9]{1,8})/
env.ChangeNr = chngnr[0][1] /* put test if nothing is extracted */
}
}
}
}
}
In groovy when you use the =~ (find operator) it actually creates a java.util.regex.Matcher and therefore you can use any of its standard methods like find() or size(), so in your case you can jest use the size function to test if there are any matched patterns before you attempt to extract any groups:
def chngnr = gitmsg =~/([0-9]{1,8})/
assert chngnr.size() > 0
env.ChangeNr = chngnr[0][1]
Another nice option is to use the =~ operator in context of boolean, in this case, Groovy implicitly invokes the matcher.find() method, which means that the expression evaluates to true if any part of the string matches the pattern:
def chngnr = gitmsg =~/([0-9]{1,8})/
if(chngnr){
env.ChangeNr = chngnr[0][1]
}
else {
...
}
You can read more info on Groovy Regular Expressions Here

stumped by simple groovy variable comparison

I have a groovy script (a Jenkins pipeline) and a simple variable comparison is not working like I expect. I have a class defined to hold some constants, like this:
class email_when {
static final int ON_FAILURE = 0
static final int ALWAYS = 1
}
At a certain point in the script I set an environment variable to one of these states, like this:
env.EMAIL_WHEN = email_when.ALWAYS
Then later, I check the value. This check is always failing and I don't understand why.
echo ("email when = "+env.EMAIL_WHEN+ " always = "+email_when.ALWAYS);
if (env.EMAIL_WHEN == email_when.ALWAYS)
{
echo ("Send email.")
}
else
{
echo ("NO EMAIL")
}
So this always prints
email when = 1 always = 1
NO EMAIL
I don't understand why?
I thought maybe it was some sort of object/value comparison thing? Although I am directly setting env.EMAIL_WHEN to email_when.ALWAYS.
I tried this and it still did the same thing:
if (env.EMAIL_WHEN.equals(email_when.ALWAYS))
Can anyone explain what I am missing?
Thanks!
Everything in env map automatically converted to String
so 1 != '1'

Checking for null pointer exception in an array in Jenkins scripted pipeline method

I am injecting Active Choices parameter value(s) in the Jenkins scripted pipeline.
PFB sample values passed to active choice parameter block:
return['ABC','DEF','GHI',JKL']
PFB my sample script:
node(){
selectModName()
}
def selectModName(){
stage 'Multi selection'
String[] mods = "${modName}".split(',')
modsz = mods.size()
echo ''+modsz+''
for(mod in mods){
if (modsz == null || modsz == 0){
echo 'There is nothing to be printed'
} else {
echo ''+mod+' is name of the module \n'
}
}
}
The else block is executed when I pass greater than or equal to 1 value(s) (working as intended). But if block is not executing its logic when I don't pass any parameter and press build now.
Funny thing is- size() is returning 1 instead of 0 (echo ''+modsz+'') when values aren't passed.
How to make if block execute its logic when no values are passed?
Your code always jumps to the "else" block, because
"".split(',')
produces an array with a single empty string.
assert "".split(',').size() == 1
assert "".splti(',') == [""] as String[]
When you use active choice parameter with multiple values selection and you don't select anything, your variable name stores an empty string. You should check first if the modName parameter is not an empty string and only otherwise split and display values.
node(){
selectModName()
}
def selectModName(){
stage 'Multi selection'
if (modName) {
String[] mods = modName?.split(',')
for (mod in mods) {
echo " ${mod} is name of the module"
}
} else {
echo 'There is nothing to be printed'
}
}

Jenkinsfile Compare variable

I have a Jenkinsfile where I would like to compare two variables in expression. Say my target should have v1 and v2 the identical value, the step should be skipped, but both variables don't have the identical value, jenkins should cancel the job.
My stage for it looks like this at the moment. Unfortunately, jenkins overrides this step even if both variable ones have different values.
stage('Compare') {
when {
expression { myVar = myVar2}
}
steps {
exit
}
}
You made a mistake in your when condition. The expression myVar = myVar2 is an assignment expression (you assign value of myVar2 to variable myVar). If you want to test if two variables are equal, you need to use == operator.
stage('Compare') {
when {
expression { myVar == myVar2 }
}
steps {
exit
}
}

Accessing config variables stored in maps by key

I have a variable in groovy like below:
project.Map
{
time.'1 1 * ?' = ['T1']
time.'2 1 * ?' = ['T2']
templates.'T1' = ['Z','X','Y']
templates.'T2' = ['Q']
}
Sorry but I am new to groovy ,when i try to access the individual
variable values in project.map how do i access them
i tried something like below
log.info(grailsApplication.config.project.Map.time[1])
log.info(grailsApplication.config.project.Map.get('time.'2 1 * ?'' ))
log.info(grailsApplication.config.project.Map.get('time[0]' ))
log.info(grailsApplication.config.project.Map.time.get('1 1 * ?'))
but they all print null value or object references.how do i access values for
time and templates both within a for loop and without it.
please see http://grails.org/doc/latest/guide/conf.html#config for the ways the config is allowed to nest. your outer syntax is especially mentioned to not be allowed:
However, you can't nest after using the dot notation. In other words, this won't work:
// Won't work!
foo.bar {
hello = "world"
good = "bye"
}
You have to write it as
project { Map { ... } }
The inner dotted parts (with the assignment) are ok (according to the doc)

Resources