Saltar al contenido

Jenkins Declarative Pipeline, ejecute un script maravilloso en el agente esclavo

Posteriormente a consultar con expertos en esta materia, programadores de varias áreas y profesores hemos dado con la solución al problema y la plasmamos en este post.

Solución:

Resulta que los comandos de Groovy File se consideran inseguros y, aunque se ejecutarán en el maestro, no se ejecutarán en el esclavo. Si los llama desde un script que tiene el agente configurado en otro nodo, seguirá ejecutando el comando sin problemas, solo en el nodo maestro, no en el agente. Aquí hay un extracto de una publicación de artículo https://support.cloudbees.com/hc/en-us/articles/230922508-Pipeline-Files-manipulation


La operación con la clase File se ejecuta en el maestro, por lo que solo funciona si la compilación se ejecuta en el maestro, en este ejemplo creo un archivo y verifico si puedo acceder a él en un nodo con el método existente, no existe porque el new File(file) se ejecuta en el maestro, para verificar esto busco la carpeta Users que existen en mi maestro pero no en el nodo.

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
  

Para ejecutar el comando de manipulación de archivos, recomendamos utilizar comandos nativos.

Este es un ejemplo simple de operaciones en 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
}

Implementé el código que instala automáticamente Groovy en esclavo (para canalización con script). Quizás esta solución sea un poco engorrosa, pero las canalizaciones no ofrecen ninguna otra forma de lograr la misma funcionalidad que las cosas “Ejecutar Groovy Script” del antiguo Jenkins, porque el complemento https://wiki.jenkins.io/display/ El complemento JENKINS / Groovy + aún no es compatible con la canalización.

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) 

/* 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) 
    
    
    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)

Simplemente coloque estos 3 métodos en su secuencia de comandos de canalización y podrá obtener la ruta del ejecutable Groovy con la ayuda del método getGroovy (). Si aún no está instalado, la instalación se realizará automáticamente. Puede probar este código con la canalización simple, como esta:

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

Para mí el resultado fue:

[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

Me encontré con este mismo problema recientemente. Tenía un archivo de Python que se ejecuta y escribe los resultados en un archivo JSON. Estaba intentando acceder al archivo JSON para recuperar los datos desde allí. Aquí está el código que estaba usando dentro de un bloque de etapa de una canalización declarativa:

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

Como todos dijeron ya, lo anterior estaba fallando con FileNotFoundException porque cualquier cosa dentro script solo se ejecutará en el maestro y no en el agente. Para solucionar el problema, he utilizado el Pasos de la utilidad de canalización complemento (referencia: https://plugins.jenkins.io/pipeline-utility-steps/ – Cómo usar: https://www.jenkins.io/doc/pipeline/steps/pipeline-utility-steps/#writejson -write-json-to-a-file-in-the-workspace) El complemento le permitirá realizar cualquier operación de lectura / escritura en múltiples formatos de archivo.

Aquí hay un ejemplo del código que utilicé después de instalar el complemento:

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

Nota: estaba usando jenkins 2.249.1

Puntuaciones y comentarios

Acuérdate de que tienes la capacidad de esclarecer tu experiencia si te fue preciso.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *