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.
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'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!"
}
}
}
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.
I'm hoping to add a conditional stage to my Jenkinsfile that runs depending on how the build was triggered. Currently we are set up such that builds are either triggered by:
changes to our git repo that are picked up on branch indexing
a user manually triggering the build using the 'build now' button in the UI.
Is there any way to run different pipeline steps depending on which of these actions triggered the build?
The following code should works to determine if a user has started the pipeline or a timer/other trigger:
def isStartedByUser = currentBuild.rawBuild.getCause(hudson.model.Cause$UserIdCause) != null
In Jenkins Pipeline without currentBuild.rawBuild access the build causes could be retrieved in the following way:
// started by commit
currentBuild.getBuildCauses('jenkins.branch.BranchEventCause')
// started by timer
currentBuild.getBuildCauses('hudson.triggers.TimerTrigger$TimerTriggerCause')
// started by user
currentBuild.getBuildCauses('hudson.model.Cause$UserIdCause')
You can get a boolean value with:
isTriggeredByTimer = !currentBuild.getBuildCauses('hudson.triggers.TimerTrigger$TimerTriggerCause').isEmpty()
Or, as getBuildCauses() returns an array, the array's size will work correctly with Groovy truthy semantics:
if (currentBuild.getBuildCauses('hudson.triggers.TimerTrigger$TimerTriggerCause')) {
The ability to get causes for a workflow run was released in version 2.22 (2018 Nov 02) to the Pipeline Supporting APIs Plugin. The feature was requested in JENKINS-41272.
A couple methods were added to the currentBuild global variable with that release:
getBuildCauses
Returns a JSON array of build causes for the current build
EXPERIMENTAL - MAY CHANGE getBuildCauses(String causeClass)
Takes a string representing the fully qualified Cause class and returns a JSON array of build causes filtered by that type for the current build, or an empty JSON array if no causes of the specified type apply to the current build
And an example from me submitting:
echo "${currentBuild.buildCauses}" // same as currentBuild.getBuildCauses()
echo "${currentBuild.getBuildCauses('hudson.model.Cause$UserCause')}"
echo "${currentBuild.getBuildCauses('hudson.triggers.TimerTrigger$TimerTriggerCause')}"
And the output:
[Pipeline] echo
[[_class:hudson.model.Cause$UserIdCause, shortDescription:Started by user anonymous, userId:null, userName:anonymous], [_class:org.jenkinsci.plugins.workflow.cps.replay.ReplayCause, shortDescription:Replayed #12]]
[Pipeline] echo
[]
[Pipeline] echo
[]
[Pipeline] End of Pipeline
Finished: SUCCESS
NOTE
There appears to be an issue with the currentBuild.getBuildCauses(type) when the type is a type of Cause contributed by a plugin. For example, currentBuild.getBuildCauses('org.jenkinsci.plugins.workflow.cps.replay.ReplayCause') fails with a java.lang.ClassNotFoundException. This was reported in JENKINS-54673 for the 2.22 version of the Pipeline: Supporting APIs (workflow-support) plugin. It is reportedly fixed in the 2.24 version.
I might be missing something, but you can achieve what you want easily by making use of the when directive:
pipeline {
agent any
stages {
stage('Always') {
steps {
echo "I am always executed"
}
}
stage('ManualTimed') {
steps {
echo "I am only executed when triggered manually or timed"
}
when {
beforeAgent true
anyOf {
triggeredBy 'TimerTrigger'
triggeredBy cause: 'UserIdCause'
}
}
}
stage('GitLabWebHookCause') {
steps {
echo "I am only executed when triggered by SCM push"
}
when {
beforeAgent true
triggeredBy 'GitLabWebHookCause'
}
}
}
}
You will find many similar useful examples for various use cases in the documentation of the when directive.
Edit:
thanks to Jean-Francois Larvoire's answer, I was able to figure out 'my trigger' GitLabWebHookCause I required for my use case.
#vitalii-blagodir:
Your answer works for detecting builds triggered by users and timers, but not by commits.
Instead, I found this to work in my case:
def isTriggeredByIndexing = currentBuild.getBuildCauses('jenkins.branch.BranchIndexingCause').size()
def isTriggeredByCommit = currentBuild.getBuildCauses('com.cloudbees.jenkins.GitHubPushCause').size()
def isTriggeredByUser = currentBuild.getBuildCauses('hudson.model.Cause$UserIdCause').size()
def isTriggeredByTimer = currentBuild.getBuildCauses('hudson.triggers.TimerTrigger$TimerTriggerCause').size()
The .size() suffix returns 0 if the object is missing, or 1 if it's present. This makes the result usable as a boolean.
For finding the object name to use, I found it convenient to display this in the log:
echo "# Build causes"
def buildCauses = currentBuild.buildCauses
def numCause = 0
for (cause in buildCauses) {
echo "${numCause++}: ${cause.shortDescription}" // Display a human-readable index and description
echo "${cause}" // Display the object class name. This allows knowing what names to use in getBuildCauses(name) calls below.
}
Finally, if the goal is to abort a pipeline build in specific cases, then the test must be done before the beginning of the pipeline.
For example, we had a problem with the branch indexing triggering extra useless builds. This was fixed by adding this before the pipeline:
// Avoid useless buils: The branch indexing should only trigger the initial build of a new branch.
def isTriggeredByBranchIndexing = currentBuild.getBuildCauses('jenkins.branch.BranchIndexingCause').size()
if (isTriggeredByBranchIndexing && currentBuild.previousBuild) { // Then it's not the initial build.
echo "# Reindexing a branch already built. It is useless to rebuild it now. Aborting."
currentBuild.result = 'SUCCESS' // Make sure the build is not displayed in red in the Jenkins UI.
return // Abort before the pipeline even starts. (Inside the pipeline, this would only abort one stage.)
}
I think that the answers here are incomplete and do not provide an actual ready to use answer. Here's my code to get it working:
import com.cloudbees.groovy.cps.NonCPS
#NonCPS
def isStartedByTimer() {
def buildCauses = currentBuild.rawBuild.getCauses()
echo buildCauses
boolean isStartedByTimer = false
for (buildCause in buildCauses) {
if ("${buildCause}".contains("hudson.triggers.TimerTrigger\$TimerTriggerCause")) {
isStartedByTimer = true
}
}
echo isStartedByTimer
return isStartedByTimer
}
// [...]
// Other pipeline stuff
script {
isStartedByTimer()
}
When started by user:
00:00:01.353 [hudson.model.Cause$UserIdCause#fa5cb22a]
[Pipeline] echo
00:00:01.358 false
When started by timer:
00:00:01.585 [hudson.triggers.TimerTrigger$TimerTriggerCause#5]
[Pipeline] echo
00:00:01.590 true
Note: the NonCPS decorator is needed because otherwise the next non-script step will throw.
Assuming the two different build causes are "timer" and "push" (to a git repo), you can add the following stage to your Jenkinsfile (in a declarative Jenkins pipeline) to make use of getBuildCauses():
pipeline {
stages {
stage('preparation') {
steps {
script {
// get build cause (time triggered vs. SCM change)
def buildCause = currentBuild.getBuildCauses()[0].shortDescription
echo "Current build was caused by: ${buildCause}\n"
// e.g. "Current build was caused by: Started by GitHub push by mirekphd"
// vs. "Started by timer"
}
}
}
}
}
Then I can decide whether to perform certain stages conditionally (depending on the build cause). For example, pulling a docker base image and inspecting for changes in system libraries (likely security updates) should be done periodically, regardless of whether there was a source code change or not.
We can use "BUILD_CAUSE" variable for getting the information about who initiated the run
for [jenkins-pipeline] you may use
currentBuild.rawBuild.getCauses()
(see github.com/jenkinsci/pipeline-examples/blob/master/… for more details)
There was a similar requirement, where user detail who triggered the build should be there in success / failure notification. The job was already had time based triggered, hence could not use wrap([$class: 'BuildUser']) directly.
I used below step, which print username if the job is triggered manually or timer triggered. So, I used this:
pipeline {
agent any
stages {
stage('Test') {
steps {
script{
env.buildCauses = currentBuild.rawBuild.getCauses()
if (buildCauses.contains("hudson.triggers.TimerTrigger")){
env.builduser = "TimerTrigger"
} else {
wrap([$class: 'BuildUser']) {
env.builduser = "${BUILD_USER}"
}
}
}
echo "Initiated by: ${env.builduser}"
}
}
}
}
I have a Jenkinsfile that I've set up with a cron for a pipelineTriggers parameter. I can't seem to figure out how to disable the job from building from a merge to the master branch of the repo. Is there a way in the Jenkinsfile to disable the automatic build from an scm change?
If you're using a Multibranch Pipeline, you should be able to do this on the job's Configure page:
Scroll down to "Branch Sources"
Under "Property strategy", choose "Named branches get different properties"
Click "Add exception", enter "master" as the branch name
Click "Add property", choose "Suppress automatic SCM triggering"
Save
That would prevent changes to the master branch from triggering a build of the corresponding job.
For declarative pipelines, use the when directive with a triggeredBy condition, e.g.
when { triggeredBy 'TimerTrigger' }
With the multibranch pipeline, I could not figure out a way to prevent the next build being triggered. As a workaround, I added the following code to my Jenkinsfile (using scripted syntax), to abort the following build if the only changes contain "[ci-skip]" in the commit message:
def abortBuildIfTriggeredBySkippableCommit() {
def changeSetCount = 0;
def ciSkipCount = 0;
if (currentBuild.changeSets != null) {
for (changeSetList in currentBuild.changeSets) {
for (changeSet in changeSetList) {
changeSetCount++;
if (changeSet.msg.contains('[ci-skip]')) {
ciSkipCount++;
}
}
}
}
if (changeSetCount > 0 && changeSetCount == ciSkipCount) {
currentBuild.result = 'NOT_BUILT'
error("Stopping to prevent auto trigger. All commits contained [ci-skip]")
}
}
Note that this code assumes you are using the git plugin, and that the objects in currentBuild.changeSets will be GitChangeSetList.
In a jenkins job you can navigate to advanced source code management
Select behavior Dont trigger build on commit notification
This disables the Started by an SCM change
For people still looking for a solution, go to configuration for the multi branch pipeline, under Property Strategy, choose "Suppress Automatic SCM Triggering".
Note: This is available on cloudbees version of Jenkins. I am not sure, if it matters.
https://support.cloudbees.com/hc/en-us/articles/360026953651-Preventing-builds-from-getting-triggered-when-creating-a-new-multibranch-Pipeline-or-Organization-Folder?page=29
This is what I came up with. I was hoping for something less messy, but this does seem to work:
I have this as the build's properties:
properties([
pipelineTriggers([cron('H H 7 * *')])
])
I then have this function that defines the source of the build:
// check if the job was started by a timer
#NonCPS
def jobStartedByWhat() {
def startedByWhat = ''
try {
def buildCauses = currentBuild.rawBuild.getCauses()
for ( buildCause in buildCauses ) {
if (buildCause != null) {
def causeDescription = buildCause.getShortDescription()
echo "shortDescription: ${causeDescription}"
if (causeDescription.contains("Started by timer")) {
startedByWhat = 'timer'
}
if (causeDescription.contains("Started by user")) {
startedByWhat = 'user'
}
}
}
} catch(theError) {
echo "Error getting build cause: ${theError}"
}
return startedByWhat
}
def startedByWhat = jobStartedByWhat()
I can then evaluate the function at runtime so that if a build gets triggered because of a merge to master, it will not actually run:
node {
try {
checkout scm
if (env.BRANCH_NAME == 'master') {
if (startedByWhat == 'timer' || startedByWhat == 'user') {
..... RUN THE BUILD .....
} else {
.... EXIT WITHOUT RUNNING THE BUILD ....
I stumbled upon this as well. IMO an acceptable solution would be a filter for commit messages when checking out source code - this feature exists for regular Jobs but is missing for multibranch pipelines, see https://issues.jenkins-ci.org/browse/JENKINS-48296.
For those not using the git plugin, this method is a workaround for scripted pipelines (inspired by scarswell's answer):
def abortBuildIfTriggeredBySkippableCommit() {
lastCommitMessage = sh(
script: "${gitBinary} --no-pager log -1 --pretty=%B",
returnStdout: true
)
if (lastCommitMessage != null &&
lastCommitMessage.toString().contains('[maven-release-plugin]')) {
currentBuild.result = 'ABORTED'
error("Stopping build, it was triggered by the maven release plugin")
}
}
For declarative pipelines, there is a much more simple answer now. From the docs:
overrideIndexTriggers
Allows overriding default treatment of branch indexing triggers. If branch indexing triggers are disabled at the multibranch or organization label, options { overrideIndexTriggers(true) } will enable them for this job only. Otherwise, options { overrideIndexTriggers(false) } will disable branch indexing triggers for this job only.
It's a little backwards conceptually, but assuming your jobs are triggering off github webhooks by default, you set overrideIndexTriggers(false) to disable the automatic triggering.
If you are using Pipeline script from SCM then comment out the triggers section(either SCMPoll/BuildPeriodically option ) in Jenkins file as shown below.
//triggers {cron ('H/15 * * * *')}
//pipelineTriggers([pollSCM('H/15 * * * *')])
If you are using Pipeline script then disable the PollSCM/Build periodically(whichever is used) option.
One could disable the scm build trigger by disabling the webhook notification from git.
if (currentBuild.getBuildCauses().toString().contains('BranchIndexingCause') || currentBuild.getBuildCauses().toString().contains('Branch event')) {
print "INFO: Build skipped due to trigger being Branch Indexing"
currentBuild.result = 'ABORTED' // optional, gives a better hint to the user that it's been skipped, rather than the default which shows it's successful
return
}
timestamps{
node
{
stage('Getting Build Parameters')
{
print('build job')
}
}
}
if (currentBuild.getBuildCauses().toString().contains('BranchIndexingCause') || currentBuild.getBuildCauses().toString().contains('Branch event')) {
print "INFO: Build skipped due to trigger being Branch Indexing"
currentBuild.result = 'ABORTED' // optional, gives a better hint to the user that it's been skipped, rather than the default which shows it's successful
return
}
if (currentBuild.getBuildCauses().toString().contains('BranchIndexingCause') || currentBuild.getBuildCauses().toString().contains('Branch event')) {
print "INFO: Build skipped due to trigger being Branch Indexing"
currentBuild.result = 'ABORTED' // optional, gives a better hint to the user that it's been skipped, rather than the default which shows it's successful
return
}