使用环境变量加载文件 Jenkins Pipeline

Posted

技术标签:

【中文标题】使用环境变量加载文件 Jenkins Pipeline【英文标题】:Load file with environment variables Jenkins Pipeline 【发布时间】:2017-01-03 10:10:47 【问题描述】:

我正在做一个简单的管道:

构建 -> 暂存 -> 生产

我需要不同的环境变量用于暂存和生产,所以我正在尝试获取变量。

sh 'source $JENKINS_HOME/.envvars/stacktest-staging.sh' 

但它返回未找到

[Stack Test] Running shell script
+ source /var/jenkins_home/.envvars/stacktest-staging.sh
/var/jenkins_home/workspace/Stack Test@tmp/durable-bcbe1515/script.sh: 2: /var/jenkins_home/workspace/Stack Test@tmp/durable-bcbe1515/script.sh: source: not found

路径是正确的,因为我通过 ssh 登录时运行相同的命令,并且运行良好。

这是管道的想法:

node 
    stage name: 'Build'
    // git and gradle build OK
    echo 'My build stage'

    stage name: 'Staging'
    sh 'source $JENKINS_HOME/.envvars/stacktest-staging.sh' // PROBLEM HERE
    echo '$DB_URL' // Expects http://production_url/my_db
    sh 'gradle flywayMigrate' // To staging
    input message: "Does Staging server look good?"    

    stage name: 'Production'
    sh 'source $JENKINS_HOME/.envvars/stacktest-production.sh'
    echo '$DB_URL' // Expects http://production_url/my_db
    sh 'gradle flywayMigrate' // To production
    sh './deploy.sh'

我该怎么办?

我在考虑不使用管道(但我将无法使用我的 Jenkinsfile)。 或者使用 EnvInject 插件为登台和生产制作不同的工作(但我失去了我的舞台视图) 或者 make withEnv(但是代码变大了,因为今天我使用了 12 个环境变量)

【问题讨论】:

【参考方案1】:

另一种解决方案是使用自定义方法而不允许额外的权限,例如new Properties(),这会在允许之前导致此错误:

org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException: Scripts not permitted to use new java.util.Properties

或添加额外的插件方法,例如readProperties

这是一个读取名为env_vars的简单文件的方法:

FOO=bar
FOO2=bar
pipeline 
<... skipped lines ...>
    script 
        loadEnvironmentVariablesFromFile("env_vars")

        echo "show time! $BAR $BAR2"
    
<... skipped lines ...>


private void loadEnvironmentVariablesFromFile(String path) 
    def file = readFile(path)
    file.split('\n').each  envLine ->
        def (key, value) = envLine.tokenize('=')
        env."$key" = "$value"
    

【讨论】:

【参考方案2】:

这是一个外部化环境变量并在 Jenkins 管道执行中加载它们的完整示例。管道写在declarative style中。

stage('Reading environment variable defined in groovy file') 
            steps 
                script 
                    load "./pipeline/basics/extenvvariable/env.groovy"
                    echo "$env.env_var1"
                    echo "$env.env_var2"
                
            
        

完整的代码示例: https://github.com/dhruv-bansal/jenkins-pipeline-exploration/blob/master/pipeline/basics/extenvvariable/Jenkinsfile

其中变量是从仅与管道代码一起放置的 groovy 文件中加载的。 https://github.com/dhruv-bansal/jenkins-pipeline-exploration/blob/master/pipeline/basics/extenvvariable/env.groovy

当您创建可跨团队使用的通用管道时,此模式非常方便。 您可以将此类 groovy 文件中的因变量外部化,每个团队都可以根据他们的生态系统定义他们的值。

【讨论】:

【参考方案3】:

使用 withEnv() 从由换行符分割的文件中传递环境变量并转换为列表:

writeFile file: 'version.txt', text: 'version=6.22.0'
withEnv(readFile('version.txt').split('\n') as List) 
    sh "echo $version"

【讨论】:

这对我来说是一个优雅的解决方案,又好又简单,谢谢! 删除 version.txt 文件中可能的空行: readFile('version.txt').replaceAll("(?m)^[ \t]*\r?\n", " ").split('\n') @gogo_gorilla,另外,您可以准备 env.list 以查找所有包含相同字符的字符串: def list = ['version=6.22.0', ' ', '', 'text 123' ] list.findAll it.contains('=') ​​​​​​​​​【参考方案4】:

使用 declarative 管道,您可以在一行中完成(将 path 更改为您的值):

script 
  readProperties(file: path).each key, value -> env[key] = value 

【讨论】:

你会把这个放在哪里?您不能在步骤中使用 staticMethods(至少对于 Jenkins 2.176 中的声明性管道)。 @SCote 我已经用“脚本”步骤包围了该步骤。然后,它应该在声明性管道中工作:steps ... put it here ... 【参考方案5】:

