Creating an instance of a domain class inside a grails script - grails

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)

Related

How to run a single jenkinsfile for specific github components

I want to be able to execute specific components that I organized in Files in the repository with only one main jenkinsfile.
For example I have this repo structure:
And I have three different components: Topic_A, Topic_B, Topic_C (same type of components but will be created for different teams).
I want to be able to modify only Topic_A and C and after I push the branch I want my jenkinsfile to able to execute just those changes instead also redeploying Topic_B which it was not modified.
My question is if this possible? Could it be done with a jenkins pipeline? or any other component? (script)
Thank you.
There is a changeset directive that allows you to check whether a file has changed in the Git repository. But it doesn't support checking directories, hence you can do something like the below.
pipeline {
agent any
stages {
stage('Cloning') {
steps {
// Get some code from a GitHub repository
git branch: 'main', url: 'https://github.com/xxxx/sample.git'
}
}
stage('TOPIC_A') {
when { expression { isChanged("Topic_A") } }
steps {
echo 'Doing something for Topic A'
}
}
stage('TOPIC_B') {
when { expression { isChanged("Topic_B") } }
steps {
echo 'Doing something for Topic B'
}
}
}
}
def isChanged(dirName) {
def changeLogSets = currentBuild.changeSets
def folderName = dirName
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]
def files = new ArrayList(entry.affectedFiles)
for (int k = 0; k < files.size(); k++) {
def file = files[k]
if(file.path.contains(folderName)){
return true
}
}
}
}
return false
}

Jenkins get changes since last successful build when using global shared library

In my scripted pipeline, I want to get changes since last successful build and based on files which have changed I want to enable or disable some parts of the pipeline. I am using Global Shared Library which contains definitions of some additional steps and the whole pipeline. To print changes since last successful build I am using the following code:
def showChanges(def build) {
if ((build != null) && (build.result != 'SUCCESS')) {
def changeLogSets = build.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]
echo "${entry.commitId} by ${entry.author} on ${new Date(entry.timestamp)}: ${entry.msg}"
def files = new ArrayList(entry.affectedFiles)
for (int k = 0; k < files.size(); k++) {
def file = files[k]
echo " ${file.editType.name} ${file.path}"
}
}
}
showChanges(build.getPreviousBuild())
}
}
However, when I do some change in global library then it prints just this change and not the change which happened on the main repository. The changeSet contains no info regarding files which have changed in the main cloned repository.
This is because Jenkins loads all changes from all repositories and shared libraries referenced in your Pipeline into rawBuild.changeSets. There's nothing you can really do about this except manually filter out repositories. For instance, if you only want changes that come from the my_awesome_repo repository:
changeSets = rawBuild.changeSets.findAll { changeSet->
try {
changeSet.getBrowser().getRepoUrl() =~ /my_awesome_repo/
} catch(groovy.lang.MissingMethodException e) {
false // repository has no `browser` property
}
}

Prioritize Jenkins Build via Groovy Postbuild

I want to schedule a job to be at the top of the build queue in Jenkins via a Groovy Postbuild.
For example, for Build X
If previously the build queue looks like this:
Generic Build 1
Generic Build 2
Generic build 3
Now it should look like:
Build X
Generic Build 1
Generic Build 2
Generic Build 3
So far I've been able to schedule the job using
def waitingItem = Jenkins.get().getQueue().schedule(job, 0)
if (waitingItem == null) {
manager.listener.logger.println "Error scheduling ${job}!"
} else {
manager.listener.logger.println "${job.name} was scheduled!"
}
but now I also want waitingItem to be at the top of the queue.
Any help is much appreciated.
Ok so after countless hours of browsing the web I've been able to come up with a Groovy Postbuild script that does the following: once it finishes the build, it triggers another build (let's call it buildToBeTriggered) and buildToBeTriggered automatically gets pushed at the front of the queue.
The code is below:
import hudson.model.AbstractProject
import hudson.model.Queue
import hudson.model.queue.QueueSorter
import jenkins.model.Jenkins
def job = (AbstractProject)Jenkins.get().getItem('gcimpoies-toTrigger')
def isSuccess = manager.getResult() == 'SUCCESS'
def isRelease = manager.getEnvVariable('RELEASE') == 'true'
def secondsToWait = 20
if (isSuccess) {
def waitingItem = Jenkins.get().getQueue().schedule(job, 0)
if (waitingItem == null) {
manager.listener.logger.println "Error scheduling ${job}!"
} else {
manager.listener.logger.println "${job.name} was scheduled!"
}
Thread.sleep(secondsToWait * 1000)
//replace the original queue sorter with one that will place our project build first in the queue
QueueSorter originalQueueSorter = Jenkins.get().getQueue().getSorter()
AcceleratedBuildNowSorter acceleratedBuildNowSorter = new AcceleratedBuildNowSorter(job, originalQueueSorter)
Jenkins.get().getQueue().setSorter(acceleratedBuildNowSorter);
// we sort the queue so that our project is next to be built on the list
Jenkins.get().getQueue().getSorter().sortBuildableItems(Jenkins.getInstance().getQueue().getBuildableItems())
}
class AcceleratedBuildNowSorter extends QueueSorter {
private final AbstractProject project
private final QueueSorter originalQueueSorter
private final AcceleratedBuildNowComparator comparator
AcceleratedBuildNowSorter(AbstractProject project, QueueSorter originalQueueSorter) {
this.project = project
this.originalQueueSorter = originalQueueSorter
comparator = new AcceleratedBuildNowComparator(this.project)
}
#Override
void sortBuildableItems(List<Queue.BuildableItem> buildables) {
if (this.originalQueueSorter != null) {
this.originalQueueSorter.sortBuildableItems(buildables)
}
Collections.sort(buildables, comparator)
}
}
class AcceleratedBuildNowComparator implements Comparator<Queue.BuildableItem> {
private final AbstractProject mostPriorityProject;
AcceleratedBuildNowComparator(AbstractProject mostPriorityProject) {
this.mostPriorityProject = mostPriorityProject;
}
int compare(Queue.BuildableItem buildableItem0, Queue.BuildableItem buildableItem1) {
AbstractProject<?, ?> project0 = (AbstractProject<?, ?>) buildableItem0.task
AbstractProject<?, ?> project1 = (AbstractProject<?, ?>) buildableItem1.task
if (project0.equals(mostPriorityProject)) {
return -1
}
if (project1.equals(mostPriorityProject)) {
return 1
}
return 0
}
}

