简体   繁体   English

Jenkins 声明性管道,在从代理上运行 groovy 脚本

[英]Jenkins Declarative Pipeline, run groovy script on slave agent

I have a Jenkins declarative pipeline I have been running on the Jenkins master and it works fine.我有一个 Jenkins 声明性管道,我一直在 Jenkins 主机上运行,它工作正常。 However, now that I have moved to trying to execute this on a slave node, the groovy scripts which are called in the pipeline can not access the files in the workspace.但是,现在我已经开始尝试在从节点上执行此操作,在管道中调用的 groovy 脚本无法访问工作区中的文件。

My jenkinsfile looks like this...我的詹金斯文件看起来像这样......

pipeline {

agent {
  label {
        label "windows"
        customWorkspace "WS-${env.BRANCH_NAME}"
  }
}

stages {
  stage('InitialSetup') {
   steps {
     "${env.WORKSPACE}/JenkinsScripts/myScript.groovy"
    }
  }
}

I can see on the slave that it is creating the workspace, doing the checkout from git, and executing the script correctly.我可以在从站上看到它正在创建工作区,从 git 进行结帐,并正确执行脚本。 However, if something in the script try's to interact with the files in the workspace it fails.但是,如果脚本中的某些内容尝试与工作区中的文件进行交互,则会失败。

If I have something simple like this...如果我有这样简单的东西......

def updateFile(String filename) {
  echo env.NODE_NAME
  filename = "${env.WORKSPACE}/path/to/file"
  def myFile = new File(filename)
  <do other things with the file>
}

...it says it can not find the file specified. ...它说它找不到指定的文件。 It gives me the path it is looking for and I can confirm the file exists, and that the code runs when just building on the master.它给了我它正在寻找的路径,我可以确认文件存在,并且代码在构建主服务器时运行。

Why can the script not find the files this way when in can just running on the master node?为什么脚本只能在主节点上运行时无法以这种方式找到文件? I added the "echo env.NODE_NAME" command into my groovy file and it says the script is executing on the correct node.我将“echo env.NODE_NAME”命令添加到我的 groovy 文件中,它说脚本正在正确的节点上执行。

Thanks.谢谢。

Turns out Groovy File commands are considered insecure, and although they will run on the master, they will not run on the slave.结果证明 Groovy File 命令被认为是不安全的,尽管它们会在主服务器上运行,但它们不会在从服务器上运行。 If you call them from a script that has the agent set to another node, it will still execute the command just fine, just on the master node, not the agent.如果你从一个将代理设置到另一个节点的脚本中调用它们,它仍然会很好地执行命令,只是在主节点上,而不是在代理上。 Here's an excerpt of an article post https://support.cloudbees.com/hc/en-us/articles/230922508-Pipeline-Files-manipulation这是一篇文章的摘录https://support.cloudbees.com/hc/en-us/articles/230922508-Pipeline-Files-manipulation


The operation with File class are run on master, so only works if build is run on master, in this example I create a file and check if I can access it on a node with method exists, it does not exist because the new File(file) is executed on master, to check this I search for folder Users that exist on my master but not in the node. File 类的操作在 master 上运行,所以只有在 build 在 master 上运行时才有效,在这个例子中,我创建了一个文件并检查我是否可以在具有方法的节点上访问它,它不存在,因为new File(file)在 master 上执行,为了检查这个,我搜索存在于我的 master 但不在节点中的文件夹Users

stage 'file move wrong way'

  //it only works on master
  node('slave') {

    def ws = pwd()
    def context  = ws + "/testArtifact"
    def file = ws + '/file'
    sh 'touch ' + file
    sh 'ls ' + ws

    echo 'File on node : ' + new File(file).exists()
    echo 'Users : ' + new File('/Users').exists()

    sh 'mv ' + file + ' ' + context
    sh 'ls ' + ws
  }

To execute file manipulation command we recommend to use native commands.要执行文件操作命令,我们建议使用本机命令。

This is a simple example of operations in shell这是一个简单的shell操作示例

stage 'Create file'
  sh 'touch test.txt'

stage 'download file'
  def out='$(pwd)/download/maven.tgz'
  sh 'mkdir -p ./download'
  sh 'curl -L http://ftp.cixug.es/apache/maven/maven-3/3.3.9/binaries/apache-maven-3.3.9-bin.tar.gz -o ' + out

stage 'move/rename'
  def newName = 'mvn.tgz'
  sh 'mkdir -p $(pwd)/other'
  sh 'mv ' + out + ' ' + newName
  sh 'cp ' + newName + ' ' + out
}

I have implemented the code which automatically installs Groovy on slave (for scripted pipeline).我已经实现了在从站上自动安装 Groovy 的代码(用于脚本管道)。 Perhaps this solution is a little bit cumbersome, but pipelines don't offer any other way to achieve the same functionality as "Execute Groovy Script" stuff from the old Jenkins, because the plugin https://wiki.jenkins.io/display/JENKINS/Groovy+plugin is not supported yet for pipeline.也许这个解决方案有点麻烦,但是管道没有提供任何其他方式来实现与旧 Jenkins 的“执行 Groovy 脚本”相同的功能,因为插件https://wiki.jenkins.io/display/管道尚不支持JENKINS/Groovy+plugin

import hudson.tools.InstallSourceProperty;
import hudson.tools.ToolProperty;
import hudson.tools.ToolPropertyDescriptor;
import hudson.tools.ToolDescriptor;
import hudson.tools.ToolInstallation;
import hudson.tools.ToolInstaller;
import hudson.util.DescribableList;
import hudson.plugins.groovy.GroovyInstaller;
import hudson.plugins.groovy.GroovyInstallation;
/* 
  Installs Groovy on the node.
  The idea was taken from: https://devops.lv/2016/12/05/jenkins-groovy-auto-installer/
  and https://github.com/jenkinsci/jenkins-scripts/blob/master/scriptler/configMavenAutoInstaller.groovy

  COMMENT 1: If we use this code directly (not as a separate method) then we get
  java.io.NotSerializableException: hudson.plugins.groovy.GroovyInstaller

  COMMENT 2: For some reason inst.getExecutable(channel) returns null. I use inst.forNode(node, null).getExecutable(channel) instead.
  
  TODO: Check if https://jenkinsci.github.io/job-dsl-plugin/#method/javaposse.jobdsl.dsl.helpers.step.MultiJobStepContext.groovyCommand
  works better.
 */
@NonCPS
def installGroovyOnSlave(String version) {

    if ((version == null) || (version == "")) {
        version = "2.4.7" // some default should be
    }
    
    /* Set up properties for our new Groovy installation */
    def node = Jenkins.getInstance().slaves.find({it.name == env.NODE_NAME})
    def proplist = new DescribableList<ToolProperty<?>, ToolPropertyDescriptor>()
    def installers = new ArrayList<GroovyInstaller>()
    def autoInstaller = new GroovyInstaller(version)
    installers.add(autoInstaller)
    def InstallSourceProperty isp = new InstallSourceProperty(installers)
    proplist.add(isp)
    def inst = new GroovyInstallation("Groovy", "", proplist)
 
    /* Download and install */
    autoInstaller.performInstallation(inst, node, null)

    /* Define and add our Groovy installation to Jenkins */
    def descriptor = Jenkins.getInstance().getDescriptor("hudson.plugins.groovy.Groovy")
    descriptor.setInstallations(inst)
    descriptor.save()
    
    /* Output the current Groovy installation's path, to verify that it is ready for use */
    def groovyInstPath = getGroovyExecutable(version)
    println("Groovy " + version + " is installed in the node " + node.getDisplayName())
}

/* Returns the groovy executable path on the current node
   If version is specified tries to find the specified version of groovy,
   otherwise returns the first groovy installation that was found.
 */
@NonCPS
def getGroovyExecutable(String version=null) {
    
    def node = Jenkins.getInstance().slaves.find({it.name == env.NODE_NAME})
    def channel = node.getComputer().getChannel()
    
    for (ToolInstallation tInstallation : Jenkins.getInstance().getDescriptor("hudson.plugins.groovy.Groovy").getInstallations()) {
        if (tInstallation instanceof GroovyInstallation) {
            if ((version == null) || (version == "")) {
                // any version is appropriate for us
                return tInstallation.forNode(node, null).getExecutable(channel)
            }
            // otherwise check for version
            for (ToolProperty prop in tInstallation.getProperties()) {
                if (prop instanceof InstallSourceProperty) {
                    for (ToolInstaller tInstaller: prop.installers) {
                        if (
                            (tInstaller instanceof GroovyInstaller) &&
                            (tInstaller.id.equals(version))
                        )
                        return tInstallation.forNode(node, null).getExecutable(channel)
                    }
                }
            }
        }
    }
    
    return null
}

/* Wrapper function. Returns the groovy executable path as getGroovyExecutable()
   but additionally tries to install if the groovy installation was not found.
 */
def getGroovy(String version=null) {
    def installedGroovy = getGroovyExecutable(version)
    if (installedGroovy != null) {
        return installedGroovy
    } else {
        installGroovyOnSlave(version)
    }
    return getGroovyExecutable(version)
}

