Jenkins only executes first line of groovy closure - jenkins

I have the following two Classes which work just fine locally in groovy, but once I use them with Jenkins Shared Libraries I run into some issues.
./Template.groovy
class Template {
String arch
String type
def body = {}
Template(type, arch, body) {
this.arch = arch
this.type = type
this.body = body
}
}
./TemplateBook.groovy
import Template
class TemplateBook {
def templates = []
TemplateBook() {
def template = new Template("test", "lnx", { args -> sh('echo "Hello World!"'); sh("echo Test"); sh("echo $args")})
templates.push(template)
}
def getTemplate(type, arch) {
def template
for (def i = 0; i < templates.size(); i++) {
if (templates[i].arch == arch && templates[i].type == type) {
template = templates[i].getBody()
i = templates.size()
}
}
return template
}
}
Using the Template.body directly works just fine (locally and on Jenkins), but using the TemplateBook.getTemplate() only executes the first line of the closure (body).
def templateBook = new TemplateBook()
def body = templateBook.getTemplate("test", "lnx")
body.resolveStrategy = Closure.DELEGATE_FIRST
body.delegate = this
body("test")
Output:
Hello World!
def template = new Template("type", "arch", { args -> sh('echo "Hello World!"'); sh("echo Test"); sh("echo $args")})
def body2 = template.body
body2.resolveStrategy = Closure.DELEGATE_FIRST
body2.delegate = this
body2("test")
Output:
Hello World!
Test
test

Related

How to check if a variable is defined in a Jenkins folder?

I created a Jenkins folder which has multiple jobs in it. I've also defined some variables in the folder itself. How can I check if the folder has a variable defined? I tied
import jenkins.model.*
private def getBuildJob(final String folder) {
def jobFolder = null
for (f in Jenkins.instance.getAllItems(AbstractItem.class)) {
if (f.fullName == folder) {
jobFolder = f
break
}
}
return jobFolder
}
node("linux-ubuntu") {
stage("test") {
def folder = getBuildJob("MyFolderName")
println folder.hasVariable("MY_VARIABLE")
}
}
But I get
hudson.remoting.ProxyException: groovy.lang.MissingMethodException: No signature of method: com.cloudbees.hudson.plugins.folder.Folder.hasVariable() is applicable for argument types: (java.lang.String) values: [MY_VARIABLE]
at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxInterceptor.onMethodCall(SandboxInterceptor.java:153)
at org.kohsuke.groovy.sandbox.impl.Checker$1.call(Checker.java:161)
If you are using the folder-properties plugin you can use the following script.
#NonCPS
def checkVar() {
def folderName = "Folderycr"
def propToCheck = "YCRPROP"
def folderItem = Jenkins.instance.getAllItems(com.cloudbees.hudson.plugins.folder.AbstractFolder.class).find{ (it.name == folderName) }
println folderItem.getProperties().each { prop ->
if(prop instanceof com.mig82.folders.properties.FolderProperties){
prop.getProperties().each{
if(it.key == propToCheck) {
println "Prop is available. Vale is : " + it.value
}
}
}
}
}
node("linux-node") {
stage("Run test") {
checkVar()
}
}

How to change the Git URL in all Jenkins jobs

