Passing closures from a class in Groovy/Jenkins - jenkins

I'm trying to create a JobGenerator class that will pass a build step down to the calling instance. I'm running into an issue where if I get this error when I try to run this:
hudson.remoting.ProxyException: groovy.lang.MissingMethodException: No signature of method: org.jenkinsci.plugins.workflow.cps.CpsClosure2.build() is applicable for argument types: (java.util.LinkedHashMap) values: [[job:FooJob]]
class BuildGenerator implements Serializable {
static def generateJob() {
return [
"TestJob",
{ ->
build(
job: 'FooJob'
)
},
]
}
}
node(){
def tasks = [:]
def label
def task
stage("Build") {
def generator = new BuildGenerator()
tasks["Testing"] = generator.generateJob()[1]
parallel tasks
}
}
If I remove the generateJob function outside of the class then it works fine. What am I doing wrong with closures here? I'm new to groovy/jenkins world.

Well... This is the way Groovy/Jenkins pipeline work. build is available inside node as the rest of steps and functions. If you wish to access these you have to pass the CPS instance to the method, like this (or use constructor to pass the instance only once):
class BuildGenerator implements Serializable {
static def generateJob(script) {
return [
"TestJob",
{ ->
script.build(
job: 'FooJob'
)
},
]
}
}
node(){
def tasks = [:]
def label
def task
stage("Build") {
def generator = new BuildGenerator()
tasks["Testing"] = generator.generateJob(this)[1]
parallel tasks
}
}

Related

Jenkins/Groovy: How to call withCredentials() from a class constructor?

