Jenkins执行Gerrit review设置verified标签报LazyMap序列化异常
该报错和gerrit review命令的参数格式无关,是Jenkins Pipeline CPS序列化机制的典型问题:使用JsonSlurper解析gerrit query返回结果时,CURRENT_PATCHSET变量实际绑定了groovy.json.internal.LazyMap类型的非序列化对象引用,并非纯字符串/数字类的基础值。Jenkins在Pipeline执行到需要持久化工作流状态(如等待外部节点响应、跨步骤保存上下文)的节点时,会尝试序列化所有上下文中的变量,遇到不可序列化的LazyMap就会抛出该异常。
为什么传commit ID的命令可正常执行? 提取GERRIT_COMMIT_ID时刚好取到了基础类型值,没有保留LazyMap引用;提取CURRENT_PATCHSET时直接赋值了解析得到的对象,未做类型转换,导致非序列化对象残留在Pipeline上下文里。
- 在
CURRENT_PATCHSET赋值语句后增加打印逻辑,输出变量实际类型:echo CURRENT_PATCHSET.getClass().getName(),如果输出包含LazyMap或其他非基础类型(如Map、JsonObject),即可确认是序列化问题。 - 临时将
CURRENT_PATCHSET替换为硬编码的patchset编号执行命令,如果命令运行正常,可完全排除Gerrit端参数格式问题,定位为变量类型异常。
方案1:提取字段时强制转换为基础类型
从JsonSlurper解析结果中取值时,不要直接赋值对象,强制转换为字符串/整数基础类型,彻底切断和LazyMap的引用关联:
// 错误写法(残留LazyMap引用,触发序列化异常) // def queryResult = new JsonSlurper().parseText(gerritQueryOutput) // def CURRENT_PATCHSET = queryResult.currentPatchSet // 正确写法:明确取目标字段,强制转基础类型 def queryResult = new JsonSlurper().parseText(gerritQueryOutput) def CURRENT_PATCHSET = queryResult.currentPatchSet.number.toString() def GERRIT_CHANGE_NUMBER = queryResult.number.toString()
转换完成后再执行ssh命令,即可避免序列化异常。
方案2:用@NonCPS注解隔离Json解析逻辑
如果不想逐个字段做类型转换,可以把所有JsonSlurper解析逻辑封装到加了@NonCPS注解的方法中。被该注解修饰的方法不会被CPS引擎做序列化检查,注意方法返回值只能包含纯基础类型,不能携带LazyMap对象:
@NonCPS def getGerritChangeInfo(gerritQueryOutput) { def queryResult = new JsonSlurper().parseText(gerritQueryOutput) return [ changeNumber: queryResult.number.toString(), patchsetNumber: queryResult.currentPatchSet.number.toString(), commitId: queryResult.currentPatchSet.revision ] } // 调用方法拿到纯基础类型变量 def changeInfo = getGerritChangeInfo(gerritQueryRawOutput) def GERRIT_CHANGE_NUMBER = changeInfo.changeNumber def CURRENT_PATCHSET = changeInfo.patchsetNumber def GERRIT_COMMIT_ID = changeInfo.commitId
Gerrit参数格式校验
如果序列化问题修复后命令仍执行失败,检查变更编号,patchset编号的格式:两个值之间不要加空格,patchset编号为纯数字,例如12345,3代表12345号变更的第3个patchset,该格式和commit ID传参的权限、执行效果完全一致。
内容的提问来源于stack exchange,提问作者HollowDev

