Объединение нескольких сборников в Groovy и Jenkins Pipeline

Я пытаюсь использовать несколько collectEntries последовательно в моем сценарии Groovy. Лучше посмотреть код, сейчас у меня есть:

stage('Test') {
            // Reading content of the file
            def portsFileContent = readFile 'UsedPorts.txt'

            // Split the file by next line
            def ports = portsFileContent.split('\n')

            def steps = ports.collectEntries { port ->
                ["UI Test on port $port", {
                    sh "#!/bin/bash -lx \n startServerWithDifferentPort --params=port=$port"
                }]
            }
            parallel steps
        }

В файле "UsedPorts.txt" разные порты разделены разрывом строки, например:

4723
4733
4743

Таким образом, эти числа сохраняются в переменной ports, и эта переменная затем используется для запуска экземпляра сервера для каждого порта. Таким образом, в этом случае он запустит 3 разных экземпляра сервера с помощью этой команды:

def steps = ports.collectEntries { port ->
                ["UI Test on port $port", {
                    sh "#!/bin/bash -lx \n startServerWithDifferentPort --params=port=$port"
                }]
            }
            parallel steps

Из-за parallel steps в нем запускаются 3 экземпляра сервера одновременно с разными портами.

Работает нормально, но у меня есть другой файл, и мне нужно сделать то же самое снова. Итак, в моем втором файле есть такие записи, как:

name1
name2
name3

Я снова создал переменную, в которой храню свои 3 записи:

def anotherFile = readFile 'Names.txt'
def names = anotherFile.split('\n')

Вот что я пробовал:

stage('Test') {
            // Reading content of the file
            def portsFileContent = readFile 'UsedPorts.txt'

            // Split the file by next line
            def ports = portsFileContent.split('\n')

            // Do the same again
            def anotherFile = readFile 'Names.txt'
            def names = anotherFile.split('\n')

            def steps = ports.collectEntries, names.collectEntries { port, name ->
                ["UI Test on $name", {
                    sh "#!/bin/bash -lx \n someMoreShellStuff --params=port=$port"
                }]
            }
            parallel steps
        }

Но я не могу разделить свой второй collectEntries запятой, потому что это дает мне синтаксическую ошибку. И теперь моя проблема в том, как я могу использовать эту переменную в одной команде. Это вообще возможно?

Спасибо

Обновление №1

После использования ответа Шимона Степняка мой новый код выглядит так:

stage('Test') {
            // Reading content of the file
            def portsFileContent = readFile 'AppiumUsedPorts.txt'

            // Split the file by next line
            def ports = portsFileContent.split('\n')

            // Getting device IDs to get properties of device
            def deviceIDFileContent = readFile 'DeviceIDs.txt'
            def deviceIDs = deviceIDFileContent.split('\n')

            // Define port and id as an pair
            def pairs = (0..Math.min(ports.size(), deviceIDs.size())).collect { i -> [id: deviceIDs[i], port: ports[i]] }

            def steps = pairs.collectEntries { pair ->
                ["UI Test on ${pair.id}", {
                    sh "echo 'Running test with port ${pair.port}'"
                }]
            }
            parallel steps
        }

Это вызывает ошибку java.lang.ArrayIndexOutOfBoundsException

Обновление №2

Содержание AppiumUsedPorts.txt:

4723
4733

Содержание DeviceIDs.txt

5353352c
G000KU0663550R92

