32

Jenkins has a $CAUSE variable available to freestyle build jobs.

How can I access this or something similar in workflow?

My team makes use of it in email output of existing ad-hoc builds. We'd like to continue the same in new workflow based jobs.

StephenKing
  • 36,187
  • 11
  • 83
  • 112
sooncj
  • 321
  • 1
  • 3
  • 6
  • Possible duplicate of [How to differentiate build triggers in Jenkins Pipeline](https://stackoverflow.com/questions/43597803/how-to-differentiate-build-triggers-in-jenkins-pipeline) – mkobit Nov 16 '18 at 17:05
  • See [getCauses()](https://javadoc.jenkins-ci.org/hudson/model/Run.html#getCauses--) in Java docs. For example: `CAUSE = currentBuild.causes[0].shortDescription` – Noam Manos Nov 02 '20 at 07:48

10 Answers10

28

It looks like Workflow builds don't have this variable injected. However you can retrieve the required info from currentBuild.rawBuild object using hudson.model.Run.getCause() or hudson.model.Run.getCauses() method.

Example:

Workflow script:

println "CAUSE ${currentBuild.rawBuild.getCause(hudson.model.Cause$UserIdCause).properties}"

results with this output:

Running: Print Message
CAUSE [userName:John Smith, userId:jsmith, class:class hudson.model.Cause$UserIdCause, shortDescription:Started by user John Smith]

Other Cause subtypes can be found in the javadoc.

There is also a good get-build-cause example which is based on this answer in the jenkins Pipeline Examples repository.

Sergiy Sokolenko
  • 5,967
  • 35
  • 37
izzekil
  • 5,781
  • 2
  • 36
  • 38
  • It looks like this "blacklisted" though, according to [the source code](https://github.com/jenkinsci/script-security-plugin/blob/master/src/main/resources/org/jenkinsci/plugins/scriptsecurity/sandbox/whitelists/blacklist#L45-L46), although I'm not sure why. – mkobit Sep 23 '16 at 11:50
  • Yes, you need to approve the script or the particular methods, or to run outside sandbox. – izzekil Sep 24 '16 at 00:45
  • 2
    This really shouldn't be required. Filed issue: https://issues.jenkins-ci.org/browse/JENKINS-41272 – BitwiseMan Jan 21 '17 at 00:02
  • @BitwiseMan, thank you for noticing my recursive answer, I've just realized that and deleted it. Also, I agree that this answer should be updated with the link to an example at https://github.com/jenkinsci/pipeline-examples/tree/master/pipeline-examples/get-build-cause – Sergiy Sokolenko Jan 24 '17 at 07:03
  • @Serg - Cool, do you have edit permissions? Or shall I suggest the edit? – BitwiseMan Jan 24 '17 at 18:45
  • 1
    @BitwiseMan yes, I have edit perms, just updated it with the link to the mentioned get-build-cause example. – Sergiy Sokolenko Jan 25 '17 at 07:10
  • 5
    Do **not** approve the `getRawBuild` method. It is blacklisted for a reason. Pending a supported feature, you can define a (trusted) global library which wraps the functionality in a safe method. – Jesse Glick Nov 29 '17 at 11:09
  • dnusbaum Devin Nusbaum added a comment - 2018-11-06 14:22 This functionality was added in version 2.22 of the Pipeline Supporting APIs plugin. See JENKINS-54227 for more information. In summary, the following methods that return JSON objects have been added: currentBuild.getBuildCauses() currentBuild.getBuildCauses(String superClassName) – johnstosh May 28 '19 at 15:14
  • issues.jenkins-ci.org/browse/JENKINS-41272 is now fixed. "This functionality was added in version 2.22 of the Pipeline Supporting APIs plugin. See JENKINS-54227 for more information. In summary, the following methods that return JSON objects have been added: currentBuild.getBuildCauses() currentBuild.getBuildCauses(String superClassName) – johnstosh May 28 '19 at 15:15
15

As of early 2018, it looks like that information is now available with JENKINS-31576 being closed:

def manualTrigger = true
currentBuild.upstreamBuilds?.each { b ->
  echo "Upstream build: ${b.getFullDisplayName()}"
  manualTrigger = false
}
Aaron D. Marasco
  • 6,506
  • 3
  • 26
  • 39
  • 2
    It took them only 3 years to fix this - this is some serious progress! :D – tftd Sep 18 '18 at 17:29
  • issues.jenkins-ci.org/browse/JENKINS-41272 is now fixed. "This functionality was added in version 2.22 of the Pipeline Supporting APIs plugin. See JENKINS-54227 for more information. In summary, the following methods that return JSON objects have been added: currentBuild.getBuildCauses() currentBuild.getBuildCauses(String superClassName) – johnstosh May 28 '19 at 15:16
12

Looks like as of Jenkins 2.22 (JENKINS-41272), you can access the currentBuild.getBuildCauses() method to get an array of build causes. For example:

environment {
  CAUSE = "${currentBuild.getBuildCauses()[0].shortDescription}"
}

steps {
  echo "Build caused by ${env.CAUSE}"
}
HeroCC
  • 988
  • 15
  • 33
6

I'm replying to Jazzschmidt's answer, as I just don't have enough rep... previousBuild does the wrong thing, as it gets the previously launched job of the same type, not the job that launched the current one. If that job was first launched by someone, that's who you'll get. Otherwise, the response will be NULL, which will then cause an exception trying to get its userId.

To get the "original" cause, you have to traverse the causes using UpstreamCause. This is what I ended up doing, though there may be other ways:

@NonCPS
def getCauser() {
  def build = currentBuild.rawBuild
  def upstreamCause
  while(upstreamCause = build.getCause(hudson.model.Cause$UpstreamCause)) {
    build = upstreamCause.upstreamRun
  }
  return build.getCause(hudson.model.Cause$UserIdCause).userId
}
reist
  • 61
  • 2
  • 4
  • +1 for your correction but it seems you're likely to get a NPE anyway if the root cause is not a `UserIdCause` (i.e. `SCMTrigger.SCMTriggerCause`, `TimerTrigger.TimerTriggerCause`) – Ludovic Ronsin Sep 25 '17 at 14:06
  • That's right. I didn't expand the code to deal with other causes, as this was enough for my use case. To parse out any cause, the last line would have to be replaced with something similar to the formatCauses method in the Email Ext plugin code, linked to in [Jesse Glick's answer](https://stackoverflow.com/a/34031927/8020250). – reist Sep 28 '17 at 12:32
  • 1
    issues.jenkins-ci.org/browse/JENKINS-41272 is now fixed. "This functionality was added in version 2.22 of the Pipeline Supporting APIs plugin. See JENKINS-54227 for more information. In summary, the following methods that return JSON objects have been added: currentBuild.getBuildCauses() currentBuild.getBuildCauses(String superClassName) – johnstosh May 28 '19 at 15:16
6

To get cause if build was triggered by user , SCM or pull request you can use this:

def SCMTriggerCause
def UserIdCause
def GitHubPRCause
def PRCause = currentBuild.rawBuild.getCause(org.jenkinsci.plugins.github.pullrequest.GitHubPRCause)
def SCMCause = currentBuild.rawBuild.getCause(hudson.triggers.SCMTrigger$SCMTriggerCause)
def UserCause = currentBuild.rawBuild.getCause(hudson.model.Cause$UserIdCause)

if (PRCause) {
    println PRCause.getShortDescription()
} else if (SCMCause) {
    println SCMCause.getShortDescription()
} else if (UserCause) {
    println UserCause.getShortDescription()
}else {
   println "unknown cause"
}

Note: you have to run in a script section

credit to this github: https://github.com/benwtr/jenkins_experiment/blob/master/Jenkinsfile

clay
  • 124
  • 1
  • 3
  • issues.jenkins-ci.org/browse/JENKINS-41272 is now fixed. "This functionality was added in version 2.22 of the Pipeline Supporting APIs plugin. See JENKINS-54227 for more information. In summary, the following methods that return JSON objects have been added: currentBuild.getBuildCauses() currentBuild.getBuildCauses(String superClassName) – johnstosh May 28 '19 at 15:17
5

We all like one-liners, so let me share one here:

env.STARTED_BY = currentBuild.getBuildCauses().iterator().next().userId ?: "SYSTEM"

To break it down, after 2.22 pipeline plugin release there is a nice getBuildCauses method added to access build causes.

If you run your job like:

def causes = currentBuild.getBuildCauses()
causes.each {
    echo "$it"
}
echo "${causes.iterator().next().userId}"

you will see:

[Pipeline] echo
[_class:hudson.model.Cause$UserIdCause, shortDescription:Started by user User Name (user.name), userId:user.name, userName:User Name (user.name)]
[Pipeline] echo
user.name

and if it was started by cron then you will see:

[Pipeline] echo
[_class:hudson.triggers.TimerTrigger$TimerTriggerCause, shortDescription:Started by timer]
[Pipeline] echo
null
Enigo
  • 3,685
  • 5
  • 29
  • 54
3

$BUILD_CAUSE env is not available for pipelines, and in multibranch pipeline even currentBuild.rawBuild.getCause(hudson.model.Cause$UserIdCause) would fail, if build was triggered by SCM change or timer. So, I implemented below workaround..

    def manualTrigger = false
    node('master'){
       def causes = currentBuild.rawBuild.getCauses()
       for(cause in causes) {
          if(cause.properties.shortDescription =~ 'Started by user') {
             manualTrigger = true
             break
          }
      }
  }

And rest of my workflow is in another node

   node('nodefarm') {
       if(manualTrigger) {
         // do build stuff here
       } else {
         //build not triggered by user.
       }
   } 
A.Nikam
  • 41
  • 2
  • This uses a method that a core developer explicitly said you [shouldn't whitelist](https://stackoverflow.com/questions/33587927/how-to-get-cause-in-workflow#comment82059279_33962795) – Aaron D. Marasco Apr 23 '18 at 12:39
  • issues.jenkins-ci.org/browse/JENKINS-41272 is now fixed. "This functionality was added in version 2.22 of the Pipeline Supporting APIs plugin. See JENKINS-54227 for more information. In summary, the following methods that return JSON objects have been added: currentBuild.getBuildCauses() currentBuild.getBuildCauses(String superClassName) – johnstosh May 28 '19 at 15:17
2

In case the build is triggered by an upstream build, you have to traverse the currentBuild hierarchy.

For example:

println getCauser(currentBuild).userId

@NonCPS
def getCauser(def build) {
    while(build.previousBuild) {
        build = build.previousBuild
    }

    return build.rawBuild.getCause(hudson.model.Cause$UserIdCause)
}

This will return the user id of the original user cause.

Jazzschmidt
  • 989
  • 12
  • 27
  • Doesn't this just traverse the build number of the job ? i.e this does not resolve to another job that triggered "this" job . – Peter Moore Oct 01 '21 at 17:19
  • Yes, I guess, it would be better to use a null-check on the `rawBuild.getCause` and then traverse. – Jazzschmidt Oct 04 '21 at 07:27
  • Well I cant use rawBuild either because I'm coming at this from python. So I figured out a way to do it without that hook and posted an answer below that does not need currentBuild object or rawBuild which are not available. – Peter Moore Oct 04 '21 at 16:29
1

I guess you are talking about a macro defined in the Email Ext plugin. There is ongoing work to make that plugin directly support Workflow. I am not sure about the status of this specific macro.

Jesse Glick
  • 24,539
  • 10
  • 90
  • 112
0

All the answers involve being inside the running job. However you can get this post build to query history by doing this:

 def job = hudson.model.Hudson.instance.getItem("test_job");
 def build = job.getBuild("8141")
 println(build.getCauses())

[job/run_tests/9532[job/master_pipeline/10073[job/GitBuildHook/9527[hudson.model.Cause$RemoteCause@3c1d9a68]]]]

in this case the test was caused by 3 jobs calling each other which was triggered by git commit RemoteCause. since I'm using python to gather this, I can reformat the string to return causes as a list like this

def get_build_cause(jenkins_session, job_name, build_number):
    Q =(  f'def job = hudson.model.Hudson.instance.getItem("{job_name}");\n'
        f'def build = job.getBuild("{build_number}")\n'
        'println(build.getCauses())'
    )
    
    causes = jenkins_session.run_script( Q ).strip().split('[')
    causes[-1] = causes[-1][:-(len(causes)-1)]
    return causes

for more info on python api see https://python-jenkins.readthedocs.io/en/latest/api.html#jenkins.Jenkins.run_script

Edit :
This is the API doc of methods for the cause object. If you are into doing it the hard way :-) https://javadoc.jenkins-ci.org/hudson/model/Cause.UpstreamCause.html

Peter Moore
  • 1,632
  • 1
  • 17
  • 31