Трубопровод Дженкинса NotSerializableException: groovy.json.internal.LazyMap
Решено: Благодаря приведенному ниже ответу С.Ричмонда. Мне нужно было сбросить все сохраненные карты groovy.json.internal.LazyMap
тип, который означал обнуление переменных envServers
а также object
после использования.
Дополнительно: людям, которые ищут эту ошибку, может быть интересно использовать шаг конвейера Jenkins. readJSON
вместо этого - найдите больше информации здесь.
Я пытаюсь использовать Jenkins Pipeline для получения ввода от пользователя, который передается в задание в виде строки json. Затем конвейер анализирует это с помощью slurper, и я выбираю важную информацию. Затем эта информация будет использоваться для запуска 1 задания несколько раз параллельно с различными параметрами задания.
До тех пор, пока я не добавлю код ниже "## Error when below here is added"
скрипт будет работать нормально Даже код ниже этой точки будет работать сам по себе. Но при объединении я получаю ошибку ниже.
Я должен отметить, что вызванное задание вызывается и выполняется успешно, но возникает ошибка ниже и не выполняется основное задание. Из-за этого основная работа не ждет возврата сработавшего задания. Я мог бы попытаться / поймать во всем build job:
однако я хочу, чтобы основная работа дожидалась окончания сработавшей работы.
Кто-нибудь может помочь здесь? Если вам нужна дополнительная информация, дайте мне знать.
ура
def slurpJSON() {
return new groovy.json.JsonSlurper().parseText(BUILD_CHOICES);
}
node {
stage 'Prepare';
echo 'Loading choices as build properties';
def object = slurpJSON();
def serverChoices = [];
def serverChoicesStr = '';
for (env in object) {
envName = env.name;
envServers = env.servers;
for (server in envServers) {
if (server.Select) {
serverChoicesStr += server.Server;
serverChoicesStr += ',';
}
}
}
serverChoicesStr = serverChoicesStr[0..-2];
println("Server choices: " + serverChoicesStr);
## Error when below here is added
stage 'Jobs'
build job: 'Dummy Start App', parameters: [[$class: 'StringParameterValue', name: 'SERVER_NAME', value: 'TestServer'], [$class: 'StringParameterValue', name: 'SERVER_DOMAIN', value: 'domain.uk'], [$class: 'StringParameterValue', name: 'APP', value: 'application1']]
}
Ошибка:
java.io.NotSerializableException: groovy.json.internal.LazyMap
at org.jboss.marshalling.river.RiverMarshaller.doWriteObject(RiverMarshaller.java:860)
at org.jboss.marshalling.river.RiverMarshaller.doWriteObject(RiverMarshaller.java:569)
at org.jboss.marshalling.river.BlockMarshaller.doWriteObject(BlockMarshaller.java:65)
at org.jboss.marshalling.river.BlockMarshaller.writeObject(BlockMarshaller.java:56)
at org.jboss.marshalling.MarshallerObjectOutputStream.writeObjectOverride(MarshallerObjectOutputStream.java:50)
at org.jboss.marshalling.river.RiverObjectOutputStream.writeObjectOverride(RiverObjectOutputStream.java:179)
at java.io.ObjectOutputStream.writeObject(Unknown Source)
at java.util.LinkedHashMap.internalWriteEntries(Unknown Source)
at java.util.HashMap.writeObject(Unknown Source)
...
...
Caused by: an exception which occurred:
in field delegate
in field closures
in object org.jenkinsci.plugins.workflow.cps.CpsThreadGroup@5288c
13 ответов
Я столкнулся с этим сам сегодня и через некоторую грубую силу я понял, как решить эту проблему и, возможно, почему.
Наверное, лучше всего начать с почему:
У Jenkins есть парадигма, где все задания могут быть прерваны, приостановлены и возобновлены через перезагрузки сервера. Чтобы достичь этого, конвейер и его данные должны быть полностью сериализуемыми - т.е. он должен иметь возможность сохранять состояние всего. Точно так же он должен иметь возможность сериализовать состояние глобальных переменных между узлами и подзадачами в сборке, что, как я думаю, происходит с вами и с вами, и почему это происходит, только если вы добавляете этот дополнительный шаг сборки.
По какой-то причине JSONObject не сериализуются по умолчанию. Я не Java-разработчик, поэтому я не могу сказать больше по этой теме, к сожалению. Существует множество ответов о том, как можно исправить это должным образом, хотя я не знаю, насколько они применимы к Groovy и Jenkins. Смотрите этот пост для немного больше информации.
Как вы это исправите:
Если вы знаете, как, вы можете сделать сериализуемый JSONObject как-то. В противном случае вы можете решить эту проблему, убедившись, что глобальные переменные не относятся к этому типу.
Попробуйте сбросить настройки object
var или оборачивая его в метод, чтобы его область видимости не была глобальной.
Использование JsonSlurperClassic
вместо.
Начиная с Groovy 2.3 (примечание: Jenkins 2.7.1 использует Groovy 2.4.7) JsonSlurper
возвращается LazyMap
вместо HashMap
, Это делает новую реализацию JsonSlurper
не потокобезопасен и не сериализуем. Это делает его непригодным для использования вне функций @NonDSL в конвейерных сценариях DSL.
Однако вы можете отступить к groovy.json.JsonSlurperClassic
который поддерживает старое поведение и может быть безопасно использован в сценариях конвейера.
пример
import groovy.json.JsonSlurperClassic
@NonCPS
def jsonParse(def json) {
new groovy.json.JsonSlurperClassic().parseText(json)
}
node('master') {
def config = jsonParse(readFile("config.json"))
def db = config["database"]["address"]
...
}
пс. Вам все еще нужно будет одобрить JsonSlurperClassic
прежде чем это можно было бы назвать.
РЕДАКТИРОВАТЬ: Как отметил Sunvic в комментариях, приведенное ниже решение не работает как есть для массивов JSON.
Я имел дело с этим с помощью JsonSlurper
а затем создание нового HashMap
из ленивых результатов. HashMap
является Serializable
,
Я считаю, что это потребовало внесения в белый список new HashMap(Map)
и JsonSlurper
,
@NonCPS
def parseJsonText(String jsonText) {
final slurper = new JsonSlurper()
return new HashMap<>(slurper.parseText(jsonText))
}
В целом, я бы рекомендовал использовать плагинPipeline Utility Steps, так как он имеет readJSON
шаг, который может поддерживать файлы в рабочей области или текст.
Я хочу поднять один из ответов: я бы порекомендовал просто использовать плагин Pipeline Utility Steps, так как он имеет шаг readJSON, который может поддерживать файлы в рабочей области или текст: https://jenkins.io/doc/pipeline/steps / трубопровод-коммунальный-шаги /#readjson-readjson-из-файлов-в-рабочее пространство
script{
def foo_json = sh(returnStdout:true, script: "aws --output json XXX").trim()
def foo = readJSON text: foo_json
}
Это НЕ требует никаких белых списков или дополнительных вещей.
Это подробный ответ, который был запрошен.
У меня работал unset:
String res = sh(script: "curl --header 'X-Vault-Token: ${token}' --request POST --data '${payload}' ${url}", returnStdout: true)
def response = new JsonSlurper().parseText(res)
String value1 = response.data.value1
String value2 = response.data.value2
// unset response because it's not serializable and Jenkins throws NotSerializableException.
response = null
Я читаю значения из проанализированного ответа и, когда мне больше не нужен объект, я сбрасываю его.
Вы можете использовать следующую функцию для преобразования LazyMap в обычную LinkedHashMap (она сохранит порядок исходных данных):
LinkedHashMap nonLazyMap (Map lazyMap) {
LinkedHashMap res = new LinkedHashMap()
lazyMap.each { key, value ->
if (value instanceof Map) {
res.put (key, nonLazyMap(value))
} else if (value instanceof List) {
res.put (key, value.stream().map { it instanceof Map ? nonLazyMap(it) : it }.collect(Collectors.toList()))
} else {
res.put (key, value)
}
}
return res
}
...
LazyMap lazyMap = new JsonSlurper().parseText (jsonText)
Map serializableMap = nonLazyMap(lazyMap);
или лучше использовать шаг readJSON, как отмечалось в предыдущих комментариях:
Map serializableMap = readJSON text: jsonText
Несколько более обобщенная форма ответа от @mkobit, которая позволила бы расшифровать массивы и карты:
import groovy.json.JsonSlurper
@NonCPS
def parseJsonText(String json) {
def object = new JsonSlurper().parseText(json)
if(object instanceof groovy.json.internal.LazyMap) {
return new HashMap<>(object)
}
return object
}
ПРИМЕЧАНИЕ. Имейте в виду, что это преобразует только объект LazyMap верхнего уровня в HashMap. Любые вложенные объекты LazyMap по-прежнему будут там и будут вызывать проблемы с Дженкинсом.
В соответствии с передовыми практиками, опубликованными в блоге Jenkins ( передовая практика масштабирования конвейера), настоятельно рекомендуется использовать инструменты командной строки или скрипты для такого рода работы:
Попался: особенно избегайте синтаксического анализа Pipeline XML или JSON с использованием Groovy XmlSlurper и JsonSlurper! Я очень предпочитаю инструменты или сценарии командной строки.
я. Реализации Groovy сложны и, как следствие, более неустойчивы в использовании конвейера.
II. XmlSlurper и JsonSlurper могут нести высокую стоимость памяти и процессора в конвейерах.
iii. xmllint и xmlstartlet - это инструменты командной строки, предлагающие извлечение XML через xpath
iv. jq предлагает ту же функциональность для JSON
v. Эти инструменты извлечения могут быть связаны с curl или wget для получения информации из HTTP API.
Таким образом, это объясняет, почему большинство решений, предлагаемых на этой странице, по умолчанию блокируются песочницей плагина сценария безопасности Jenkins.
Философия языка Groovy ближе к Bash, чем к Python или Java. Кроме того, это означает, что выполнять сложную и тяжелую работу в родном Groovy неестественно.
Учитывая это, я лично решил использовать следующее:
sh('jq <filters_and_options> file.json')
См. Jq Manual и Select objects with jq stackru post для получения дополнительной помощи.
Это интуитивно понятно, поскольку Groovy предоставляет множество универсальных методов, которых нет в белом списке по умолчанию.
Если вы решили использовать Groovy язык в любом случае большую часть своей работы, с песочницей включена и чистый (не легко, потому что не естественно), я рекомендую вам проверить белые списки для версии вашей безопасности скрипта плагина, чтобы знать, что ваши возможности: Script белые списки плагинов безопасности
Способ реализации подключаемого модуля конвейера имеет довольно серьезные последствия для нетривиального кода Groovy. Эта ссылка объясняет, как избежать возможных проблем: https://github.com/jenkinsci/pipeline-plugin/blob/master/TUTORIAL.md#serializing-local-variables
В вашем конкретном случае я хотел бы добавить @NonCPS
аннотация к slurpJSON
и возвращая карту-карты вместо объекта JSON. Не только код выглядит чище, но и более эффективен, особенно если этот JSON сложен.
Нуб ошибка с моей стороны. Кто-то перенес код из старого конвейерного плагина, jenkins 1.6? на сервер под управлением последней версии 2.x Jenkins.
Не удалось по этой причине: "java.io.NotSerializableException: groovy.lang.IntRange" Я продолжал читать и читать этот пост несколько раз для вышеуказанной ошибки. Реализовано: for (num in 1..numSlaves) { IntRange - не сериализуемый тип объекта.
Переписано в простой форме: for (num = 1; num <= numSlaves; num++)
Все хорошо с миром.
Я не использую Java или Groovy очень часто.
Спасибо, парни.
Другие идеи в этом посте были полезны, но не совсем то, что я искал - поэтому я извлек части, которые соответствуют моим потребностям, и добавил некоторые из моих собственных магиксов...
def jsonSlurpLaxWithoutSerializationTroubles(String jsonText)
{
return new JsonSlurperClassic().parseText(
new JsonBuilder(
new JsonSlurper()
.setType(JsonParserType.LAX)
.parseText(jsonText)
)
.toString()
)
}
Да, как я отметил в своем собственном git commit кода, "Дико-неэффективный, но крошечный коэффициент: решение для разрыва JSON" (с этим у меня все в порядке). Аспекты, которые мне нужно было решить:
- Полностью уйти от
java.io.NotSerializableException
проблема, даже когда текст JSON определяет вложенные контейнеры - Работать как с картой, так и с массивом
- Поддержка синтаксического анализа LAX (самая важная часть для моей ситуации)
- Простота реализации (даже с неудобными вложенными конструкторами, которые устраняют
@NonCPS
)
Если вы не можете использовать JsonSlupurClassic, вот простой способ преобразовать LazyMap в LinkedHashMap.
def deserialize(String jsonStr) {
def obj = new JsonSlurper().parseText(jsonStr)
if (obj instanceof Map) {
return cloneMap(obj)
} else if (obj instanceof List) {
return cloneList(obj)
} else {
return obj
}
return
}
def cloneMap(Map map) {
def res = [:]
map.each {e ->
def key = e.getKey()
def value = e.getValue()
if (value instanceof Map) {
res.put(key, cloneMap(value))
} else if (value instanceof List) {
res.put(key, cloneList(value))
} else {
res.put(key, value)
}
}
return res
}
def cloneList(List list) {
def res = []
list.each {i ->
if (i instanceof List) {
res.add(cloneList(i))
} else if (i instanceof Map) {
res.add(cloneMap(i))
} else {
res.add(i)
}
}
return res
}
Я нашел более простой путь в документах для конвейера Jenkins
Пример работы
import groovy.json.JsonSlurperClassic
@NonCPS
def jsonParse(def json) {
new groovy.json.JsonSlurperClassic().parseText(json)
}
@NonCPS
def jobs(list) {
list
.grep { it.value == true }
.collect { [ name : it.key.toString(),
branch : it.value.toString() ] }
}
node {
def params = jsonParse(env.choice_app)
def forBuild = jobs(params)
}
Из-за ограничений в Workflow - то есть JENKINS-26481 - на самом деле невозможно использовать Groovy-замыкания или синтаксис, который зависит от замыканий, поэтому вы не можете> выполнить Groovy-стандарт использования.collectEntries в списке и генерировать шаги как значения для полученных записей. Вы также не можете использовать стандартный> синтаксис Java для циклов For, т. Е. "For (String s: strings)", и вместо этого вам придется использовать циклы, основанные на старой школе.