Jenkinsfile - Jenkins 构建用户 getUserName() NullPointerException:无法在空对象 Mutli 分支索引扫描上调用方法 getUserName()

Jenkinsfile - Jenkins build user getUserName() NullPointerException: Cannot invoke method getUserName() on null object Mutli Branch Indexing Scanning

Jenkins 2.138.1.2-rolling --and-- 使用 MultiBranch Pipeline(从 master 构建,分支和拉取请求等)。

我想在构建描述中显示启动构建的 Jenkins 构建作业的用户。

例如:

为了实现相同的功能,在我的 Jenkinsfile 中,我在顶部有以下代码:

@NonCPS
def getBuildUser() {
    def build = currentBuild.rawBuild
    def cause = build.getCause(hudson.model.Cause.UserIdCause.class)
    def BUILD_USER = cause.getUserName()
    
    return BUILD_USER
}

pipeline {
    ...
}

Class 定义:https://javadoc.jenkins-ci.org/hudson/model/Cause.UserIdCause.html shows the method is NOT Deprecated (like it's in the case of UserCause class).

在 Jenkinsfile 的阶段部分下,我有以下代码,它成功地实现了这个。

stages {
   stage ('Start') {
       steps {
           script {
                   // Set Build Description
                   def BUILD_USER= getBuildUser()
                   currentBuild.description = "${BUILD_USER}: ${RELEASE_TAG} => ${DOCKER_IMAGES}"
           }

           sh '''
                 set +x
                 echo -e "\n\n-- Starting build process.\n"
              '''
       }
   }
   .. more stages are here ..
 }

因为我正在使用多分支管道作业,所以我看到了所有 branches/PR,包括 master 和侧边栏,我看到了以下链接:

多分支设置扫描周期设置为每10分钟。

我看到有时 构建失败 并出现以下错误,即 java.lang.NullPointerException: Cannot invoke method getUserName() on null object 和其他时候,构建一直是成功的,构建描述是也不错

[Bitbucket] Build result notified
java.lang.NullPointerException: Cannot invoke method getUserName() on null object
    at org.codehaus.groovy.runtime.NullObject.invokeMethod(NullObject.java:91)
    at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:48)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48)
    at org.codehaus.groovy.runtime.callsite.NullCallSite.call(NullCallSite.java:35)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:113)
    at org.kohsuke.groovy.sandbox.impl.Checker.call(Checker.java:158)
    at org.kohsuke.groovy.sandbox.impl.Checker.checkedCall(Checker.java:160)
    at org.kohsuke.groovy.sandbox.impl.Checker$checkedCall.callStatic(Unknown Source)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallStatic(CallSiteArray.java:56)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callStatic(AbstractCallSite.java:194)
    at WorkflowScript.getBuildUser(WorkflowScript:5)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93)
    at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1213)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1022)
    at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:42)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:113)
    at org.kohsuke.groovy.sandbox.impl.Checker.call(Checker.java:158)
    at org.kohsuke.groovy.sandbox.GroovyInterceptor.onMethodCall(GroovyInterceptor.java:23)
    at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxInterceptor.onMethodCall(SandboxInterceptor.java:157)
    at org.kohsuke.groovy.sandbox.impl.Checker.call(Checker.java:156)
    at org.kohsuke.groovy.sandbox.impl.Checker.checkedCall(Checker.java:160)
    at org.kohsuke.groovy.sandbox.impl.Checker.checkedCall(Checker.java:125)
    at org.kohsuke.groovy.sandbox.impl.Checker.checkedCall(Checker.java:130)
    at com.cloudbees.groovy.cps.sandbox.SandboxInvoker.methodCall(SandboxInvoker.java:17)
    at WorkflowScript.run(WorkflowScript:75)
    at ___cps.transform___(Native Method)
    at com.cloudbees.groovy.cps.impl.ContinuationGroup.methodCall(ContinuationGroup.java:57)
    at com.cloudbees.groovy.cps.impl.FunctionCallBlock$ContinuationImpl.dispatchOrArg(FunctionCallBlock.java:109)
    at com.cloudbees.groovy.cps.impl.FunctionCallBlock$ContinuationImpl.fixName(FunctionCallBlock.java:77)
    at sun.reflect.GeneratedMethodAccessor333.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.cloudbees.groovy.cps.impl.ContinuationPtr$ContinuationImpl.receive(ContinuationPtr.java:72)
    at com.cloudbees.groovy.cps.impl.ConstantBlock.eval(ConstantBlock.java:21)
    at com.cloudbees.groovy.cps.Next.step(Next.java:83)
    at com.cloudbees.groovy.cps.Continuable.call(Continuable.java:174)
    at com.cloudbees.groovy.cps.Continuable.call(Continuable.java:163)
    at org.codehaus.groovy.runtime.GroovyCategorySupport$ThreadCategoryInfo.use(GroovyCategorySupport.java:122)
    at org.codehaus.groovy.runtime.GroovyCategorySupport.use(GroovyCategorySupport.java:261)
    at com.cloudbees.groovy.cps.Continuable.run0(Continuable.java:163)
    at org.jenkinsci.plugins.workflow.cps.SandboxContinuable.access[=12=]1(SandboxContinuable.java:18)
    at org.jenkinsci.plugins.workflow.cps.SandboxContinuable.run0(SandboxContinuable.java:51)
    at org.jenkinsci.plugins.workflow.cps.CpsThread.runNextChunk(CpsThread.java:174)
    at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.run(CpsThreadGroup.java:347)
    at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.access0(CpsThreadGroup.java:93)
    at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.call(CpsThreadGroup.java:259)
    at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.call(CpsThreadGroup.java:247)
    at org.jenkinsci.plugins.workflow.cps.CpsVmExecutorService.call(CpsVmExecutorService.java:64)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at hudson.remoting.SingleLaneExecutorService.run(SingleLaneExecutorService.java:131)
    at jenkins.util.ContextResettingExecutorService.run(ContextResettingExecutorService.java:28)
    at jenkins.security.ImpersonatingExecutorService.run(ImpersonatingExecutorService.java:59)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
