1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2024-12-14 11:03:09 +02:00
sap-jenkins-library/vars/handlePipelineStepErrors.groovy
Oliver Nocon 0c3e5f1ea9
use new unstable step to better visualize pipeline errors (#804)
With https://jenkins.io/blog/2019/07/05/jenkins-pipeline-stage-result-visualization-improvements/ it has been made possible to allow for a better visualization in case certain pipeline stages are 'UNSTABLE'

This is about using the new feature if available with a fall-back to old behavior.
2019-07-26 17:40:22 +02:00

142 lines
6.0 KiB
Groovy

import com.cloudbees.groovy.cps.NonCPS
import com.sap.piper.GenerateDocumentation
import com.sap.piper.ConfigurationHelper
import com.sap.piper.analytics.InfluxData
import groovy.text.GStringTemplateEngine
import groovy.transform.Field
import hudson.AbortException
import org.jenkinsci.plugins.workflow.steps.FlowInterruptedException
@Field STEP_NAME = getClass().getName()
@Field Set GENERAL_CONFIG_KEYS = []
@Field Set STEP_CONFIG_KEYS = GENERAL_CONFIG_KEYS.plus([
/**
* Defines the behavior, in case an error occurs which is handled by this step. When set to `false` an error results in an "UNSTABLE" build result and the pipeline can continue.
* @possibleValues `true`, `false`
*/
'failOnError',
/** Defines the url of the library's documentation that will be used to generate the corresponding links to the step documentation.*/
'libraryDocumentationUrl',
/** Defines the url of the library's repository that will be used to generate the corresponding links to the step implementation.*/
'libraryRepositoryUrl',
/** Defines a list of mandatory steps (step names) which have to be successful (=stop the pipeline), even if `failOnError: false` */
'mandatorySteps',
/**
* Defines a Map containing step name as key and timout in minutes in order to stop an execution after a certain timeout.
* This helps to make pipeline runs more resilient with respect to long running steps.
* */
'stepTimeouts'
])
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS.plus([
/**
* If it is set to true details will be output to the console. See example below.
* @possibleValues `true`, `false`
*/
'echoDetails',
/** Defines the name of the step for which the error handling is active. It will be shown in the console log.*/
'stepName',
/** Defines the documented step, in case the documentation reference should point to a different step. */
'stepNameDoc',
/** Passes the parameters of the step which uses the error handling onto the error handling. The list of parameters is then shown in the console output.*/
'stepParameters'
])
/**
* Used by other steps to make error analysis easier. Lists parameters and other data available to the step in which the error occurs.
*/
@GenerateDocumentation
void call(Map parameters = [:], body) {
// load default & individual configuration
def cpe = parameters.stepParameters?.script?.commonPipelineEnvironment ?: null
Map config = ConfigurationHelper.newInstance(this)
.loadStepDefaults()
.mixinGeneralConfig(cpe, GENERAL_CONFIG_KEYS)
.mixinStepConfig(cpe, STEP_CONFIG_KEYS)
.mixinStageConfig(cpe, parameters.stepParameters?.stageName?:env.STAGE_NAME, STEP_CONFIG_KEYS)
.mixin(parameters, PARAMETER_KEYS)
.withMandatoryProperty('stepParameters')
.withMandatoryProperty('stepName')
.addIfEmpty('stepNameDoc' , parameters.stepName)
.use()
def message = ''
try {
if (config.echoDetails)
echo "--- Begin library step of: ${config.stepName} ---"
if (!config.failOnError && config.stepTimeouts?.get(config.stepName)) {
timeout(time: config.stepTimeouts[config.stepName]) {
body()
}
} else {
body()
}
} catch (AbortException | FlowInterruptedException ex) {
if (config.echoDetails)
message += formatErrorMessage(config, ex)
writeErrorToInfluxData(config, ex)
if (config.failOnError || config.stepName in config.mandatorySteps) {
throw ex
}
def failureMessage = "[${STEP_NAME}] Error in step ${config.stepName} - Build result set to 'UNSTABLE'"
try {
//use new unstable feature if available: see https://jenkins.io/blog/2019/07/05/jenkins-pipeline-stage-result-visualization-improvements/
unstable(failureMessage)
} catch (java.lang.NoSuchMethodError nmEx) {
if (config.stepParameters?.script) {
config.stepParameters?.script.currentBuild.result = 'UNSTABLE'
} else {
currentBuild.result = 'UNSTABLE'
}
echo failureMessage
}
List unstableSteps = cpe?.getValue('unstableSteps') ?: []
if(!unstableSteps) {
unstableSteps = []
}
// add information about unstable steps to pipeline environment
// this helps to bring this information to users in a consolidated manner inside a pipeline
unstableSteps.add(config.stepName)
cpe?.setValue('unstableSteps', unstableSteps)
} catch (Throwable error) {
if (config.echoDetails)
message += formatErrorMessage(config, error)
writeErrorToInfluxData(config, error)
throw error
} finally {
if (config.echoDetails)
message += "--- End library step of: ${config.stepName} ---"
echo message
}
}
@NonCPS
private String formatErrorMessage(Map config, error){
Map binding = [
error: error,
libraryDocumentationUrl: config.libraryDocumentationUrl,
libraryRepositoryUrl: config.libraryRepositoryUrl,
stepName: config.stepName,
stepParameters: (config.stepParameters?.verbose == true) ? config.stepParameters?.toString() : '*** to show step parameters, set verbose:true in general pipeline configuration\n*** WARNING: this may reveal sensitive information. ***'
]
return GStringTemplateEngine
.newInstance()
.createTemplate(libraryResource('com.sap.piper/templates/error.log'))
.make(binding)
.toString()
}
private void writeErrorToInfluxData(Map config, error){
if(InfluxData.getInstance().getFields().pipeline_data?.build_error_message == null){
InfluxData.addTag('pipeline_data', 'build_error_step', config.stepName)
InfluxData.addTag('pipeline_data', 'build_error_stage', config.stepParameters.script?.env?.STAGE_NAME)
InfluxData.addField('pipeline_data', 'build_error_message', error.getMessage())
}
}