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

1tu0hz3e  于 2022-11-02  发布在  Jenkins
关注(0)|答案(7)|浏览(164)

我在Jenkins主节点上运行了一个Jenkins声明性管道,它运行得很好。但是,现在我已经尝试在一个从节点上执行它,在管道中调用的groovy脚本无法访问工作区中的文件。
我的jenkinsfile看起来像这样...

pipeline {

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

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

我可以看到slave正在创建工作空间,从git checkout ,并正确执行脚本。但是,如果脚本中的某些内容试图与工作空间中的文件交互,它将失败。
如果我有像这样简单的东西...

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

...它说它找不到指定的文件。它给我它正在寻找的路径,我可以确认文件存在,并且代码运行时只是在主上构建。
为什么脚本在主节点上运行时无法通过这种方式找到文件呢?我将“echo env.NODE_NAME”命令添加到我的groovy文件中,它显示脚本正在正确的节点上执行。

  • 谢谢-谢谢
csga3l58

csga3l581#

结果发现Groovy File命令被认为是不安全的,尽管它们可以在主节点上运行,但不能在从节点上运行。如果您从将代理设置为另一个节点的脚本中调用它们,它仍然可以很好地执行命令,只是在主节点上,而不是在代理上。
File类的操作在master上运行,因此只有在master上运行构建时才起作用,在本例中,我创建了一个文件,并使用方法exists检查是否可以在节点上访问它,它不存在,因为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
  }

要执行文件操作命令,我们建议使用本机命令。
这是一个简单的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
}
3j86kqsm

3j86kqsm2#

我最近遇到了同样的问题。我有一个python文件,它运行并将结果写入JSON文件。我试图访问JSON文件以从中检索数据。下面是我在声明性管道的stage块中使用的代码:

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

正如每个人都已经说过的,上面的失败与FileNotFoundException,因为script{}内的任何东西将只在master上运行,而不是在代理上运行。为了解决这个问题,我使用了Pipeline Utility Steps插件(参考:https://plugins.jenkins.io/pipeline-utility-steps/--如何用途:这个插件可以让你对多种文件格式进行任何读/写操作。
下面是我在安装插件后使用的代码示例:

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

注:我使用的是jenkins 2.249.1

oymdgrw7

oymdgrw73#

我已经实现了在slave上自动安装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.
 */
@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)
}

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

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

对我来说,输出结果是:

[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
omqzjyyz

omqzjyyz4#

若要使用从属工作区上的档案,请使用readFile、writeFile、findFiles等步骤。
或者如果它们很大就像FloatingCoder说的那样使用本机工具;它可能正在运行Groovy脚本。

mrfwxfqh

mrfwxfqh5#

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

sh 'groovy libraryName.groovy'

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

ipakzgxi

ipakzgxi6#

即使没有管道,也没有选项来限制基于从代理标签的作业。因此,我认为,管道仅用于主节点执行。

vxf3dgd4

vxf3dgd47#

从Groovy插件的2.4版本开始,提供了withGroovy步骤,用于在代理上设置环境,以便您可以在预期的环境中执行sh 'groovy yourscript.groovy'。它还支持管道和groovy脚本之间的有限交互。
有关该步骤的详细信息,请参见https://www.jenkins.io/doc/pipeline/steps/groovy/

相关问题