Does anyone have an updated version of ceilfors answer that works for both AbstractProject and WorkflowJob?
This is the solution I came up with. It was tested on Jenkins 2.355
The test was run from the script console.
For testing purposes, I limited the test to one Freestyle (AbstractProject) and one Pipeline (WorkflowJob) job each. You would need to modify the code below.
I hope others find this useful
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
import hudson.plugins.git.*
import jenkins.*
import jenkins.model.*
def modifyGitUrl(url) {
def updatedUrl = url.toString().replace("git#gitlab", "git#github")
// println "updatedUrl = ${updatedUrl}"
return updatedUrl
}
Jenkins.instance.getAllItems(Job.class).each {
project = it.getFullName()
if(project.toString().equals("PL_Quick_Testing") || project.toString().equals("A_Freestyle_Job")) {
try {
if (it instanceof AbstractProject){
def oldScm = it.scm
def newUserRemoteConfigs = oldScm.userRemoteConfigs.collect {
new UserRemoteConfig(modifyGitUrl(it.url), it.name, it.refspec, it.credentialsId)
}
def newScm = new GitSCM(newUserRemoteConfigs, oldScm.branches, oldScm.doGenerateSubmoduleConfigurations,
oldScm.submoduleCfg, oldScm.browser, oldScm.gitTool, oldScm.extensions)
it.scm = newScm
it.save()
println "Done"
} else if (it instanceof WorkflowJob) {
def oldScm = it.getTypicalSCM()
def definition = it.getDefinition()
String scriptPath = it.getDefinition().getScriptPath()
def newUserRemoteConfigs = oldScm.userRemoteConfigs.collect {
new UserRemoteConfig(modifyGitUrl(oldScm.userRemoteConfigs.url[0]), it.name, it.refspec, it.credentialsId)
}
def newScm = new GitSCM(newUserRemoteConfigs, oldScm.branches, oldScm.doGenerateSubmoduleConfigurations,
oldScm.submoduleCfg, oldScm.browser, oldScm.gitTool, oldScm.extensions)
def newDefinition = new org.jenkinsci.plugins.workflow.cps.CpsScmFlowDefinition(newScm, scriptPath)
it.definition = newDefinition
it.save()
println "Done"
} else {
println("${project} has no SCM")
}
} catch (Exception e) {
// e.printStackTrace()
}
}
}

How can I get revision/commitID in changeSet in Jenkins

I am trying to get the commitID/revision in changeSet in Jenkins. And here is my code:
The commitId I got is null but I can get the revision from the summary under the Changes page when the build finished.
def project = hudson.model.Hudson.instance.getItem(project_name)
def buildnum = some_number
build = project.getBuildByNumber(buildnum)
def upstreamBuilds = build.getUpstreamBuilds()
upstreamBuilds.each() { key, value ->
def prj = key;
def num = value;
build = prj.getBuildByNumber(num);
}
def changeSet = build.changeSet
if(changeSet != null) {
def hadChanges = false
changeSet.each() { cs ->
hadChanges = true
println(cs.author)
println(cs.commitId)
println(cs.msgAnnotated)
}
}
How can I get the correct commitId from changeSet of the build?

How to get the changes since the last successful build in jenkins pipeline?

