I am trying to develop shared libraries and has the following directory structure
my Jenkinsfile calls only method available in the test.groovy with required input. which imports MyTest and creates object and calls constructor and followed by the actual method that executes the functionality available in the MyTest.groovy file
Here the constructor class never called from global vars/test.groovy
I tried calling class and method from groovy it is working fine but when i am calling it from jenkinsfile it is not working. I don't know why i am not able to call constructor and what i am missing.
I have placed @NonCPS on above test.groovy method but still it is not working.
//MyTest.groovy
package com.mycompany
class MyTest implements Serializable {
public _notification
MyTest(Closure content) {
notification = [:]
content.resolveStrategy = Closure.DELEGATE_FIRST
content.delegate = notification
content()
println("Entered Constructor")
this._notification = notification
}
}
//test.groovy
import com.mycopany.MyTest
def notify(Closure content) {
println("This is line i am getting in the output but nothing after that")
MyTest tester = new MyTest(content)
println(tester._notification.colorcode)
}
//Jenkinsfile
@library("MysharedLibrary@master") _
pipeline{
stages {
stage {
steps {
test.notify {
buildno = 12
jobname = "Mytest"
colorcode = "Blue"
}
}
}
}
}
here i just want to call constructor from jenkins file and print the value of input value in my vars/test.groovy.
EDIT1: When I use @NonCPS above the method "def nofity" my build is getting successful but i am getting nothing except print statement in the first line in the notify method.
If I don't use @NonCPS I am getting following Exception
Error when executing always post condition:
com.cloudbees.groovy.cps.impl.CpsCallableInvocation
hudson.remoting.ProxyException:com.cloudbees.groovy.cps.impl.CpsCallableInvocation
Finished: FAILURE
You see this error because you are calling closure inside the constructor. Instead, you should extract it to a separate method. The constructor should be used to initialize the object with values passed during initialization. For instance, the common practice is to pass the reference to the WorkflowScript
object, so you can use pipeline steps like echo
.
Consider the following changes to your shared library code:
src/com/mycompany/MyTest.groovy
package com.mycompany
class MyTest {
final Map notification = [:]
final Script script
MyTest(Script script) {
this.script = script
}
def notify(Closure content) {
processContent(content)
script.echo "Notification processed = ${notification}"
}
def processContent(Closure content) {
content.resolveStrategy = Closure.DELEGATE_FIRST
content.delegate = notification
content()
}
}
vars/test.groovy
import com.mycompany.MyTest
import groovy.transform.Field
@Field
final MyTest tester = new MyTest(this)
def notify(Closure content) {
println("This is line i am getting in the output but nothing after that")
tester.notify(content)
println(tester.notification.colorcode)
}
Jenkinsfile
pipeline {
agent any
stages {
stage("Test") {
steps {
script {
test.notify {
buildno = 12
jobname = "Mytest"
colorcode = "Blue"
}
}
}
}
}
}
The output:
[Pipeline] Start of Pipeline
[Pipeline] node
[Pipeline] {
[Pipeline] stage
[Pipeline] { (Test)
[Pipeline] script
[Pipeline] {
[Pipeline] echo
This is line i am getting in the output but nothing after that
[Pipeline] echo
Notification processed = [buildno:12, jobname:Mytest, colorcode:Blue]
[Pipeline] echo
Blue
[Pipeline] }
[Pipeline] // script
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Keep in mind that this is just a one way of how you can refactor your shared pipeline library code. Your main goal should be to move the closure call from inside the class constructor. It's up to you where to put it so it works for you best.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With