Just put these 3 methods to your pipeline script and you will be able to get the Groovy executable path with the help of the method getGroovy().只需将这 3 个方法放到您的管道脚本中,您就可以在方法 getGroovy() 的帮助下获得 Groovy 可执行路径。 If it is not installed yet then the installation will be done automatically.如果尚未安装,则安装将自动完成。 You can test this code with the simple pipeline, like this:您可以使用简单的管道测试此代码,如下所示:

// Main
parallel(
    'Unix' : {
        node ('build-unix') {
            sh(getGroovy() + ' --version')
        }
    },
    'Windows' : {
        node ('build-win') {
            bat(getGroovy() + ' --version')
        }
    }
)

For me the output was:对我来说,输出是:

[build-unix] Groovy Version: 2.4.7 JVM: 1.8.0_222 Vendor: Private Build OS: Linux
[build-win] Groovy Version: 2.4.7 JVM: 11.0.1 Vendor: Oracle Corporation OS: Windows 10

I run into this same issue recently.我最近遇到了同样的问题。 I had a python file that runs and writes the results to a JSON file.我有一个 python 文件,它运行并将结果写入 JSON 文件。 I was trying to access the JSON file to retrieve the data from there.我试图访问 JSON 文件以从那里检索数据。 Here is the code I was using inside a stage block of a declarative pipeline:这是我在声明性管道的阶段块中使用的代码:

script {
    def jsonSlurper = new JsonSlurper()
    def fileParsed = new File("parameters.json")
    def dataJSON = jsonSlurper.parse(fileParsed)
}

As everyone stated already, the above was failing with FileNotFoundException because anything inside script{} will only run on master and not the agent.正如每个人已经说过的那样,上述操作失败并出现 FileNotFoundException,因为script{}任何内容都只会在主服务器上运行,而不会在代理上运行。 To work around the issue, I have used the Pipeline Utility Steps plugin (reference: https://plugins.jenkins.io/pipeline-utility-steps/ -- How to use: https://www.jenkins.io/doc/pipeline/steps/pipeline-utility-steps/#writejson-write-json-to-a-file-in-the-workspace ) The plugin will allow you to do any read/write operation on multiple file formats.为了解决这个问题,我使用了Pipeline Utility Steps插件(参考: https : //plugins.jenkins.io/pipeline-utility-steps/——如何使用: https : //www.jenkins.io/doc /pipeline/steps/pipeline-utility-steps/#writejson-write-json-to-a-file-in-the-workspace ) 该插件将允许您对多种文件格式进行任何读/写操作。

Here is an example of the code I used after installing the plugin:这是我安装插件后使用的代码示例:

script {
    def props = readJSON file: 'parameters.json'
    println("just read it..")
    println(props)
}

Note: I was using jenkins 2.249.1注意:我使用的是 jenkins 2.249.1

To work with files on the slave workspace use the readFile, writeFile, findFiles etc steps.要处理从属工作区上的文件,请使用 readFile、writeFile、findFiles 等步骤。

Or if they are large as FloatingCoder said use native tooling;或者,如果它们像 FloatingCoder 所说的那样大,则使用本机工具; which may be running a groovy script.这可能正在运行一个 groovy 脚本。

A workaround could be load the library via sh command in Jenkinsfile.解决方法是通过 Jenkinsfile 中的 sh 命令加载库。 So, if you use in Jenkinsfile:所以,如果你在 Jenkinsfile 中使用:

sh 'groovy libraryName.groovy' 

You can load the lib locally and in this way you can store File on the slave node.您可以在本地加载 lib,这样您就可以将 File 存储在从节点上。

Even without pipelines, there is no option to restrict a job based on slave agent label.即使没有管道,也无法根据从代理标签限制作业。 So, I think, pipelines are only for master node execution.所以,我认为,管道仅用于主节点执行。

Starting from release 2.4 of the Groovy plugin there is withGroovy step available which sets up the environment on the agent so that you can do sh 'groovy yourscript.groovy' with expected environments.从 Groovy 插件的 2.4 版开始,可以使用withGroovy步骤在代理上设置环境,以便您可以在预期环境中执行sh 'groovy yourscript.groovy' It also enables limited interaction between Pipeline and groovy script.它还支持 Pipeline 和 groovy 脚本之间的有限交互。

See https://www.jenkins.io/doc/pipeline/steps/groovy/ for some details about the step.有关该步骤的一些详细信息,请参阅https://www.jenkins.io/doc/pipeline/steps/groovy/

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM