web-dev-qa-db-fra.com

Pourquoi une boucle dans un fichier Jenkins s'arrête à la première itération

Voici le contenu de mon Jenkinsfile:

node {
    // prints only the first element 'a'
    [ 'a', 'b', 'c' ].each {
        echo it
    }
}

Lors de l'exécution du travail dans Jenkins (avec Plugin Pipeline ), seul le premier élément de la liste est imprimé.

Quelqu'un peut-il m'expliquer ce comportement étrange? Est-ce un bug? ou est-ce juste moi qui ne comprends pas la syntaxe Groovy?

Edit : la for (i in items) fonctionne comme prévu:

node {
    // prints 'a', 'b' and 'c'
    for (i in [ 'a', 'b', 'c' ]) {
        echo i
    }
}
14
Eric Citaire

La réponse acceptée ici indique qu'il s'agit d'un bogue connu et utilise une solution de contournement qui ne fonctionnait pas pour moi, donc je proposerai une mise à jour avec ce que j'ai trouvé récemment.

Malgré la résolution de JENKINS-26481 (assez récente, au moment d'écrire ces lignes), de nombreuses personnes peuvent être bloquées avec une ancienne version de Jenkins où le correctif n'est pas disponible. L'itération en boucle sur une liste littérale peut parfois fonctionner, mais des problèmes connexes tels que JENKINS-46749 et JENKINS-46747 semblent continuer de perturber de nombreux utilisateurs. En outre, selon le contexte exact de votre fichier Jenkins, echo fonctionnera peut-être tandis que sh échouera, et les choses pourraient échouer en silence ou planter la génération avec des échecs de sérialisation.

Si vous n'aimez pas les surprises (boucles sautées et échecs silencieux) et si vous voulez que vos fichiers Jenkins soient les plus portables sur plusieurs versions de Jenkins, l'idée principale semble être que vous devriez toujours utiliser des compteurs classiques dans vos boucles for et ignorez les autres fonctionnalités groovy.

This Gist est la meilleure référence que j'ai vue et énonce de nombreux cas qui, selon vous, devraient fonctionner de la même manière mais ont un comportement étonnamment différent. C'est un bon point de départ pour établir des contrôles d'intégrité et déboguer votre configuration, quel que soit le type d'itération que vous regardez et que vous essayiez d'utiliser @NonCPS, faites votre itération directement dans node{}, ou appelez une fonction distincte.

Encore une fois, je ne prends aucun crédit pour le travail lui-même, mais j'intègre les scénarios de test Gist of iteration ci-dessous pour la postérité:

abcs = ['a', 'b', 'c']

node('master') {
    stage('Test 1: loop of echo statements') {
        echo_all(abcs)
    }
    stage('Test 2: loop of sh commands') {
        loop_of_sh(abcs)
    }
    stage('Test 3: loop with preceding SH') {
        loop_with_preceding_sh(abcs)
    }
    stage('Test 4: traditional for loop') {
        traditional_int_for_loop(abcs)
    }
}

@NonCPS // has to be NonCPS or the build breaks on the call to .each
def echo_all(list) {
    list.each { item ->
        echo "Hello ${item}"
    }
}
// outputs all items as expected

@NonCPS
def loop_of_sh(list) {
    list.each { item ->
        sh "echo Hello ${item}"
    }
}
// outputs only the first item

@NonCPS
def loop_with_preceding_sh(list) {
    sh "echo Going to echo a list"
    list.each { item ->
        sh "echo Hello ${item}"
    }
}
// outputs only the "Going to echo a list" bit

//No NonCPS required
def traditional_int_for_loop(list) {
    sh "echo Going to echo a list"
    for (int i = 0; i < list.size(); i++) {
        sh "echo Hello ${list[i]}"
    }
}
// echoes everything as expected
18
mvr

Merci à @ batmat on # jenkins IRC channel pour avoir répondu à cette question!

C'est en fait un bug connu: JENKINS-26481 .

5
Eric Citaire

Une solution de contournement pour ce problème consiste à développer toutes les commandes dans un fichier texte plat en tant que script groovy. Utilisez ensuite l'étape de chargement pour charger le fichier et l'exécuter.

Par exemple:

@NonCPS
def createScript(){
    def cmd=""
    for (i in [ 'a', 'b', 'c' ]) {
        cmd = cmd+ "echo $i"
    }
    writeFile file: 'steps.groovy', text: cmd
}

Appelez ensuite la fonction comme

createScript()
load 'steps.groovy'
2
Shengyue