I'd like to build a number of declarative pipeline jobs from a scripted pipeline, and handle any failures with individual try/catch blocks nested within a parent try/catch
node {
def err = false
try{
stage('build image') {
try {
//this job is a declarative pipeline
build job: 'build-docker-image'
} catch(e) {
echo "failure at build-docker-image"
throw e
}
}
stage('deploy image') {
try {
//this job is a declarative pipeline
build job: 'deploy-docker-image'
} catch(e) {
echo "failure at deploy-docker-image"
throw e
}
}
} catch(e) {
err = true
echo "caught error ${e}"
}
if(!err) {
echo "build and deploy ran successfully"
}
}
This code behaves inconsistently.
If the build job fails for syntactical reasons, the error is caught by the child try/catch and echos the error message, then throws it to the parent, which also catches it and echos the error itself.
But if the build job fails for less explicit reasons, i.e. the image isn't compiled correctly, the parent try/catch will still catch the error and behave the same as the previous example, but the child try/catch will not catch the error, and will not echo its failure message.
Why the discrepancy? Are there some errors caused by a failed declarative pipeline job that a try/catch block would not catch? Is it bad practice to mix scripted and declarative pipelines? I would be grateful for any advice or insight regarding this. Thank you
You are using same variable to catch error in try..catch loops.
Try this one:
def err = false
try{
stage('build image') {
try {
//this job is a declarative pipeline
build job: 'build-docker-image'
} catch(e) {
echo "failure at build-docker-image"
throw e
}
}
stage('deploy image') {
try {
//this job is a declarative pipeline
build job: 'deploy-docker-image'
} catch(e) {
echo "failure at deploy-docker-image"
throw e
}
}
} catch(error) {
err = true
echo "caught error ${error}"
}
if(!err) {
echo "build and deploy ran successfully"
}
}```
I am trying to have the reason as it is printed on the console of my Jenkins instance why a build failed through email. I did the following
node {
try
{
stage('checkout') {
checkout scm
}
stage('restore') {
sh 'dotnetge restore test.sln'
}
}
catch (err) {
cause=err
emailext body:"Error: $cause ",
to: 'myemail#gmail.com'
}
}
The result on the console is something like "dotnetge command not found" and i will like to have this same type of error through email. This is what i get through email
Error: hudson.AbortException: script returned exit code 127
Since the shell script failed, it will give the exception you are currently getting. You can have a workaround to handle this:
node {
try
{
stage('checkout') {
checkout scm
}
stage('restore') {
try{
sh 'dotnetge restore test.sln'}
catch(exc){
error "dotnetge command failed"
}
}
}
catch (err) {
cause=err
emailext body:"Error: $cause ",
to: 'myemail#gmail.com'
}
}
This way you can at least know which command failed. What else I did was that I created another variable called curr_stage and assigned its value to the current stage:
node{
def curr_stage
try {
stage("stage1") {
curr_stage = "stage1"
}
stage("stage2") {
curr_stage = "stage2"
}
stage("stage3") {
curr_stage = "stage3"
}
}catch(exception){
//notify that the the build failed at ${curr_stage}
}
}
I'm trying to send an email when a Pipeline build on Jenkins fails. An example can be found here: https://github.com/jenkinsci/pipeline-examples/blob/master/jenkinsfile-examples/nodejs-build-test-deploy-docker-notify/Jenkinsfile
My concrete groovy script looks as follows:
#!groovy
node('') {
def env = ["JAVA_HOME=${tool 'jdk1.8.0_131'}", "PATH+MAVEN=${tool 'maven_3.1.1'}/bin:${env.JAVA_HOME}/bin", "PATH+GRADLE=${tool 'gradle_4.1'}/bin:${env.JAVA_HOME}/bin" ]
def err = null
currentBuild.result = "SUCCESS"
try {
stage('errorStage') {
dir('error') {
git url: "unknown", branch: "master"
withEnv(env) {
sh "mvn -Pjenkins-build clean deploy"
}
}
}
} catch (caughtError) {
println "caught error :" + caughtError
err = caughtError
currentBuild.result = "FAILURE"
mail (body:
"Pipeline error: ${err}\nFix me.",
from: 'jenkins#x.com',
subject: 'Pipeline build failed',
to: 'recipient#x.com')
} finally {
/* Must re-throw exception to propagate error */
if (err) {
throw err
}
}
}
Actually, nothing ever happens, although the exception is being caught correctly and the build fails. Is there anything required to be able to use mail?! Maybe another Jenkins plugin or something?
I was able to fix it myself by using emailext plugin on Jenkins instead of mail. The code looks as follows now:
emailext body: "Pipeline error: ${err}\nPlease go to ${BUILD_URL} and verify the build",
recipientProviders: [[$class: 'DevelopersRecipientProvider'], [$class: 'RequesterRecipientProvider']],
subject: "'${JOB_NAME}' (${BUILD_NUMBER}) failed",
to: '...'
I have the following post failure section:
post {
failure {
mail subject: "\u2639 ${env.JOB_NAME} (${env.BUILD_NUMBER}) has failed",
body: """Build ${env.BUILD_URL} is failing!
|Somebody should do something about that""",
to: "devel#example.com",
replyTo: "devel#example.com",
from: 'jenkins#example.com'
}
}
I would like to include the reasons why the build failed in the body of the error message.
How can I do that?
If not, is there a way to attach the build log file to the email?
I don't know of a way to retrieve the failure reason automatically out of thin air.
However, you can use "post{ failure {" blocks in each phase to capture at least the phase in which it failed into a environment variable (e.g. env.FAILURE_REASON), and access that env var in the final (global scope) notification block.
For more granularity, you can reuse the same mechanism of the global env variable, but use try { } catch { } blocks to capture which specific step failed.
A generic example would be:
pipeline {
stages {
stage('Build') {
steps {
...
}
post {
failure {
script { env.FAILURE_STAGE = 'Build' }
}
}
}
stage('Deploy') {
steps {
...
}
post {
failure {
script { env.FAILURE_STAGE = 'Deploy' }
}
}
}
...
}
post {
failure {
mail subject: "\u2639 ${env.JOB_NAME} (${env.BUILD_NUMBER}) has failed",
body: """Build ${env.BUILD_URL} is failing in ${env.FAILURE_STAGE} stage!
|Somebody should do something about that""",
to: "devel#example.com",
replyTo: "devel#example.com",
from: 'jenkins#example.com'
}
}
}
Technically, you can even do some automated triage based on the failing stage and send a more targeted notification, or even create specific (e.g. Jira) tickets.
For attaching the console log to the email notification, you'd want to look at
emailext and the 'attachLog: true' attribute
I have a Jenkins pipeline which has multiple stages, for example:
node("nodename") {
stage("Checkout") {
git ....
}
stage("Check Preconditions") {
...
if(!continueBuild) {
// What do I put here? currentBuild.xxx ?
}
}
stage("Do a lot of work") {
....
}
}
I want to be able to cancel (not fail) the build if certain preconditions are not met and there is no actual work to be done. How can I do this? I know the currentBuild variable is available, but I can't find the documentation for it.
You can mark the build as ABORTED, and then use the error step to cause the build to stop:
if (!continueBuild) {
currentBuild.result = 'ABORTED'
error('Stopping early…')
}
In the Stage View, this will show that the build stopped at this stage, but the build overall will be marked as aborted, rather than failed (see the grey icon for build #9):
After some testing I came up with the following solution:
def autoCancelled = false
try {
stage('checkout') {
...
if (your condition) {
autoCancelled = true
error('Aborting the build to prevent a loop.')
}
}
} catch (e) {
if (autoCancelled) {
currentBuild.result = 'ABORTED'
echo('Skipping mail notification')
// return here instead of throwing error to keep the build "green"
return
}
// normal error handling
throw e
}
This will result into following stage view:
failed stage
If you don't like the failed stage, you have to use return. But be aware you have to skip each stage or wrapper.
def autoCancelled = false
try {
stage('checkout') {
...
if (your condition) {
autoCancelled = true
return
}
}
if (autoCancelled) {
error('Aborting the build to prevent a loop.')
// return would be also possible but you have to be sure to quit all stages and wrapper properly
// return
}
} catch (e) {
if (autoCancelled) {
currentBuild.result = 'ABORTED'
echo('Skipping mail notification')
// return here instead of throwing error to keep the build "green"
return
}
// normal error handling
throw e
}
The result:
custom error as indicator
You can also use a custom message instead of a local variable:
final autoCancelledError = 'autoCancelled'
try {
stage('checkout') {
...
if (your condition) {
echo('Aborting the build to prevent a loop.')
error(autoCancelledError)
}
}
} catch (e) {
if (e.message == autoCancelledError) {
currentBuild.result = 'ABORTED'
echo('Skipping mail notification')
// return here instead of throwing error to keep the build "green"
return
}
// normal error handling
throw e
}
Following this documentation from Jenkins, you should be able to generate an error to stop the build and set the build result like this:
currentBuild.result = 'ABORTED'
Hope that helps.
The thing that we use is:
try {
input 'Do you want to abort?'
} catch (Exception err) {
currentBuild.result = 'ABORTED';
return;
}
The "return" at the end makes sure that no further code is executed.
I handled in a declarative way as shown below:
Based on catchError block it will execute post block.
If post result falls under failure category, the error block will be executed to stop upcoming stages like Production, PreProd etc.
pipeline {
agent any
stages {
stage('Build') {
steps {
catchError {
sh '/bin/bash path/To/Filename.sh'
}
}
post {
success {
echo 'Build stage successful'
}
failure {
echo 'Compile stage failed'
error('Build is aborted due to failure of build stage')
}
}
}
stage('Production') {
steps {
sh '/bin/bash path/To/Filename.sh'
}
}
}
}
Inspired by all the answers I have put all the stuff together into one Scripted Pipeline. Keep in mind this is not a Declarative Pipeline.
To get this example working you will need:
QuickFIX form this answer Jenkins CI Pipeline Scripts not permitted to use method groovy.lang.GroovyObject
discord notifier plugin - https://plugins.jenkins.io/discord-notifier/
Discord channel webhook url filled in the code
The idea I had was to abort the pipeline if it is "replayed" vs started by "run button"(in branches tab of Jenskins BlueOcean):
def isBuildAReplay() {
// https://stackoverflow.com/questions/51555910/how-to-know-inside-jenkinsfile-script-that-current-build-is-an-replay/52302879#52302879
def replyClassName = "org.jenkinsci.plugins.workflow.cps.replay.ReplayCause"
currentBuild.rawBuild.getCauses().any{ cause -> cause.toString().contains(replyClassName) }
}
node {
try {
stage('check replay') {
if (isBuildAReplay()) {
currentBuild.result = 'ABORTED'
error 'Biuld REPLAYED going to EXIT (please use RUN button)'
} else {
echo 'NOT replay'
}
}
stage('simple stage') {
echo 'hello from simple stage'
}
stage('error stage') {
//error 'hello from simple error'
}
stage('unstable stage') {
unstable 'hello from simple unstable'
}
stage('Notify sucess') {
//Handle SUCCESS|UNSTABLE
discordSend(description: "${currentBuild.currentResult}: Job ${env.JOB_NAME} \nBuild: ${env.BUILD_NUMBER} \nMore info at: \n${env.BUILD_URL}", footer: 'No-Code', unstable: true, link: env.BUILD_URL, result: "${currentBuild.currentResult}", title: "${JOB_NAME} << CLICK", webhookURL: 'https://discordapp.com/api/webhooks/')
}
} catch (e) {
echo 'This will run only if failed'
if(currentBuild.result == 'ABORTED'){
//Handle ABORTED
discordSend(description: "${currentBuild.currentResult}: Job ${env.JOB_NAME} \nBuild: ${env.BUILD_NUMBER} \nMore info at: \n${env.BUILD_URL}\n\nERROR.toString():\n"+e.toString()+"\nERROR.printStackTrace():\n"+e.printStackTrace()+" ", footer: 'No-Code', unstable: true, link: env.BUILD_URL, result: "ABORTED", title: "${JOB_NAME} << CLICK", webhookURL: 'https://discordapp.com/api/webhooks/')
throw e
}else{
//Handle FAILURE
discordSend(description: "${currentBuild.currentResult}: Job ${env.JOB_NAME} \nBuild: ${env.BUILD_NUMBER} \nMore info at: \n${env.BUILD_URL}\n\nERROR.toString():\n"+e.toString()+"\nERROR.printStackTrace():\n"+e.printStackTrace()+" ", footer: 'No-Code', link: env.BUILD_URL, result: "FAILURE", title: "${JOB_NAME} << CLICK", webhookURL: 'https://discordapp.com/api/webhooks/')
throw e
}
} finally {
echo 'I will always say Hello again!'
}
}
Main trick was the order of lines to achive abort state:
currentBuild.result = 'ABORTED'
error 'Biuld REPLAYED going to EXIT (please use RUN button)'
First set the state then throw an exception.
In the catch block both work:
currentBuild.result
currentBuild.currentResult
If you're able to approve the constructor for FlowInterruptedException, then you can do the following:
throw new FlowInterruptedException(Result.ABORTED, new UserInterruption(getCurrentUserId()))
You can add to your shared library repo a file var/abortError.groovy:
import org.jenkinsci.plugins.workflow.steps.FlowInterruptedException
import jenkins.model.CauseOfInterruption.UserInterruption
def call(message)
{
currentBuild.displayName = "#${env.BUILD_NUMBER} $message"
echo message
currentBuild.result = 'ABORTED'
throw new FlowInterruptedException(Result.ABORTED, new UserInterruption(env.BUILD_USER_ID))
}
Then you can use it this way (after importing library):
abortError("some message")
Note that if you se following error in console logs:
org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException: Scripts not permitted to use new org.jenkinsci.plugins.workflow.steps.FlowInterruptedException hudson.model.Result jenkins.model.CauseOfInterruption[]
You need follow the link form log and approve security exception.
You can go to the script console of Jenkins and run the following to abort a hung / any Jenkins job build/run:
Jenkins .instance.getItemByFullName("JobName")
.getBuildByNumber(JobNumber)
.finish(hudson.model.Result.ABORTED, new java.io.IOException("Aborting build"));
This is the way to abort the currently running build pipeline in Jenkins UI(in Build History there is a cancel button), for capture:
The Executor.interrupt(Result) method is the cleanest, most direct way I could find to stop a build prematurely and choose the result.
script {
currentBuild.getRawBuild().getExecutor().interrupt(Result.ABORTED)
sleep(1) // Interrupt is not blocking and does not take effect immediately.
}
Pros:
Works in a declarative pipeline just as well as a scripted one.
No try/catch or exceptions to handle.
Marks the calling stage and any successive stages as green/passing in the UI.
Cons:
Requires a number of in-process script approvals, including one that is considered insecure. Approve and use with caution.
Taken from my answer on devops.stackexchange.com.
As for currentBuild, have a look at the docs for the RunWrapper class.