How do I call withCredentials() from a Groovy class constructor?
Why does this:
#Library('my-sandbox-libs#dev') sandbox_lib
pipeline {
agent any
stages {
stage( "1" ) {
steps {
script {
try {
def my_obj = new org.obj.Obj()
}
catch(Exception e) {
echo "Jenkinsfile: ${e.toString()}"
throw e
}
}
}
}
}
}
// src/org/obj/Obj.groovy
package org.obj
public class Obj {
def secret_
Obj() {
withCredentials([string(credentialsId: 'test_secret_text', variable: 'val')]) {
this.secret_ = val
}
}
}
...generate this error:
Jenkinsfile: groovy.lang.MissingMethodException: No signature of method: org.obj.Obj.string() is applicable for argument types: (java.util.LinkedHashMap) values: [[credentialsId:test_secret_text, variable:val]]
Possible solutions: toString(), toString(), print(java.io.PrintWriter), print(java.lang.Object), find(), split(groovy.lang.Closure)
?
Update:
Tried the following, in line with #daggett's answer:
// src/org/obj/Obj.groovy
package org.obj
public class Obj {
def secret_
Obj(pipeline) {
pipeline.withCredentials([string(credentialsId: 'test_secret_text',
variable: 'val')]) {
this.secret_ = val
}
}
}
#Library('my-sandbox-libs#dev') sandbox_lib
pipeline {
agent any
stages {
stage( "1" ) {
steps {
script { def my_obj = new org.obj.Obj(this) }
}
}
}
}
...which generated the error Posting build status of FAILED to bitbucket.company.comhudson.remoting.ProxyException: groovy.lang.MissingMethodException: No signature of method: org.obj.Obj.string() is applicable for argument types: (java.util.LinkedHashMap) values: [[credentialsId:test_secret_text, variable:val]] Possible solutions: toString(), toString(), print(java.io.PrintWriter), print(java.lang.Object), find(), split(groovy.lang.Closure)
However, the following -- just an experiment -- runs fine:
// src/org/obj/Obj.groovy
package org.obj
public class Obj {
def secret_
Obj(pipeline) {
pipeline.echo "hello world"
}
}
Note: I also tried public class Obj implements Serializable {...}, which did not change the noted error.
I'm still having a hard time building a mental model of the Jenkins/Groovy language, but to me this appears as though there's something "special" about withCredentials() preventing it from being called from a class constructor.
In case it's relevant, I have Jenkins version 2.190.3, which has Groovy version 2.4.12.
this should help: https://www.jenkins.io/doc/book/pipeline/shared-libraries/#accessing-steps
the easiest way - to pass pipeline as argument into constructor
public class Obj {
def secret_
Obj(pipeline) {
pipeline.withCredentials(...
}
and call it like this
...
steps {
script {
def my_obj = new org.obj.Obj(this)
I solved it by doing the following:
Create a file in /vars which will do the withCredential.
File name for example: /vars/dockerCommands.groovy
(I called it docker.groovy before, but docker is an existing step already, so I advise to change it so it will not conflict.)
.
.
.
def login(String cred, String reg) {
withCredentials([
usernamePassword(credentialsId: cred ,usernameVariable: 'DOCKER_USER', passwordVariable: 'DOCKER_PASSWORD')
]){
docker login -u ${DOCKER_USER} -p ${DOCKER_PASSWORD} ${reg}"
}
}
.
.
In the class I called for this step.
.
.
public void login(String cred, String reg) {
this.steps.dockerCommands.login(cred, reg)
}
.
.
Note, steps is a private var which is equivalent to your pipeline var.
I create it in the constructor method of the class.

Jenkins spock: Mocking http-request-plugin's httpRequest

Somewhere in my shared library I got a helper class like this:
class Helper {
def script
Helper(script) {
this.script = script
}
void sendTemplate(String webhook, String template, Map<String, String> values, TemplateMapper mapper) {
def body = mapper.map(template, values)
def resp = script.httpRequest(contentType: 'APPLICATION_JSON', httpMode: 'POST',
requestBody: body, url: webhook)
if (resp.status != 200) {
throw new UnableToNotifyException()
}
}
}
I'm trying to test said class like so:
class HelperSpec extends JenkinsPipelineSpecification {
def helper
def setup() {
helper = new Helper(this)
}
def "a test"() {
setup:
def webhook = 'aWebhook'
def template = '%replaceMe'
def values = ['%replaceMe': 'hello world!']
def mapper = new SimpleTemplateMapper()
getPipelineMock('httpRequest')(_) >> [status: 200]
when:
helper.sendTemplate(webhook, template, values, mapper)
then:
1 * getPipelineMock('httpRequest')(_)
}
}
I'm using gradle and my build.gradle file has
testImplementation 'org.jenkins-ci.plugins:http_request:1.10#jar'
Other steps' tests run perfectly but with this one I always get
java.lang.IllegalStateException: There is no pipeline step mock for [httpRequest].
1. Is the name correct?
2. Does the pipeline step have a descriptor with that name?
3. Does that step come from a plugin? If so, is that plugin listed as a dependency in your pom.xml?
4. If not, you may need to call explicitlyMockPipelineStep('httpRequest') in your test's setup: block.
And when I use explicitlyMockPipelineStep('httpRequest') I get a null pointer exception, because, I presume, the default mock returns a null.
Is there anything I'm missing in the test to get it working? Thanks in advance!!!

How can I mock this closure with parameters that wraps other methods in a groovy script?

I just asked about how to mock jenkins step/global vars for testing a custom step and got help here
This is how I'm doing that now:
// vars/myCustomStep.groovy
def call(Map config) {
def paramOne = config.paramOne
container('my-container') {
echo paramOne
sh 'printenv'
}
}
// test/vars/myCustomStep.groovy
class myCustomStepTest {
Binding binding
def shell
def myCustomStep
#Before
public void setUp() throws Exception {
// Instantiate shell object and attach methods for mocking jenkins steps
binding = new Binding()
binding.echo = { String message -> println "$message" }
binding.sh = { String command -> println "sh step: $message" }
// How do I mock this??? this returns "No signature of method: myCustomStep.container() is applicable for argument types..."
binding.container = { Closure closure -> closure }
shell = new GroovyShell(binding)
// Instantiate groovy script
myCustomStep = shell.parse( new File( 'vars/myCustomStep.groovy' ) )
}
#Test
public void "sdfsdfsdf"() throws Exception {
myCustomStep paramOne: "sdlfsdfdsf"
}
}
My current challenge is figuring out how to mock things like script{} and container{} closures. I can't modify the structure of myCustomStep.groovy because this is jenkins specific. The behavior I want is for the test to essentially just ignore the container('xxx'){} method and only execute what is inside it.

Import list from one groovy file to another

I have two groovy files.
First.groovy:
class First {
def getContent() {
content = ["one", "two"]
}
}
return this
And second.groovy:
{
node(label) {
def listClass = this.class.classLoader.parseClass("First.groovy")
def CONTENT=listClass.getContent()
}
I get error from jenkins when I run the job:
hudson.remoting.ProxyException: groovy.lang.MissingMethodException: No signature of method: java.lang.Class.getContent() is applicable for argument types: () values: []
kindly advice on efficient way to import a list from one groovy file to another.
Jenkins offers the method load(filepath) for your case so you don't have to juggle with all the classloader methods.
You don't necessarely need the class in your First.groovy so simply remove it.
First.groovy
def getContent() {
def content = ["one", "two"]
return content
}
return this;
And call it like:
def first = load 'First.groovy'
def content = first.getContent()

groovy, script:this and jenkinsfile

Based on this:
println in "call" method of "vars/foo.groovy" works, but not in method in class
I am trying to get my head around printing to the console from classes created in a Jenkins pipeline using jenkins shared libraries. I have the following:
MyPipeline.groovy
node("test") {
stage("Test") {
def a = new A(script:this)
echo "Calling A.a()"
a.a()
}
}
A.groovy
class A {
Script script;
public void a() {
script.echo("Hello from A")
def b = new B(script)
echo "Calling B.b()"
b.b()
}
}
B.groovy
class B {
Script script;
public void b() {
script.echo("Hello from B")
}
}
When I run that I get:
"Hello from A"
but then I get the error from B:
groovy.lang.GroovyRuntimeException: Could not find matching constructor for: samples.B(samples.MyPipeline)
How do I make it possible to print to console/build log in my classes also when delegating to other classes - like B in the above case?
As suggested by below answer I have now tried to update A.groovy to:
class A {
Script script;
public void a() {
script.echo("Hello from A")
def b = new B()
b.script = script
//def b = new B(script)
echo "Calling B.b()"
b.b()
}
}
But that just gives a new error:
hudson.remoting.ProxyException: groovy.lang.MissingMethodException: No signature of method: samples.A.echo() is applicable for argument types: (java.lang.String) values: [Calling B.b()]
Possible solutions: each(groovy.lang.Closure), getAt(java.lang.String), wait(), a(), every(), grep()
As per groovy's Initializing beans with named parameters and the default constructor
Just call empty constructor and set the parameter script
def b = new B()
b.script = script
With a bean like:
class Server {
String name
Cluster cluster }
Instead of setting each setter in subsequent statements as follows:
def server = new Server()
server.name = "Obelix"
server.cluster = aCluster
Also replace the following echo
echo "Calling B.b()"
To using script.echo method:
script.echo("Calling B.b()")

Resources