Anyone have a Jenkins Pipeline script that can stuff all the changes since the previous successful build in a variable? I'm using git and a multibranch pipeline job.
Well I managed to cobble something together. I'm pretty sure you can iterate the arrays better but here's what I've got for now:
node('Android') {
passedBuilds = []
lastSuccessfulBuild(passedBuilds, currentBuild);
def changeLog = getChangeLog(passedBuilds)
echo "changeLog ${changeLog}"
}
def lastSuccessfulBuild(passedBuilds, build) {
if ((build != null) && (build.result != 'SUCCESS')) {
passedBuilds.add(build)
lastSuccessfulBuild(passedBuilds, build.getPreviousBuild())
}
}
#NonCPS
def getChangeLog(passedBuilds) {
def log = ""
for (int x = 0; x < passedBuilds.size(); x++) {
def currentBuild = passedBuilds[x];
def changeLogSets = currentBuild.rawBuild.changeSets
for (int i = 0; i < changeLogSets.size(); i++) {
def entries = changeLogSets[i].items
for (int j = 0; j < entries.length; j++) {
def entry = entries[j]
log += "* ${entry.msg} by ${entry.author} \n"
}
}
}
return log;
}
Based on the answer from CaptRespect I came up with the following script for use in the declarative pipeline:
def changes = "Changes:\n"
build = currentBuild
while(build != null && build.result != 'SUCCESS') {
changes += "In ${build.id}:\n"
for (changeLog in build.changeSets) {
for(entry in changeLog.items) {
for(file in entry.affectedFiles) {
changes += "* ${file.path}\n"
}
}
}
build = build.previousBuild
}
echo changes
This is quite useful in stage->when->expression parts to run a stage only when certain files were changed. I haven't gotten to that part yet though, I'd love to create a shared library from this and make it possible to pass it some globbing patterns to check for.
EDIT: Check the docs btw, in case you want to delve a little deeper. You should be able to convert all the object.getSomeProperty() calls into just entry.someProperty.
This is what I've used:
def listFilesForBuild(build) {
def files = []
currentBuild.changeSets.each {
it.items.each {
it.affectedFiles.each {
files << it.path
}
}
}
files
}
def filesSinceLastPass() {
def files = []
def build = currentBuild
while(build.result != 'SUCCESS') {
files += listFilesForBuild(build)
build = build.getPreviousBuild()
}
return files.unique()
}
def files = filesSinceLastPass()
There's the Changes Since Last Success Plugin that could help you with that.
For anyone using Accurev here is an adaptation of andsens answer. andsens answer can't be used because the Accurev plugin doesn't implement getAffectedFiles. Documentation for the AccurevTransaction that extends the ChangeLogSet.Entry class can be found at here.
import hudson.plugins.accurev.*
def changes = "Changes: \n"
build = currentBuild
// Go through the previous builds and get changes until the
// last successful build is found.
while (build != null && build.result != 'SUCCESS') {
changes += "Build ${build.id}:\n"
for (changeLog in build.changeSets) {
for (AccurevTransaction entry in changeLog.items) {
changes += "\n Issue: " + entry.getIssueNum()
changes += "\n Change Type: " + entry.getAction()
changes += "\n Change Message: " + entry.getMsg()
changes += "\n Author: " + entry.getAuthor()
changes += "\n Date: " + entry.getDate()
changes += "\n Files: "
for (path in entry.getAffectedPaths()) {
changes += "\n " + path;
}
changes += "\n"
}
}
build = build.previousBuild
}
echo changes
writeFile file: "changeLog.txt", text: changes
In order to return the changes as a list of strings, instead of just printing them, you may use this function (based on #andsens answer):
def getChangesSinceLastSuccessfulBuild() {
def changes = []
def build = currentBuild
while (build != null && build.result != 'SUCCESS') {
changes += (build.changeSets.collect { changeSet ->
(changeSet.items.collect { item ->
(item.affectedFiles.collect { affectedFile ->
affectedFile.path
}).flatten()
}).flatten()
}).flatten()
build = build.previousBuild
}
return changes.unique()
}

Creating an instance of a domain class inside a grails script

I am trying to create an instance of a domain class inside a grails 2.3.6 script:
def player = new Player(name:"Bob")
player.save()
But I keep getting an exception
java.lang.NoClassDefFoundError: gaming/Player
I've tried all the different bootstrapping tricks I've managed to find on the internet but they don't really change the result:
I've tried importing:
import gaming.Player
I've tried loading the bootstrap script:
includeTargets << grailsScript("_GrailsBootstrap")
I've tried depending on every task I managed to find:
depends(configureProxy, packageApp, classpath, loadApp, configureApp, compile, bootstrap)
I've even tried loading the class at runtime:
ApplicationHolder.application.getClassForName("gaming.Player")
Interestingly enough, this last line doesn't barf which suggests that grails can find my class, but chooses to ignore the find when I actually go to use it.
Edit. As requested, here is the current version of the script
import gaming.Player
import org.codehaus.groovy.grails.commons.ApplicationHolder
includeTargets << grailsScript("_GrailsInit")
includeTargets << grailsScript("_GrailsBootstrap")
includeTargets << grailsScript("_GrailsClasspath")
def handleHeaderLine(line) {
def retval = []
line.each {
if(!it.equals("Game Name") && !it.equals("Total # of Copies")) {
println("Creating Player: " + it)
def player = new Player(name:it)
player.save
retval << it
} else {
retval << null
}
}
return retval;
}
def handleGameLine(header, line) {
println("Creating Game: " + line[0])
for(int i = 1; i < line.length - 1; i++) {
if(!header[i].equals("Total # of Copies")) {
def count = line[i] == "" ? 0 : Integer.parseInt(line[i]);
for(int j = 0; j < count; j++) {
println "Creating copy of " + line[0] + " owned by " + header[i]
}
}
}
}
target(loadAssets: "The description of the script goes here!") {
depends(configureProxy, packageApp, classpath, loadApp, configureApp, compile, bootstrap)
ApplicationHolder.application.getClassForName("gaming.Player")
def tsv = new File("...")
def header = null;
tsv.eachLine {
def line = it.split("\t")
if(header == null) {
header = handleHeaderLine(line)
println header
} else {
handleGameLine(header, line)
}
}
}
setDefaultTarget(loadAssets)
You do not have to do all the boiler plate effort to bring up the environment while running your script. run-script does that for you. When grails run-script is used following targets are run by default: checkVersion, configureProxy, bootstrap. And finally the script run-script is run.
run-script runs your custom script in GroovyShell by providing ApplicationContext and grailsApplication as bindings to shell. So what you would end up with your script is shown below as if it is written in Groovy console/shell:
//scripts/player/PlayerScript.groovy
def handleHeaderLine(line) {
def retval = []
line.each {
if(!it.equals("Game Name") && !it.equals("Total # of Copies")) {
println("Creating Player: " + it)
def player = new Player(name: it)
player.save(flush: true)
retval << it
} else {
retval << null
}
}
return retval
}
def handleGameLine(header, line) {
println("Creating Game: " + line[0])
for(int i = 1; i < line.length - 1; i++) {
if(!header[i].equals("Total # of Copies")) {
def count = line[i] == "" ? 0 : Integer.parseInt(line[i]);
for(int j = 0; j < count; j++) {
println "Creating copy of " + line[0] + " owned by " + header[i]
}
}
}
}
def tsv = new File("...")
def header = null
tsv.eachLine {
def line = it.split("\t")
if(header == null) {
header = handleHeaderLine(line)
println header
} else {
handleGameLine(header, line)
}
}
And then use run-script as below:
grails run-script scripts/player/PlayerScript.groovy
which would by default run the script in dev environment. If you want for other envirnments then use as
grails test run-script scripts/player/PlayerScript.groovy
BUT
Due to a major bug in latest version of grails, you won't be able to run script the above mentioned way because run-script always depends on bootstrap target and would always try to bring tomcat up while running script as the plugin scope in build which would result in Error loading plugin manager: TomcatGrailsPlugin. The workaround is also mentioned in the defect but here is a groovier implementation. Change in BuildConfig.groovy as:
plugins {
if ( !System.getProperty("noTomcat") ) {
build ":tomcat:7.0.52.1"
}
....
}
and then issue run-script command as:
grails -DnoTomcat=true run-script scripts/player/PlayerScript.groovy
On a side note, the reason your script was not running is that the class Player will not be loaded at this time while running script, for use. It has to be loaded manually using classLoader and then create an instance off of it. Something like:
includeTargets << grailsScript("_GrailsInit")
includeTargets << grailsScript("_GrailsBootstrap")
target(playerScript: "The description of the script goes here!") {
depends configureProxy, packageApp, classpath, loadApp, configureApp
def playerClass = classLoader.loadClass("gaming.Player")
//Skeptical about how a domain class would behave
//But a normal POGO should be good being used this way
def player = playerClass.newInstance([[name: "Bob"]] as Object[])
player.save(flush: true)
println player
}
setDefaultTarget(playerScript)

Resources