Jenkins Library / Groovy Script - Wrap Code dynamically - groovy

I'm developing a Jenkins shared library right now.
I wasn't able to figure how to easily "wrap" a code inside a function without copy-pasting the whole code. For example: If a developer sets a value to true, then I want to wrap the whole code inside a function. Right now I want to use this to allow e.g. the gitlabIntegration to be turned off from the Jenkinsfile.
Example:
// vars/stageWrapper.groovy
def call(Map parameters = [:], body) {
stage(stageName) {
if (pushtoGitlab) {
gitlabCommitStatus(stageName) {
if (!containerName) body()
else {
container(containerName) {
body()
}
}
}
} else {
if (!containerName) body()
else {
container(containerName) {
body()
}
}
}
}
}
let the user select if the stage should be pushed to gitlab via the gitlabCommitStatus wrapper.
switch to a specified container or use default container (if none is specified)
To realize this I currently repeat the code, which I really don't like...
Is there any way of achieving the same, but without repeating the same code over and over?
Thank You!

In Groovy you can reuse a Closure in different DSL-Builders by setting it's delegate to builder's delegate.
Something like this should work:
def containerNameBody = { body ->
if (!containerName)
body()
else
container(containerName) {
body()
}
}
def call(Map parameters = [:], body) {
stage(stageName) {
containerNameBody.delegate = delegate
if (pushtoGitlab)
gitlabCommitStatus(stageName) {
containerNameBody body
}
else
containerNameBody body
}
}

How about following approach to pass down the param into function, then decide how to do inside the function by the param value.
def gitHub(gitHubOn) {
}
def gitLab(gitLabOn) {
}
def call(Map parameters = [:], body){
//some code....
foo=bar
gitLab(parameters.gitLabOn)
gitHub(parameters.gitHubOn)
body()
}

Related

Groovy syntax for enter/exit block

Is there a way to define a block/environment with custom open and close methods? Currently, I have:
script {
withCredentials([usernamePassword(credentialsId: '...', usernameVariable: 'CONFIG_USER', passwordVariable: 'CONFIG_PASS')]) {
def sql = Sql.newInstance("...", CONFIG_USER, CONFIG_PASS, "com.mysql.jdbc.Driver")
sql.rows("SELECT * FROM visualization").each { row ->
println "row ${row.branch}"
}
sql.close()
}
}
I would like to be able to do:
with sqlConnection() { sql ->
sql.rows("SELECT * FROM visualization").each { row ->
println "row ${row.branch}"
}
}
Where it automatically opens/closes the connection accordingly. I am new to Groovy, so it's the syntax I'm concerned about. In Python I would do this using an object __enter__/__exit__.
If I understand you correctly you want a new method sqlConnection() that does the withCredentials part?
You can use a closure parameter to do something before or after something else.
def sqlConnection(Closure withSqlClosure) {
withCredentials([usernamePassword(credentialsId: '...', usernameVariable: 'CONFIG_USER', passwordVariable: 'CONFIG_PASS')]) {
Sql.newInstance("...", CONFIG_USER, CONFIG_PASS, "com.mysql.jdbc.Driver").withCloseable {sql ->
withSqlClosure(sql)
}
}
}
Can be used like this
sqlConnection() { sql ->
sql.rows("SELECT * FROM visualization").each { row ->
println "row ${row.branch}"
}
}
So everything before the call to the closure (withSqlClosure(sql)) corresponds to __enter__ everything after the call is your __exit__. Note that you will need to lookout for exceptions. Usually you will want to wrap the closure call in a try { ... } finally { ... } Statement. Here I used withCloseable which does that for us (assuming Sql.newInstance returns a Closeable).
To aid your IDE and enable #CompileStatic you should also add a #ClosureParams
def sqlConnection(
#ClosureParams(value = groovy.transform.stc.SimpleType,
options = ["your.sql.type"]) Closure withSqlClosure) {
withCredentials([usernamePassword(credentialsId: '...', usernameVariable: 'CONFIG_USER', passwordVariable: 'CONFIG_PASS')]) {
Sql.newInstance("...", CONFIG_USER, CONFIG_PASS, "com.mysql.jdbc.Driver").withCloseable {sql ->
withSqlClosure(sql)
}
}
}
Here your.sql.type is the return type of Sql.newInstance.

Closure with conditional logging

I have a function like this:
private downloadAllFiles() {
sftpRetriever.listFiles().findAll {
filter.isResponse(it) || filter.isResponseTurned(it)
}.each { String fileName ->
log.info 'Downloading file: {}', fileName
sftpRetriever.downloadFile(fileName)
log.info 'File downloaded'
removeRemoteFile(fileName)
}
}
I am looking for a simple way of modyfing this closure inside of that function so if the size() of findAll is 0 it will simply log 'No more files to download' and .each won't be executed. Is there any simple way to make it in single closure? It is really simply task if I divide it in several parts, but trying to learn closures here and improve my expressiveness :) Thank you in advance for your help.
Take a look at creature below :) It works due the fact that each returns the collection on which it's invoked (+ elvis operator and quite nice Groovy's truth evaluation):
def printContents(Collection collection) {
collection.each {
println it
} ?: println('Collection is empty')
}
printContents([1,2,'hello'])
printContents([])
I don't like this syntax but it's the shorter version which came to my mind.
You can also use metaprogramming to add the method provided by Steinar. It must be added to metaClass before first use but you'll avoid an effort to make extension module:
Collection.metaClass.doIfEmpty { Closure ifEmptyClosure ->
if (delegate.empty) {
ifEmptyClosure()
}
return delegate
}
def printContents(Collection collection) {
collection.doIfEmpty {
println "Collection is empty"
}.each {
println it
}
}
printContents([1,2,'hello'])
printContents([])
One rather generic and reusable option is to extend Collection using an extension module. This is surprisingly easy to do and is even recognized in IDE's (at least in IntelliJ) so you get code completion, etc.
For example, write an the extension class for collections which will perform the closure if the collection is empty. In addtion, it should always return the collection to allow further chaining:
package stackoverflow
class CollectionExtension {
static <T extends Collection> T doIfEmpty(T self, Closure closure) {
if (self.empty) {
closure()
}
return self
}
}
You will also need to tell groovy that this file is an extension module. Add a property file as a resource on the classpath: META-INF/services/org.codehaus.groovy.runtime.ExtensionModule (note: this name and location is mandatory for extension modules, i.e. you cannot change it).
moduleName=stackoverflow-module
moduleVersion=1.0
extensionClasses=stackoverflow.CollectionExtension
Finally a simple test script to show how this can be used:
def printContents(Collection collection) {
collection.doIfEmpty {
println "Collection is empty"
}.each {
println it
}
}
printContents([1,2,'hello'])
printContents([])
Output:
1
2
hello
Collection is empty
You may try the following piece of code:
def l1 = [1,2,3,4]
def l2 = [5,6,7,8]
def m(list) {
list.findAll { it < 5}.with { l ->
size > 0 ?
l.each { e ->
println e
}
:
println('Zero elements found')
}
}
m(l1)
m(l2)
No better idea at the moment.

Dynamically generated closure

Dynamically generated closure
I've written soap request in groovy wslite :
def request = {
envelopeAttributes('xmlns:art': 'http://url')
body {
'art:validate' {
item(itemValue)
}
}
}
It's working fine, but now I have to change this to list, so at the end it will be something like that:
def request = {
envelopeAttributes('xmlns:art': 'http://url')
body {
'art:validate' {
item(itemValue)
item(itemValue2)
item(itemValue3)
}
}
}
But have know Idea how I can dynamically create this request from List. I've even extracted this to variable:
def items = {
item(itemValue)
item(itemValue2)
item(itemValue3)
}
but I don't know how to add new items to this closure. Is there any easy way ?
Builder closures are normal Groovy code, so something like
def values = [itemValue, itemValue2, itemValue3]
def request = {
envelopeAttributes('xmlns:art': 'http://url')
body {
'art:validate' {
values.each { item(it) }
}
}
}
should work fine. Or if you have
def items = {
item(itemValue)
item(itemValue2)
item(itemValue3)
}
then you can do
def request = {
envelopeAttributes('xmlns:art': 'http://url')
body {
'art:validate'(items)
}
}
(passing the existing closure to art:validate rather than defining a new one inline).
With your given items Closure, this may work:
def request = {
envelopeAttributes('xmlns:art': 'http://url')
body {
'art:validate' {
items.delegate = delegate
items()
}
}
}
if you need other things inside art:validate

