防止在 Jenkins 作业参数中扩展环境变量

Preventing expansion of environment variables in Jenkins job parameters

问题

我正在做一项 Jenkins 工作,它接受来自用户的一些参数。我遇到了一个不希望的行为:在我的脚本有机会读取它们之前,Jenkins 似乎正在扩展参数环境变量中的环境变量引用。

如果用户输入foo-$BUILD_NUMBER作为参数,我的脚本实际看到的是foo-123;环境变量被扩展。如果输入的值包含 $$,我的脚本只会看到一个 $。但是,如果它包含环境中不存在的 $variable,则该值保持不变(不会引发任何类型的错误)。

这很不方便,因为它甚至发生在密码字段中,而且我通常使用可以包含 $ 个字符的密码生成器。我不希望我的密码可能被无声地破坏。

例子

我的初始测试用例如下,使用 Jenkins Job Builder Groovy DSL。

new BaseJobBuilder(
    jobName: 'example',
    jobBuildName: 'example-${BUILD_NUMBER}',
).build(this).with {
    parameters {
        nonStoredPasswordParam('SERVICE_PASSWORD')
    }
    steps {
        shell('echo "$SERVICE_PASSWORD";')
    }
}

然而,为了更简化的测试用例,我从 jenkins/jenkins:lts Docker 图像创建了一个新的 Jenkins 安装,在没有任何插件(甚至是默认插件)的情况下配置它,并使用创建了一个等效的作业网络 UI.

当我 运行 这些作业中的任何一个为我的参数 SERVICE_PASSWORD 设置了值 hello $BUILD_NUMBER $HOME world 时,输出扩展了变量,而不是我想要的文字值。

Started by user jeremy
Building in workspace /var/jenkins_home/workspace/jeremy
[jeremy] $ /bin/sh -xe /tmp/jenkins2451955822062381529.sh
+ echo hello 3 /var/jenkins_home world
hello 3 /var/jenkins_home world
Finished: SUCCESS

问题

有没有办法在 Jenkins 受到变量 expansion/interpolation 影响之前访问它们的原始参数值,或者以其他方式禁用或规避此行为?

我如何接受可能包含 $ 美元字符的原始文本参数而不会有被损坏的风险?

相关链接

作为解决方法,我们可以添加一个初始构建步骤,直接读取所有参数并在 base64 中对它们进行编码,然后将编码值导出为新的环境变量。 Base64 编码的值不能包含任何美元字符 $,因此它们可以被后面的构建步骤安全地读取,这些步骤可以解码它们以获得原始值而无需任何扩展。

我们通过 the Groovy plugin, which runs a custom Groovy script with direct access to the build state (thanks to daspilker for the suggestion). If you're using the DSL, you could add this with a systemGroovyCommand("""…""") 调用的 "System Groovy Script" 构建步骤来实现这一点。

import hudson.EnvVars;
import hudson.model.Executor;
import hudson.model.Environment;

def build = Executor.currentExecutor().currentExecutable;

def newVariables = [:];
build.getBuildVariables().each { name, value ->
  def encodedName = name + "_B64";
  def encodedValue = value.bytes.encodeBase64().toString();
  newVariables.put(encodedName, encodedValue);
}

build.getEnvironments().add(Environment.create(new EnvVars(newVariables)))

您的 Jenkins 管理员可能需要在 the In-process Script Approval page 首次加载此脚本时批准它。因为它已经对所有参数进行了编码,所以您永远不需要修改它,并且可以在其他作业中重复使用它而无需重新批准。

后续 "Execute Shell" 步骤现在将能够解码原始值。

set -eu +vx;

echo "directly from environment: $SERVICE_PASSWORD";

SERVICE_PASSWORD="$(echo "$SERVICE_PASSWORD_B64" | base64 --decode)";
echo "via base-64 encoded value: $SERVICE_PASSWORD";

我们可以使用我们的原始测试值 hello $BUILD_NUMBER $HOME world 来确认它是否有效:

directly from environment: hello 12 /var/jenkins_home world
via base-64 encoded value: hello $BUILD_NUMBER $HOME world

当您从环境变量中读取参数时,无法禁用变量扩展。此行为与具体参数无关,但与 Jenkins 一般处理环境变量的方式有关。在 hudson.model.AbstractBuild.getEnvironment(…) method aggregates all of the environment variable for a build, it applies the hudson.EnvVars.resolve(…) function to perform mutual variable expansion on the contents of all environment variables. Depending on the exact build configuration, it may also use hudson.EnvVars.overrideExpandingAll(…), which takes the additional step of topologically sorting the variables to ensure that non-cyclic references are all expanded in the correct order. The actual string manipulation is performed by hudson.util.replaceMacro(…) 之后,有一条注释解释了不存在变量的异常处理(非替换):

Unlike shell, undefined variables are left as-is (this behavior is the same as Ant.)

这些扩展是无条件执行的,所以如果不在 Jenkins 中修改或替换几个 类 就无法禁用它。