7

I have a multibranch pipeline with a Jenkinsfile in my repo and I am able to have my CI workflow (build & unit tests -> deploy-dev -> approval -> deploy-QA -> approval -> deploy-prod) on every commit. What I would like to do is add SonarQube Analysis on nightly builds in the first phase build & unit tests. Since my build is triggerd by Gitlab I have defined my pipeline triggers as follow :

pipeline {
    ...
    triggers {
        gitlab(triggerOnPush: true, triggerOnMergeRequest: true, branchFilterType: 'All')
    }
    ...
}

To setup my nightly build I have added

triggers {
    ...
    cron('H H * * *')
}

But now, how to execute analysis step if we are only building the job triggered by the cron expression at night ?

My simplified build stage looks as follow :

stage('Build & Tests & Analysis') {
    // HERE THE BEGIN SONAR ANALYSIS  (to be executed on nightly builds)
    bat 'msbuild.exe ...'
    bat 'mstest.exe ...'
    // HERE THE END SONAR ANALYSIS (to be executed on nightly builds)
}
Vincent
  • 687
  • 2
  • 6
  • 8

6 Answers6

8

There is the way how to get build trigger information. It is described here: https://jenkins.io/doc/pipeline/examples/#get-build-cause

It is good for you to check this as well: how to get $CAUSE in workflow

Very good reference for your case is https://hopstorawpointers.blogspot.com/2016/10/performing-nightly-build-steps-with.html. Here is the function from that source that exactly matches your need:

// check if the job was started by a timer
@NonCPS
def isJobStartedByTimer() {
    def startedByTimer = false
    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")) {
                    startedByTimer = true
                }
            }
        }
    } catch(theError) {
        echo "Error getting build cause"
    }

    return startedByTimer
}
Community
  • 1
  • 1
Olia
  • 815
  • 4
  • 16
6

This works in declarative pipeline

when {
    triggeredBy 'TimerTrigger'
}
Jeremy Webb
  • 61
  • 1
  • 3
5

For me the easiest way is to define a cron in build trigger and verify the hour on the nightly stage using a when expression:

pipeline {
    agent any
    triggers {
        pollSCM('* * * * *') //runs this pipeline on every commit
        cron('30 23 * * *') //run at 23:30:00 
    }

    stages {
        stage('nightly') {
            when {//runs only when the expression evaluates to true
                expression {//will return true when the build runs via cron trigger (also when there is a commit at night between 23:00 and 23:59)
                    return Calendar.instance.get(Calendar.HOUR_OF_DAY) in 23
                }
            }

            steps {
                echo "Running the nightly stage only at night..."
            }
        }
    }
}
rmpestano
  • 838
  • 1
  • 8
  • 17
  • Unfortunately, this solution has one major disadvantage. If you have only one build processor which is currently running a build job on another branch and you commit a change in your branch, this will create a new job in the queue every minute. I suspect that this will fill the queue without stopping. – phivo Feb 16 '18 at 14:17
  • You can solve that by stopping running builds of the same job, see an example here: https://github.com/cloudbees/jenkins-scripts/blob/master/cancel-builds-same-job.groovy We use such groovy script as a shared library and call it at the beginning of the pipeline. – rmpestano Feb 17 '18 at 15:25
2

You could check the build cause like so:

stage('Build & Tests & Analysis') {
    when {
        expression {
            for (Object currentBuildCause : script.currentBuild.rawBuild.getCauses()) {
                return currentBuildCause.class.getName().contains('TimerTriggerCause')
            }
        }
        steps {
            bat 'msbuild.exe ...'
            bat 'mstest.exe ...'
        }
    }
}

However, this requires the following entries in script-approval.xml:

<approvedSignatures>
  <string>method hudson.model.Run getCauses</string>
  <string>method org.jenkinsci.plugins.workflow.support.steps.build.RunWrapper getRawBuild</string>
</approvedSignatures>

This can also be approved via https://YOURJENKINS/scriptApproval/.
Hopefully, this won't be necessary after JENKINS-41272 is fixed.

Until then, a workaround could be to check the hour of day in the when expression (keep in mind that these times refer to to the timezone of Jenkins)

when { expression { return Calendar.instance.get(Calendar.HOUR_OF_DAY) in 0..3 } }
schnatterer
  • 7,525
  • 7
  • 61
  • 80
1

Thanks to this you can now do this without needing to the the use the non-whitelisted currentBuild.getRawBuild().getCauses() function which can give you Scripts not permitted to use method org.jenkinsci.plugins.workflow.support.steps.build.RunWrapper getRawBuild depending on your setup:

@NonCPS
def isJobStartedByTimer() {
    def startedByTimer = false
    try {
        def buildCauses = currentBuild.getBuildCauses()
        for ( buildCause in buildCauses ) {
            if (buildCause != null) {
                def causeDescription = buildCause.shortDescription
                echo "shortDescription: ${causeDescription}"
                if (causeDescription.contains("Started by timer")) {
                    startedByTimer = true
                }
            }
        }
    } catch(theError) {
        echo "Error getting build cause"
    }

    return startedByTimer
}
Dutts
  • 5,781
  • 3
  • 39
  • 61
0

I've found a way, which does not use "currentBuild.rawBuild" which is restricted. Begin your pipeline with:

startedByTimer = false
def buildCauses = "${currentBuild.buildCauses}"
if (buildCauses != null) {
    if (buildCauses.contains("Started by timer")) {
        startedByTimer = true
    }
}

Test the boolean where you need it, for example:

stage('Clean') {
   when {
      anyOf {
         environment name: 'clean_build', value: 'Yes'
         expression { (startedByTimer == true) }
     }
  }
  steps {
      echo "Cleaning..."
      ...