当前位置: 首页 > 知识库问答 >
问题:

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

那鹏
2023-03-14

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

我的文件看起来像这样。。。

pipeline {

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

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

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

如果我有像这样简单的东西...

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

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

为什么脚本不能找到这样的文件时,可以只是在主节点上运行?我添加了“回声环境”。NODE_NAME"命令到我的groovy文件,它说脚本正在正确的节点上执行。

谢谢

共有3个答案

施知
2023-03-14

我已经实现了在从机上自动安装Groovy的代码(用于脚本管道)。也许这个解决方案有点麻烦,但是管道没有提供任何其他方法来实现与旧詹金斯的“执行Groovy脚本”相同的功能,因为管道还不支持插件https://wiki.jenkins.io/display/JENKINS/Groovy插件。

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
陆斌
2023-03-14

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

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

正如大家已经说过的,上面的FileNotFoundException失败了,因为脚本{}中的任何内容都将只在主机上运行,而不会在代理上运行。为了解决这个问题,我使用了管道实用程序步骤插件(参考: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)该插件允许您对多种文件格式执行任何读/写操作。

下面是我在安装插件后使用的代码html" target="_blank">示例:

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

注意:我使用的是jenkins 2.249。1.

姬康平
2023-03-14

事实证明,Groovy文件命令被认为是不安全的,尽管它们将在主机上运行,但不会在从机上运行。如果您从将代理设置为另一个节点的脚本调用它们,它仍然会很好地执行命令,只在主节点上执行,而不是在代理上执行。这是一篇文章的摘录https://support.cloudbees.com/hc/en-us/articles/230922508-Pipeline-Files-manipulation

File类的操作在master上运行,因此只有在build在master上运行时才起作用,在本例中,我创建了一个文件,并检查是否可以使用exists方法在节点上访问它,它不存在,因为新文件(File)是在master上执行的,要检查这一点,我搜索存在于我的主控上但不在节点中的文件夹用户。

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
}

 类似资料:
  • 问题内容: 我有一个在Jenkins主服务器上运行过的Jenkins声明式管道,并且运行良好。但是,既然我已经尝试在从属节点上执行此操作,则在管道中调用的常规脚本无法访问工作空间中的文件。 我的jenkinsfile看起来像这样… 我可以在从站上看到它正在创建工作区,从git进行检出并正确执行脚本。但是,如果脚本中的某些内容尝试与工作空间中的文件进行交互,它将失败。 如果我有这样简单的事情… …它

  • 问题内容: 我正在尝试将旧样式的基于项目的工作流转换为基于Jenkins的管道。在浏览文档时,我发现有两种不同的语法分别命名为和。例如最近(2016年底)发布的Jenkins网络语法。尽管有一个新的语法版本,Jenkins仍然也支持脚本语法。 现在,我不确定这两种类型的哪种情况最合适。语法将很快被弃用吗?詹金斯管道的未来会是这样吗? 任何可以分享有关这两种语法类型的想法的人。 问题答案: 最初创建

  • 当我在jenkins中运行多分支管道代码时,我收到以下的以下错误: java.lang.NoSuchMethodError:在步骤中找不到这样的DSL方法“管道”[archive,bat,build,catchError,checkout,deleteDir,dir,echo,emailext,EmailExtrecients,error,fileExists,getContext,git,inp

  • 我查看了以下文档:https://docs.sonarqube.org/display/scan/analysy+with+sonarqube+scanner+for+jenkins 然而,我不能让它工作。首先,文档似乎需要更新,因为示例中的语法是错误的。在最新版本的声明性管道中,在-tag中是必需的。此外,-关键字只有在-标记中才会解析。 null

  • 问题内容: 我的Jenkins 2.19.4使用管道:声明式代理程序API 1.0.1。如果您无法定义变量来分配读取的属性,那么如何使用readProperties? 例如,要捕获SVN版本号,我目前以脚本样式使用以下代码捕获它: 然后我可以使用: 由于以声明式定义svnProp是不合法的,因此如何使用readProperties? 问题答案: 您可以使用标记内的步骤来运行任意管道代码。 所以符合

  • 我写了一个Jenkins管道,相关部分如下所示: 和我的DockerFile: