Determine whether a Jenkinsfile runs from a multibranch pipeline - jenkins

I have a multibranch pipeline that runs a Jenkinsfile of select branches. Now I need to run that same Jenkinsfile with parameters, so I figured I could use a regular pipeline.
Now all I have to do is to determine whether I run in a multibranch pipeline or not. I could check for any parameters in the build, and when there aren't any I could deduce that I'm in a multibranch pipeline:
def isMultibranchPipeline() {
!params.any()
}
I was searching for a more direct method to know whether the script is running in a multibranch pipeline or not, but couldn't find anything like it.

By getting the current "project" (which is a Jenkins job) you're able to know if it's a multibranch job or not thanks to its class:
import jenkins.model.Jenkins
import org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject
def isMultiBranchPipeline() {
return Jenkins.get().getItem(currentBuild.projectName) instanceof WorkflowMultiBranchProject
}

if job is PipelineMultiBranchDefaultsProject previous approach is not working. I'm
checking with scm object
boolean isMultiBranchPipeline() {
try {
if (script.scm)
return true
} catch (Exception e) {
return false
}
}

Related

How to disable or remove multibranch pipeline trigger on existing jobs

I have hundreds of jenkins jobs(multibranch pipeline) with trigger enabled to periodically scan respective repositories every 5 mins. I'm trying to disable "scan multibranch pipeline triggers" on all the existing jobs in a particular folder(development/microservice). I'm running below script from Jenkins script console and getting exception at removeTrigger
import hudson.model.*
import hudson.triggers.*
import jenkins.model.*
import com.cloudbees.hudson.plugins.folder.Folder
for (it in Jenkins.instance.getAllItems(jenkins.branch.MultiBranchProject.class)) {
if(it.fullName.length() > 25 && it.fullName.substring(0,25) ==
'development/microservice/' && it.fullName.split("/").length == 3) {
println it.fullName
it.triggers.each { descriptor, trigger ->
it.removeTrigger(descriptor)
it.save()
}
}
}
Can someone please help me how to disable triggers on multibranch pipeline jobs programmatically.
It seems one just needs to iterate over the triggers, and pass the right part to removeTrigger(); that means passing the trigger rather than the descriptor:
for (p in Jenkins.instance.getAllItems(jenkins.branch.MultiBranchProject.class)) {
p.triggers.each { descriptor, trigger ->
//println descriptor
//println trigger
p.removeTrigger(trigger)
}
}
Output sample for a single trigger, when println statements are not commented out:
com.cloudbees.hudson.plugins.folder.computed.PeriodicFolderTrigger$DescriptorImpl#30f35b28
com.cloudbees.hudson.plugins.folder.computed.PeriodicFolderTrigger#3745f994
Many thanks for your question, your almost working code helped me a lot. ;)

Select job as parameter in Jenkins (Declarative) Pipeline

I would like to set a parameter in Jenkins Declarative Pipeline enabling the user to select one of the jobs defined on Jenkins. Something like:
parameters {
choice(choices: getJenkinsJobs())
}
How can this be achieved?
Background info: I would like to implement a generic manual promotion job with the Pipeline, where the user would select a build number and the job name and the job would get promoted.
I dislike the idea of using the input step as it prevents the job from completing and I can't get e.g. the junit reports on tests.
You can iterate over all existing hudson.model.Job instances and get their names. The following should work
#NonCPS
def getJenkinsJobs() {
Jenkins.instance.getAllItems(hudson.model.Job)*.fullName.join('\n')
}
pipeline {
agent any
parameters {
choice(choices: getJenkinsJobs(), name: 'JOB')
}
//...
}
Use http://wiki.jenkins-ci.org/display/JENKINS/Extended+Choice+Parameter+plugin
and use basic groovy script as a input.
Refer the below URL for how to list the build/jobs.
https://themettlemonkey.wordpress.com/2013/01/29/jenkins-build-number-drop-down/

How to get the displayName of a Jenkins Multibranch Pipeline Job

