GitLab API获取项目URL脚本在Jenkins控制台可用但流水线报错
解决Jenkins脚本化流水线中GitLab4J递归获取项目的Stream flatMap错误
问题背景
我是Jenkins新手,尝试编写脚本化流水线,通过GitLab API动态获取指定GitLab组及其子组下仓库的Git URL。使用gitlab4j-api工具,由于该工具无法直接获取组及子组的所有项目,参考文件目录递归遍历思路编写了递归flatMap子组的函数。脚本在Jenkins控制台运行正常,但嵌入流水线时出现错误。
原代码
import java.util.stream.Stream import org.gitlab4j.api.GitLabApi import org.gitlab4j.api.models.Group import org.gitlab4j.api.models.Project url = "https://gitlab.domain.com" token = "xxxxxxx" groupPath = 'MY/GROUP' gitLabApi = new GitLabApi(url,token) println("Connected to Gitlab, version: "+ gitLabApi.getApiVersion()) getProjectsRecursive(groupPath).each { project -> println project.getSshUrlToRepo() } def getProjectsRecursive(path) { //println 'getProjectsRecursive : ' + path return getDirectProjectsAndSubGroups(path).flatMap { projectOrSubgroup -> projectOrSubgroup instanceof Group ? getProjectsRecursive(((Group)projectOrSubgroup).getFullPath()) : Stream.of((Project) projectOrSubgroup) } } def getDirectProjectsAndSubGroups(path2) { //println 'getDirectProjectsAndSubGroups : ' + path2 def projectsAndGroups = [] projectsAndGroups.addAll(gitLabApi.getGroupApi().getProjectsStream(path2).toList()) projectsAndGroups.addAll(gitLabApi.getGroupApi().getSubGroupsStream(path2).toList()) return projectsAndGroups.stream() }
初始报错信息
[Pipeline] Start of Pipeline [Pipeline] echo Connected to Gitlab, version: V4 [Pipeline] End of Pipeline hudson.remoting.ProxyException: groovy.lang.MissingMethodException: No signature of method: java.util.stream.ReferencePipeline$Head.flatMap() is applicable for argument types: (org.jenkinsci.plugins.workflow.cps.CpsClosure2) values: [org.jenkinsci.plugins.workflow.cps.CpsClosure2@2fd9965c] at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxInterceptor.onMethodCall(SandboxInterceptor.java:158) at org.kohsuke.groovy.sandbox.impl.Checker$1.call(Checker.java:178) at org.kohsuke.groovy.sandbox.impl.Checker.checkedCall(Checker.java:182) at com.cloudbees.groovy.cps.sandbox.SandboxInvoker.methodCall(SandboxInvoker.java:17) at WorkflowScript.getProjectsRecursive(WorkflowScript:19) at WorkflowScript.run(WorkflowScript:13) at ___cps.transform___(Native Method) at com.cloudbees.groovy.cps.impl.ContinuationGroup.methodCall(ContinuationGroup.java:90)
添加@NonCPS注解后的报错
[Pipeline] Start of Pipeline [Pipeline] echo Connected to Gitlab, version: V4 [Pipeline] End of Pipeline hudson.remoting.ProxyException: groovy.lang.MissingMethodException: No signature of method: java.util.stream.ReferencePipeline$Head.flatMap() is applicable for argument types: (WorkflowScript$_getProjectsRecursive_closure1) values: [WorkflowScript$_getProjectsRecursive_closure1@5795b4cf]
解决方案
问题核心是Jenkins流水线的CPS转换机制会修改Groovy闭包类型,导致Java Stream无法识别其为合法的Function参数。以下两种方案可解决:
方案1:改用Groovy集合递归处理
放弃Java Stream,使用Groovy原生List操作,彻底避免CPS转换冲突:
import org.gitlab4j.api.GitLabApi import org.gitlab4j.api.models.Group import org.gitlab4j.api.models.Project @NonCPS def getProjectsRecursive(path) { def result = [] // 获取当前组直接项目 result.addAll(gitLabApi.getGroupApi().getProjects(path)) // 递归遍历子组并收集项目 gitLabApi.getGroupApi().getSubGroups(path).each { subGroup -> result.addAll(getProjectsRecursive(subGroup.getFullPath())) } return result } url = "https://gitlab.domain.com" token = "xxxxxxx" groupPath = 'MY/GROUP' gitLabApi = new GitLabApi(url, token) println("Connected to Gitlab, version: " + gitLabApi.getApiVersion()) getProjectsRecursive(groupPath).each { project -> println project.getSshUrlToRepo() }
方案2:使用Java匿名类替代Groovy闭包
若坚持使用Stream,需将Groovy闭包替换为Java的Function匿名类,绕开CPS转换干扰:
import java.util.stream.Stream import java.util.function.Function import org.gitlab4j.api.GitLabApi import org.gitlab4j.api.models.Group import org.gitlab4j.api.models.Project @NonCPS def getProjectsRecursive(path) { return getDirectProjectsAndSubGroups(path).flatMap(new Function<Object, Stream<?>>() { @Override Stream<?> apply(Object projectOrSubgroup) { if (projectOrSubgroup instanceof Group) { return getProjectsRecursive(((Group) projectOrSubgroup).getFullPath()) } else { return Stream.of((Project) projectOrSubgroup) } } }) } @NonCPS def getDirectProjectsAndSubGroups(path2) { def projectsAndGroups = [] projectsAndGroups.addAll(gitLabApi.getGroupApi().getProjectsStream(path2).toList()) projectsAndGroups.addAll(gitLabApi.getGroupApi().getSubGroupsStream(path2).toList()) return projectsAndGroups.stream() } url = "https://gitlab.domain.com" token = "xxxxxxx" groupPath = 'MY/GROUP' gitLabApi = new GitLabApi(url, token) println("Connected to Gitlab, version: " + gitLabApi.getApiVersion()) getProjectsRecursive(groupPath).each { project -> println project.getSshUrlToRepo() }
关键注意事项
@NonCPS注解必须添加到所有涉及Java API操作的方法上,禁止CPS转换修改这些方法内的代码逻辑;但@NonCPS方法内不能调用任何Jenkins流水线步骤(如echo、sh等)。
内容的提问来源于stack exchange,提问作者Essadiq BAICH
相关产品推荐
相关产品推荐

