Parse groovy script syntactically - parsing

Unable to parse (check for syntax) for groovy code. parse() method doesn't throw any exception but while executing it throws Exception
GroovyShell gEngine = new GroovyShell();
gEngine.evaluate("def n; print ddd"); // throws an exception
gEngine.parse("def n; print ddd"); // don't throws Excption

From the groovy API:
parse
Parses the given script and returns it ready to be run
evaluate
Evaluates some script against the current Binding and returns the result
As #tim_yates notes in his comment, evaluate run the script while parse return a script object without running it, this is why evaluate throws the exception: groovy.lang.MissingPropertyException: No such property: ddd
However if you run the script returned by parse it will throw the same exception:
GroovyShell gEngine = new GroovyShell()
def script = gEngine.parse("def n; print ddd")
script.run() // throws missingPropertyException

Related

Jenkins Groovy Pipeline org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException: No such field found: field groovy.util.Node

I am retrieving an XML file from a remote host and parsing it using XmlParser. The content of the file is as follows:
<?xml version="1.0" encoding="utf-8"?><Metrics> <Safety> <score>81.00</score> <Percentrules>98.00</Percentrules> </Safety> </Metrics>
I am able to retrieve the score value in the following way when I execute the script outside the Groovy sandbox.
def report = readFile(file: 'Qualitycheck.xml')
def metrics = new XmlParser().parseText(report)
println metrics
double score = Double.parseDouble(metrics.Safety.score[0].value()[0])
However, when I execute the script using SCM I get the following:
org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException: No such field found: field groovy.util.Node
The issue persist even though I have installed the Permissive-Script-Security-Plugin and enabled the plugin using the -Dpermissive-script-security.enabled=no_securityJVM option. Is there something different about this method? No other method is causing issues. Why?
Edit
I decided to use XmlSlurper(), and retrieved the value 81.00. However the result was type groovy.util.slurpersupport.NodeChildren
def metrics2 = new XmlSlurper().parseText(report)
def score = metrics2.Safety.score
print score
print score.getClass()
=> 81.0098.00
=> groovy.util.slurpersupport.NodeChildren
How do I use XmlSlurper to extract the value 81.00 and cast it as double? Will that be a good alternative?
There seems to be some issues with the script sandbox with Node and NodeList field access. You can work around this like the following, its not nice but works at least.
node() {
def xml = readFile "${env.WORKSPACE}/Qualitycheck.xml"
def rootNode = new XmlParser().parseText(xml)
print Double.parseDouble(rootNode.value()[0].value()[0].value()[0])
// Next line if position isnt fixed, can return an array
// if theres more than 1 with structure "Safety.score", [0] at the end takes the first.
print Double.parseDouble(rootNode.find{it.name() == "Safety"}.value().find{it.name() == "score"}.value()[0])
}
You also need to approve following signatures in the In-process Script Approval section in Manage Jenkins menu.
method groovy.util.Node name
method groovy.util.Node value
method groovy.util.XmlParser parseText java.lang.String
new groovy.util.XmlParser
staticMethod java.lang.Double parseDouble java.lang.String
staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods find java.lang.Object groovy.lang.Closure

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.

NotSerializableException in jenkinsfile

I'm working on a jenkinsfile and I'm getting and exception in the third stage:
an exception which occurred:
in field com.cloudbees.groovy.cps.impl.BlockScopeEnv.locals
in object com.cloudbees.groovy.cps.impl.BlockScopeEnv#7bbae4fb
in field com.cloudbees.groovy.cps.impl.ProxyEnv.parent
in object com.cloudbees.groovy.cps.impl.CaseEnv#6896a2e3
in field com.cloudbees.groovy.cps.impl.ProxyEnv.parent
in object com.cloudbees.groovy.cps.impl.BlockScopeEnv#605ccbbc
in field com.cloudbees.groovy.cps.impl.CallEnv.caller
in object com.cloudbees.groovy.cps.impl.FunctionCallEnv#7b8ef914
in field com.cloudbees.groovy.cps.Continuable.e
in object org.jenkinsci.plugins.workflow.cps.SandboxContinuable#11e73f3c
in field org.jenkinsci.plugins.workflow.cps.CpsThread.program
in object org.jenkinsci.plugins.workflow.cps.CpsThread#b2df9bb
in field org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.threads
in object org.jenkinsci.plugins.workflow.cps.CpsThreadGroup#2b30596a
in object org.jenkinsci.plugins.workflow.cps.CpsThreadGroup#2b30596a
Caused: java.io.NotSerializableException: java.util.regex.Matcher
at org.jboss.marshalling.river.RiverMarshaller.doWriteObject(RiverMarshaller.java:860)
at org.jboss.marshalling.river.BlockMarshaller.doWriteObject(BlockMarshaller.java:65)
at org.jboss.marshalling.river.BlockMarshaller.writeObject(BlockMarshaller.java:56)
I've been reading about it and I know I can't create non-serializable variables. So, I think it has to be with this part of my code:
def artifact_name = sh (
script: "ls -b *.jar | head -1",
returnStdout: true
).trim()
def has_snapshot = artifact_name =~ /-TEST\.jar/
if (has_snapshot) {
//Do something
}
My question is, how do I define that two variables in order to avoid that exception?
Your problem is this line:
def has_snapshot = artifact_name =~ /-TEST\.jar/
The =~ is the Groovy find operator. It returns a java.util.regex.Matcher instance, which is not Serializable. If Jenkins decides to pause your script after you have stored the result in a local variable that is serialized by Jenkins that is when you get the exception. This can be easily tested by immediately adding a sleep(1) step after your invocation and watch as that same exception is thrown.
To resolve this, you should :
Not store the java.util.regex.Matcher result in CPS transformed code
Move the usage into a #NonCPS annotated method or use the match operator (==~) which returns a boolean (if it fits your use case)
Building on the accepted answer I came up with this solution:
def hello = {
def matcher = ("Hello" =~ /Hello/)
matcher.find()
return matcher.group()
}.call()
I guess the stability of this is not so good, but I assume the likeliness of this failing to be very low. So if the impact of this code failing is also low it might be reasonable risk management to use this code.
The following seems to fit the case of running in a NonCPS context, but I am not 100% sure. It definitely is working though
#NonCPS def hello = {
def matcher = ("Hello" =~ /Hello/)
matcher.find()
return matcher.group()
}
hello = hello.call()
println hello
The accepted answer is certainly correct. In my case, I was trying to parse some JSON from an API response like so:
#NonCPS
def parseJson(rawJson) {
return new groovy.json.JsonSlurper().parseText(rawJson)
}
All this does is return a JsonSlurper that can then be used to walk down your JSON structure, like so:
def jsonOutput = parseJson(createIssueResponse)
echo "Jira Ticket Created. Key: ${jsonOutput.key}"
This snippet actually worked fine in my script, but later on in the script, it was using the jsonOutput.key to make a new web request. As stated in the other answer, if the script pauses when you have something stored into a local variable that cannot be serialized, you will get this exception.
When the script attempted to make the web request, it would pause (presumably because it was waiting for the request to respond), and the exception would get thrown.
In my scenario, I was able to fix this by doing this instead:
def ticketKey = parseJson(createIssueResponse).key.toString()
echo "Jira Ticket Created. Key: ${ticketKey}"
And later on when the script attempts to send the web request, it no longer throws the exception. Now that no JsonSlurper object is present in my running script when it is paused, it works fine. I previously assumed that because the method was annotated with #NonCPS that its returned object was safe to use, but that is not true.