解决此安装“管道实用程序步骤”插件的另一种方法,它为我们提供了 readProperties 方法(参考请转到链接https://jenkins.io/doc/pipeline/steps/pipeline-utility-steps/#pipeline-utility-steps) 在示例中,我们可以看到它们将键存储到数组中并使用键来检索值。 但是在那种情况下,如果我们稍后将任何变量添加到属性文件中,那么在生产中问题将类似于该变量也需要添加到 Jenkins 文件的数组中。 为了摆脱这种紧密耦合,我们可以这样编写代码,以便 Jenkins 构建环境可以自动获取有关当前出现在属性文件中的所有现有键的信息。这是一个参考的例子

def loadEnvironmentVariables(path)
    def props = readProperties  file: path
    keys= props.keySet()
    for(key in keys) 
        value = props["$key"]
        env."$key" = "$value"
    
 

客户端代码看起来像

path = '\\ABS_Output\\EnvVars\\pic_env_vars.properties'
loadEnvironmentVariables(path)

【讨论】:

【参考方案6】:

如果您使用的是 Jenkins 2.0,您可以加载属性文件(其中包含所有必需的环境变量及其对应的值)并自动读取其中列出的所有环境变量并将其注入到 Jenkins 提供的 env 实体中。

这是执行上述操作的方法。

def loadProperties(path) 
    properties = new Properties()
    File propertiesFile = new File(path)
    properties.load(propertiesFile.newDataInputStream())
    Set<Object> keys = properties.keySet();
    for(Object k:keys)
    String key = (String)k;
    String value =(String) properties.getProperty(key)
    env."$key" = "$value"
    

要调用此方法,我们需要将属性文件的路径作为字符串变量传递例如,在我们使用 groovy 脚本的 Jenkins 文件中,我们可以像这样调用

path = "$workspace/pic_env_vars.properties"
loadProperties(path)

如有疑问请咨询我

【讨论】:

【参考方案7】:

从cmets到accepted answer

不要使用全局 'env' 而是使用 'withEnv' 构造,例如: 问题 #9:不要在 top 10 best practices jenkins pipeline plugin 中使用全局 env 设置 env vars

在下面的例子中:VAR1 是一个普通的 java 字符串(没有 groovy 变量扩展),VAR2 是一个 groovy 字符串(所以变量 'someGroovyVar' 被扩展了)。

传递的脚本是一个纯 java 字符串,因此 $VAR1 和 $VAR2 被逐字传递给 shell,并且 echo 正在访问环境变量 VAR1 和 VAR2。

stage('build') 
    def someGroovyVar = 'Hello world'
    withEnv(['VAR1=VALUE ONE',
             "VAR2=$someGroovyVar"
            ]) 
        def result = sh(script: 'echo $VAR1; echo $VAR2', returnStdout: true)
        echo result
    

对于秘密/密码,您可以使用credentials binding plugin

例子:

注意:CREDENTIALS_ID1 是 Jenkins 设置中注册的用户名/密码。

stage('Push') 
    withCredentials([usernamePassword(
                         credentialsId: 'CREDENTIALS_ID1',
                         passwordVariable: 'PASSWORD',
                         usernameVariable: 'USER')]) 
        echo "User name: $USER"
        echo "Password:  $PASSWORD"
    

jenkisn 控制台日志输出隐藏了真实值:

[Pipeline] echo
User name: ****
[Pipeline] echo
Password:  ****

Jenkins 和凭据是个大问题,大概看:credentials plugin

为了完整性:大多数时候,我们需要环境变量中的秘密,因为我们在 shell 脚本中使用它们,所以我们将 withCredentials 和 withEnv 组合如下:

stage('Push') 
    withCredentials([usernamePassword(
                         credentialsId: 'CREDENTIALS_ID1',
                         passwordVariable: 'PASSWORD',
                         usernameVariable: 'USER')]) 
        withEnv(["ENV_USERNAME=$USER",
                 "ENV_PASSWORD=$PASSWORD"
            ]) 
                def result = sh(script: 'echo $ENV_USERNAME', returnStdout: true)
                echo result

        
    

【讨论】:

为了它的价值 - 我能够将凭据作为 shell env vars 引用,而无需在 withEnv 步骤中添加它们。 当然,有可能。但是withEnv 仅对步骤是本地的,而 env var 是全局的。 (这就是问题 #9 的重点:不要将 env vars 设置为全局 env)。【参考方案8】:

从文件加载环境变量的一种方法是加载 Groovy 文件。

例如:

    假设您在“$JENKINS_HOME/.envvars”中有一个名为“stacktest-staging.groovy”的 groovy 文件。

    在这个文件中,你定义了 2 个你想加载的环境变量

    env.DB_URL="hello"
    env.DB_URL2="hello2"
    

    然后您可以使用以下方式加载它

    load "$JENKINS_HOME/.envvars/stacktest-staging.groovy"
    

    然后您可以在后续的 echo/shell 步骤中使用它们。

例如,这是一个简短的管道脚本:

node 
   load "$JENKINS_HOME/.envvars/stacktest-staging.groovy"
   echo "$env.DB_URL"
   echo "$env.DB_URL2"

【讨论】:

请注意不鼓励使用 env 变量,因为 env 变量是全局的。推荐的做法是加载局部变量,然后如果需要在管道的一部分中使用withEnv 构造,最终将它们设置为环境变量。 @stoned 我如何在 withEnv 和 Jenkinsfile 中使用密码? (不要在 Jenkinsfile 上公开我的密码,因为我不希望它在 git 存储库中)。有没有办法不用load "myenvvars.groovy" @Rafael 您可以使用凭据绑定插件,该插件将添加一个“withCredentials”管道功能,允许传递存储在 Jenkins 凭据存储中的凭据。如果凭据是静态的,您还可以查看 maskpassword 插件。您甚至可以考虑使用第三方服务来存储凭据,例如 HasciCorp Vault,它拥有自己的插件和相关管道功能。 @stoned 谢谢。这很好用。这是credential plugin。 “从管道作业中,定义您的凭据,然后检查片段生成器以获取 withCredentials 步骤的语法示例” 为什么是“环境”。有必要的 ?因为我试过没有,但没有用

以上是关于使用环境变量加载文件 Jenkins Pipeline的主要内容,如果未能解决你的问题,请参考以下文章

Linux登录shell和非登录(交互式shell)环境变量配置

Linux登录shell和非登录(交互式shell)环境变量配置

Jenkins设置环境变量

如何使用Jenkins中的环境变量

Jenkins 自定义环境变量

Jenkins可用环境变量以及使用方法