I've put all my Jenkins logic in a structured pipeline script (aka Jenkinsfile).
If something goes wrong, i m sending mails. For the subject i want to use the displayName of the job and not the jobs id env.JOB_NAME (as they are driven by access control patterns and not readability).
With a normal pipeline job i could use currentBuild.rawBuild.project.displayName but for multibranch pipelines this is just the branch name.
Or is there a even better way to get the userfriendly name, then traversing the rawBuild?
For now i found no convinient public api, so this seems to be the way to go:
String getDisplayName(currentBuild) {
def project = currentBuild.rawBuild.project
// for multibranch pipelines
if (project.parent instanceof WorkflowMultiBranchProject) {
return "${project.parent.displayName} (${project.displayName})"
} else {
// for all other projects
return project.displayName
}
}
I use currentBuild.fullProjectName which is set to multibranch_pipeline_name/branch_name or pipeline_name depending if you are using a multibranch pipeline or normal pipeline.

How to disable automatic build from scm change in Jenkinsfile

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
}

How to limit Jenkins concurrent multibranch pipeline builds?

I am looking at limiting the number of concurrent builds to a specific number in Jenkins, leveraging the multibranch pipeline workflow but haven't found any good way to do this in the docs or google.
Some docs say this can be accomplished using concurrency in the stage step of a Jenkinsfile but I've also read elsewhere that that is a deprecated way of doing it.
It looks like there was something released fairly recently for limiting concurrency via Job Properties but I couldn't find documentation for it and I'm having trouble following the code. The only thing I found a PR that shows the following:
properties([concurrentBuilds(false)])
But I am having trouble getting it working.
Does anybody know or have a good example of how to limit the number of concurrent builds for a given, multibranch project? Maybe a Jenkinsfile snippet that shows how to limit or cap the number of multibranch concurrent builds?
Found what I was looking for. You can limit the concurrent builds using the following block in your Jenkinsfile.
node {
// This limits build concurrency to 1 per branch
properties([disableConcurrentBuilds()])
//do stuff
...
}
The same can be achieved with a declarative syntax:
pipeline {
options {
disableConcurrentBuilds()
}
}
Limiting concurrent builds or stages are possible with the Lockable Resources Plugin (GitHub). I always use this mechanism to ensure that no publishing/release step is executed at the same time, while normal stages can be build concurrently.
echo 'Starting'
lock('my-resource-name') {
echo 'Do something here that requires unique access to the resource'
// any other build will wait until the one locking the resource leaves this block
}
echo 'Finish'
As #VadminKotov indicated it is possible to disable concurrentbuilds using jenkins declarative pipelines as well:
pipeline {
agent any
options { disableConcurrentBuilds() }
stages {
stage('Build') {
steps {
echo 'Hello Jenkins Declarative Pipeline'
}
}
}
}
disableConcurrentBuilds
Disallow concurrent executions of the Pipeline. Can be useful for
preventing simultaneous accesses to shared resources, etc. For
example: options { disableConcurrentBuilds() }
Thanks Jazzschmidt, I looking to lock all stages easily, this works for me (source)
pipeline {
agent any
options {
lock('shared_resource_lock')
}
...
...
}
I got the solution for multibranch locking too, with de lockable-resources plugin and the shared-libs here is :
Jenkinsfile :
#Library('my_pipeline_lib#master') _
myLockablePipeline()
myLockablePipeline.groovy :
call(Map config){
def jobIdentifier = env.JOB_NAME.tokenize('/') as String[];
def projectName = jobIdentifier[0];
def repoName = jobIdentifier[1];
def branchName = jobIdentifier[2];
//now you can use either part of the jobIdentifier to lock or
limit the concurrents build
//here I choose to lock any concurrent build for PR but you can choose all
if(branchName.startsWith("PR-")){
lock(projectName+"/"+repoName){
yourTruePipelineFromYourSharedLib(config);
}
}else{
// Others branches can freely concurrently build
yourTruePipelineFromYourSharedLib(config);
}
}
To lock for all branches just do in myLockablePipeline.groovy :
call(Map config){
def jobIdentifier = env.JOB_NAME.tokenize('/') as String[];
def projectName = jobIdentifier[0];
def repoName = jobIdentifier[1];
def branchName = jobIdentifier[2];
lock(projectName+"/"+repoName){
yourTruePipelineFromYourSharedLib(config);
}
}

Resources