I'm attempting to set up a script to kill/abort all Jenkins jobs with a certain name in them. I've had trouble finding documentation on Jenkins classes and what's contained in them.
I know there are plugins available, but I've been directed not to use them. Otherwise, I've referred to a few semi-related questions here (How to stop an unstoppable zombie job on Jenkins without restarting the server?), (Cancel queued builds and aborting executing builds using Groovy for Jenkins), and I attempted to rework some of the code from those, however it doesn't quite result in killed jobs:
import hudson.model.*
def jobList = Jenkins.instance.queue
jobList.items.findAll { it.task.name.contains('searchTerm') }.each { jobList.kill(it.task) }
I've also tried the following:
def jobname = ""
def buildnum = 85
def job = Jenkins.instance.getItemByFullName(jobname)
for (build in job.builds) {
if (buildnum == build.getNumber().toInteger()){
if (build.isBuilding()){
build.doStop();
build.doKill();
}
}
}
Instead of hard-killing jobs, the first script does nothing, while the second throws a NullPointerException:
java.lang.NullPointerException: Cannot get property 'builds' on null object
I managed to get it working; my second example wasn't working because I brainfarted and the job I was testing it on had no builds. :(
def searchTerm = ""
def matchedJobs = Jenkins.instance.items.findAll { job ->
job.name.contains(searchTerm)
def desiredState = "stop"
if (desiredState.equals("stop")) {
println "Stopping all current builds ${job.name}"
for (build in job.builds) {
if (build.isBuilding()){
build.doStop();
println build.name + " successfully stopped!"
}
}
}
Related
How can I trigger a replay of a build from another job?
Context of Problem: I want to be able to have a job that can prioritize a build over others for another job (that has concurrency disabled). I was thinking I could do this by killing / cancelling jobs in the queue, triggering the new job, and then replay the ones that were cancelled.
I think I know how to cancel the jobs in the queue. I.e. by something like:
def buildNumbers = []
def job = Jenkins.instance.getItemByFullName(TARGET_JOB)
def builds = job.builds
job = null
for (build in builds) {
if (build.isBuilding() && !(build.isInProgress())) {
if(build instanceof WorkflowRun) {
WorkflowRun run = (WorkflowRun) build
if(!dryRun) {
//hard kill
run.doKill()
//release pipeline concurrency locks
StageStepExecution.exit(run)
}
println "Killed ${run}"
buildNumbers.add(build.getNumber())
} else if(build instanceof FreeStyleBuild) {
FreeStyleBuild run = (FreeStyleBuild) build
if(!dryRun) {
run.executor.interrupt(Result.ABORTED)
}
println "Killed ${run}"
} else {
println "WARNING: Don't know how to handle ${item.class}"
}
}
}
But say I have saved these builds or build numbers that were killed, how can I replay them?
I am open to other alternatives as well that solves this problem of prioritizing one build ahead of another.
I try to select all current running processes to get the BuildNumber an JobName of the running pipelines to finaly have the possibility to kill them.
However, if I use the Jenkins API to collect the BuildNumber and JobName I will get as return:
[(master)]:
[org.jenkinsci.plugins.workflow.job.WorkflowJob#6b41187c[myPipeline], org.jenkinsci.plugins.workflow.job.WorkflowJob#6296243a[myDeploy]]
I assumed that "#6296243a[myDeploy]" is the information I need. But the "6296243a" is not a valid BuildNumber. I really do not know what this number stands for. Maybe the process id.
The "myDeploy" is the JobName I need to have.
So my question why I will not get a valid "BuildNumber" here?
import jenkins.model.*
import hudson.model.*
def result = []
def buildingJobs = Jenkins.instance.getAllItems(Job.class).findAll {
it.isBuilding()
}
println buildingJobs
Finaly I want to use the both information at:
Jenkins.instance
.getItemByFullName(<JobName>)
.getBuildByNumber(<BuldNumber>)
.finish(hudson.model.Result.ABORTED, new java.io.IOException("Aborting build"));
if I use here the values BuildNumber: 6296243a JobName: myDeploy it will not work. If I use the BuildNumber from the view (2357) it works well.
So anybody has a hint how to receive the real BuildNumber?
From the example code, you provided, buildingJobs will have a List of WorkflowJob objects. Hence you are getting a reference to the objects when you print it.
Here is how you can get the Build IDs and Job names of currently running and paused builds.
Jenkins.instance.getAllItems(Job.class).findAll{it.isBuilding()}.each { job ->
job.getBuilds().each { b ->
if(b.isInProgress()) {
println "Build: " + b.getNumber() + " of Job: " + job.getName() + " is Running. Hence Killing!!!"
}
}
}
I have a Jenkins job (pipeline) that updates the status page. The job is triggered externally. I want a currently running job to be stopped/cancelled is a new is scheduled.
Is there an option for that?
Here's what we use. It doesn't stop this build, but lets the other (newer) build to stop this one. In the end, this ensures that a newer build is allowed to proceed while an older build is stopped, so this may fit you.
// invoke early in your pipeline
def killOtherBuilds() {
def jobname = env.JOB_NAME
def my_buildnum = env.BUILD_NUMBER.toInteger()
echo "Job is ${jobname}, build number is ${my_buildnum}"
def job = Jenkins.instance.getItemByFullName(jobname)
def builds = job.builds
job = null
for (build in builds) {
this_buildnum = build.getNumber().toInteger()
if (!build.isBuilding()) {
println "Build ${this_buildnum} isn't building."
continue;
}
if (my_buildnum == this_buildnum)
{
println "Build ${this_buildnum} is building and it's this build."
continue;
}
else if (my_buildnum < this_buildnum)
{
errorMsg = "A newer build is already scheduled"
currentBuild.result = "ABORTED"
currentBuild.description = errorMsg
error(errorMsg)
}
echo "Kill build ${build} number ${this_buildnum}."
build.displayName += "(stopped by #${my_buildnum})"
killBuild(build)
Thread.sleep(5000)
}
}
#NonCPS
def killBuild(some_build){
some_build.doStop()
}
Stopping this build can involve checking frequently if a newer build is already scheduled, you may want to modify this according to your exact requirements.
In a Jenkins job, I want to trigger another Jenkins job from a Groovy script :
other_job.scheduleBuild();
But other_job is not launched on the same node than the parent job. How can I modify my script to launch other_job on the same node than the parent job ?
I used to do that with the "Trigger/call builds on other project" and "NodeLabel Parameter" plugins but I would like now to do that inside a script.
Firstly, check Restrict where this project can be run option in 'other_job' configuration - you must specify the same node name there.
Then, this should work:
import hudson.model.*
def job = Hudson.instance.getJob('other_job')
job.scheduleBuild();
If you don't want to use this option in your 'other_job', then you can use NodeLabel Parameter Plugin (which you already used) and pass the NodeLabel parameter to downstream job.
In this case, see example from Groovy plugin page how to start another job with parameters (you need to use NodeParameterValue instead of StringParameterValue):
def job = Hudson.instance.getJob('MyJobName')
def anotherBuild
try {
def params = [
new StringParameterValue('FOO', foo),
]
def future = job.scheduleBuild2(0, new Cause.UpstreamCause(build), new ParametersAction(params))
println "Waiting for the completion of " + HyperlinkNote.encodeTo('/' + job.url, job.fullDisplayName)
anotherBuild = future.get()
} catch (CancellationException x) {
throw new AbortException("${job.fullDisplayName} aborted.")
}
println HyperlinkNote.encodeTo('/' + anotherBuild.url, anotherBuild.fullDisplayName) + " completed. Result was " + anotherBuild.result
If it's not working, probably the issue is with node restrictions (e.g., there is only one executor for the node).
NOTE: I prefer to use Jenkins pipelines for job configurations. It allows you to store your build configs in Jenkinsfiles which can be loaded from repository (e.g., from GitLab). See example of triggering job with NodeParameterValue.
Based on the answer of biruk1230, here is a full solution :
import hudson.model.*;
import jenkins.model.Jenkins
import java.util.concurrent.*
import hudson.AbortException
import org.jvnet.jenkins.plugins.nodelabelparameter.*
def currentBuild = Thread.currentThread().executable
current_node = currentBuild.getBuiltOn().getNodeName()
def j = Hudson.instance.getJob('MyJobName')
try {
def params = [
new NodeParameterValue('node', current_node, current_node),
]
def future = j.scheduleBuild2(0, new Cause.UpstreamCause(build), new ParametersAction(params))
println "Waiting for the completion of " + j.getName()
anotherBuild = future.get()
} catch (CancellationException x) {
throw new AbortException("aborted.")
}
I have a project setup that runs on the MISC label everytime it builds, and it had been working great.
However, I've encountered a problem where, if the previous build on one machine fails, it can cause further builds on that machine to fail as well. It would be fine on another slave.
We will like the job to run on a different node in the label, if possible, in case this happens again in the future.
Thanks,
I've run into similar problems. My solution is to take the node offline if certain types of errors happen.
I'm using this plugin to run a groovy script after every build ttps://wiki.jenkins-ci.org/display/JENKINS/Global+Post+Script+Plugin
My script looks like this
import jenkins.model.Jenkins
import hudson.model.*
import hudson.slaves.OfflineCause
// this script is designed to be called by https://wiki.jenkins-ci.org/display/JENKINS/Global+Post+Script+Plugin
if (BUILD_RESULT == "FAILURE") {
println("The job failed. The build failure cause will be checked.")
job = Jenkins.instance.getItemByFullName(JOB_NAME)
build = job.getBuildByNumber(BUILD_NUMBER.toInteger())
def buildLog = build.log
if (buildLog.contains("something indicating an unrecoverable error")) {
Node buildNode = build.getBuiltOn();
// Never set master offline
if (Hudson.getInstance() != buildNode) {
println("This is fatal. The node ${NODE_NAME} is being taken offline.")
buildNode.toComputer().setTemporarilyOffline(true, OfflineCause.create(new OfflineMessage()));
} else {
println("The error is marked to take the node offline, but the node is not being taken offline because it is the master")
}
}
}
class OfflineMessage extends org.jvnet.localizer.Localizable {
def message
OfflineMessage() {
super(null, null, [])
def timestr = new Date().format("HH:mm dd/MM/yy z", TimeZone.getDefault())
this.message = "This node was taken offline because of a failed job at " + timestr
}
String toString() {
this.message
}
String toString(java.util.Locale l) {
toString()
}
}