Jenkins pipeline replaces method call in vars/mavenBuildSpike.groovy with assignment "new NullObject" - jenkins

I have this code in vars/mavenBuildSpike.groovy:
#NonCPS
def createSqBuilder(SqBuildConfig config) {
System.out.println("createSqBuilder=${config}")
// The constructor contains code which the CPS transformer can't handle.
def result = new SqBuilder(config)
System.out.println("result=${result}")
return result
}
def call(Closure body) {
echo 'Creating ConfigBuilderWrapper'
def wrapper = new ConfigBuilderWrapper()
echo 'Calling apply()'
wrapper.apply(body)
echo 'Done processing closure'
def config = wrapper.builder.build()
echo "config=${config.dump()}"
echo 'Creating builder'
def builder = createSqBuilder(config) // <<--- This doesn't work.
echo "builder=${builder}"
echo builder.dump()
...
The output is:
...everything looks good...
[Pipeline] echo
Creating builder
[Pipeline] echo
builder=null
[Pipeline] End of Pipeline
java.lang.NullPointerException: Cannot invoke method hashCode() on null object
at org.codehaus.groovy.runtime.NullObject.hashCode(NullObject.java:174)
at org.codehaus.groovy.runtime.DefaultGroovyMethods.dump(DefaultGroovyMethods.java:291)
...
at org.kohsuke.groovy.sandbox.impl.Checker.checkedCall(Checker.java:159)
at com.cloudbees.groovy.cps.sandbox.SandboxInvoker.methodCall(SandboxInvoker.java:17)
at mavenBuildSpike.call(...\branches\master\builds\16\libs\sq-pipeline-library-spike\vars\mavenBuildSpike.groovy:33)
at WorkflowScript.run(WorkflowScript:4)
at ___cps.transform___(Native Method)
....
That is, the method createSqBuilder is never called and just replaced with an assignment: def builder = new NullObject().
Why is that and how can I fix it?

Before running the code, Jenkins will do an AST transformation called "CPS transformation". This transformer doesn't support everything that Groovy can do and it won't tell you when it can't - you'll just get weird or useless error messages running the resulting code and sometimes no errors at all - the build will simply fail without any error message or stack trace anywhere.
It seems that the CPS transform doesn't like calling constructors with arguments. This worked for me:
#Field // groovy.transform.Field
SqBuilder builder = new SqBuilder()
def call(Closure body) {
...
//def builder = createSqBuilder(config) // Doesn't work!!!
builder.init(config) // This works; move the code from the constructor to the init() method.
...
The #Field annotation is necessary to turn the local variable builder into a field of the class which Groovy will create at runtime. The name of this class is WorkflowScript.
You could also just type builder = new SqBuilder() (without type or def before the variable name). But that would put builder into the pool of global variables (called "Binding" in Groovy). Jenkins puts it's own stuff there (like env or scm) so that could cause strange problems when you install more plugins.
See also: Strange variable scoping behavior in Jenkinsfile

Related

Getting java.lang.IllegalArgumentException: One or more variables have some issues with their values in jenkins pipeline

I have a Jenkins declarative pipeline wherein I am trying to store the value returned from a method into environment variable as shown below.
steps {
script {
def job = getJob(JOB_NAME)
def param = getParam(job, "Ser")
echo param.getValue()
}
}
environment {
p_values = param.getValue()
}
But while running above script I am getting below error.
java.lang.IllegalArgumentException: One or more variables have some issues with their values: p_values
Could you please assist me here to resolve this issue?
I think the environment block will be executed prior to the script block.
You can try assign value to an new environment variable within script block, rather then in environment block as following:
script {
def job = getJob(JOB_NAME)
def param = getParam(job, "Ser")
echo param.getValue()
env.p_values = param.getValue()
}

No such field found: field java.lang.String sinput error when accessing cppcheck plugin classes

]I am a junior dev trying to lear about Jenkins, I have been learning on my own for a couple of months. Currently I have a pipeline (just for learning purposes) which runs static analysis on a folder, and then publish it, I have been able to send a report through email using jelly templates, from there I realized it is posbile to instantiate the classes of a plugin to use its methods so I went to the cppcheck javadoc and did some trial and error so I can get some values of my report and then do something else with them, so I had something like this in my pipeline:
pipeline {
agent any
stages {
stage('analysis') {
steps {
script{
bat'cppcheck "E:/My_project/Source/" --xml --xml-version=2 . 2> cppcheck.xml'
}
}
}
stage('Test'){
steps {
script {
publishCppcheck pattern:'cppcheck.xml'
for (action in currentBuild.rawBuild.getActions()) {
def name = action.getClass().getName()
if (name == 'org.jenkinsci.plugins.cppcheck.CppcheckBuildAction') {
def cppcheckaction = action
def totalErrors = cppcheckaction.getResult().report.getNumberTotal()
println totalErrors
def warnings = cppcheckaction.getResult().statistics.getNumberWarningSeverity()
println warnings
}
}
}
}
}
}
}
which output is:
[Pipeline] echo
102
[Pipeline] echo
4
My logic (wrongly) tells me that if I can access to the report and statistics classes like that and uses their methods getNumberTotal() and getNumberWarningSeverity() respectively, therefore I should be able to also access the DiffState class in the same way and use the valueOf() method to get an enum of the new errors. But adding this to my pipeline:
def nueva = cppcheckaction.getResult().diffState.valueOf(NEW)
println nueva
Gives me an error:
org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException: No such field found: field org.jenkinsci.plugins.cppcheck.CppcheckBuildAction diffState
at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxInterceptor.unclassifiedField(SandboxInterceptor.java:425)
at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxInterceptor.onGetProperty(SandboxInterceptor.java:409)
...
I can see in the javadoc there is a diffState class with a valueOf() method, but I cannot access to it is therre any other way to get the new errors between the last build and the current one?
I see 2 issues that could be causing this:
CppcheckResult doesn't have a member variable diffState so you can't access it obviously
If you check the javadoc of CppcheckResult the class does have:
private CppcheckReport report;
public CppcheckStatistics getReport()
and
private CppcheckStatistics statistics;
public CppcheckStatistics getStatistics()
there is no member (and getter method) for diffState so maybe try to call:
/**
* Get differences between current and previous statistics.
*
* #return the differences
*/
public CppcheckStatistics getDiff(){
my suggestion: cppcheckaction.getResult().getDiff().valueOf(NEW). Furthermore CppcheckWorkspaceFile does have a method getDiffState().
Please have a look at the script approval of your Jenkins (see here).
The syntax error might appear because Jenkins (Groovy Sandbox) blocks the execution of an (for the Jenkins) "unknown" and potential dangerous method.
Jenkins settings - Script Approval - Approve your blocked method

XmlSlurper() in jenkins pipeline. how to avoid java.io.NotSerializableException: groovy.util.slurpersupport.NodeChild

I'm trying to read properties from my pom.xml file. I tried the following and it worked:
steps {
script {
def xmlfile = readFile "pom.xml"
def xml = new XmlSlurper().parseText(xmlfile)
def version = "${xml.version}"
echo version
}
}
When I tried to do something like this:
steps {
script {
def xmlfile = readFile "pom.xml"
def xml = new XmlSlurper().parseText(xmlfile)
def version = "${xml.version}"
def mystring = "blabhalbhab-${version}"
echo mystring
}
}
the pipeline suddenly fails with the error:
Caused: java.io.NotSerializableException: groovy.util.slurpersupport.NodeChild
What might be the problem here?
EDIT: just adding this for others finding it with the same use case. My specific question was about how to avoid the CPS related error with XmlSlurper(). BUT for anyone else trying to parse POMs, afraid of that PR that supposedly will deprecate readMavenPom, the safest most maveny way of doing this is probably something like:
def version = sh script: "mvn help:evaluate -f 'pom.xml' -Dexpression=project.version -q -DforceStdout", returnStdout: true trim()
This way your using maven itself to tell you what the version is and not grepping or sedding all over the damn place. How to get Maven project version to the bash command line
In general, using groovy.util.slurpersupport.NodeChild (the type of your xml variable) or groovy.util.slurpersupport.NodeChildren (the type of xml.version) inside CPS pipeline is a bad idea. Both classes are not serializable, so you can't predicate their behavior in the Groovy CPS. For instance, I run successfully your second example in my Jenkins Pipeline. Most probably because the example you gave is not complete or something like that.
groovy:000> xml = new XmlSlurper().parseText("<tag></tag>")
===>
groovy:000> xml instanceof Serializable
===> false
groovy:000> xml.tag instanceof Serializable
===> false
groovy:000> xml.dump()
===> <groovy.util.slurpersupport.NodeChild#0 node=groovy.util.slurpersupport.Node#5b1f29fa parent= name=tag namespacePrefix=* namespaceMap=[xml:http://www.w3.org/XML/1998/namespace] namespaceTagHints=[xml:http://www.w3.org/XML/1998/namespace]>
groovy:000> xml.tag.dump()
===> <groovy.util.slurpersupport.NodeChildren#0 size=-1 parent= name=tag namespacePrefix=* namespaceMap=[xml:http://www.w3.org/XML/1998/namespace] namespaceTagHints=[xml:http://www.w3.org/XML/1998/namespace]>
groovy:000>
If you want to read pom.xml file, use the readMavenPom pipeline step. It is dedicated to read pom files and what is most important - it is safe to do it without applying any workarounds. This step comes with the pipeline-utility-steps plugin.
However, if you want to use XmlSlurper for some reason, you need to use it inside the method that is annotated with #NonCPS. That way you can access "pure" Groovy and avoid problems you have faced. (Yet still using readMavenPom is the safest way to achieve what you are trying to do.) The point here is to use any non-serializable objects inside a #NonCPS scope so the pipeline does not try to serialize it.
Below you can find a simple example of the pipeline that shows both approaches.
pipeline {
agent any
stages {
stage("Using readMavenPom") {
steps {
script {
def xmlfile = readMavenPom file: "pom.xml"
def version = xmlfile.version
echo "version = ${version}"
}
}
}
stage("Using XmlSlurper") {
steps {
script {
def xmlfile = readFile "pom.xml"
def version = extractFromXml(xmlfile) { xml -> xml.version }
echo "version = ${version}"
}
}
}
}
}
#NonCPS
String extractFromXml(String xml, Closure closure) {
def node = new XmlSlurper().parseText(xml)
return closure.call(node)?.text()
}
PS: not to mention that using XmlSlurper requires at least script 3 approvals before you can start using it.

Scripted pipeline: wrap stage

I would like to be able to wrap a 'stage' in Jenkins, so I can execute custom code at the start and end of a stage, so something like:
myStage('foo') {
}
I thought I could do this by using metaClass:
//Wrap stages to automatically trace
def originalMethod = this.metaClass.getMetaMethod("stage", null)
this.metaClass.myStage = { args ->
println "Beginning of stage"
println "Args: " + args
def result = originalMethod.invoke(delegate, args)
println "End of stage"
return result
}
But it appears the Groovy script itself is a Binding, which doesn't have a metaClass:
groovy.lang.MissingPropertyException: No such property: metaClass for class: groovy.lang.Binding
I'm still learning how Groovy and Jenkins Pipeline work, so perhaps I'm just missing something.
I am not familiar with the metaclass concept but I think that a simple solution to your problem is to define a wrapped stage as a function.
Here's an example of how you'd define such a function:
def wrappedStage(name, Closure closure) {
stage(name) {
echo "Beginning of stage"
def result = closure.call()
echo "End of stage"
return result
}
}
and this is how you would call it:
wrappedStage('myStage') {
echo 'hi'
}
The return value of wrappedStage would only make sense when the body of your stage actually returns something, for example:
If you call another job, eg:
wrappedStage('myStage') {
build job: 'myJob'
}
you will get back org.jenkinsci.plugins.workflow.support.steps.build.RunWrapper which you can use to access info of the job you run, like result, variables etc
If you print something to the console, eg:
wrappedStage('myStage') {
echo 'hi'
}
you will get back null.
Note that in my example I am not printing args because the way I understand stage, it only takes 2 arguments; the stage name and the closure it should run. The name of the stage will already be printed in the log, and I don't know how much value you'd get from printing the code you're about to execute but if that's something you want to do, take a look at this.
If you have a more specific case in mind for what you'd want to wrap, you can add more params to the wrapper and print all the information you want through those extra parameters.

println in "call" method of "vars/foo.groovy" works, but not in method in class

I'm iterating through building a Jenkins pipeline shared library, so my Jenkinsfile is a little cleaner.
I'm using the following page for guidance: https://jenkins.io/doc/book/pipeline/shared-libraries/ .
I first defined several methods in individual files, like "vars/methodName.groovy", with a "call()" method in the code. This works ok, and I particularly note that "println" calls in these methods are seen in the Jenkins console output.
I then decided I wanted to save some state between method calls, so I added a new file in "vars" named "uslutils.groovy" that begins like this (minus some required imports):
class uslutils implements Serializable {
I defined some "with<property>" methods that set a property and return this.
I then wrote a "public String toString()" method in "uslutils" that looks something like this:
public String toString() {
println "Inside uslutils.toString()."
return "[currentBuild[${currentBuild}] mechIdCredentials[${mechIdCredentials}] " +
"baseStashURL[${baseStashURL}] jobName[${jobName}] codeBranch[${codeBranch}] " +
"buildURL[${buildURL}] pullRequestURL[${pullRequestURL}] qBotUserID[${qBotUserID}] " +
"qBotPassword[${qBotPassword}]]"
}
Then, inside my Jenkinsfile, after setting the uslutils properties, I added a line like this:
println "uslutils[${uslutils}]"
Then, I ran my job, and the curious thing that happened is that I didn't see the "uslutils" line, or the Inside uslutils.toString(). line. However, I did modify the one functional method I've added so far to "uslutils" (besides the "with" methods), which returns a string value, and I just added an "x" to the value. My Jenkinsfile was printing the result from that, and it did show the additional "x".
Note that no errors occurred here, it just seemed to omit the println output from within the shared library class, and even stranger, omitted the output from the println call in the Jenkinsfile that was implicitly calling the uslutils.toString() method. Note that the println calls in the original call() methods WERE seen in the console output.
Any ideas what might be happening here?
Update:
I now have the following lines in my Jenkinsfile (among others):
println "uslutils.qBotPassword[${uslutils.qBotPassword}]"
println "uslutils[${uslutils}]"
println "uslutils.toString()[${uslutils.toString()}]"
And to repeat, here is the "uslutils.toString()" method:
public String toString() {
println "Inside uslutils.toString()."
return "[currentBuild[${currentBuild}] mechIdCredentials[${mechIdCredentials}] " +
"baseStashURL[${baseStashURL}] jobName[${jobName}] codeBranch[${codeBranch}] " +
"codeURL[${codeURL}] buildURL[${buildURL}] pullRequestURL[${pullRequestURL}] qBotUserID[${qBotUserID}] " +
"qBotPassword[${qBotPassword}]]"
}
Here are corresponding lines of output from the build:
[Pipeline] echo
uslutils.qBotPassword[...]
[Pipeline] echo
uslutils.toString()[[currentBuild[org.jenkinsci.plugins.workflow.support.steps.build.RunWrapper#41fb2c94] mechIdCredentials[121447d5-0fe4-470d-b785-6ce88225ef01] baseStashURL[https://...] jobName[unified-service-layer-build-pipeline] codeBranch[master] codeURL[ssh://git#...] buildURL[http://...] pullRequestURL[] qBotUserID[...] qBotPassword[...]]
As you can see, the line attempting to print "uslutils[${uslutils}]" was simply ignored. The line attempting to print "uslutils.toString()[${uslutils.toString()}]" did render, but also note that the Inside uslutils.toString(). did not render.
I'm still looking for an explanation for this behavior, but perhaps this summarizes it more succinctly.
I did some digging and found this issue, https://issues.jenkins-ci.org/browse/JENKINS-41953, basically in normal pipeline script println is aliased to echo step. But when you're in a class, e.g. outside of the pipeline CPS, then the echo step isn't available and the println is ignored (since, as I understand it, there is no logger available).
What you can do is to propagate the script environment into your class methods using a variable and call echo through the variable (found solution in this thread). Like this:
class A {
Script script;
public void a() {
script.echo("Hello")
}
}
def a = new A(script:this)
echo "Calling A.a()"
a.a()
outputs:
Started by user jon
[Pipeline] echo
Calling A.a()
[Pipeline] echo
Hello
[Pipeline] End of Pipeline
Finished: SUCCESS
Which is what we want. For comparison, here is without propagation:
class A {
public void a() {
println "Hello"
}
}
def a = new A()
echo "Calling A.a()"
a.a()
Gives:
Started by user jon
[Pipeline] echo
Calling A.a()
[Pipeline] End of Pipeline
Finished: SUCCESS

Resources