Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Impossibility to iterate over a Map using Groovy within Jenkins Pipeline

Tags:

We are trying to iterate over a Map, but without any success. We reduced our issue to this minimal example:

def map = [            'monday': 'mon',            'tuesday': 'tue',            ] 

If we try to iterate with:

map.each{ k, v -> println "${k}:${v}" } 

Only the first entry is output: monday:mon


The alternatives we know of are not even able to enter the loop:

for (e in map) {     println "key = ${e.key}, value = ${e.value}" } 

or

for (Map.Entry<String, String> e: map.entrySet()) {     println "key = ${e.key}, value = ${e.value}" } 

Are failing, both only showing the exception java.io.NotSerializableException: java.util.LinkedHashMap$Entry. (which could be related to an exception occurring while raising the 'real' exception, preventing us from knowing what happened).

We are using latest stable jenkins (2.19.1) with all plugins up-to-date as of today (2016/10/20).

Is there a solution to iterate over elements in a Map within a Jenkins pipeline Groovy script ?

like image 285
Ad N Avatar asked Oct 20 '16 15:10

Ad N


People also ask

How do I iterate a map in Groovy?

We can iterate through entries using the each() and eachWithIndex() methods. The each() method provides implicit parameters, like entry, key, and value, which correspond to the current Entry. The eachWithIndex() method also provides an index in addition to Entry. Both the methods accept a Closure as an argument.

How do I read a map in Groovy?

Look up the key in this Map and return the corresponding value. If there is no entry in this Map for the key, then return null. Obtain a Set of the keys in this Map. Associates the specified value with the specified key in this Map.

Are Groovy maps ordered?

Maps don't have an order for the elements, but we may want to sort the entries in the map. Since Groovy 1.7. 2 we can use the sort() method which uses the natural ordering of the keys to sort the entries. Or we can pass a Comparator to the sort() method to define our own sorting algorithm for the keys.


1 Answers

Its been some time since I played with this, but the best way to iterate through maps (and other containers) was with "classical" for loops, or the "for in". See Bug: Mishandling of binary methods accepting Closure

To your specific problem, most (all?) pipeline DSL commands will add a sequence point, with that I mean its possible to save the state of the pipeline and resume it at a later time. Think of waiting for user input for example, you want to keep this state even through a restart. The result is that every live instance has to be serialized - but the standard Map iterator is unfortunately not serializable. Original Thread

The best solution I can come up with is defining a Function to convert a Map into a list of serializable MapEntries. The function is not using any pipeline steps, so nothing has to be serializable within it.

@NonCPS def mapToList(depmap) {     def dlist = []     for (def entry2 in depmap) {         dlist.add(new java.util.AbstractMap.SimpleImmutableEntry(entry2.key, entry2.value))     }     dlist } 

This has to be obviously called for each map you want to iterate, but the upside it, that the body of the loop stays the same.

for (def e in mapToList(map)) {     println "key = ${e.key}, value = ${e.value}" } 

You will have to approve the SimpleImmutableEntry constructor the first time, or quite possibly you could work around that by placing the mapToList function in the workflow library.

like image 122
Norbert Lange Avatar answered Sep 27 '22 22:09

Norbert Lange