Reduce code repetition in Groovy closures

In a piece of Gradle build script, the amount of code i'm repeating is increasing. All tasks have a big part in common, except for a few lines:
task copyZipFile() {
doLast {
def remoteBuildProperties = getRemoteBuildProperties(project)
ant {
taskdef(name: 'ftp',
classname: 'org.apache.tools.ant.taskdefs.optional.net.FTP',
classpath: configurations.ftpAntTask.asPath)
ftp(server: remoteBuildProperties['host.name'],
userid: remoteBuildProperties['username'],
password: remoteBuildProperties['password'],
remotedir: 'some-folder', // This value differs from call to call
passive: 'true') {
// The next two lines also are different per case, and might be less or more lines
fileset(dir: rootProject.buildDir) { include(name: 'build.zip') }
fileset(dir: rootProject.projectDir) { include(name: 'build.properties') }
}
}
}
}
I don't like to repeat myself, so I'd like to reduce this code to a new helper method that does this trick, and a simple caller, something like:
task copyZipFile() {
doLast {
def remoteBuildProperties = getRemoteBuildProperties(project)
upload(remoteBuildProperties, 'some-folder') {
fileset(dir: rootProject.buildDir) { include(name: 'build.zip') }
fileset(dir: rootProject.projectDir) { include(name: 'build.properties') }
}
}
}
How would I achieve this?
You can pass the inner closure to your upload method as the final parameter. Set the delegate to the original builder delegate so the inner closure calls get handled properly. For example:
def upload(remoteBuildProperties, folder, body) {
ant {
taskdef(name: 'ftp',
classname: 'org.apache.tools.ant.taskdefs.optional.net.FTP',
classpath: configurations.ftpAntTask.asPath)
ftp(server: remoteBuildProperties['host.name'],
userid: remoteBuildProperties['username'],
password: remoteBuildProperties['password'],
remotedir: folder,
passive: 'true') {
body.delegate = delegate
body()
}
}
}

Call closure's delegate method from a function in Groovy?

In a Gradle script I have a Groovy closure with a delegate, and I have created a function calling methods on that delegate as described below:
// Simplified example
ant.compressFiles() {
addFile(file: "A.txt")
addFile(file: "B.txt")
addAllFilesMatching("C*.txt", getDelegate())
}
def addAllFilesMatching(pattern, closureDelegate) {
// ...
foundFiles.each {
closureDelegate.addFile(file: it)
}
}
Is it possible to do this in a prettier way, without having to pass the delegate to a function? Is it for example possible to somehow extend the delegate with new methods?
This can be solved by creating a function that returns a Closure:
ant.compressFiles() addAllFilesMatching("A.txt", "B.txt", "C*.txt")
Closure addAllFilesMatching(String... patterns) {
// Calculate foundFiles from patterns...
return {
foundFiles.each { foundFile ->
addFile(file: foundFile)
}
}
}
You can declare the closure first, set its delegate, resolveStrategy and then pass it to each:
def addAllFilesMatching(pattern, delegate) {
def closure = {
addFile file: it
}
closure.delegate = delegate
closure.resolveStrategy = Closure.DELEGATE_FIRST
foundFiles = ["a.txt", "b.txt", "c.txt", "d.txt"]
foundFiles.each closure
}
How about this?
This is a minute modification to WillP's answer (which is absolutely correct and the way should be done) and should be prettier (as per your request) because it uses a closure instead of a method.
def addAllFilesMatching = {pattern ->
// ... foundFiles based on pattern
foundFiles.each {
delegate.addFile(file: it)
}
}
ant.compressFiles() {
addFile(file: "A.txt")
addFile(file: "B.txt")
addAllFilesMatching.delegate = getDelegate()
addAllFilesMatching("C*.txt")
}

Resources