Trigger Jenkins job on same node than parent with Groovy - jenkins

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.")
}

Related

Get real Build Number from Jenkins API

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!!!"
}
}
}

How can I abort all jobs with a certain name?

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!"
}
}
}

Check if Jenkins node is online for the job, otherwise send email alert

Having the Jenkins job dedicated to special node I'd like to have a notification if the job can't be run because the node is offline. Is it possible to set up this functionality?
In other words, the default Jenkins behavior is waiting for the node if the job has been started when the node is offline ('pending' job status). I want to fail (or don't start at all) the job in this case and send 'node offline' mail.
This node checking stuff should be inside the job because the job is executed rarely and I don't care if the node is offline when it's not needed for the job. I've tried external node watching plugin, but it doesn't do exactly what I want - it triggers emails every time the node goes offline and it's redundant in my case.
I found an answer here.
You can add a command-line or PowerShell block which invokes the curl command and processes a result
curl --silent $JENKINS_URL/computer/$JENKINS_NODENAME/api/json
The result json contains offline property with true/false value
I don't think checking if the node is available can be done inside the job (e.g JobX) you want to run. The act of checking, specifically for your JobX at time of execution, will itself need a job to run - I don't know of a plugin/configuration option that'll do this. JobX can't check if the node is free for JobX.
I use a lot of flow jobs (in process of converting to pipeline logic) where JobA will trigger the JobB, thus JobA could run on master check the node for JobB, JobX in your case, triggering it if up.
JobA would need to be a freestyle job and run a 'execute system groovy script > Groovy command' build step. The groovy code below is pulled together from a number of working examples, so untested:
import hudson.model.*;
import hudson.AbortException;
import java.util.concurrent.CancellationException;
def allNodes = jenkins.model.Jenkins.instance.nodes
def triggerJob = false
for (node in allNodes) {
if ( node.getComputer().isOnline() && node.nodeName == "special_node" ) {
println node.nodeName + " " + node.getComputer().countBusy() + " " + node.getComputer().getOneOffExecutors().size
triggerJob = true
break
}
}
if (triggerJob) {
println("triggering child build as node available")
def job = Hudson.instance.getJob('JobB')
def anotherBuild
try {
def params = [
new StringParameterValue('ParamOne', '123'),
]
def future = job.scheduleBuild2(0, new Cause.UpstreamCause(build), new ParametersAction(params))
anotherBuild = future.get()
} catch (CancellationException x) {
throw new AbortException("${job.fullDisplayName} aborted.")
}
} else {
println("failing parent build as node not available")
build.getExecutor().interrupt(hudson.model.Result.FAILURE)
throw new InterruptedException()
}
To get the node offline email, you could just trigger a post build action to send emails on failure.

How to get all branches/jobs of a multibranch pipeline job?

Is there a way to get the names of all branches that the scan of a multibranch pipeline job has gathered?
I would like to set up a nightly build with dependencies on existing build jobs and therefore need to check if the multibranch jobs contain some certain branches. An other way would be to check for an existing job.
I found a way by using the Jenkins API.
In case anyone else is having this question: here is my groovy solution:
(Critics and edits welcome)
import java.util.ArrayList
import hudson.model.*;
def ArrayList<String> call(String pipelineName) {
def hi = hudson.model.Hudson.instance;
def item = hi.getItemByFullName(pipelineName);
def jobs = item.getAllJobs();
def arr = new ArrayList<String>();
Iterator<?> iterator = jobs.iterator();
while (iterator.hasNext()) {
def job = iterator.next();
arr.add(pipelineName + "/" + job.name);
}
return arr;
}

Call a jenkins job by using a variable for build the name

I try to launch a job from a parametrized trigger and I would compute the name from a given variable.
Is it possible to set in field :
Build Triggers Projects to build
a value like this
${RELEASE}-MAIN-${PROJECT}-LOAD_START
?
Unfortunately, this isn't possible with the Build Triggers. I looked for a solution for this "higher order build job" that would allow you to create a dynamic build name with a one of the parameterized build plugins, but I couldn't find one.
However, using the Groovy Postbuild Plugin, you can do a lot of powerful things. Below is a script that can be modified to do what you want. In particular, notice that it gets environmental variables using build.buildVariables.get("MY_ENV_VAR"). The environmental variable TARGET_BUILD_JOB specifies the name of the build job to build. In your case, you would want to build TARGET_BUILD_JOB using these two environmental variables:
build.buildVariables.get("RELEASE")
build.buildVariables.get("PROJECT")
The script is commented so that if you're not familiar with Groovy, which is based off Java, it should hopefully make sense!
import hudson.model.*
import hudson.model.queue.*
import hudson.model.labels.*
import org.jvnet.jenkins.plugins.nodelabelparameter.*
def failBuild(msg)
{
throw new RuntimeException("[GROOVY] User message, exiting with error: " + msg)
}
// Get the current build job
def thr = Thread.currentThread()
def build = thr?.executable
// Get the parameters for the current build job
// For ?:, see "Elvis Operator" (http://groovy.codehaus.org/Operators#Operators-ElvisOperator)
def currentParameters = build.getAction(ParametersAction.class)?.getParameters() ?:
failBuild("There are no parameters to pass down.")
def nodeName = build.getBuiltOnStr()
def newParameters = new ArrayList(currentParameters); newParameters << new NodeParameterValue("param_NODE",
"Target node -- the node of the previous job", nodeName)
// Retrieve information about the target build job
def targetJobName = build.buildVariables.get("TARGET_BUILD_JOB")
def targetJobObject = Hudson.instance.getItem(targetJobName) ?:
failBuild("Could not find a build job with the name $targetJobName. (Are you sure the spelling is correct?)")
println("$targetJobObject, $targetJobName")
def buildNumber = targetJobObject.getNextBuildNumber()
// Add information about downstream job to log
def jobUrl = targetJobObject.getAbsoluteUrl()
println("Starting downstream job $targetJobName ($jobUrl)" + "\n")
println("======= DOWNSTREAM PARAMETERS =======")
println("$newParameters")
// Start the downstream build job if this build job was successful
boolean targetBuildQueued = targetJobObject.scheduleBuild(5,
new Cause.UpstreamCause(build),
new ParametersAction(newParameters)
);
if (targetBuildQueued)
{
println("Build started successfully")
println("Console (wait a few seconds before clicking): $jobUrl/$buildNumber/console")
}
else
failBuild("Could not start target build job")

Resources