Jenkins pipeline: how to access custom globals from groovy scripts in vars dir? - groovy

I am able to declare a class instance as global like this :
gitUtils = new GitUtils()
pipeline {
...
echo "hello: " + gitUtils.doSomething()
}
But if the pipeline calls a function defined as groovy script in "vars" directory, gitUtils is not visible anymore
def call() {
def something = gitUtils.doSomething()
}
I also tryed to use #Field but it changes nothing. Note that all the pipeline is defined in the shared library (project jenkinsfile just calls a function from this shared library).
How to access gitUtils from groovy scripts in /vars in this example ?
I know we could pass the instance as parameter of function declared in /vars but more you have functions using you utilitary class, more it is ugly. Would you imagine to pass 'echo' or 'sh' function as parameter ? No, here is the same.
I know we could not use at all classes defined in src and define groovy script with multiple public methods. Here we could imagine create in /vars a gitutils.groovy with many public method. But this would imply to use 'script' closure in the pipeline to choose which method we want, like this:
script {
gitutils.doSomething()
}
I dont want this. I would pref to create a single function per groovy script in /vars. Thus, we can call them directly in steps, like this:
steps {
myGroovyScriptFunction()
}
But by doing such, the number of function increase and functions are not organized correctly. That is why the idea is to create "big step function" in /vars which use inside more generic functions, from instances of classes (even static in the better case). So, instead of creating a new instance in each groovy script, I would like a global instance.
Context: declarative pipeline, openshift jenkins, slave with dynamic pod template

It's not possible because of JENKINS-42360.
The best way to use global steps without a script block is in my opinion to define an instance of GitUtils and use it directly in the global steps. If you need the pipeline step context you can pass it to the constructor:
# vars/myStep.groovy
import my.packagename.GitUtils
def call() {
GitUtils gitUtils = new GitUtils(steps)
...
}
then you can use the steps with inside GitUtils as described here. Remember to define GitUtils to implement Serializable as per documentation.

Related

How to declare a constructor or extends a class of a groovy script?

I am working on a shared library for Jenkins, and I want to access some utilities methods between some classes, but not all of them, thus I have established some statements:
I would like to avoid using static methods, since it does not access pipeline steps directly, and passing the pipeline instance every call would be a pain;
I would like to avoid a singleton as well, or prefixing every method call with the util class' instance;
Since it is not supposed to be shared between all classes I would like to avoid putting every method as a file on vars/ special directory, but I would like a similar behavior;
Despite extending the class would be a anti-pattern, it would be acceptable, though I would like to avoid the verbose Java syntax for declaring the class the same name as the file, once it is implicit in groovy;
This question does solve my problem partially, although there are issues with serialization, I noted that when I use checkpoint and some build is resumed from some stage, the instance loses all extra methods.
This other question would have helped me fix the serialization issue, however the author seems the have solved the root cause of his problem using a way that is not the original question titled for.
Is there a way to extends a implicit script class in groovy without using the class NameOfFile extends SomeOtherClass { put every thing inside this block } syntax? And without working with inner-class?
Or else, is there a way to declare a constructor using the script groovy syntax analogue as the previous question?
Or even, is there a way to change the serialization behavior to install the extra methods again after unserializing?
Appendix
The script syntax works more-or-less like this:
Consider the content of file src/cicd/pipeline/SomePipeline.groovy:
package cicd.pipeline
// there is no need to wrap everything inside class SomePipeline,
// since it is implicit
def method() {
// instance method, here I can access pipeline steps freely
}
def static otherMethod() {
// static method, here it is unable to access pipeline steps
// without a instance
}
#groovy.transform.Field
def field
def call() {
// if the class is used as method it will run
this.method()
SomePipeline.otherMethod() // or simply otherMethod() should work
this.field = 'foo'
println "this instance ${this.getClass().canonicalName} should be cicd.pipeline.SomePipeline"
}
// any code other than methods or variables with #Field
// annotation will be inside a implicit run method that is
// triggered likewise main method but isn't a static one
def localVar = 'foo'
println "It will not execute on constructor since it is on run: $localVar"
println "Method: ${org.codehaus.groovy.runtime.StackTraceUtils.sanitize(new Throwable()).stackTrace[0].methodName}"
println "this instance ${this.getClass().canonicalName} should be cicd.pipeline.SomePipeline"
If I was going to use the Java verbose syntax I would have to wrap almost everything inside a class SomePipeline which is implicit in groovy, this is the script syntax I want to keep.
I realised that this.getClass().superclass.canonicalName when outside Jenkins pipeline is groovy.lang.Script and when inside pipeline is org.jenkinsci.plugins.workflow.cps.CpsScript and based on this resource I was able to elaborate the following solution:
abstract class CustomScript extends org.jenkinsci.plugins.workflow.cps.CpsScript {
public CustomScript() {
// do something here, it will always execute regardless
// serialization, and before everything
}
}
#groovy.transform.BaseScript CustomScript baseScript
That is it, worked as expected! Of course you can elaborate this solution better in order to reduce repeating and avoid inner-classes, but I will leave it for your imagination.

