Jenkins-pipeline Posting Failure Output to Slack - jenkins

I have a Python script that I'm running in my pipeline that is forced to fail. I'm trying to send the failure output to Slack.
testing.py
import sys
print("Hello World")
sys.exit(1)
pipeline
def slackChannel = "default-ops"
def slackDetails = "${env.JOB_NAME} - (<${env.BUILD_URL}|Open>)"
def shResult() {
script {
sh script: 'set -e; python3 testing.py', returnStdout: true
}
}
pipeline {
agent any
stages {
stage('Execute testing.py') {
steps {
script{
echo "${shResult()}".trim()
if (shResult != 0) {
currentBuild.result = 'FAILURE'
}
}
}
}
}
post {
success {
slackSend(color: '#00FF00',
channel: "${slackChannel}",
message: "SUCCESSFUL: " + slackDetails)
}
failure {
slackSend(color: '#FF0000',
channel: "${slackChannel}",
message: "FAILED: " + slackDetails + shResult())
}
aborted {
slackSend(color: '#808080',
channel: "${slackChannel}",
message: "ABORTED: " + slackDetails)
}
}
}
This results in the following error:
Error when executing failure post condition:
hudson.AbortException: script returned exit code 1
at org.jenkinsci.plugins.workflow.steps.durable_task.DurableTaskStep$Execution.handleExit(DurableTaskStep.java:659)
at org.jenkinsci.plugins.workflow.steps.durable_task.DurableTaskStep$Execution.check(DurableTaskStep.java:605)
at org.jenkinsci.plugins.workflow.steps.durable_task.DurableTaskStep$Execution.run(DurableTaskStep.java:549)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
How do I send the output "Hello World" to Slack?

First of all the line message: "FAILED: " + slackDetails + shResult()) executes the script again, which is probably not what you want.
The second thing is that returnStdout: true does not return the status code, but the stdout output. So in this case only "Hello World". Use returnStatus: true to get 0 or 1.
Sadly you can't return both stdout and the status. One way of doing it would be to only use returnStatus: true and pipe stdout to a file.
The pipeline would look something like this:
def slackChannel = "default-ops"
def slackDetails = "${env.JOB_NAME} - (<${env.BUILD_URL}|Open>)"
def shResult() {
script {
sh script: 'set -e; python3 testing.py > /tmp/somefile 2>&1', returnStatus: true
}
}
pipeline {
agent any
stages {
stage('Execute testing.py') {
steps {
script{
status = shResult()
if (status != 0) {
currentBuild.result = 'FAILURE'
errorMsg = readFile '/tmp/somefile'
}
}
}
}
}
post {
success {
slackSend(color: '#00FF00',
channel: "${slackChannel}",
message: "SUCCESSFUL: " + slackDetails)
}
failure {
slackSend(color: '#FF0000',
channel: "${slackChannel}",
message: "FAILED: " + slackDetails + errorMsg
}
aborted {
slackSend(color: '#808080',
channel: "${slackChannel}",
message: "ABORTED: " + slackDetails)
}
}
}
I can't test this pipeline right now but it should work.

Related

Jenkins file groovy issues

Hi My jenkins file code is as follows : I am basically trying to make call to a python script and execute it, I have defined some variables in my code : And when i am trying to run it, It gives no such property error in the beginning and I cant find out the reason behind it.
I would really appreciate any suggestions on this .
import groovy.json.*
pipeline {
agent {
label 'test'
}
parameters {
choice(choices: '''\
env1
env2'''
, description: 'Environment to deploy', name: 'vpc-stack')
choice(choices: '''\
node1
node2'''
, description: '(choose )', name: 'stack')
}
stages {
stage('Tooling') {
steps {
script {
//set up terraform
def tfHome = tool name: 'Terraform 0.12.24'
env.PATH = "${tfHome}:${env.PATH}"
env.TFHOME = "${tfHome}"
}
}
}
stage('Build all modules') {
steps {
wrap([$class: 'BuildUser']) {
// build all modules
script {
if (params.refresh) {
echo "Jenkins refresh!"
currentBuild.result = 'ABORTED'
error('Jenkinsfile refresh! Aborting any real runs!')
}
sh(script: """pwd""")
def status_code = sh(script: """PYTHONUNBUFFERED=1 python3 scripts/test/test_script.py /$vpc-stack""", returnStatus: true)
if (status_code == 0) {
currentBuild.result = 'SUCCESS'
}
if (status_code == 1) {
currentBuild.result = 'FAILURE'
}
}
}
}
}
}
post {
always {
echo 'cleaning workspace'
step([$class: 'WsCleanup'])
}
}
}
And this code is giving me the following error :
hudson.remoting.ProxyException: groovy.lang.MissingPropertyException: No such property: vpc for class
Any suggestions what can be done to resolve this.
Use another name for the choice variable without the dash sign -, e.g. vpc_stack or vpcstack and replace the variable name in python call.

Declarative jenkins pipeline do not abort on input

Simple question I got input in declarative pipeline in jenkins. When I click abort on prompt I do not want it to mark build as aborted. To prevent answers that stack already have, I am looking for solution in declarative pipeline, without escaping to scripting.
options {
timeout(time: 1, unit: 'HOURS')
}
steps {
input 'Deploy to UAT?'
deploy()
}
post {
aborted {
script {
//Throws exception(not allowed to use rawBuild)
currentBuild.rawBuild.#result = hudson.model.Result.SUCCESS
//Do not change status because it can only be worse
currentBuild.result = 'SUCCESS'
//Do not change status because it can only be worse
currentBuild.currentResult = 'SUCCESS'
}
}
}
I don't think it's possible to not abort pipeline with the simple input field since that the purpose of it.
What You could do is to use checkbox in the input like
def deployToUat
steps {
script {
deployToUat = input(
id: 'Proceed', message: 'Deploy to UAT?', parameters: [
[$class: 'BooleanParameterDefinition', defaultValue: true, description: '', name: 'Proceed with deployment?']
])
}
}
stage('UAT deployment') {
when {
expression { deployToUat == true }
}
steps {
deploy()
}
}
Well, you could do
script {
try {
input 'Deploy to UAT?'
} catch(err) {
currentBuild.result = 'SUCCESS'
return
}
}
I guess the above is the only correct way since the result can only worsen.
public void setResult(#Nonnull Result r) {
if (state != State.BUILDING) {
throw new IllegalStateException("cannot change build result while in " + state);
}
// result can only get worse
if (result==null || r.isWorseThan(result)) {
result = r;
LOGGER.log(FINE, this + " in " + getRootDir() + ": result is set to " + r, LOGGER.isLoggable(Level.FINER) ? new Exception() : null);
}
}

How to include pipeline errors in email (Email-ext plugin)

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}
}
}