person rm -rf    schedule 12.01.2018    source источник
comment
Чего вы ждете от ports.collectEntries, names.collectEntries { port, name -> ...? Этот синтаксис неверен.   -  person Szymon Stepniak    schedule 12.01.2018
comment
Я знаю, что синтаксис неверен, это именно то, чего я пытаюсь добиться. Я хочу использовать значения портов и имен в одном сценарии оболочки   -  person rm -rf    schedule 12.01.2018
comment
Но как вы хотите его использовать? Вы этого не объяснили.   -  person Szymon Stepniak    schedule 12.01.2018
comment
В первом кодовом блоке все работало, как ожидалось, но мне нужен второй collectEntries, и я хочу использовать его с той же переменной steps, потому что я могу выполнить steps только один раз. Как вы можете видеть во втором блоке кода, я пытался разделить collectEntries запятой, но, как вы упомянули, это дает мне синтаксическую ошибку. Теперь мне нужна ваша помощь, как я могу использовать значения ports и names в переменной steps?   -  person rm -rf    schedule 12.01.2018
comment
то, как вы написали, это ерунда для любого языка программирования JVM, включая саму java. Отсюда снова вопрос: чего вы хотите достичь?   -  person injecteer    schedule 12.01.2018
comment
Я отредактировал свой ответ, надеюсь, теперь стало более понятно, чего я пытаюсь достичь   -  person rm -rf    schedule 12.01.2018


Ответы (1)


Похоже, вы хотите заархивировать элементы из двух списков - ports и names и использовать эти пары при создании шагов для параллельного выполнения. Предположим, что ports и names содержат что-то вроде:

def ports = [8080, 8081, 8082, 8083]
def names = ['Host A', 'Host B', 'Host C', 'Host D', 'Host E']

вам нужен список пар, например:

def pairs = [[port: 8080, name: 'Host A'], [port: 8081, name: 'Host B'], [port: 8082, name: 'Host C'], [port:8083, 'Host D']]

Я специально использовал два списка разного размера, чтобы объяснить, что результат сжатия двух списков всегда имеет тот же размер, что и самый короткий список.

Groovy имеет метод GroovyCollections.transpose(List lists), который берет список списков (например, [[8080, 8081, 8082, 8083], ['Host A', 'Host B', 'Host C', 'Host D', 'Host E']]) и "скрепляет" два списка вместе, например:

[[8080, 'Host A'], [8081, 'Host B'], [8082, 'Host C'], [8083, 'Host D']]

но он не будет работать в Jenkins Pipeline - если вы попытаетесь его использовать, вы получите:

org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException: Scripts not permitted to use staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods transpose java.util.List

В любом случае вы можете просто сделать то же самое, используя collect в диапазоне от 0 до min(ports.size(), names.size()), чтобы создать список этих пар / карт. Взгляните на следующий пример:

node {
    stage('Test') {
        def ports = [8080, 8081, 8082, 8083]
        def names = ['Host A', 'Host B', 'Host C', 'Host D', 'Host E']

        def pairs = (0..<Math.min(ports.size(), names.size())).collect { i -> [name: names[i], port: ports[i]] }

        def steps = pairs.collectEntries { pair ->
            ["UI Test on ${pair.name}", {
                sh "echo 'Running test with port ${pair.port}'"
            }]
        }

        parallel steps
    }
}

В этом примере мы транспонируем два списка в список карт, например [port: ..., name: ...], и вызываем collectEntries в этом списке карт, чтобы получить и порт, и имя на одном этапе выполнения. Запуск этого сценария в Jenkins Pipeline дает следующий результат:

[Pipeline] {
[Pipeline] stage
[Pipeline] { (Test)
[Pipeline] parallel
[Pipeline] [UI Test on Host A] { (Branch: UI Test on Host A)
[Pipeline] [UI Test on Host B] { (Branch: UI Test on Host B)
[Pipeline] [UI Test on Host C] { (Branch: UI Test on Host C)
[Pipeline] [UI Test on Host D] { (Branch: UI Test on Host D)
[Pipeline] [UI Test on Host A] sh
[UI Test on Host A] [test-pipeline] Running shell script
[Pipeline] [UI Test on Host B] sh
[UI Test on Host B] [test-pipeline] Running shell script
[Pipeline] [UI Test on Host C] sh
[UI Test on Host C] [test-pipeline] Running shell script
[Pipeline] [UI Test on Host D] sh
[UI Test on Host A] + echo Running test with port 8080
[UI Test on Host A] Running test with port 8080
[UI Test on Host B] + echo Running test with port 8081
[UI Test on Host B] Running test with port 8081
[UI Test on Host D] [test-pipeline] Running shell script
[Pipeline] [UI Test on Host A] }
[UI Test on Host C] + echo Running test with port 8082
[UI Test on Host C] Running test with port 8082
[UI Test on Host D] + echo Running test with port 8083
[UI Test on Host D] Running test with port 8083
[Pipeline] [UI Test on Host B] }
[Pipeline] [UI Test on Host C] }
[Pipeline] [UI Test on Host D] }
[Pipeline] // parallel
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS

Надеюсь, поможет.

person Szymon Stepniak    schedule 12.01.2018
comment
Это именно то, что мне нужно, но когда он пытается прочитать второй файл, я всегда получаю java.lang.ArrayIndexOutOfBoundsException - person rm -rf; 12.01.2018
comment
Выдает это исключение при чтении файла? Дважды проверьте, что вызывает это исключение. Math.min(names.size(), ports.size()) используется для предотвращения такого исключения при транспонировании двух списков разного размера. В этом случае он создает список пар размера более короткого списка. - person Szymon Stepniak; 12.01.2018
comment
В моем выводе я вижу, что чтение первого файла было успешным, но при попытке прочитать значение второго он дает мне указанную ошибку. У меня оба списка всегда одинаковой длины, если это как-то помогает? - person rm -rf; 12.01.2018
comment
Но можете ли вы println names или в этот момент возникает исключение? Проверьте, какая строка вызывает это исключение, иначе трудно предложить решение - person Szymon Stepniak; 12.01.2018
comment
приводит к той же ошибке, вот изображение ошибки: picload.org/view/ ddllwdgi / error.png.html, но вывод консоли не предоставляет дополнительной информации: [Pipeline] [install-apk]} [Pipeline] // parallel [Pipeline]} [Pipeline] // stage [Pipeline] stage [Pipeline ] {(Тест) [Конвейер] readFile [Конвейер] readFile [Конвейер]} [Конвейер] // стадия [Конвейер] echo java.lang.ArrayIndexOutOfBoundsException: 2 [Конвейер] стадия [Конвейер] {(Очистить) [Конвейер] archiveArtifacts - person rm -rf; 12.01.2018
comment
Да, если вы хотите, чтобы мы помогли вам найти причину. Я не могу вывести из вывода консоли, что происходит. - person Szymon Stepniak; 12.01.2018
comment
Можете ли вы также публиковать AppiumUsedPorts.txt и DeviceIDs.txt содержимое? Тестирование обновленного скрипта с фиксированными списками ports и deviceIDs не вызывает никаких исключений, поэтому я предполагаю, что что-то происходит с текстовыми файлами, которые вы пытаетесь прочитать. - person Szymon Stepniak; 12.01.2018
comment
Конечно, я редактирую свой вопрос с помощью Обновление № 2. Спасибо за вашу помощь - person rm -rf; 12.01.2018
comment
Хорошо понял. Используемый здесь диапазон def pairs = (0..Math.min(ports.size(), deviceIDs.size())) должен быть исключен - def pairs = (0..<Math.min(ports.size(), deviceIDs.size())), поэтому в вашем случае он переходит в цикл [0,1] вместо [0,1,2]. Пропустил, извините. Я обновил свой ответ, чтобы использовать исключающий диапазон. - person Szymon Stepniak; 12.01.2018