Calling a GLOBAL Jenkins Library from a function in a pipeline - jenkins

Given:
#!groovy
#Library('GreatUtils')
def utils = new com.X.Utils(script:this)
node {
stage('Call utils.check directly') {
utils.check()
}
}
This code works, and the library utils is called.
#!groovy
#Library('GreatUtils')
def utils = new com.X.Utils(script:this)
node {
stage('Call utils.check indirectly') {
checkUtils()
}
}
def checkUtils() {
utils.check() << -- throws exception
}
This throws -
No such property: utils
for class: groovy.lang.Binding`
Any ideas?

In groovy function don't have access to variable declare outside of their scope, the error is simply because your variable utils is out of scope.
Passing it by parameter like this should work
#!groovy
#Library('GreatUtils')
def utils = new com.X.Utils(script:this)
node {
stage('Call utils.check indirectly') {
checkUtils(utils)
}
}
def checkUtils(utils) {
utils.check()
}
Or if you don't want to use parameter you could go for a functional programming style and use Closures like
#!groovy
#Library('GreatUtils')
def utils = new com.X.Utils(script:this)
def check = { -> utils.check() }
node {
stage('Call utils.check indirectly') {
check()
}
}
Edit:
Adding the Global initialization possibility.
#!groovy
#Library('GreatUtils')
utils = new com.X.Utils(script:this)
node {
stage('Call utils.check indirectly') {
checkUtils()
}
}
def checkUtils() {
utils.check()
}
Should work.

Related

GroovyShell script needs to call local methods

I need to create a Script from a String and execute it in the context of the current test class. Here's my simplified code:
import spock.lang.Specification
class MyTestSpec extends Specification {
Integer getOne() { return 1 }
Integer getTwo() { return 2 }
void 'call script with local methods'() {
given:
GroovyShell shell = new GroovyShell()
Script script = shell.parse("getOne() + getTwo()")
when:
def result = script.run()
then:
result == 3
}
}
This gives me the following error:
No signature of method: Script1.getOne() is applicable for argument types: () values: []
I see that to set variables one can use shell.setProperty but how do I pass the method's implementation to the script?
Of course, as soon as I posted this, I found my answer.
import org.codehaus.groovy.control.CompilerConfiguration
import spock.lang.Specification
class MyTestSpec extends Specification {
Integer getOne() { return 1 }
Integer getTwo() { return 2 }
void 'call script with local methods'() {
given:
CompilerConfiguration cc = new CompilerConfiguration()
cc.setScriptBaseClass(DelegatingScript.name)
GroovyShell sh = new GroovyShell(this.class.classLoader, new Binding(), cc)
DelegatingScript script = (DelegatingScript) sh.parse("getOne() + getTwo()")
script.setDelegate(this)
when:
def result = script.run()
then:
result == 3
}
}

How to replicate dir step in groovy

Below is my pipeline code :
dir(my_directory) {
retry(1) {
// something
}
}
Is there a possibility to access dir step in groovy through the pipeline context ?
I'm thinking of something like this below
class StepExecutor {
// some code
void dir(String directory, Closure statement) {
this.steps.dir(directory) { statement }
}
}
Yes you can. It requires you to pass the steps-object of the pipeline though.
class StepExecutor {
def steps;
public StepExecutor(def steps) {
this.steps = steps
}
// some code
void dir(String directory, Closure statement) {
this.steps.dir(directory) { statement }
}
}
creating the object from inside of your pipeline:
pipeline { ....
def stepExecutor = new StepExecutor(this);
...}

Is there a way to override a closure in a Spock test?

While this is specific to Jenkins code this question I think is more generally about the metaprogramming features of Groovy.
This is what my tests looks like:
// MyDeclarativePipelineTest.groovy
class MyDeclarativePipelineTest extends DeclarativePipelineTest {
#Override
#Before
public void setUp() throws Exception {
super.setUp();
helper.registerAllowedMethod("parameters", [ArrayList.class], null)
}
}
// someTests.groovy
class someTests extends MyDeclarativePipelineTest {
#Override
#Before
public void setUp() throws Exception {
super.setUp();
}
#Test
public void pipeline_should_execute_without_errors() throws Exception {
addParam('MYPARAM', 'sdfsdfsdf')
def script = loadScript("Jenkinsfile")
script.run()
assertJobStatusSuccess()
}
}
// Jenkinsfile
pipeline {
stages {
stage('Build') {
steps {
If(true) {print("sdfsdfsd")} // should fail because not wrapped in script{} step
echo 'Building..'
}
}
}
}
I'm using this library in my Spock tests: JenkinsPipelineUnit
I want to overrided the steps{} closure in my MyDeclarativePipelineTest class
The steps closure is defined in this class StageDeclaration.groovy
StageDeclaration is used in the GenericPipelineDeclaration class like this:
def stage(String name,
#DelegatesTo(strategy = DELEGATE_FIRST, value = StageDeclaration) Closure closure) {
this.stages.put(name, createComponent(StageDeclaration, closure).with { it.name = name; it })
}
And then finally the GenericPipelineDeclaration is used in DeclarativePipelineTest like this:
def pipelineInterceptor = { Closure closure ->
GenericPipelineDeclaration.binding = binding
GenericPipelineDeclaration.createComponent(DeclarativePipeline, closure).execute(delegate)
}
I'm extending DeclarativePipelineTest with my own class MyDeclarativePipelineTest
Is there a way I can override the steps closure inside of MyDeclarativePipelineTest? I want it to throw an error if raw Groovy code is defined in the steps{} closure that isn't wrapped in a script{} closure.

Jenkins/Groovy: how to call readJSON from shared library class constructor?

How can I define a class in a Jenkins shared library that contains an empty JSON object?
// src/org/build/Report.groovy
package org.build
public class Report implements Serializable {
def steps
def json
Report(steps) {
this.steps = steps
this.json = emptyJson()
}
#NonCPS
def emptyJson() {
return this.steps.readJSON( text: '{}' )
}
}
...is instantiated from this pipeline:
#Library('my-library')
import org.build.Report
pipeline {
agent any
stages {
stage("foo") {
steps {
script {
rep = new org.build.Report(this)
}
}
}
}
}
...and fails with the error: expected to call org.build.Report.<init> but wound up catching readJSON; see: https://jenkins.io/redirect/pipeline-cps-method-mismatches/
I had only earlier today thought I'd figured out how to solve this "class" of problem.
Earlier, I encountered the same error when invoking a shared-library function from a shared-library class. I fixed that problem per the guidance at the link that the error message noted, i.e. annotating the shared-library function with #NonCPS.
I.e. in the code below, class FirstClass is able to invoke function firstNonNull() because the function is annotated with #NonCPS; without the annotation, this code generated the same error as in the question above:
// src/org/example/FirstClass.groovy
package org.example
public class FirstClass implements Serializable {
def steps
def var
FirstClass(steps) {
this.steps = steps
this.var = steps.utils.firstNonNull( [null, null, "assigned_from_ctor"] )
}
}
// vars/utils.groovy
#NonCPS
def firstNonNull( arr ) {
for ( def i in arr ) { if ( i ) { return i } }
return null
}
#Library('my-library')
import org.example.FirstClass
pipeline {
agent any
stages {
stage("foo") {
steps {
script {
first_class = new org.example.FirstClass(this)
}
}
}
}
}
Why does this approach not work with the Report class invoking readJSON?

groovy binding issue

I have sample code as below
import org.codehaus.groovy.control.CompilerConfiguration
abstract class MyClass extends Script {
void testMethod(Integer x) {
println "x = $x"
}
}
public static void main(String[] args) {
compilerConfiguration = new CompilerConfiguration();
compilerConfiguration.setScriptBaseClass("MyClass");
GroovyShell shell = new GroovyShell(new Binding(), compilerConfiguration);
shell.evaluate("testMethod 1")
}
When I run this class it prints x = 1
now if I change the "testMethod 1" to "testMethod -1" it fails with
Caught: groovy.lang.MissingPropertyException: No such property: testMethod for class: Script1
groovy.lang.MissingPropertyException: No such property: testMethod for class: Script1
at Script1.run(Script1.groovy:1)
at Test.run(Test.groovy:15)
Now I change "testMethod -1" to "testMethod (-1)". It again works and printed x = -1
What I need to understand is why Groovy is asking for the parentheses for negative numbers.
Because without parentheses, it is assuming you are trying to subtract 1 from a property called testMethod (ie: testMethod - 1)
You need the parentheses to inform the parser that this is a method call rather than a subtraction operation
Edit
I came up with a horrible way to get this to work:
import java.lang.reflect.Method
import org.codehaus.groovy.control.CompilerConfiguration
abstract class MyClass extends Script {
private methods = [:]
class MinusableMethod {
Script declarer
Method method
MinusableMethod( Script d, Method m ) {
this.declarer = d
this.method = m
}
def minus( amount ) {
method.invoke( declarer, -amount )
}
}
public MyClass() {
super()
methods = MyClass.getDeclaredMethods().grep {
it.name != 'propertyMissing' && !it.synthetic
}.collectEntries {
[ (it.name): new MinusableMethod( this, it ) ]
}
}
def propertyMissing( String name ) {
methods[ name ]
}
void testMethod(Integer x) {
println "x = $x"
}
}
static main( args ) {
def compilerConfiguration = new CompilerConfiguration();
compilerConfiguration.setScriptBaseClass( 'MyClass' );
GroovyShell shell = new GroovyShell(new Binding(), compilerConfiguration);
shell.evaluate("testMethod - 1")
}
But this will probably break under other conditions
In the long run, getting people to write valid scripts is probably the better route to take...

Resources