Why am I not seeing terminal output in the Build Console for this Jenkinsfile?

I'm a little confused as to why I am not seeing the output from the echo() statements in the jenkinsSlack() function in this Jenkinsfile. The stages are definitely running as I can see them executing in the Pipeline visualization.
#!groovy
def slack_channel() {
try { if ('' != SLACK_CHANNEL) { return SLACK_CHANNEL } }
catch (MissingPropertyException e) { return '#nathan-webhooks' }
}
// simplify the generation of Slack notifications for start and finish of Job
def jenkinsSlack(type, channel=slack_channel()) {
echo("echo 'In jenkinsSlack()...")
echo("echo 'type specified as : ${type}'")
echo("echo 'channel specified as : ${channel}'")
if ( 'SUCCESS' == currentBuild.result ) {
slackSend channel: channel, color: 'good', message: "type: ${type}"
} else if ( 'FAILURE' == currentBuild.result ) {
slackSend channel: channel, color: 'danger', message:"type: ${type}"
} else {
slackSend channel: channel, color: 'warning', message: "type: ${type}"
}
// node - action starts here
node {
wrap([$class: 'AnsiColorBuildWrapper', 'colorMapName': 'XTerm', 'defaultFg': 2, 'defaultBg':1]) {
stage ("send Slack start message") {
checkout scm
jenkinsSlack('start')
}
stage("send Slack finish message") {
jenkinsSlack('finish')
}
} // AnsiColorBuildWrapper
}
Thx
Echo messages are missing because jenkinsSlack(type, channel=slack_channel()) just returns the value of slack_channel() without executing the method body including echo.
This is Jenkins specific problem related to CPS transform. Jenkins pipeline script is based on groovy but it has some constraints with regard to its syntax and usage. See more details here: https://github.com/jenkinsci/workflow-cps-plugin/blob/master/README.md#technical-design
Possible workarounds below.
1.Use #NonCPS annotation for slack_channel()
#NonCPS
def slack_channel() {
try { if ('' != SLACK_CHANNEL) { return SLACK_CHANNEL } }
catch (MissingPropertyException e) { return '#nathan-webhooks' }
}
2.determine SLACK_CHANNEL in advance and pass it to the default argument channel:
def slack_channel() {
try { if ('' != SLACK_CHANNEL) { return SLACK_CHANNEL } }
catch (MissingPropertyException e) { return '#nathan-webhooks' }
}
SLACK_CHANNEL = slack_channel()
def jenkinsSlack(type, channel=SLACK_CHANNEL) {
echo type
echo channel
}

Access Stage name during the build in Jenkins pipeline

Let's say we have the following Jenkinsfile:
stage name: "Cool stage"
sh 'whoami'
stage name: "Better stage"
def current_stage = getCurrentStageName()
echo "CONGRATULATIONS, you are on stage: $current_stage"
The question is how to implement getCurrentStageName(). I know, that I can get an access to build run-time using currentBuild.rawBuild.
But how to get stage name from that point?
I need this for some customization in email notifications, so that I can always catch failed stage name and include it into email body.
You can now do this in a built-in manner, since Jenkins 2.3. Like so:
steps {
updateGitlabCommitStatus name: STAGE_NAME, state: 'running'
echo '${STAGE_NAME}'
}
For more information see: https://issues.jenkins-ci.org/browse/JENKINS-44456
This should work from a pipeline shared library:
#!/usr/bin/env groovy
import hudson.model.Action;
import org.jenkinsci.plugins.workflow.graph.FlowNode
import org.jenkinsci.plugins.workflow.cps.nodes.StepStartNode
import org.jenkinsci.plugins.workflow.actions.LabelAction
def getStage(currentBuild){
def build = currentBuild.getRawBuild()
def execution = build.getExecution()
def executionHeads = execution.getCurrentHeads()
def stepStartNode = getStepStartNode(executionHeads)
if(stepStartNode){
return stepStartNode.getDisplayName()
}
}
def getStepStartNode(List<FlowNode> flowNodes){
def currentFlowNode = null
def labelAction = null
for (FlowNode flowNode: flowNodes){
currentFlowNode = flowNode
labelAction = false
if (flowNode instanceof StepStartNode){
labelAction = hasLabelAction(flowNode)
}
if (labelAction){
return flowNode
}
}
if (currentFlowNode == null) {
return null
}
return getStepStartNode(currentFlowNode.getParents())
}
def hasLabelAction(FlowNode flowNode){
def actions = flowNode.getActions()
for (Action action: actions){
if (action instanceof LabelAction) {
return true
}
}
return false
}
def call() {
return getStage(currentBuild)
}
Example usage:
node {
stage('Stage One'){
echo getCurrentStage()
}
stage('Stage Two'){
echo getCurrentStage()
}
}
Aleks' workaround works fine, just thought it's worth sharing the code
node ("docker") {
def sendOk = {
String stage -> slackSend color: 'good', message: stage + " completed, project - ${env.JOB_NAME}:1.0.${env.BUILD_NUMBER}"
}
def sendProblem = {
String stage, error -> slackSend color: 'danger', message: stage + " did not succeed, project - ${env.JOB_NAME}:1.0.${env.BUILD_NUMBER}, error: ${error}, Find details here: ${env.BUILD_URL}"
}
def exec = {
work, stageName ->
stage (stageName) {
try {
work.call();
sendOk(stageName)
}
catch(error) {
sendProblem(stageName, error)
throw error
}
}
}
exec({
git credentialsId: 'github-root', url: 'https://github.com/abc'
dir ('src') {
git credentialsId: 'github-root', url: 'https://github.com/abc-jenkins'
}
sh "chmod +x *.sh"
}, "pull")
exec({ sh "./Jenkinsfile-clean.sh \"1.0.${env.BUILD_NUMBER}\"" }, "clean")
exec({ sh "./Jenkinsfile-unit.sh \"1.0.${env.BUILD_NUMBER}\"" }, "unit")
exec({ sh "./Jenkinsfile-build.sh \"1.0.${env.BUILD_NUMBER}\"" }, "build")
exec({ sh "./Jenkinsfile-dockerize.sh \"1.0.${env.BUILD_NUMBER}\"" }, "dockerize")
exec({ sh "./Jenkinsfile-push.sh \"1.0.${env.BUILD_NUMBER}\"" }, "push")
exec({ sh "./Jenkinsfile-prod-like.sh \"1.0.${env.BUILD_NUMBER}\"" }, "swarm")
}
As a workaround, in the failure email I include a link to the Pipeline Steps page. This page clearly shows green and red balls for each step, making it easy for the email recipient to figure out not just the stage, but the step that failed.
In the following example email body, the FlowGraphTable link links to Pipeline Steps:
def details = """<p>Job '${env.JOB_NAME}', build ${env.BUILD_NUMBER} result was ${buildStatus}.
Please scrutinize the build and take corrective action.</p>
<p>Quick links to the details:
<ul>
<li>${env.JOB_NAME} job main page</li>
<li>Build ${env.BUILD_NUMBER} main page</li>
<ul>
<li>Console output</li>
<li>Git changes</li>
<li>Pipeline steps.
This page will show you which step failed, and give you access
to the job workspace.</li>
</ul>
</ul></p>"""
This is an excerpt from my implementation of notifyBuild() that BitwiseMan of CloudBees presents in his article, Sending Notifications in Pipeline.

Resources