Jenkins: View last X builds

I want to view the more recent X builds for several Jenkins jobs.
So if I wanted to show the last 5 builds for jobs 1-5 it would looks something like this:
status build time
-------------------------
pass job1#3 13:54
fail job1#2 13:05
fail job1#1 13:01
pass job5#1 12:17
pass job3#1 11:03
How can I accomplish this?
Notice the builds of the jobs are woven together so if one job has run many builds recently it will show up more than other jobs that haven't run as much.
Executing the following script in the Script Console (Manage Jenkins -> Script Console):
import java.text.SimpleDateFormat
def numHoursBack = 24
def dateFormat = new SimpleDateFormat("HH:mm")
def buildNameWidth = 30
def cutOfTime = System.currentTimeMillis() - numHoursBack * 3600 * 1000
SortedMap res = new TreeMap();
for (job in Jenkins.instance.getAllItems(BuildableItem.class)) {
for (build in job.getBuilds()) {
if (build.getTimeInMillis() < cutOfTime) {
break;
}
res.put(build.getTimeInMillis(), build)
}
}
def format = "%-10s%-${buildNameWidth}s%-10s"
println(String.format(format, "status", "build", "Time"))
for (entry in res.descendingMap().entrySet()) {
def build = entry.getValue()
println(String.format(format, build.getResult(), build.getFullDisplayName(), dateFormat.format(build.getTime())))
}
For me this gives:
status build Time
SUCCESS xxx #107393 17:53
SUCCESS xxx #107392 17:48
SUCCESS xxx #107391 17:43
null yyy #3030 17:38
SUCCESS xxx #107390 17:38
FAILURE zzz #3248 17:37
...
You might need to change the numHoursBack constant, it controls the number of hours back to look for builds. As well as the buildNameWidth which determines the column width of the build column (if you have really long job and build names you might need to extend this).
Here is a bit cleanup version of Jon S Groovy script. Also displaying worst build information.
import hudson.model.*;
import java.text.SimpleDateFormat;
//
// Settings
//
def numHoursBack = 24;
def dateFormat = new SimpleDateFormat("HH:mm");
def cutOfTime = System.currentTimeMillis() - numHoursBack * 3600 * 1000;
/**
* Basic build information.
*/
def printBuildInfo(finalizedBuild) {
String level = "INFO";
String result = finalizedBuild.getResult().toString();
switch (result) {
case "UNSTABLE":
level = "WARNING";
break;
case "FAILURE":
level = "ERROR";
break;
}
// basic info and URL
println(String.format(
"[%s] Build %s result is: %s.",
level,
finalizedBuild.getFullDisplayName(),
result
));
// pipe description from downstream
def description = finalizedBuild.getDescription();
if (description != null && !description.isEmpty()) {
println(description.replaceAll("<br>", ""));
}
return finalizedBuild;
}
/**
* Get recent build items.
*/
def getRencentBuilds(cutOfTime) {
SortedMap res = new TreeMap();
for (job in Jenkins.instance.getAllItems(BuildableItem.class)) {
for (build in job.getBuilds()) {
if (build.getTimeInMillis() < cutOfTime) {
break;
}
res.put(build.getTimeInMillis(), build);
}
}
return res;
}
/**
* Print build items.
*
* minResult - minimum to print
*/
def printBuilds(builds, minResult, dateFormat) {
def format = "%-10s %-8s %s";
Result worstResult = Result.SUCCESS;
def worstBuild = null;
// header
println(String.format(format, "status", "Time", "build"));
// list
for (entry in builds.descendingMap().entrySet()) {
def build = entry.getValue();
Result result = build.getResult();
if (result.isWorseThan(worstResult)) {
worstResult = result;
worstBuild = build;
}
if (result.isWorseOrEqualTo(minResult)) {
println(String.format(
format, build.getResult(), dateFormat.format(build.getTime()), build.getFullDisplayName()
));
}
}
return worstBuild;
}
def builds = getRencentBuilds(cutOfTime);
println ("\n\n----------------------\n Failed builds:\n");
def worstBuild = printBuilds(builds, Result.FAILURE, dateFormat);
println ("\n\n----------------------\n Worst build:\n");
if (worstBuild != null) {
printBuildInfo(worstBuild);
}
println ("\n\n----------------------\n All builds:\n");
printBuilds(builds, Result.SUCCESS, dateFormat);

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

Resources