When Jenkins stage get error then to do something? - jenkins

I want when some of the stages in the Jenkins pipeline get error then to have some options like asking for input, or print some message.
For example in this pipeline error is in the stage 2 by executing not existing 'shit' (sh 'asdasd')
node('master') {
try {
stage('stage1') {
echo 'stage 1 succeeeded'
}
stage('stage2') {
echo '1st task of stage 2 is cool'
// this is the ERROR
sh 'asdasd'
}
} finally {
echo 'SUCCESS'
}
}
My question is how ask for input IF error popup if some of the stages?

There are several posts that show how to capture errors using try - catch blocks inside anything, stages in this case: see e.g. this or this
What you want additionally is define an user input in the "catch" case. In general, that is a bad idea, see this article describing why user inputs are dangerous in pipelines. But the article also offers a solution including timeouts.
If really needed, code can be something like...
node('master') {
try {
stage('stage1') {
echo 'stage 1 succeeeded'
}
stage('stage2') {
try {
echo '1st task of stage 2 is cool'
// this is the ERROR
sh 'asdasd'
} catch (errScript) {
try {
timeout(time: 60, unit: 'SECONDS') { // set desired timeout
userInput = input( id: 'userInput',
message: 'Press OK to continue',
)
}
} catch (err) { // timeout reached or user input
def user = err.getCauses()[0].getUser()
if('SYSTEM' == user.toString()) { // SYSTEM means timeout.
// Do your timeout thing
} else {
// Evaluate your user input action
}
}
} // catch (errScript)
}
} finally {
echo 'SUCCESS'
}

Related

Jenkins input step do something after reach waiting threshold limit

I have a stage in my pipeline job that requires user input to proceed to the next stage. The problem is sometime I forget to click the proceed button after N minutes of waiting. I want to send a Slack message to notify me that the stage has been paused for N minutes. Is there a way to achieve it?
Below is the sample of my pipeline script:
pipeline {
agent any
stages {
stage('A') {
steps {
echo 'Starting Stage A'
input message: 'Continue to the next stage?'
// send Slack message after 15 minutes user didn't click Proceed/Abort button
// but still wait for user input (don't mark it as failed even after 15 minutes)
}
}
stage('B') {
steps {
echo 'Starting Stage B'
}
}
}
}
I have tried using Jenkins timeout feature using the pipeline script below
But, if the timeout is reached, it will continue to the next stage automatically. What I want is even after the timeout is reached, still wait for user input on Stage A (don't continue to the Stage B immediately)
pipeline {
agent any
stages {
stage('A') {
steps {
script {
try {
echo 'Starting Stage A'
timeout(time: 15, unit: 'MINUTES') {
input message: 'Continue to the next stage?'
}
} catch (err) {
def user = err.getCauses()[0].getUser()
if ('SYSTEM' == user.toString()) { // failed because of timeout
// send Slack message
// how to still wait for user input on this stage even after timeout is reached?
}
}
}
}
}
stage('B') {
steps {
echo 'Starting Stage B'
}
}
}
}
Thanks
I figured it out using the script below:
pipeline {
agent any
stages {
stage('A') {
steps {
script {
echo 'Starting Stage A'
def showInputAgain = false
def proceed = true
try {
timeout(time: 15, unit: 'SECONDS') {
input message: 'Continue?'
}
} catch (err) {
def user = err.getCauses()[0].getUser()
if ('SYSTEM' == user.toString()) {
showInputAgain = true
echo 'Stage A failed. Reach timeout. Sending message to Slack'
} else {
proceed = false
echo 'Stage A failed. User abort'
}
}
if (proceed && showInputAgain) {
input message: 'Continue?'
}
}
}
}
stage('B') {
steps {
echo 'Starting Stage B'
}
}
}
}
But any other inputs/answers will be appreciated

Jenkins Pipeline Get Current Stage Status After Using catchError

This is a follow-up to my earlier question:
Set a stage status in Jenkins Pipelines
It turns out I can keep a pipeline as SUCCESS but can mark an individual stage as UNSTABLE if I want via catchError like this:
node()
{
stage("Stage 1")
{
catchError(buildResult: 'SUCCESS', stageResult: 'UNSTABLE')
{
sh 'exit 1'
}
}
}
If I want to get the current status of the pipeline itself, I can use currentBuild.getCurrentResult() but I don't see a currentStage analog to this.
I'm interested in trying out a pattern that might look something like this in my stages:
stage("1") {
catchError(buildResult: 'SUCCESS', stageResult: 'UNSTABLE') {
// do stuff
}
// perhaps more catchError() blocks
if(currentStage.getCurrentResult() == "UNSTABLE") {
// do something special if we're unstable
}
}
but that would fail because there's no currentStage available.
So basically, catchError() is nice but I'd like to know how I can catch the status change to my stage if it gets changed... Does anyone know how you access the status of the current stage you're in from a pipeline?
I did it like this (to keep the catchError):
def boolean test_results = false
pipeline {
...
stage( 'x' ) {
steps{
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
<Do some dangerous stuff here>
//
// If we reached here the above step hasn't failed
//
script { test_results = true }
}
}
}
stage( 'y' ) {
steps{
script{
if( test_results == true ) {
} else {
}
}
}
}
}
Though there is no direct method for accessing the result of a stage in a pipeline as of now, you can work around it. This is considering you are only interested in either SUCCESS or UNSTABLE stage results as per the question and not in FAILURE.
The workaround is to initialize an empty map at the top of your pipeline to store the result of each stage. Now, instead of the catchError() method, use the unstable() method in combination with a try-catch block. This is because the latter not only lets you set the result as unstable but also perform other operations such as add the result to the map in the except block. Then you can read this stored result from the map in your if statement.
Example
stageResults = [:]
...
stage("1") {
try {
// do stuff
// Add to map as SUCCESS on successful execution
stageResults."{STAGE_NAME}" = "SUCCESS"
} catch (Exception e) {
// Set the result and add to map as UNSTABLE on failure
unstable("[ERROR]: ${STAGE_NAME} failed!")
currentBuild.result = "SUCCESS"
stageResult."{STAGE_NAME}" = "UNSTABLE"
}
if(stageResults.find{ it.key == "{STAGE_NAME}" }?.value == "UNSTABLE") {
// do something special if we're unstable
}
}
As for me the most elegant way to do it, it's a post section.
In your example, you mark the stage as UNSTABLE so after that you can catch it using post->unstable.
stage("1") {
steps {
catchError(buildResult: 'SUCCESS', stageResult: 'UNSTABLE') {
error 'Something goes wrong'
}
}
post {
always { echo 'Executed on every build'}
unstable { echo 'Executed only if build is unstable (marked by catchError)'}
}
}
As an alternative to add to Dibakar Aditya's answer, it is possible to wrap everything in a function which resembles regular steps. I.e.:
stage("1") {
def localSuccess = catchLocalError {
// do stuff
}
if(!localSuccess) {
// do something special if we're unstable
}
}
boolean catchLocalError(Closure c) {
try {
c()
return true
} catch (Exception e) {
return false
}
}

Jenkinsfile fail a step and continue the rest

I have a Jenkinsfile like this:
pipeline {
agent any
options {
buildDiscarder(logRotator(numToKeepStr: '7'))
disableConcurrentBuilds()
timeout(time: 10, unit: 'MINUTES')
timestamps()
}
stages {
stage('Admin') {
steps {
script {
try {
result = "FAIL"
} catch(error) {
result = "FAIL"
}
}
}
}
stage('Normal') {
steps {
script {
try {
sh("echo 'hi'")
} catch(error) {
}
}
}
}
}
}
How do I fail the first step and have it show red and have the pipeline continue for more steps?
I have looked at all the SO answers for this and can't make it work.
Setting result = "FAIL" does not cause the step to fail. How can I do this but continue the next step(s)?
You can mark a build status as failed by setting a new value for the global jenkins variable currentBuild. The variable has 3 states: SUCCESS (green), FAILURE (red) and UNSTABLE (yellow). The catch should prevent the build from exiting so you can set the status of the currentBuild and simply continue.
try {
//so something
} catch(error) {
//mark build as failed
currentBuild.result = 'FAILURE'
}
}
I think this should work. Sadly I don't have a way to validate this atm. If it does not work, please tell me.
As per my knowledge we can implement same as below.
Use script step and try-catch block
stage('someStage') {
steps {
script {
try {
//Do something
} catch (err) {
echo err
}
}
echo currentBuild.result = 'FAILURE'
}
}
You can use propagate: false which is available in build step. Somethng like below
stage("example") {
b = build(job: "example-job", propagate: false).result
if(b == 'FAILURE') {
echo "First job failed"
currentBuild.result = 'FAILURE' // of FAILURE
}
}
stage("test") {
build("test-job")
}
Links :
https://jenkins.io/doc/pipeline/steps/pipeline-build-step/
https://medium.com/#Lenkovits/jenkins-pipelines-and-their-dirty-secrets-1-9e535cd603f4
Do you want to show one stage "red" and all other (not failing) "green"?
As far as I know, that is not possible.
Do you want to know which stage is failing and which one runs correctly?
Well, it depends how you want it to be displayed
The easy way:
as suggested in a previous answer, you can use "echo" to print it in the console
The 'I want it full power' way:
capsulate a try-catch inside another try-catch and use the "error" signal:
stages {
try {
stage ('Normal') {
try {
/* Do your thing */
} catch (err) {
/* Mark this stage as "failure" using error
error 'Stage Normal failing'
}
} // stage 'Normal'
} catch (err) {
// here is the 'error' signal captured and omitted by now
}
Stage failing appears with the text "failed" shown in the box and in a darker red

Abort current build from pipeline in Jenkins

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.

Show a Jenkins pipeline stage as failed without failing the whole job

Here's the code I'm playing with
node {
stage 'build'
echo 'build'
stage 'tests'
echo 'tests'
stage 'end-to-end-tests'
def e2e = build job:'end-to-end-tests', propagate: false
result = e2e.result
if (result.equals("SUCCESS")) {
stage 'deploy'
build 'deploy'
} else {
?????? I want to just fail this stage
}
}
Is there any way for me to mark the 'end-to-end-tests' stage as failed without failing the whole job? Propagate false just always marks the stage as true, which is not what I want, but Propagate true marks the job as failed which I also don't want.
This is now possible, even with declarative pipelines:
pipeline {
agent any
stages {
stage('1') {
steps {
sh 'exit 0'
}
}
stage('2') {
steps {
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
sh "exit 1"
}
}
}
stage('3') {
steps {
sh 'exit 0'
}
}
}
}
In the example above, all stages will execute, the pipeline will be successful, but stage 2 will show as failed:
As you might have guessed, you can freely choose the buildResult and stageResult, in case you want it to be unstable or anything else. You can even fail the build and continue the execution of the pipeline.
Just make sure your Jenkins is up to date, since this is a fairly new feature.
Stage takes a block now, so wrap the stage in try-catch. Try-catch inside the stage makes it succeed.
The new feature mentioned earlier will be more powerful. In the meantime:
try {
stage('end-to-end-tests') {
node {
def e2e = build job:'end-to-end-tests', propagate: false
result = e2e.result
if (result.equals("SUCCESS")) {
} else {
sh "exit 1" // this fails the stage
}
}
}
} catch (e) {
result = "FAIL" // make sure other exceptions are recorded as failure too
}
stage('deploy') {
if (result.equals("SUCCESS")) {
build 'deploy'
} else {
echo "Cannot deploy without successful build" // it is important to have a deploy stage even here for the current visualization
}
}
Sounds like JENKINS-26522. Currently the best you can do is set an overall result:
if (result.equals("SUCCESS")) {
stage 'deploy'
build 'deploy'
} else {
currentBuild.result = e2e.result
// but continue
}
I recently tried to use vaza's answer
Show a Jenkins pipeline stage as failed without failing the whole job as template for writing a function that excutes a job in an own stage named like the job name. Surprisingly it worked, but maybe some groovy experts have a look at it :)
Here is how it looks like if one of the jobs is aborted:
def BuildJob(projectName) {
try {
stage(projectName) {
node {
def e2e = build job:projectName, propagate: false
result = e2e.result
if (result.equals("SUCCESS")) {
} else {
error 'FAIL' //sh "exit 1" // this fails the stage
}
}
}
} catch (e) {
currentBuild.result = 'UNSTABLE'
result = "FAIL" // make sure other exceptions are recorded as failure too
}
}
node {
BuildJob('job1')
BuildJob('job2')
}
In order to show a successful build with a failed stage when a downstream job fails AND support a user being able to cancel a build (including all subsequent stages), I had to use a combination of various solutions, specifically when, try/catch, throw and catchError().
env.GLOBAL_BUILD_ABORTED = false // Set if the user aborts the build
pipeline {
agent any
stages {
stage('First Stage') {
when { expression { env.GLOBAL_BUILD_ABORTED.toBoolean() == false } }
steps {
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
myLocalBuildMethod('Stage #1, build #1')
myLocalBuildMethod('Stage #1, build #2')
}
}
}
stage('Second Stage') {
when { expression { env.GLOBAL_BUILD_ABORTED.toBoolean() == false } }
steps {
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
myLocalBuildMethod('Stage #2, build #1')
myLocalBuildMethod('Stage #2, build #2')
myLocalBuildMethod('Stage #2, build #3')
}
}
}
}
}
def myLocalBuildMethod(myString) {
/* Dummy method to show User Aborts vs Build Failures */
echo "My Local Build Method: " + myString
try {
build (
job: "Dummy_Downstream_Job"
)
} catch (e) {
/* Build Aborted by user - Stop All Test Executions */
if (e.getMessage().contains("was cancelled") || e.getMessage().contains("ABORTED")) {
env.GLOBAL_BUILD_ABORTED = true
}
/* Throw the execiption to be caught by catchError() to mark the stage failed. */
throw (e)
}
// Do other stuff...
}
You could add a explicit fail task, such as 'sh "not exist command"' in the stage.
if (result.equals("SUCCESS")) {
stage 'deploy'
build 'deploy'
} else {
try {
sh "not exist command"
}catch(e) {
}
}
Solution steps
You must emit an error in a stage to mark it as an error
Outside the scope of the stage, handle the exception and choose the build status
This makes the effect desired by a couple of users here, including myself, #user3768904, #Sviatlana
Success with failed Step Example
node("node-name") {
try {
stage("Process") {
error("This will fail")
}
} catch(Exception error) {
currentBuild.result = 'SUCCESS'
return
}
stage("Skipped") {
// This stage will never run
}
}
Aborted with failure Step Example
node("node-name") {
try {
stage("Process") {
error("This will fail")
}
} catch(Exception error) {
currentBuild.result = 'ABORTED'
return
}
stage("Skipped") {
// This stage will never run
}
}
You can use the following code in your else statement:
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
error "some err msg"
}
This could be a general pattern showing how to customize the stage result with nice messages using the built-in functions and propagate the sub-job's result to the stage result. That the overall build is marked unstable if a sub-job is not successful is just a implementation choice for this example.
def run_sub_job() {
def jobBuild = build(job: 'foo', wait: true, propagate: false)
def result = jobBuild.getResult()
def msg = 'sub-job: ' + result
if ('SUCCESS' == result) {
println(msg)
} else if ('UNSTABLE' == result) {
unstable(msg) // will also set buildResult to UNSTABLE
} else { // anything else (FAILURE, ABORTED ...) is considered an error
catchError(
buildResult: 'UNSTABLE',
stageResult: result // propagate sub-job result
) {
error(msg)
}
}
}

Resources