-
Bug
-
Resolution: Unresolved
-
Minor
-
None
Hi!
In the current implementation, load step generate ordered class names for loaded scripts ignoring file pathname. That leads to the problem with a replay if load steps get executed based on condition and can be skipped:
Let's say we have a pipeline with three stages that contains two load step calls
node { stage('Build') { if (!isAlreadyBuilt) { load('scripts/build.groovy')() // (1) load call } } stage('Deploy') { load('scripts/deploy.groovy') // (2) load call } }
Following the logic from here https://github.com/jenkinsci/workflow-cps-plugin/blob/261df120d1c2f228b2137c3f880a7741293ea5f2/src/main/java/org/jenkinsci/plugins/workflow/cps/steps/LoadStepExecution.java#L39-L41
String text = cwd.child(step.getPath()).readToString(); String clazz = execution.getNextScriptName(step.getPath()); // ignores parameter and return ordered generated name Script1, Script2, ... String newText = ReplayAction.replace(execution, clazz); if (newText != null) { listener.getLogger().println("Replacing Groovy text with edited version"); text = newText; }
First run:
(1)
text = the content of `scripts/build.groovy` clazz = 'Script1' newText = null (since it's not a replay) if (newText != null) {...} // skipping... Result: text = scripts/build.groovy
(2)
text = the content of `scripts/deploy.groovy` clazz = 'Script2' newText = null (since it's not a replay) if (newText != null) {...} // skipping... Result: text = the content of `scripts/deploy.groovy`
Replay:
(1) Will be skipped due to condition in the pipeline
(2)
text = the content of `scripts/deploy.groovy` clazz = 'Script1' // because it's a first call of load step newText = the content of Script1(scripts/build.groovy) if (newText != null) { listener.getLogger().println("Replacing Groovy text with edited version"); text = newText; // text get replaced from the content of deploy.groovy to build.groovy } Result: text = the content of `scripts/build.groovy`
As a result script `scripts/build.groovy` will be executed during the replay build instead of expected `scripts/deploy.groovy`.
A workaround here is to load all external scripts before the conditional logic that helps to keep consistent order for external scripts class names no matter whether we use them or not during a build.
node { def externalScripts = [:] stage('Load external scripts') { externalScripts = ['build': load 'scripts/build.groovy', 'deploy': load 'scripts/deploy.groovy'] // 'build' is always Script1, 'deploy' is always Script2] } stage('Build') { if (!isAlreadyBuilt) { externalScripts['build']() // Script1 } } stage('Deploy') { externalScripts['deploy']() // Script2 } }
Would be nice to have a fix for load step unexpected behaviour or at least update documentation with a mention of this.