Populating variables in a groovy script from a java program

I know how to call Groovy code from Java code, but can you, while running Groovy script script.groovy, ask a Java program to populate its varabies?
// script.groovy
import static org.mydomain.WiseJavaProgram.*;
pleasePopulateMeWithVariables();
println numberOfLegs // 2
// We didn't declare numberOfLegs in our sript, the Java program set it for us.
I guess maybe for that script.groovy should pass its environment to the pleasePopulateMeWithVariables method, but I haven't found how to do that. Is this even possible?
The case where I want to use such behavior: I have script.groovy, and also an undefined number of groovy scripts of which Java program knows, but of which script.groovy doesn't know, and I want to add what is declared in them to script.groovy. Basically those additional scripts are data, for example descriptions of various products and their features. This is easy if you run a Java program that creates GroovyShell and evaluates in it data-scripts and then script.groovy, but for me it is necessary that we initially call script.groovy, not the Java program.
Just as usual, while asking the question I was one step behind the answer -__-
The "environment" I was searching for is the Script object itself, available from script.groovy simply as this. So:
// script.groovy
import static org.mydomain.WiseJavaProgram.*;
populateWithVariables(this);
println numberOfLegs // 2
// WiseJavaProgram.java
public class WiseJavaProgram {
public static void populateWithVariables(Script script) {
script.evaluate('numberOfLegs = 2');
script.evaluate(new File("another.groovy"));
script.evaluate(new File("yetAnother.groovy"));
}
}

How do I add a globally available MetaMethod on Object in Groovy?

(this is a generalized example)
I'd like to create a utility method that can be called on any object, it'll have a signature like:
class StringMetaData {
Object value
String meta
}
Object.metaClass.withStringMetaData = { meta ->
new StringMetaData(delegate, meta)
}
With the idea that then anywhere in my program I could do something like:
def foo = 1.withStringMetaData("bar")
And now I can grab foo.value for the value or foo.meta for the attached String.
Within a local context, I'm able to define this meta method on Object, but I'd like to make it available globally within my application, what's the right way to make this metamethod available everywhere?
Perhaps a groovy extension module could help you. I never tried it myself, but the documentation states, that you can add custom methods to JDK classes.

How to define a global class in SoapUI as a groovy script?

