【问题标题】:Preventing expansion of environment variables in Jenkins job parameters防止 Jenkins 作业参数中的环境变量扩展
【发布时间】:2019-07-07 15:45:28
【问题描述】:

问题

我正在从事一项 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 安装,在没有任何插件(甚至是默认插件)的情况下对其进行了配置,并使用 Web 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 受到变量扩展/插值之前访问它们的原始参数值,或者以其他方式禁用或规避这种行为?

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

相关链接

【问题讨论】:

    标签: jenkins groovy environment-variables jenkins-plugins jenkins-job-dsl


    【解决方案1】:

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

    我们使用来自the Groovy plugin 的“系统 Groovy 脚本”构建步骤来实现这一点,该步骤运行自定义 Groovy 脚本并直接访问构建状态(感谢 daspilker 的建议)。如果您使用的是 DSL,则可以通过 systemGroovyCommand("""…""") 调用添加它。

    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 批准此脚本。因为它已经对所有参数进行了编码,所以您永远不需要修改它,并且可以在其他工作中重复使用它而无需重新批准。

    随后的“执行 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(…) 方法为构建聚合所有环境变量后,它应用hudson.EnvVars.resolve(…) 函数对所有环境变量的内容执行相互变量扩展。根据确切的构建配置,it may 也使用hudson.EnvVars.overrideExpandingAll(…),它采取了对变量进行拓扑排序的额外步骤,以确保非循环引用都以正确的顺序展开。实际的字符串操作由hudson.util.replaceMacro(…) 执行,其中有一条注释解释了不存在变量的异常处理(非替换):

    与 shell 不同,未定义的变量保持原样(此行为与 Ant 相同。)

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

    【讨论】:

    • Job DSL 代码在种子作业运行时执行。如果要在生成的作业中执行 Groovy 代码,请使用“系统 Groovy 脚本”构建步骤。应该可以在 System Groovy 脚本步骤中访问原始值,但这可能无济于事,因为传递状态到下一个构建步骤的唯一方法是值再次扩展的环境。
    • @daspilker 感谢您的指点,这正是我所需要的!我能够对这些值进行 base64 编码,以使原始值可用于后续构建步骤。它似乎工作。使用此解决方法更新了答案。
    猜你喜欢
    • 1970-01-01
    • 2021-01-21
    • 1970-01-01
    • 2014-03-11
    • 2016-12-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多