I'm having a Jenkins pipeline that does both build and deploy tasks in a scripted pipeline. The pipeline is maintained in a central repository. For each deployment stage, it will wait for the user input and the wait time can be up to several days. During the waiting period, if the pipeline script got changed, I want the latest script to be loaded after the deployment approval (user input).
The first load
step of the script is working fine and when I try to load again, I'm getting an error below. It appears that the GroovyClassLoader is throwing this error.
I tried to remove the loadedScripts
via currentBuild.rawBuild.getExecution().loadedScripts
but that didn't help. Is there a way to achieve my use-case of reloading the script on every stage? Any pointers would be helpful.
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
CodePudding user response:
maybe i don't fully understant your problem (in which case, i apologize), but i don't see why your 'somepath/external.groovy' is not a simple groovy library, (something like this: http://rtyler.github.io/jenkins.io/doc/book/pipeline/shared-libraries/), declared as used at the beginning of the pipeline, and then your JobConfig and sayHello can be called as many times as you want and have different behavior based on some parameter - like the stage name .
CodePudding user response:
The short answer is that shared libraries are intended to solve this sort of issue. If that's not an option, then some restructuring of your script file may resolve this as a workaround.
The error you're receiving is stating that you're redefining a class with the same name which is not allowed. Since you're loading the same script a second time, that's basically the same as loading a class of the same name twice.
Option 1 would be to split your pipeline into multiple pipelines that all ingest the same config. I've personally never had good luck with long-running pipelines that wait for user input to continue. Instead, you could split your pipeline into a "build" pipeline that basically loads your config and runs the build step, then later on when you're ready to deploy, you run a "deploy-dev" pipeline that loads the config, then runs a deploy-dev step. Then another pipeline that does the same for "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
}
Option 2 would be to split the script into multiple scripts. One script would contain the class definition and would only be loaded a single time at the top of the pipeline. Then other scripts would contain each of your other methods which will be used by each pipeline step. This would look something like this:
// 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
}
Then when you load the later scripts, they can be brought up to date and loaded without redefining the initial class definition or redefining individual methods (which I imagine would also be an issue).