I want to define a class in a groovy script that i could reuse trough other groovy script inside SoapUI.
I alredy tried to define my class inside a TestSuite property but it doesn't work. I would like to avoid defining the class in a JAR because we work in team and every one would have to import the JAR in their SoapUI in order to run my tests. I use SoapUI 3.6.1
Here's how my TestSuite is made :
TestSuite
TestCase
TestSteps
Init (Groovy Script)
GetResponse1 (Test Request)
Test1 (Groovy Script)
GetResponse2 (Test Request)
Test2 (Groovy Script)
To simplify me tests, i defined a class in 'Test1' and i would like to reuse this class in 'Test2'. So ideally i would define that class in 'Init' and it would be accessible to any other groovy script.
How can i achieve that?
Based on #Abhey Rathore's link, here is how to define a global class in SoapUI's groovy script:
Step 1: Declare the global class
Create a project called MyRepository
Under this project create a disabled TestSuite called lib
Under this test suite create a TestCase called MyClass
Under this test case create a groovy script called class
Your project tree should look like the image below:
Step 2: Write the global class code
Inside the groovy script called class, copy and and paste the code below:
class MyClass {
// The three following fields are MANDATORY
def log
def context
def testRunner
// The constructor below is MANDATORY
def MyClass(logIn, contextIn, testRunnerIn) {
this.log = logIn
this.context = contextIn
this.testRunner = testRunnerIn
}
def myMethod() {
this.log.info 'Hello World!'
}
}
// Other scripts elsewhere in SoapUI will access the reused class through their context
context.setProperty( "MyClass", new MyClass(log, context, testRunner) )
Step 3: Import the global class
From any project, you can import the global class by using the following snippet:
// Import the class
def currentProject = testRunner.testCase.testSuite.project
currentProject
.testSuites["lib"]
.testCases["MyClass"]
.testSteps["class"]
.run(testRunner, context)
// Now use it
context.MyClass.myMethod()
SoapUI 5.2.1
try this, I think will help you in reusable code.
http://forum.soapui.org/viewtopic.php?t=15744
I am pretty sure that you will have to create a JAR file and put it in \bin\ext.
SoapUI will automatically pick it up on restart (you should see it mentioned in the startup stuff).
You basically just create a Java or Groovy project, Export it (with Eclipse) and it will work. SoapUI will probably have your dependencies covered, but if not you can add those JARs too (safer than creating a runnable JAR since SoapUI might use different versions of what you use).
If you need help, post related questions.
In SoapUI 5.3.0 you can use properties that can be set and get on the context variable. That variable is available in script assertion snippet.
For example:
context.setProperty("prop", "value");
And from different test script you can get the value by:
context.getProperty("prop");
You can use the property from when you define property as a step of test suite or as a value for field in header. You can do it by ${=context.getProperty("prop");}
Write this into your Project's load script, then reload your project or just run it.
com.eviware.soapui.support.ClasspathHacker.addFile( project.context.expand('${projectDir}') )
After it has run, you can now put .groovy files into the same folder as your soapui project xml. Plus, there is no need to compile anything.
e.g. in MyClass.groovy:
class MyClass {String name}
You can import those classes as normal in all your other scripts:
import MyClass
assert new MyClass(name:"me") instanceof MyClass
The only caveat is that your load script (the one setting the classpath), cannot have those imports, because the script will fail to compile. If you want to import something during your project load script, just have your load script 'evaluate' another groovy script (in other words, run it), in which you can use the imports:
evaluate (new File (project.context.expand('${projectDir}') + 'myProjectLoadScript.groovy'))
//in myProjectLoadScript.groovy:
import MyClass
/* Do things using MyClass... */
This seems the easiest way to me, so I'm not sure why I couldn't find this answer elsewhere online.

cast closure map to object with a private constructor in groovy

I am using groovy to create some mock classes for a test case. I am basically creating dummy objects where all the methods return null so that i can run my testcase.
I am using the following syntax:
MessageFactory.instance = ["getMessage": {a,b,c,d -> "dummy"}] as MessageFactory
So here i am trying to overwrite the singleton instance with my on fake factory object. The problem is that MessageFactory's constructor happens to be a private method. This gives me an illigal access exception when i run the code above. Is there a away i can create a proxy in groovy and overcome the private constructor issue?
If you have access to the MessageFactory, and are willing to modify it, then you use the standard dependency-injection solution, as detailed here: mock singleton
..Though it's not particularly Groovy.
Otherwise, the best workaround I've found is to override the method(s) on the singleton instance itself, like so:
#Singleton
class Test{
def method(){"Unmocked method called"}
}
def test = Test.instance
test.metaClass.method = {-> null}
test.method() // Now returns null
Naturally, as a singleton, this instance doesn't change (at least in theory)... So, overriding methods in this manner is effectively global.
Edit: Or you can use GMock, which supports constructor mocking (among other things).

Resources