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

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.

Related

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.

How to figure out which parameter of a Jenkins Groovy script expects String and which one GString?

I've defined a class SomeClass with a method
class SomeClass implements Serializable {
void someMethod(String var1, String var2, String... vars) {
...
}
}
which I'm using in a declarative pipeline as follows:
SomeClass instance = new SomeClass(this)
pipeline {
environment {
VAR1 = "var1"
VAR2 = sh(returnStdout: true, script: "echo var2").trim()
VAR3 = "var3"
}
stages {
stage("Stage X") {
steps {
script {
instance.someMethod("${VAR1}", "${VAR2}", "${VAR3}")
}
}
}
}
}
which fails due to
hudson.remoting.ProxyException: groovy.lang.MissingMethodException: No signature of method: org.jenkinsci.plugins.docker.workflow.SomeClass.someMethod() is applicable for argument types: (org.codehaus.groovy.runtime.GStringImpl, org.codehaus.groovy.runtime.GStringImpl, org.codehaus.groovy.runtime.GStringImpl) values: [var1, var2, var3]
If I change the invocation to instance.someMethod(VAR1.toString(), VAR2.toString(), VAR3.toString()) it fails due to
hudson.remoting.ProxyException: groovy.lang.MissingMethodException: No signature of method: org.jenkinsci.plugins.docker.workflow.SomeClass.someMethod() is applicable for argument types: (java.lang.String, java.lang.String, java.lang.String) values: [var1, var2, var3]
Afaik at least one of them should work. I think Groovy should be capable of making both work, but that's a second step. How can I invoke the method with the given arguments?
You can pass script object to the method. You can find information here and here.
Code below print variable:
SomeClass instance = new SomeClass()
pipeline {
agent any
environment {
VAR1 = "var1"
VAR2 = sh(returnStdout: true, script: "echo var2").trim()
VAR3 = "var3"
}
stages {
stage("Stage X") {
steps {
script {
instance.someMethod(this, "${VAR1}", "${VAR2}", "${VAR3}")
//instance.someMethod("${VAR1}", "${VAR2}", "${VAR3}")
}
}
}
}
}
class SomeClass implements Serializable {
void someMethod(def script, String var1, String var2, String... vars) {
script.echo "someMethod: ${var1}"
}
}
The reason was a type in instance when invoking instance.someMethod. Sorry for not doing a clean copy and paste from the reproducer. I'm surprised by the exception, though - very misleading to say the method doesn't exist if the instance it's called on doesn't even exist.

Passing closures from a class in Groovy/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
}
}

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()")

Jenkins pipeline MissingMethodException: No signature of method:

I wrote a function to insert inject a variable through the EnvInj-plugin. Following script I used:
import hudson.model.*
import static hudson.model.Cause.RemoteCause
#com.cloudbees.groovy.cps.NonCPS
def call(currentBuild) {
def ipaddress=""
for (CauseAction action : currentBuild.getActions(CauseAction.class)) {
for (Cause cause : action.getCauses()) {
if(cause instanceof RemoteCause){
ipaddress=cause.addr
break;
}
}
}
return ["ip":ipaddress]
}
When I put it the the folder $JENKINS_HOME/workflow-libs/vars as a global function, i get the following error:
hudson.remoting.ProxyException: groovy.lang.MissingMethodException: No signature of method: org.jenkinsci.plugins.workflow.support.steps.build.RunWrapper.getActions() is applicable for argument types: (java.lang.Class) values: [class hudson.model.CauseAction]
I am completly new in groovy, so I don't know why it is not working. With the EnvInj-plugin it was fine. Can anyone help me?
You will probably need the rawbuild property of the currentBuild.
The following script should do it for you.
//$JENKINS_HOME/workflow-libs/vars/getIpAddr.groovy
#com.cloudbees.groovy.cps.NonCPS
def call() {
def addr = currentBuild.rawBuild.getActions(CauseAction.class)
.collect { actions ->
actions.causes.find { cause ->
cause instanceof hudson.model.Cause.RemoteCause
}
}
?.first()?.addr
[ ip: addr ]
}
if you use it like:
def addressInfo = getIpAddr()
def ip = addressInfo.ip
Note that it will be null if there are no RemoteCause actions
You might want to return only the addr instead of the hashmap [ ip: addr ], like so
//$JENKINS_HOME/workflow-libs/vars/getIpAddr.groovy
#com.cloudbees.groovy.cps.NonCPS
def call() {
currentBuild.rawBuild.getActions(CauseAction.class)
.collect { actions ->
actions.causes.find { cause ->
cause instanceof hudson.model.Cause.RemoteCause
}
}
?.first()?.addr
}
and then
def addressInfo = [ ip: getIpAdder() ]
Alos note that, depending on the security of your Jenkins, you might need to allow the running of extension methods in sandboxed scripts. You will notice a RejectedSandboxException
You can solve this by approving this through Manage Jenkins -> In-process Script Approval
Hope it works

Resources