I am trying to send a Slack message when the build succeeds/fails through a scripted Jenkins pipeline, but I have noticed that currentBuild.result is null on master branch for some reason. Is there a better way to do this?
try {
//pipline code
} finally {
branch_name = env.BRANCH_NAME
if (branch_name == 'master') {
message_body = """"""
if (currentBuild.result == 'SUCCESS') {
message_body = """:green_check: Build succeeded for `$branch_name`. See $env.BUILD_URL for details"""
}
if (currentBuild.result == 'FAILURE') {
message_body = """:red_x: Build failed for `$branch_name`. See $env.BUILD_URL for details"""
}
if (message_body != '') {
slackSend(channel: "#channel", message: message_body.stripIndent().trim())
}
}
}
Are you using declarative or scripted pipelines?
There are some differences between currentBuild.resultand currentBuild.currentResult. See here
The best way is to use a post condition.
Related
stage('Checkout')
{
steps
{
checkout scm
script
{
if (params.myParam == '') { // and/or whatever condition you want
currentBuild.result = 'ABORTED'
error('myParam not set')
}
}
}
}
Above will Abort the build if parameter is null, working as expected.But when i give the value on parameter the job is still failing, not taking the value or passing the build.
The == '' (equality operator) also needs to be declared that it represents 'null' to satisfy the boolean value.
As this is not declared, any value in that parameter will meet the condition to error/false, see the slight change to your stage below:
stage('Checkout') {
steps {
checkout scm
script {
if(params.myParam == '' || params.myParam == null) {
currentBuild.result = 'ABORTED'
error('myParam not set')
} else {
//whatever condition you want
}
}
}
}
I am sure I am not the only one who is interested in how to handle something like this: docker build stage in Jenkins pipeline fails with Unexpected EOF (there can be a lot of reasons, in my case the docker daemon was restarted on the slave)
appImage = docker.build ("${projectName}:${env.BRANCH_NAME}-${gitCommit}", "--build-arg APP_ENV=${appEnv} --build-arg SKIP_LINT=true .")
The deploy phase kicks in, because the Unexpected EOF does not actually throw any error, there is no exception to catch so the build status is null.
I know that it's not a regular situation but still how can we handle smth like this so that the following stages do not run in case the build is interrupted.
Additional details:
#JRichardsz , thanks for the answer! Usually currentBuild.result . defaults to null e.g. https://issues.jenkins-ci.org/browse/JENKINS-46325 so unless you set it to success explicitly upon successful stage's execution , it will be null. But all in all the same can be achieved with try catch like :
if (deployableBranches.contains(env.BRANCH_NAME)) {
try {
stage('Build image') {
ansiColor('xterm') {
appImage = docker.build
("${projectName}:${env.BRANCH_NAME}-${gitCommit}", "--build-arg
SKIP_LINT=true .")
}
}
stage('Push image') {
docker.withRegistry("${registryUrl}", "${dockerCredsId}") {
appImage.push()
appImage.push "${env.BRANCH_NAME}-latest"
}
}
stage('Deploy') {
build job: 'kubernetes-deploy', parameters: [
/////
]
}
} catch (e) {
// A shell step returns with a nonzero exit code
// When pipeline is in a shell step, and a user presses abort
if (e.getMessage().contains('script returned exit code 143')) {
currentBuild.result = "ABORTED"
} else {
currentBuild.result = "FAILED"
}
throw e
} finally {
// Success or failure or abort, always send notifications
stage('Send deployment status') {
helpers.sendDeploymentStatus(projectName, currentBuild.result,
helpers.getCommitHashShort())
}
}
}
But the issue is that stage('Build image') may exit without any error code like it was in my case.
I had a similar requirement : "If some rule is executed in stage A, following stages must not run"
This worked for me :
def flag;
node {
stage('A') {
flag = 1;
}
stage('B') {
// exit this stage if flag == 1
if(flag == 1){
return;
}
//start of stage B tasks
...
}
}
Also you could use some jenkins variable like currentBuild.result instead of flag like this :
node {
stage('A') {
//stage A tasks
//this stage could modify currentBuild.result variable
}
stage('B') {
// exit this stage if currentBuild.result is null , empty, "FAILURE", etc
if(currentBuild.result == null ||
currentBuild.result == "" ||
currentBuild.result=="FAILURE" ){
return;
}
//stage B tasks
}
}
As far as declarative pipelines go in Jenkins, I'm having trouble with the when keyword.
I keep getting the error No such DSL method 'when' found among steps. I'm sort of new to Jenkins 2 declarative pipelines and don't think I am mixing up scripted pipelines with declarative ones.
The goal of this pipeline is to run mvn deploy after a successful Sonar run and send out mail notifications of a failure or success. I only want the artifacts to be deployed when on master or a release branch.
The part I'm having difficulties with is in the post section. The Notifications stage is working great. Note that I got this to work without the when clause, but really need it or an equivalent.
pipeline {
agent any
tools {
maven 'M3'
jdk 'JDK8'
}
stages {
stage('Notifications') {
steps {
sh 'mkdir tmpPom'
sh 'mv pom.xml tmpPom/pom.xml'
checkout([$class: 'GitSCM', branches: [[name: 'origin/master']], doGenerateSubmoduleConfigurations: false, submoduleCfg: [], userRemoteConfigs: [[url: 'https://repository.git']]])
sh 'mvn clean test'
sh 'rm pom.xml'
sh 'mv tmpPom/pom.xml ../pom.xml'
}
}
}
post {
success {
script {
currentBuild.result = 'SUCCESS'
}
when {
branch 'master|release/*'
}
steps {
sh 'mvn deploy'
}
sendNotification(recipients,
null,
'https://link.to.sonar',
currentBuild.result,
)
}
failure {
script {
currentBuild.result = 'FAILURE'
}
sendNotification(recipients,
null,
'https://link.to.sonar',
currentBuild.result
)
}
}
}
In the documentation of declarative pipelines, it's mentioned that you can't use when in the post block. when is allowed only inside a stage directive.
So what you can do is test the conditions using an if in a script:
post {
success {
script {
if (env.BRANCH_NAME == 'master')
currentBuild.result = 'SUCCESS'
}
}
// failure block
}
Using a GitHub Repository and the Pipeline plugin I have something along these lines:
pipeline {
agent any
stages {
stage('Build') {
steps {
sh '''
make
'''
}
}
}
post {
always {
sh '''
make clean
'''
}
success {
script {
if (env.BRANCH_NAME == 'master') {
emailext (
to: 'engineers#green-planet.com',
subject: "${env.JOB_NAME} #${env.BUILD_NUMBER} master is fine",
body: "The master build is happy.\n\nConsole: ${env.BUILD_URL}.\n\n",
attachLog: true,
)
} else if (env.BRANCH_NAME.startsWith('PR')) {
// also send email to tell people their PR status
} else {
// this is some other branch
}
}
}
}
}
And that way, notifications can be sent based on the type of branch being built. See the pipeline model definition and also the global variable reference available on your server at http://your-jenkins-ip:8080/pipeline-syntax/globals#env for details.
Ran into the same issue with post. Worked around it by annotating the variable with #groovy.transform.Field. This was based on info I found in the Jenkins docs for defining global variables.
e.g.
#!groovy
pipeline {
agent none
stages {
stage("Validate") {
parallel {
stage("Ubuntu") {
agent {
label "TEST_MACHINE"
}
steps {{
sh "run tests command"
recordFailures('Ubuntu', 'test-results.xml')
junit 'test-results.xml'
}
}
}
}
}
post {
unsuccessful {
notify()
}
}
}
// Make testFailures global so it can be accessed from a 'post' step
#groovy.transform.Field
def testFailures = [:]
def recordFailures(key, resultsFile) {
def failures = ... parse test-results.xml script for failures ...
if (failures) {
testFailures[key] = failures
}
}
def notify() {
if (testFailures) {
... do something here ...
}
}
I have a pipeline groovy script in Jenkins v2.19. Also I have a
"Slack Notification Plugin" v2.0.1 and "Groovy Postbuild Plugin" installed.
I can successfully send "build started" and "build finished" messages.
When a build fails, how can I send the "Build failed" message to a Slack channel?
You could do something like this and use a try catch block.
Here is some example Code:
node {
try {
notifyBuild('STARTED')
stage('Prepare code') {
echo 'do checkout stuff'
}
stage('Testing') {
echo 'Testing'
echo 'Testing - publish coverage results'
}
stage('Staging') {
echo 'Deploy Stage'
}
stage('Deploy') {
echo 'Deploy - Backend'
echo 'Deploy - Frontend'
}
} catch (e) {
// If there was an exception thrown, the build failed
currentBuild.result = "FAILED"
throw e
} finally {
// Success or failure, always send notifications
notifyBuild(currentBuild.result)
}
}
def notifyBuild(String buildStatus = 'STARTED') {
// build status of null means successful
buildStatus = buildStatus ?: 'SUCCESSFUL'
// Default values
def colorName = 'RED'
def colorCode = '#FF0000'
def subject = "${buildStatus}: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]'"
def summary = "${subject} (${env.BUILD_URL})"
// Override default values based on build status
if (buildStatus == 'STARTED') {
color = 'YELLOW'
colorCode = '#FFFF00'
} else if (buildStatus == 'SUCCESSFUL') {
color = 'GREEN'
colorCode = '#00FF00'
} else {
color = 'RED'
colorCode = '#FF0000'
}
// Send notifications
slackSend (color: colorCode, message: summary)
}
Complete snippet can be found here Jenkinsfile Template
Just in case if in Declarative Syntax,
Now, Jenkins provides post. You can check result at the end of pipeline.
https://jenkins.io/doc/book/pipeline/syntax/#post-example
Using like:
pipeline {
stages { ... }
post {
// only triggered when blue or green sign
success {
slackSend ...
}
// triggered when red sign
failure {
slackSend ...
}
// trigger every-works
always {
slackSend ...
}
}
}
It would be used in every stage also. See the document link please.
Based on Liam Newman's blog post, have a look at this cleaned up snippet for Slack only in scripted pipelines (declarative pipeline users scroll down). It uses original Jenkins results, message formatting, better colors (based on EclEmma), and some Groovy features like default arguments:
def notifySlack(String buildStatus = 'STARTED') {
// Build status of null means success.
buildStatus = buildStatus ?: 'SUCCESS'
def color
if (buildStatus == 'STARTED') {
color = '#D4DADF'
} else if (buildStatus == 'SUCCESS') {
color = '#BDFFC3'
} else if (buildStatus == 'UNSTABLE') {
color = '#FFFE89'
} else {
color = '#FF9FA1'
}
def msg = "${buildStatus}: `${env.JOB_NAME}` #${env.BUILD_NUMBER}:\n${env.BUILD_URL}"
slackSend(color: color, message: msg)
}
node {
try {
notifySlack()
// Existing build steps.
} catch (e) {
currentBuild.result = 'FAILURE'
throw e
} finally {
notifySlack(currentBuild.result)
}
}
The output will be something like this (play around with different formatting styles here):
Maybe env.JOB_NAME contains encoded slashes (%2F) which can be fixed with replaceAll("%2F", "/"). Check out this Gist to see how to notify HipChat as well.
If you have a declarative pipeline, have a look at the Jenkins documentation on "Cleaning up and notifications" or Liam Newman's follow-up post "Declarative Pipeline: Notifications and Shared Libraries".
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)
}
}
}