Nahshon paz
Nahshon paz

Reputation: 4563

Iterating Jenkins groovy map, with multiple sets

I'd like to ask for help with a a Jenkins groovy pipeline, copied from here: Is it possible to create parallel Jenkins Declarative Pipeline stages in a loop?

I'd like for a several sets of vars to be passed in a under a map, for several stages under a parallel run. However, only the last set (square brackets at the bottom of the map) gets registered for my map.

When the parallel stage runs, the map iterates successfully, but only with the last set (currently install_Stage(it)), ignoring other sets. Meaning that I get a pipeline showing four "stage: install ${product}" stages in parallel, and that's it. I'd like to get three parallels with four stages (network setup, revert, and install), as per my code below:

#!groovy

@Library('ci_builds')

def products = ["A", "B", "C", "D"]

def parallelStagesMap = products.collectEntries {

    switch (it) {
        case "A":
            static_ip_address = "10.100.100.6"; static_vm_name = "install-vm1"; version = "14.1.60"
            break
        case "B":
            static_ip_address = "10.100.100.7"; static_vm_name = "install-vm2"; version = "15.1"
            break
        case "C":
            static_ip_address = "10.100.100.8"; static_vm_name = "install-vm3"; version = "15.1"
            break
        case "D":
            static_ip_address = "10.100.100.9"; static_vm_name = "install-vm4"; version = "15.2"
            break
        default:
            static_ip_address = "The product name is not on the switch list - please enter an ip address"
            version = "The product name is not on the switch list - please enter a version"
            break
    }

    ["${it}" : network_reg(it)]

    ["${it}" : revert_to_snapshot_Stage(it)]

    ["${it}" : install_Stage(it)]

}

def network_reg(product) {
return {
    stage("stage: setup network for ${product}") {
        echo "setting network on ${static_vm_name} with ${static_ip_address}."
        sh script: "sleep 15"
    }
}
}

def revert_to_snapshot_Stage(product) {
return {
    stage("stage: revert ${product}") {
        echo "reverting ${static_vm_name} for ${product} on ${static_ip_address}."
        sh script: "sleep 15"
    }
}
}


def install_Stage(product) {
return {
    stage("stage: install ${product}") {
        echo "installing ${product} on ${static_ip_address}."
        sh script: "sleep 15"
    }
}
}

pipeline {
agent any

stages {
    stage('non-parallel env check') {
        steps {
            echo 'This stage will be executed first.'
        }
    }

    stage('parallel stage') {
        steps {
            script {
                parallel parallelStagesMap
            }
        }
    }
}
}

The network_reg and revert_to_snapshot_Stage won't run (unless I place them as the last set instead of ["${it}" : install_Stage(it)] , in which case, again, only the one of the parallel stages is run)

I don't mind a different approach to run several map definitions, but others such as: How to define and iterate over map in Jenkinsfile don't allow for a full multi variable map (more than a key+value pair)

Any help would be appreciated, Thanks!

Upvotes: 1

Views: 6605

Answers (1)

Joerg S
Joerg S

Reputation: 5129

I assume you have a similar issue like I had trying to dynamically build the parallel branches for parallel execution.

Two things were very important:

  1. Make a copy of the loop variable (in you case: it) and use that copy only inside the parallel branch; if you don't all branches (closures) will reference the very same variable which of course will have the same value. That is particular to closures. See also: http://groovy-lang.org/closures.html.

  2. Don't use collectEntries{}. Stick to the java-style loops as groovy loops most of the time do not work properly. Some .each{} constructs may work already but if in doubt switch to the java loops. See also: Impossibility to iterate over a Map using Groovy within Jenkins Pipeline

Following stripped down example works for me. I believe you'll be able to adjust it to your needs.

def products = ["A", "B", "C", "D"]
def parallelStagesMap = [:]

// use java-style loop
for (def product: products) {
    // make a copy to ensure that each closure will get it's own variable
    def copyOfProduct = product
    parallelStagesMap[product] = {echo "install_Stage($copyOfProduct)"}
}

echo parallelStagesMap.toString()

pipeline {
agent any
    stages {
        stage('parallel stage') {
            steps {
                script {
                    parallel parallelStagesMap
                }
            }
        }
    }
}
  1. If it still doesn't work: Check whether there's and upgrade your Pipeline: Groovy plugin as they usually fix lot of issues which usually work in groovy but won`t in pipeline.

You may want to check following related question which contains a minimal example as well: Currying groovy CPS closure for parallel execution

Upvotes: 6

Related Questions