Jenkins Pipeline Groovy Script: Use `mail` in Jenkinsfile - jenkins

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: '...'

Related

Jenkins Pipeline failure reason is at the end of console instead at the step where it occurred

I'm creating a multi-branch pipeline with a Jenkinsfile and if one step fails I'd like to show the reason in the email that is sent in the post step.
I can fetch the log just fine but my problem is that the failure reason is printed at the end of the console log instead of at the point where it occurred.
If I create a freestyle project then the error messages will always be in the console log where they occurred and the email works as desired.
My Jenkinsfile looks something like this:
pipeline {
agent any
parameters {
string(name: 'VERSION', defaultValue: '', description: 'version number to build')
}
stages {
stage("Check parameters") {
steps {
script {
if (VERSION == null || VERSION == '') {
error("Build failed because required parameter VERSION not set")
}
}
}
}
stage("Do something") {
...
}
stage("Rename successful build") {
steps {
script {
currentBuild.displayName = "v${VERSION}"
currentBuild.description = "Successful build for v${VERSION}"
}
}
}
}
post {
failure {
withFolderProperties {
echo "Recipients: ${env.MAIL_RECIPIENTS}"
script {
if (env.MAIL_RECIPIENTS) {
emailext (
to: env.MAIL_RECIPIENTS,
replyTo: '$DEFAULT_REPLYTO',
subject: '$DEFAULT_SUBJECT',
body: '$DEFAULT_CONTENT',
mimeType: 'text/html'
);
}
}
}
}
}
}
This will generate the following console log if I start the build without a version parameter.
This is just an example and the same persists if another stage fails at some point (I cleaned some of the [Pipeline] lines):
[Pipeline] }
[Pipeline] // stage
[Pipeline] withEnv
[Pipeline] {
[Pipeline] stage
[Pipeline] { (Check parameters)
[Pipeline] script
[Pipeline] {
[Pipeline] error
[Pipeline] }
[Pipeline] // script
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (build)
Stage "Do Something" skipped due to earlier failure(s)
...
Stage "Rename successful build" skipped due to earlier failure(s)
...
Sending email to: xxx
...
ERROR: Build failed because required parameter VERSION not set
Finished: FAILURE
The last two lines are missing in the email because it is sent before they are printed; however, why is the ERROR printed at the end of the log instead of at the step where it occurred? Is there a way where i can configure this?
I'm expecting that the error is printed before the next step is shown in the log.
Your expectation can be easily achieved with Jenkins scripted pipeline but a little difficult with Jenkins declarative pipeline.
but In the Declarative pipeline, If you are raising an Error based on some condition you know what could be the error at the end of the build, so while you raising the error you could grab the error string in a variable and make use of it in the failure stage.
In Scripted Pipeline, it would be something like this
def isItFail = false
def failureReason = ""
node {
try {
stage('check parameter') {
if (VERSION == null || VERSION == '') {
throw new RuntimeException("Version number should not be empty")
}
}
stage('some other stage') {
.....
}
}catch(Exception e ) {
isItFail = true
failureReason = e.getMessage()
}finally {
if(isItFail) {
echo "Send An Email with this Text with Reason: ${failureReason}"
}
}
}
In Declarative Pipeline, it would be
def failReason = ""
pipeline {
agent any;
stages {
stage('check version') {
steps {
script {
script {
if (VERSION == null || VERSION == '') {
failReason = "Build failed because required parameter VERSION not set"
error(failReason)
}
}
}
}
}
stage('other stage') {
....
}
}
post {
failure {
echo "Send An Email with Reason : ${failReason}"
}
}
}
In the declarative pipeline, there is no any catch{} DSL and you can't use try..catch..finally outside of script{} DSL, hence it little hard to bring the failure reason text to the end of the pipeline

Extract error description from Jenkin console output

Is there any way to extract only error description from a Jenkins Console output to send the same via Email notification??
Thanks in advance!!
If you are using a scripted pipeline, you can use a try catch block to assign the error message to a variable. Then pass this to an email plugin as required (see body field in mail step)
For example:
try {
sh 'might fail'
} catch (err) {
def errorMessage = err
}
step([$class: 'mail', body: "Failed due to: ${errorMessage}", to: 'admin#somewhere' ])
Otherwise, if you are using a declarative pipeline, you can still use the scripted syntax by wrapping it all in a script block like this:
script {
try {
sh 'might fail'
} catch (err) {
def errorMessage = err
}
step([$class: 'mail', body: "Failed due to: ${errorMessage}", to: 'admin#somewhere'
])
}

How to add email notification in jenkins if the build is failed

I need to add email notification in jenkins for both freestyle and pipeline job if the build is failed
reg. Email-ext plugin
In pipeline job you can use post build actions / try catch with proper step - ref. to mail
pipeline {
agent any
stages {
stage('Test') {
steps {
sh 'echo "Fail!"; exit 1'
}
}
}
post {
always {
echo 'This will always run'
}
success {
echo 'This will run only if successful'
}
failure {
echo 'This will run only if failed'
}
unstable {
echo 'This will run only if the run was marked as unstable'
}
changed {
echo 'This will run only if the state of the Pipeline has changed'
echo 'For example, if the Pipeline was previously failing but is now successful'
}
}
}
or try-catch (scripted way)
try{
//code to handle
} catch (e) {
emailext (
from: 'sender#domain.com',
to: 'recepient#domain.com',
subject: "job failed- ${env.JOB_NAME}, Build #${env.BUILD_NUMBER}, FAILED",
attachLog: true,
body: """
Foooooo text
For current build refer to: ${env.BUILD_URL}
job: ${env.JOB_NAME}
build number: #${env.BUILD_NUMBER}
With ERROR:
${e.message}
For full log refer to
${env.BUILD_URL}
"""
)
throw e
}
Post Build Actions > Email Notification
Part of the Mailer plugin.

Set the build name and description from a Jenkins Declarative Pipeline

I would like to set the build name and description from a Jenkins Declarative Pipeline, but can't find the proper way of doing it. I tried using an environment bracket after the pipeline, using a node bracket in an agent bracket, etc. I always get syntax error.
The last version of my Jenkinsfile goes like so:
pipeline {
stages {
stage("Build") {
steps {
echo "Building application..."
bat "%ANT_HOME%/bin/ant.bat clean compile"
currentBuild.name = "MY_VERSION_NUMBER"
currentBuild.description = "MY_PROJECT MY_VERSION_NUMBER"
}
}
stage("Unit Tests") {
steps {
echo "Testing (JUnit)..."
echo "Testing (pitest)..."
bat "%ANT_HOME%/bin/ant.bat run-unit-tests"
}
}
stage("Functional Test") {
steps {
echo "Selenium..."
}
}
stage("Performance Test") {
steps {
echo "JMeter.."
}
}
stage("Quality Analysis") {
steps {
echo "Running SonarQube..."
bat "%ANT_HOME%/bin/ant.bat run-sonarqube-analysis"
}
}
stage("Security Assessment") {
steps {
echo "ZAP..."
}
}
stage("Approval") {
steps {
echo "Approval by a CS03"
}
}
stage("Deploy") {
steps {
echo "Deploying..."
}
}
}
post {
always {
junit '/test/reports/*.xml'
}
failure {
emailext attachLog: true, body: '', compressLog: true, recipientProviders: [[$class: 'CulpritsRecipientProvider'], [$class: 'DevelopersRecipientProvider']], subject: '[JENKINS] MY_PROJECT build failed', to: '...recipients...'
}
success {
emailext attachLog: false, body: '', compressLog: false, recipientProviders: [[$class: 'DevelopersRecipientProvider']], subject: '[JENKINS] MY_PROJECT build succeeded', to: '...recipients...'
}
}
}
Error is:
org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
WorkflowScript: 11: Expected a step # line 11, column 5.
currentBuild.name = "MY_VERSION_NUMBER"
^
WorkflowScript: 12: Expected a step # line 12, column 5.
currentBuild.description = "MY_PROJECT MY_VERSION_NUMBER"
^
Ideally, I'd like to be able to read MY_PROJECT and MY_VERSION_NUMBER from the build.properties file, or from the Jenkins build log. Any guidance about that requirement would be appreciated as well.
UPDATE
Based on the answer I had below, the following worked:
stage("Build") {
steps {
echo "Building application..."
bat "%ANT_HOME%/bin/ant.bat clean compile"
script {
def props = readProperties file: 'build.properties'
currentBuild.displayName = "v" + props['application.version']
}
}
Now the build version is automatically set during the pipeline by reading the build.properties file.
I think this will do what you want. I was able to do it inside a script block:
pipeline {
stages {
stage("Build"){
steps {
script {
currentBuild.displayName = "The name."
currentBuild.description = "The best description."
}
... do whatever.
}
}
}
}
The script is kind of an escape hatch to get out of a declarative pipeline. There is probably a declarative way to do it but i couldn't find it. And one more note. I think you want currentBuild.displayName instead of currentBuild.name In the documentation for Jenkins globals I didn't see a name property under currentBuild.
If you want to set build name to a job from a parameter, you can use
currentBuild.displayName = "${nameOfYourParameter}".
Make sure you use double quotes instead of single quotes.
Job Configuration
Build job with parameter
Build History
REFERENCE: How to set build name in Pipeline job?

Use Jenkins 'Mailer' inside pipeline workflow

I'd like to leverage the existing Mailer plugin from Jenkins within a Jenkinsfile that defines a pipeline build job. Given the following simple failure script I would expect an email on every build.
stage 'Test'
node {
try {
sh 'exit 1'
} finally {
step([$class: 'Mailer', notifyEveryUnstableBuild: true, recipients: 'me#me.com', sendToIndividuals: true])
}
}
The output from the build is:
Started by user xxxxx
[Pipeline] stage (Test)
Entering stage Test
Proceeding
[Pipeline] node
Running on master in /var/lib/jenkins/jobs/rpk-test/workspace
[Pipeline] {
[Pipeline] sh
[workspace] Running shell script
+ exit 1
[Pipeline] step
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
ERROR: script returned exit code 1
Finished: FAILURE
As you can see, it does record that it performs the pipeline step immediately after the failure, but no emails get generated.
Emails in other free-style jobs that leverage the mailer work fine, its just invoking via pipeline jobs.
This is running with Jenkins 2.2 and mailer 1.17.
Is there a different mechanism by which I should be invoking failed build emails? I don't need all the overhead of the mail step, just need notifications on failures and recoveries.
In Pipeline failed sh doesn't immediately set the currentBuild.result to FAILURE whereas its initial value is null. Hence, build steps that rely on the build status like Mailer might work seemingly incorrect.
You can check it by adding a debug print:
stage 'Test'
node {
try {
sh 'exit 1'
} finally {
println currentBuild.result // this prints null
step([$class: 'Mailer', notifyEveryUnstableBuild: true, recipients: 'me#me.com', sendToIndividuals: true])
}
}
This whole pipeline is wrapped with exception handler provided by Jenkins that's why Jenkins marks the build as failed in the the end.
So if you want to utilize Mailer you need to maintain the build status properly. For instance:
stage 'Test'
node {
try {
sh 'exit 1'
currentBuild.result = 'SUCCESS'
} catch (any) {
currentBuild.result = 'FAILURE'
throw any //rethrow exception to prevent the build from proceeding
} finally {
step([$class: 'Mailer', notifyEveryUnstableBuild: true, recipients: 'me#me.com', sendToIndividuals: true])
}
}
If you don't need to re-throw the exception, you can use catchError. It is a Pipeline built-in which catches any exception within its scope, prints it into console and sets the build status. For instance:
stage 'Test'
node {
catchError {
sh 'exit 1'
}
step([$class: 'Mailer', notifyEveryUnstableBuild: true, recipients: 'me#me.com', sendToIndividuals: true])
}
In addition to izzekil's excellent answer, you may wish to choose email recipients based on the commit authors. You can use email-ext to do this (based on their pipeline examples):
step([$class: 'Mailer',
notifyEveryUnstableBuild: true,
recipients: emailextrecipients([[$class: 'CulpritsRecipientProvider'],
[$class: 'RequesterRecipientProvider']])])
If you're using a recent email-ext (2.50+), you can use that in your pipeline:
emailext(body: '${DEFAULT_CONTENT}', mimeType: 'text/html',
replyTo: '$DEFAULT_REPLYTO', subject: '${DEFAULT_SUBJECT}',
to: emailextrecipients([[$class: 'CulpritsRecipientProvider'],
[$class: 'RequesterRecipientProvider']]))
If you're not using a declarative Jenkinsfile, you will need to put checkout scm so Jenkins can find the committers. See JENKINS-46431.
If you're still on an older version of email-ext, you'll hit JENKINS-25267. You could roll your own HTML email:
def emailNotification() {
def to = emailextrecipients([[$class: 'CulpritsRecipientProvider'],
[$class: 'DevelopersRecipientProvider'],
[$class: 'RequesterRecipientProvider']])
String currentResult = currentBuild.result
String previousResult = currentBuild.getPreviousBuild().result
def causes = currentBuild.rawBuild.getCauses()
// E.g. 'started by user', 'triggered by scm change'
def cause = null
if (!causes.isEmpty()) {
cause = causes[0].getShortDescription()
}
// Ensure we don't keep a list of causes, or we get
// "java.io.NotSerializableException: hudson.model.Cause$UserIdCause"
// see http://stackoverflow.com/a/37897833/509706
causes = null
String subject = "$env.JOB_NAME $env.BUILD_NUMBER: $currentResult"
String body = """
<p>Build $env.BUILD_NUMBER ran on $env.NODE_NAME and terminated with $currentResult.
</p>
<p>Build trigger: $cause</p>
<p>See: $env.BUILD_URL</p>
"""
String log = currentBuild.rawBuild.getLog(40).join('\n')
if (currentBuild != 'SUCCESS') {
body = body + """
<h2>Last lines of output</h2>
<pre>$log</pre>
"""
}
if (to != null && !to.isEmpty()) {
// Email on any failures, and on first success.
if (currentResult != 'SUCCESS' || currentResult != previousResult) {
mail to: to, subject: subject, body: body, mimeType: "text/html"
}
echo 'Sent email notification'
}
}
I think a better way to send mail notifications in jenkins pipelines is to use the post section of a pipeline as described in the jenkins docs instead of using try catch:
pipeline {
agent any
stages {
stage('whatever') {
steps {
...
}
}
}
post {
always {
step([$class: 'Mailer',
notifyEveryUnstableBuild: true,
recipients: "example#example.com",
sendToIndividuals: true])
}
}
}
}
}

Resources