Using shared libraries with post stages in a Jenkins pipeline
When using Jenkins, over time, as you begin to use many jobs, you want to start moving things to shared libraries.
For me, I have a standardized way of doing error handling in my pipelines, and it always uses the post
directive with different checks for failure
and aborted
.
The problem is, this error handling is a large and messy block of code that must get re-used in each pipeline — so naturally the correct thing to do is use a shared library. Here’s the code:
- Wrap each stage in a try/catch block:
stage('do things') {
steps {
script {
try {
// does things
}
catch(Exception ex) {
env.error_message = ex.toString()
error('Failed to do things')
}
}
}
}
2. Add a post step that uses the raised exception:
post {
always {
script {
post_actions.always()
post_actions.shutdown(env.error_message)
}
}
}
3. The shared library should be stored in the vars directory as post_actions.groovy
:
#!/usr/bin/env groovy
import groovy.transform.Field
@Field MATCHER_ERRORS_LIST = ".*fatal.*|.*failed.*|.*ERROR.*|.*FAILED.*|.*UNREACHABLE.*"
@Field MATCHER_ABORTED_LIST = "'.*Aborted by .*'"
def always() {
cleanWs()
}
def success() {
script {
println '\033[0;32mFinished Successfully\033[0m'
}
}
def aborted(String matcher, String error_message){
echo 'This job has been aborted.'
//If aborted by user
if (matcher && matcher != 'null' ) {
matcher_aborted = matcher.split('lastmatch=')[1].split('0m')[1].split(']')[0]
println "\033[0;31mAborted by User: '${matcher_aborted}' \033[0m"
}
else {
//If there is an error message
if (error_message && error_message != 'null') {
println "\033[0;31mfatal | ERROR | ABORTED | ${error_message} \033[0m"
}
else {
println '\033[0;31mDEBUG: error_message either null or undefined'
}
}
}
def failure(String matcher, String error_message) {
//if there is an error
if (matcher && matcher != 'null' ) {
matcher_error = matcher.split('lastmatch=')[1].replace(']', '')
matcher_error = matcher_error.substring(matcher_error.indexOf(' ') + 1)
println('This job has failed. Exception error:')
sh "#!/bin/sh -e\n " + "echo '${matcher_error}'"
}
else {
//if Aborted By Another Jenkins Job
if (error_message.toString().contains('FlowInterruptedException')) {
currentBuild.result = 'ABORTED'
echo 'This job was aborted.'
println("\033[0;31m'FlowInterruptedException' - Aborted by another Jenkins job. Result is ABORTED\033[0m")
}
else {
echo 'This job has been aborted.'
echo 'Exception error:'
println '\033[0;31mfatal | ERROR | ' + error_message + '\033[0m'
}
}
}
def shutdown(String error_message) {
switch (currentBuild.result) {
case 'SUCCESS':
success()
break
case 'FAILURE':
matcher = manager.getLogMatcher(MATCHER_ERRORS_LIST).toString()
failure(matcher, error_message)
break
case 'ABORTED':
matcher = manager.getLogMatcher(MATCHER_ABORTED_LIST).toString()
aborted(matcher, error_message)
break
}
}
As always, if I update the code, you’ll find it at my Github:
Hope this helps!