How to return a function value from pcall() in lua

My code (psuedo)
function foo(cmd)
return load(cmd) --Note that this could cause an error, should 'cmd' be an invalid command
end
function moo()
return "moo"
end
function yoo(something)
something.do()
end
cmd = io.read() --Note that a syntax error will call an error in load. Therefore, I use pcall()
local result, error = pcall(cmd)
print(result)
This code looks okay, and works, but my problem is if I type in moo() then result will only show whether or not the command was executed without an error (If the command calls an error, error will have its value).
On another note, if I want to call yoo(), I won't get a return value from it, so I want pcall()'s true / false (or any alternate means other than pcall())
Is there an alternate way to call moo(), get a return value, and also be able to catch any errors?
NOTE: I couldn't find any try catch equivalent other then pcall / xpcall.
A bit outdated but still without proper answer...
What you are looking for is a combination of both load() and pcall()
Use load()to compile the entered string cmd into something that can be executed (function).
Use pcall() to execute the function returned by load()
Both functions can return error messages. Get syntax error description from load() and runtime error description from pcall()
function moo()
return "moo"
end
function yoo(something)
something.do_something()
end
cmd = io.read()
-- we got some command in the form of a string
-- to be able to execute it, we have to compile (load) it first
local cmd_fn, err = load("return "..cmd);
if not cmd_fn then
-- there was a syntax error
print("Syntax error: "..err);
else
-- we have now compiled cmd in the form of function cmd_fn(), so we can try to execute it
local ok, result_or_error = pcall(cmd_fn)
if ok then
-- the code was executed successfully, print the returned value
print("Result: "..tostring(result_or_error));
else
-- runtime error, print error description
print("Run error: "..result_or_error)
end
end

What is the effect of #NonCPS in a Jenkins pipeline script

I have a pipeline script in Jenkins.
I used to get this exception:
org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException:
Scripts not permitted to use method groovy.json.JsonSlurperClassic
parseText java.lang.String
I looked the exception up and I found some indications that I should annotate the method where theexception occurs with #NonCPS. I did this, without really understanding what this does.
After that however, an Exception that I was throwing in that method was no longer caught by a try clause.
So what's the idea behind #NonCPS? What are the effects of using it?
The exception that you are seeing is due to script security and sandboxing. Basically, by default, when you run a pipeline script, it runs in a sandbox which only allow usage of certain methods and classes. There are ways to whitelist operations, check the link above.
The #NonCPS annotation is useful when you have methods which use objects which aren't serializable. Normally, all objects that you create in your pipeline script must be serializable (the reason for this is that Jenkins must be able to serialize the state of the script so that it can be paused and stored on disk).
When you put #NonCPS on a method, Jenkins will execute the entire method in one go without the ability to pause. Also, you're not allowed to reference any pipeline steps or CPS transformed methods from within an #NonCPS annotated method. More information about this can be found here.
As for the exception handling: Not 100% sure what you are experiencing; I've tried the following and it works as expected:
#NonCPS
def myFunction() {
throw new RuntimeException();
}
try {
myFunction();
} catch (Exception e) {
echo "Caught";
}
and
#NonCPS
def myFunction() {
throw new RuntimeException();
}
def mySecondFunction() {
try {
myFunction();
} catch (Exception e) {
echo "Caught";
}
}
mySecondFunction();
and finally:
#NonCPS
def myFunction() {
throw new RuntimeException();
}
#NonCPS
def mySecondFunction() {
try {
myFunction();
} catch (Exception e) {
echo "Caught";
}
}
mySecondFunction();
All print "Caught" as expected.

Resources