在 Jenkinsfile 中调用可变参数函数意外失败

Calling a variadic function in a Jenkinsfile fails unpredictably

上下文

我是 运行 Jenkins Windows,正在编写声明性管道。我正在尝试将多个命令放在单个 bat 步骤中,同时如果任何包含的命令失败,仍然会使该步骤失败。

这样做有双重目的。

代码

我在 Jenkinsfile 中写了以下 Groovy 代码:

def ExecuteMultipleCmdSteps(String... steps)
{
    bat ConcatenateMultipleCmdSteps(steps)
}

String ConcatenateMultipleCmdSteps(String... steps)
{
    String[] commands = []
    steps.each { commands +="echo ^> Now starting: ${it}"; commands += it; }
    return commands.join(" && ")
}

problem/question

我无法让它可靠地工作。也就是说,在单个 Jenkinsfile 中,我可以对 ExecuteMultipleCmdSteps() 进行多次调用,其中一些会按预期工作,而另一些则会失败 java.lang.NoSuchMethodError: No such DSL method 'ExecuteMultipleCmdSteps' found among steps [addBadge, ...

我还没有在失败中找到任何模式。我认为只有在 warnError 块中执行时才会失败,但现在我在 dir() 块中也遇到了问题,而在另一个 Jenkinsfile 中,它工作正常。

这个问题似乎与 ExecuteMultipleCmdSteps() 是可变参数函数有关。如果我提供具有正确数量参数的重载,则使用该重载没有问题。

我在这里不知所措。非常欢迎您的意见。

失败的解决方案

在某些时候我认为它可能是 scoping/importing 的东西,所以我按照 的建议将 ExecuteMultipleCmdSteps() 包含在 class(下面的代码)中。现在,该方法被称为 Helpers.ExecuteMultipleCmdSteps(),结果是 org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException: No such static method found: staticMethod Helpers ExecuteMultipleCmdSteps org.codehaus.groovy.runtime.GStringImpl org.codehaus.groovy.runtime.GStringImpl

public class Helpers {
    public static environment

    public static void ExecuteMultipleCmdSteps(String... steps)
    {
        environment.bat ConcatenateMultipleCmdSteps(steps)
    }

    public static String ConcatenateMultipleCmdSteps(String... steps)
    {
        String[] commands = []
        steps.each { commands +="echo ^> Now starting: ${it}"; commands += it; }
        return commands.join(" && ")
    }

最少的失败示例

考虑以下几点:

hello = "Hello"

pipeline {
    agent any
    stages {
        stage("Stage") {
            steps {
                SillyEcho("Hello")
                SillyEcho("${hello}" as String)
                SillyEcho("${hello}")
            }
        }
    }
}

def SillyEcho(String... m)
{
    echo m.join(" ")
}

我希望所有对 SillyEcho() 的调用都会导致 Hello 被回显。实际上,前两个成功,最后一个结果 java.lang.NoSuchMethodError: No such DSL method 'SillyEcho' found among steps [addBadge, addErrorBadge,...

奇怪的成功例子

考虑以下 groovy 脚本,它几乎等同于上面失败的示例:

hello = "Hello"

SillyEcho("Hello")
SillyEcho("${hello}" as String)
SillyEcho("${hello}")

def SillyEcho(String... m)
{
    println m.join(" ")
}

当粘贴到 Groovy 脚本控制台(例如 Jenkins 提供的控制台)时,成功(Hello 打印了三次)。

尽管我希望这个示例能够成功,但我也希望它的行为与上面失败的示例一致,所以我对这个示例有点纠结。

不确定这是否能回答您的问题,如果不能,请将其视为更大的评论。 我喜欢你如何从 C++ 中借用 'variadic functions' :) 然而,在 groovy 中有很多优雅的方法来处理这个问题。

试试这个:

def ExecuteMultipleCmdSteps(steps)
{
    sh steps
        .collect { "echo \> Now starting: $it && $it" }
        .join(" && ")
}        

pipeline {
    agent any
    stages {
        stage ("test") {
            steps {
                ExecuteMultipleCmdSteps(["pwd", "pwd"])
            }
        }
    }
}

这对我来说很好用:

[Pipeline] Start of Pipeline
[Pipeline] node
Running on Jenkins in /var/lib/jenkins/workspace/TestJob
[Pipeline] {
[Pipeline] stage
[Pipeline] { (test)
[Pipeline] sh
+ echo > Now starting: pwd
> Now starting: pwd
+ pwd
/var/lib/jenkins/workspace/TestJob
+ echo > Now starting: pwd
> Now starting: pwd
+ pwd
/var/lib/jenkins/workspace/TestJob
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS

您可能想像这样重写您的函数。 您提到的2个错误可能有不同的原因。

第一个“没有这样的 DSL 方法……”确实是一个范围界定方法,您自己找到了解决方案,但我不明白为什么重载在同一范围内起作用。

第二个错误,可以用这个解决。但是,对我来说,第二种方法的代码也可以正常工作。

感谢您添加失败和成功的示例。 我想您的问题是由于 StringGString.

不兼容造成的

关于 运行 作为管道作业与 运行 Jenkins 脚本控制台中的脚本之间的差异,我假设基于此 Jenkins 脚本控制台并不严格类型引用或尝试根据函数签名转换参数。我的假设基于这个脚本,基于你的脚本:

hello = "Hello"
hello2 = "${hello}" as String
hello3 = "${hello}"

println hello.getClass()
println hello2.getClass()
println hello3.getClass()

SillyEcho(hello)
SillyEcho(hello2)
SillyEcho(hello3)

def SillyEcho(String... m)
{
    println m.getClass()
}

这是我在 Jenkins 脚本控制台中得到的输出:

class java.lang.String
class java.lang.String
class org.codehaus.groovy.runtime.GStringImpl
class [Ljava.lang.String;
class [Ljava.lang.String;
class [Ljava.lang.String;

我希望管道不会将 GString 转换为 String,但会失败,因为没有以 Gstring 作为参数的函数。

为了调试,您可以尝试调用 .toString() 您传递给函数的所有元素。

更新

这似乎是管道解释器的一个已知问题(或至少已报告):JENKINS-56758

在工单中描述了使用集合而不是可变参数的额外解决方法。这将省去对所有内容进行类型转换的需要。