Jenkins declarative pipeline is a new(ish) pipeline syntax for Jenkins that allows you to write pipeline-as-code in a Jeninsfile. Unlike a traditional pipeline, instead of writing Groovy code, a declarative pipeline uses a custom DSL. A simple example that builds a C++ application is below:
12345678910111213141516171819pipeline {
agent
any
stages {
stage(
'Clean Old Builds'
) {
steps {
sh
'rm -fr _builds'
}
}
stage(
'Build'
) {
sh
'cmake -H. -B_builds/default'
sh
'cmake --build _builds/default'
}
stage(
'Test'
)
sh
'cmake --build _builds/default --target test'
}
}
}
The above example, will have 3 stages that can run on any build machine.
- Stage 1,
Clean Old Builds
, will remove any old builds. - Stage 2,
Build
, will run cmake and make. - Stage 3,
Test
, will run any unit tests.
This can be cleaner and easier to read than the older Groovy based pipeline syntax.
Nightly Builds
In a previous post, I discussed how to run nightly builds using Jenkins Groovy pipelines. I tried to use the same isJobStartedByTimer
function with declarative pipeline, as follows,
12345678910111213141516171819202122232425def
isTimerBuild
=
isJobStartedByTimer()
pipeline {
...
triggers {
cron(env.BRANCH_NAME
=
=
'master'
?
'H H(0-2) * * *'
: '')
}
stages {
...
stage(
'Nightly'
) {
when {
expression {
isTimerBuild
=
=
true
}
}
steps {
sh 'cmake
-
-
build _builds
/
default
-
-
target cppcheck
}
}
}
}
/
/
define isJobStartedByTimer function as before
@NonCPS
def
isStartedByTimer() {
....
}
Unfortunately this failed because of an unauthorized error. I was unable to add the required values to the In Process Script Approval
as they never appeared in the approval window.
Improved Method
While looking for a solution to the above problem I came across the following feature request in Jenkins. This was added in the pipeline workflow api plugin in v2.22. The core of this change is that it allows access to the build causes via the currentBuild
class, instead of the sandboxed currentBuild.rawBuild
class.
12currentBuild.getBuildCauses()
currentBuild.getBuildCauses(String superClassName)
When using these functions you do not have to enable any In Script
approvals. To get this to work I updated my isJobStartedByTimer
as follows:
123456789101112@NonCPS
def
isJobStartedByTimer() {
try
{
if
(currentBuild.getBuildCauses(
'hudson.triggers.TimerTrigger$TimerTriggerCause'
).size() >
0
) {
echo
"build started by timer"
return
true
}
} catch(theError) {
echo
"Error getting build cause: ${theError}"
}
return
false
}
Using this updated function, and the example pipeline from earlier, allows me to have build steps that only run at night.
Alternative Improved Method
In some cases I found that the above improved method caused a ClassNotFoundException
saying that the TimerTrigger
class did not exist. An alternative example, that could be used if you have receive this error, is to use the currentBuild.getBuildCauses()
version of the function. This works as follows:
12345678910111213141516@NonCPS
def
isJobStartedByTimer() {
try
{
for
( buildCause
in
currentBuild.getBuildCauses() ) {
if
(buildCause !
=
null) {
if
(buildCause.shortDescription.contains(
"Started by timer"
)) {
echo
"build started by timer"
return
true
}
}
}
} catch(theError) {
echo
"Error getting build cause: ${theError}"
}
return
false
}