Jenkins Declarative Pipeline,在slave代理上运行groovy脚本

时间:2017-05-02 23:34:10

标签: jenkins jenkins-pipeline

我有一个Jenkins声明性管道,我一直在Jenkins主机上运行,​​它工作正常。但是,现在我已经开始尝试在从属节点上执行此操作,在管道中调用的groovy脚本无法访问工作空间中的文件。

我的jenkinsfile看起来像这样......

my branch

我可以在slave上看到它正在创建工作区,从git执行checkout并正确执行脚本。但是,如果脚本中的某些内容尝试与工作区中的文件进行交互,则会失败。

如果我有这样的简单......

mainline

...它说找不到指定的文件。它为我提供了它正在寻找的路径,我可以确认该文件存在,并且代码在构建主服务器时运行。

为什么脚本在主节点上运行时无法以这种方式找到文件?我添加了" echo env.NODE_NAME"命令进入我的groovy文件,它说脚本正在正确的节点上执行。

感谢。

6 个答案:

答案 0 :(得分:6)

结果Groovy文件命令被认为是不安全的,虽然它们将在主服务器上运行,但它们不会在从服务器上运行。如果从将代理程序设置为另一个节点的脚本中调用它们,它仍然会在主节点上执行命令,而不是代理程序。以下是文章https://support.cloudbees.com/hc/en-us/articles/230922508-Pipeline-Files-manipulation

的摘录

使用File类的操作在master上运行,因此只有在master上运行build时才有效,在本例中我创建了一个文件并检查是否可以在方法存在的节点上访问它,它不存在,因为new File(file)在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
  }

要执行文件操作命令,我们建议使用本机命令。

这是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
}

答案 1 :(得分:2)

我最近也遇到了同样的问题。我有一个可运行的python文件,并将结果写入JSON文件。我试图访问JSON文件以从那里检索数据。这是我在声明性管道的阶段块内使用的代码:

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

正如大家已经说过的那样,以上操作都失败了,原因是FileNotFoundException,因为script{}中的任何内容都只能在主服务器上运行,而不能在代理上运行。 要解决此问题,我使用了 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) 该插件将允许您对多种文件格式执行任何读/写操作。

这是我在安装插件后使用的代码示例:

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

注意:我正在使用jenkins 2.249.1

答案 2 :(得分:1)

要使用slave工作区上的文件,请使用readFile,writeFile,findFiles等步骤。

或者如果它们很大,因为FloatingCoder说使用原生工具;这可能是一个groovy脚本。

答案 3 :(得分:1)

我已经实现了自动在从属服务器上安装Groovy的代码(用于脚本化管道)。也许此解决方案有点麻烦,但是管道并没有提供其他方法来实现与旧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.
 */
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.
 */
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)
}

只需将这3个方法放入您的管道脚本中,您就可以在方法getGroovy()的帮助下获取Groovy可执行路径。如果尚未安装,则安装将自动完成。您可以使用简单的管道测试此代码,如下所示:

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

对我来说,输出是:

[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

答案 4 :(得分:0)

一种解决方法是通过Jenkinsfile中的sh命令加载库。 因此,如果您在Jenkinsfile中使用:

sh 'groovy libraryName.groovy' 

您可以在本地加载lib,这样就可以将File存储在从属节点上。

答案 5 :(得分:0)

即使没有管道,也无法根据从代理标签限制作业。因此,我认为,管道仅用于主节点执行。