Jenkins - 在一个作业中多次加载 groovy 脚本,但有不同的阶段

Jenkins - load groovy script multiple times in a single job but with different stages

我有一个 Jenkins 管道,它在脚本管道中执行构建和部署任务。管道在中央存储库中维护。对于每个部署阶段,它都会等待用户输入,等待时间可能长达数天。在等待期间,如果管道脚本发生变化,我希望在部署批准(用户输入)后加载最新的脚本。

脚本的第一步 load 工作正常,当我尝试再次加载时,出现以下错误。似乎是 GroovyClassLoader 抛出了这个错误。

我试图通过 currentBuild.rawBuild.getExecution().loadedScripts 删除 loadedScripts 但这没有帮助。有没有办法实现我在每个阶段重新加载脚本的用例?任何指针都会有所帮助。

java.lang.LinkageError: loader org.jenkinsci.plugins.workflow.cps.CpsGroovyShell$CleanGroovyClassLoader @8d141543 attempted duplicate class definition for Script1. (Script1 is in unnamed module of loader org.jenkinsci.plugins.workflow.cps.CpsGroovyShell$CleanGroovyClassLoader

// pipeline.groovy
def config 
node('agent') {
    stage('build') {
        config = loadScripts()
        echo 'perform some tasks'
    }
    stage('deploy-dev') {
        input message: 'approve'
        config = loadScripts()
        echo 'perform some tasks'
    }
    stage('deploy-sit') {
        input message: 'approve'
        config = loadScripts()
        echo 'perform some tasks'
    }
}

def loadScripts() {
    checkout scm
    config = load 'somepath/external.groovy'
    return config
}

// somepath/external.groovy

class JobConfig {
    def someDataVar1
    def someDataVar2
}

def sayHello() {
    println 'hello'
}

return this

也许我没有完全理解你的问题(在这种情况下,我深表歉意),但我不明白为什么你的 'somepath/external.groovy' 不是一个简单的 groovy 库, (像这样:http://rtyler.github.io/jenkins.io/doc/book/pipeline/shared-libraries/), 在管道的开头声明为已使用,然后您的 JobConfig 和 sayHello 可以根据需要多次调用,并根据某些参数(例如阶段名称)具有不同的行为。

简短的回答是共享库旨在解决此类问题。如果这不是一个选项,那么对脚本文件进行一些重组可能会解决这个问题。

您收到的错误表明您正在重新定义 class 同名,这是不允许的。由于您第二次加载相同的脚本,这与加载同名的 class 两次基本相同。

选项 1 是将您的 管道 拆分为多个管道,这些管道都摄取相同的配置。我个人从来没有在 long-running 等待用户输入继续的管道方面获得好运。相反,您可以将您的管道拆分为一个“构建”管道,该管道基本上加载您的配置和 运行 构建步骤,然后稍后当您准备好部署时,您 运行 一个“deploy-dev" 加载配置的管道,然后 运行 是 deploy-dev 步骤。然后是另一个对“deploy-sit”执行相同操作的管道。

// pipeline-build.groovy
def config 
node('agent') {
    stage('build') {
        config = loadScripts()
        echo 'perform some tasks'
    }
}

def loadScripts() {
    checkout scm
    config = load 'somepath/external.groovy'
    return config
}

// pipeline-deploy-dev.groovy
def config 
node('agent') {
    stage('deploy-dev') {
        input message: 'approve'
        config = loadScripts()
        echo 'perform some tasks'
    }
}

def loadScripts() {
    checkout scm
    config = load 'somepath/external.groovy'
    return config
}

// pipeline-deploy-sit.groovy
def config 
node('agent') {
    stage('deploy-sit') {
        input message: 'approve'
        config = loadScripts()
        echo 'perform some tasks'
    }
}

def loadScripts() {
    checkout scm
    config = load 'somepath/external.groovy'
    return config
}

选项 2 是将 脚本 拆分为多个脚本。一个脚本将包含 class 定义,并且只会在管道顶部加载一次。然后其他脚本将包含每个管道步骤将使用的每个其他方法。这看起来像这样:

// pipeline.groovy
def config 
node('agent') {
    loadScript("somepath/JobConfig.groovy")
    stage('build') {
        config = loadScript("somepath/Build.groovy")
        echo 'perform some tasks'
    }
    stage('deploy-dev') {
        input message: 'approve'
        config = loadScript("somepath/DeployDev.groovy")
        echo 'perform some tasks'
    }
    stage('deploy-sit') {
        input message: 'approve'
        config = loadScript("somepath/DeploySit.groovy")
        echo 'perform some tasks'
    }
}

def loadScript(def script) {
    checkout scm
    config = load script
    return config
}

然后当您加载后面的脚本时,它们可以更新并加载,而无需重新定义初始 class 定义或重新定义各个方法(我想这也是一个问题)。