I have a simple Declarative Pipeline with function inside. How to correctly use named arguments for a function?
def getInputParams(param1='a', param2='b') {
echo "param1 is ${param1}, param2 is ${param2}"
}
pipeline {
...
...
stages {
stage('Test') {
steps {
getInputParams(param1='x', param2='y')
}
}
}
}
I cannot understand why named params become null in function?
[Pipeline] echo
param1 is null, param2 is null
...
Well, I'm able to call function like getInputParams('x', 'y'), but it's not human readable (arguments amount may increase in future)
Groovy is executed inside the Jenkinsfile so you have to follow its syntax for named arguments.
foo(name: 'Michael', age: 24)
def foo(Map args) { "${args.name}: ${args.age}" }
Quote from Groovy's named arguments:
Like constructors, normal methods can also be called with named
arguments. They need to receive the parameters as a map. In the method
body, the values can be accessed as in normal maps (map.key).
def getInputParams(Map map) {
echo "param1 is ${map.param1}, param2 is ${map.param2}"
}
pipeline {
...
stages {
stage('Test') {
steps {
getInputParams(param1: 'x', param2: 'y')
}
}
}
}
If you are using groovy, use this.
def getInputParams(def param1, def param2) {
println("param1 is "+ param1 + ", param2 is " + param2)
}
pipeline {
...
...
stages {
stage('Test) {
steps {
getInputParams(x, y)
}
}
}
}
Related
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.
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.
I would like to know if there is a way to access the Jenkins Workflow script object during its execution.
I have a shared library, and I can pass this object to any groovy class as an argument, either directly from the Jenkins file, using 'this' keyword, or from any DSL in the vars folder, also using the 'this' keyword.
But I would like to access it using a method, even if this imply using reflexivity.
Is that possible?
Here example with pipeline, where this is a script object. Some other examples here:
MyClass myClass = new MyClass()
pipeline {
agent any
environment {
VAR1 = "var1"
VAR2 = sh(returnStdout: true, script: "echo var2").trim()
VAR3 = "var3"
}
stages {
stage("Stage 1") {
steps {
script {
myClass.myPrint(this, "${VAR1}", "${VAR2}", "${VAR3}")
}
}
}
}
}
class MyClass implements Serializable {
void myPrint(def script, String var1, String var2, String... vars) {
script.echo "myPrint: ${var1}"
}
}
I'm trying to create a base job, to reduce the duplication between our jobs. I did the following, but it's not working:
def baseJob(Map m, Closure c = {}) {
type = m.type ?: 'dev'
pipelineJob("prefix-${m.name}") {
parameters {
stringParam('ONE', 'one', 'Description one')
}
c()
}
}
baseJob(type: 'release', name: 'test') {
parameters { // <-- Fails here
stringParam('TWO', 'two', 'Description two')
}
}
I get the following error:
ERROR: (script, line 12) No signature of method: script.parameters() is applicable for argument types: (script$_run_closure1$_closure4) values: [script$_run_closure1$_closure4#18b249b3]
The following works as expected:
def baseJob(Map m, Closure c = {}) {
type = m.type ?: 'dev'
pipelineJob("prefix-${m.name}") {
parameters {
stringParam('ONE', 'one', 'Description one')
}
parameters { // <-- This is fine
stringParam('TWO', 'two', 'Description two')
}
c()
}
}
baseJob(type: 'release', name: 'test')
So the problem isn't that I call parameters multiple times. The problem seems to be that I call parameters from inside a closure.
I'd like to believe that there is a way to execute the closure, so that parameters is called correctly. However, I suspect that I will have to learn a lot more about Groovy and Jenkins Job DSL, before I am able to figure it out. So I'm hoping that there is someone that knows how to do it.
If you have an alternative solution to accomplishing an extendable base job, that's a valid answer as well.
Answer : The method parameters is not implemented inside your script. It was actually implemented inside the pipeline Closure delegate class.
This code may help you to understand what's happening there....
class Test {
void printMe()
{
println "I am in test"
}
}
void test(Closure cl)
{
Test t = new Test()
cl.delegate = t
cl()
}
def callMe = { printMe()}
test {
printMe() // This will run
callMe() // This will fail
}
In Your case :
pipeline (String arg, Closure pipeLineClosure)
pipeLineClousure had been implemented inside a class X where the parameters method can be Found. like shown below code,
class X
{
...
parameters (Closure cl)
}
So the possible implementation could be :
class Custom{
String val1
String val2
String val3
}
def baseJob(Map m, List<Custom> l) {
type = m.type ?: 'dev'
pipelineJob("prefix-${m.name}") {
l.each{v->
parameters {
stringParam(v.val1, v.val2, v.val3)
}
}
}
}
List l = []
l.add new Custom(val1: 'ONE', val2: 'one', val3: 'description')
// can be add more values
baseJob(type: 'release', name: 'test', l)
HOPE IT HELPS
You just need to set the delegate of the closure that you're calling to the delegate of the closure that you are in:
def baseJob(Map m, Closure c = {}) {
type = m.type ?: 'dev'
pipelineJob("prefix-${m.name}") {
parameters {
stringParam('ONE', 'one', 'Description one')
}
c.delegate = delegate // <-- Just add this line
c()
}
}
baseJob(type: 'release', name: 'test') {
parameters {
stringParam('TWO', 'two', 'Description two')
}
}
delegate contains the delegate of the currently executing closure.
I want to add timestamps() and colorizeOutput() features to our pipeline libraries. I find the wrappers {} in Jenkins documentation:
job('example') {
wrappers {
colorizeOutput()
timestamps()
}
}
I don`t get how to add wrappers to out library which looks like that:
// file ..src/helpers/Builder.groovy
package helpers.sw_main
def doSomething() {
// some Groovy stuff here
}
def doSomethingElse() {
// do something else
}
Our job pipeline looks like that:
#!/usr/bin/env groovy
// this is our library with custom methods
#Library('ext-lib')
def builder = new helpers.Builder()
node {
try {
stage('Some Stage') {
builder.doSomething()
}
}
catch (err) {
throw err
}
}
So, I want to add timestamps and ansi-colors to every function from library. Of course, I can do it with wrapping every function with
timestamps() {
colorizeOutput() {
// function body
}
}
But its a little stupid.
So can I easily wrap pipeline or library?
One solution to your problem is to use Global Variables (/vars/xxxxx.groovy).
To create an own build step, add a Global Variable like /vars/myOwnStep.groovy:
def call(STAGE_NAME, Closure closure) {
// do something
// return something if you like to
}
whcih you can call like this
myOwnStep("Step-name") {
// what ever you want to do
}
in your pipeline script.
Another possibility is to "overwrite" the sh step. Therefore create a file called /vars/sh.groovy with this code:
def call(String script, String encoding=null, String label=null, boolean returnStatus=null, boolean returnStdout=null) {
timestamps {
return steps.sh(script: script, endoding: encoding, label: label, returnStatus: returnStatus, returnStdout: returnStdout)
}
}
def call(Map params = [:]) {
return call(params.script, params.get('encoding', null), params.get('label', null), params.get('returnStatus', false), params.get('returnStdout', false))
}
(This can be done for other steps too, but he parameters have to match.)
I just added a GitHub repository with some examples: https://github.com/datze/jenkins_shared_library (untested!)