Finished: FAILURE

我注意到,当 Multi-Branch Scanning / Branch Indexing 在后台 运行 时,这 发生,因为所有失败构建在该构建号的控制台页面中列出了以下内容。

对于带有构建描述和所有内容的成功构建,上面的“BRANCH INDEXING”框不可见(针对成功构建#)。

为什么我会收到这个 java.lang.NullPointerException: Cannot invoke method getUserName() on null object 错误(当扫描在后台进行时)——或者我该怎么做避免构建失败?

PS:要重现此问题,用户只需单击
Scan Multibranch Pipeline Now 的侧边栏 icon/link 这将导致构建失败,控制台输出显示:

PS:安装新插件可能需要一些时间(1-2 周)/scanning/approval 安全团队的流程,所以不是一个选项我认为,至少在获得批准之前。例如:https://plugins.jenkins.io/build-user-vars-plugin/

为了避免构建失败,现在,我启用了 trycatch 并设置了 BUILD_USER 的值,如果 getCause(...) 将会失败 ...有人点击侧边栏 link Scan MultiBranch Pipeline Now,然后控制台输出或 Jenkins 没有设置在该 侧边点击 的用户- bar link 启动构建(如果有任何要构建的更改)并显示 Branch Indexing 作为输出的第一行(而不是显示:Started by user <some name> (someUserID))。

因此,以下内容会将 BUILD_USER 设置为一个有意义的值,并且不会使构建失败。

@NonCPS
def getBuildUser() {
        def build = currentBuild.rawBuild
        def BUILD_USER = "ToBeSet"

        try {
             def cause = build.getCause(hudson.model.Cause.UserIdCause.class)
             BUILD_USER = cause.getUserName()
        } catch(Exception ex) {
             println "\n\n-- Build caused by either Multi-Branch Pipeline Scanning -or- Timer i.e. not directly by a logged in user\n";
             BUILD_USER = "Multi_Branch_Scan_or_Timer"
        }

        return BUILD_USER
}

我仍然想知道是否有办法,我们可以找到,谁点击了侧边栏 link(登录用户),就会戳进去。