1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-07-17 01:42:43 +02:00

Allow resilient error handling in pipelines (#583)

* add resilience to error handling
* adapt documentation
* make ConfigLoader more resilient
This commit is contained in:
Oliver Nocon
2019-04-04 08:38:54 +02:00
committed by GitHub
parent ec3d350252
commit 138656b49b
5 changed files with 109 additions and 18 deletions

View File

@ -6,16 +6,6 @@
none none
## ${docGenParameters}
## Step configuration
none
## Exceptions
none
## Example ## Example
```groovy ```groovy
@ -53,3 +43,7 @@ Further information:
* GitHub repository for pipeline steps: https://... * GitHub repository for pipeline steps: https://...
---------------------------------------------------------- ----------------------------------------------------------
``` ```
## ${docGenParameters}
## ${docGenConfiguration}

View File

@ -221,8 +221,10 @@ steps:
testOptions: 'specs' testOptions: 'specs'
handlePipelineStepErrors: handlePipelineStepErrors:
echoDetails: true echoDetails: true
failOnError: true
libraryDocumentationUrl: 'https://sap.github.io/jenkins-library/' libraryDocumentationUrl: 'https://sap.github.io/jenkins-library/'
libraryRepositoryUrl: 'https://github.com/SAP/jenkins-library/' libraryRepositoryUrl: 'https://github.com/SAP/jenkins-library/'
mandatorySteps: []
healthExecuteCheck: healthExecuteCheck:
healthEndpoint: '' healthEndpoint: ''
influxWriteData: influxWriteData:

View File

@ -26,7 +26,11 @@ class ConfigurationLoader implements Serializable {
@NonCPS @NonCPS
static Map generalConfiguration(script){ static Map generalConfiguration(script){
try {
return script?.commonPipelineEnvironment?.configuration?.general ?: [:] return script?.commonPipelineEnvironment?.configuration?.general ?: [:]
} catch (groovy.lang.MissingPropertyException mpe) {
return [:]
}
} }
@NonCPS @NonCPS
@ -43,7 +47,12 @@ class ConfigurationLoader implements Serializable {
private static Map loadConfiguration(script, String type, String entryName, ConfigurationType configType){ private static Map loadConfiguration(script, String type, String entryName, ConfigurationType configType){
switch (configType) { switch (configType) {
case ConfigurationType.CUSTOM_CONFIGURATION: case ConfigurationType.CUSTOM_CONFIGURATION:
try {
return script?.commonPipelineEnvironment?.configuration?.get(type)?.get(entryName) ?: [:] return script?.commonPipelineEnvironment?.configuration?.get(type)?.get(entryName) ?: [:]
} catch (groovy.lang.MissingPropertyException mpe) {
return [:]
}
case ConfigurationType.DEFAULT_CONFIGURATION: case ConfigurationType.DEFAULT_CONFIGURATION:
return DefaultValueCache.getInstance()?.getDefaultValues()?.get(type)?.get(entryName) ?: [:] return DefaultValueCache.getInstance()?.getDefaultValues()?.get(type)?.get(entryName) ?: [:]
default: default:

View File

@ -1,4 +1,6 @@
#!groovy #!groovy
import hudson.AbortException
import static org.hamcrest.Matchers.is import static org.hamcrest.Matchers.is
import static org.hamcrest.Matchers.not import static org.hamcrest.Matchers.not
import static org.hamcrest.Matchers.containsString import static org.hamcrest.Matchers.containsString
@ -81,4 +83,62 @@ class HandlePipelineStepErrorsTest extends BasePiperTest {
assertThat(loggingRule.log, containsString('[something:anything]')) assertThat(loggingRule.log, containsString('[something:anything]'))
} }
} }
@Test
void testHandleErrorsIgnoreFailure() {
def errorOccured = false
try {
stepRule.step.handlePipelineStepErrors([
stepName: 'test',
stepParameters: [jenkinsUtilsStub: jenkinsUtils, script: nullScript],
failOnError: false
]) {
throw new AbortException('TestError')
}
} catch (err) {
errorOccured = true
}
assertThat(errorOccured, is(false))
assertThat(nullScript.currentBuild.result, is('UNSTABLE'))
}
@Test
void testHandleErrorsIgnoreFailureBlacklist() {
def errorOccured = false
//define blacklist in defaults
helper.registerAllowedMethod("readYaml", [Map], { Map m ->
return [steps: [handlePipelineStepErrors: [mandatorySteps: ['step1', 'test']]]]
})
try {
stepRule.step.handlePipelineStepErrors([
stepName: 'test',
stepParameters: [jenkinsUtilsStub: jenkinsUtils, script: nullScript],
failOnError: false
]) {
throw new AbortException('TestError')
}
} catch (err) {
errorOccured = true
}
assertThat(errorOccured, is(true))
}
@Test
void testHandleErrorsIgnoreFailureNoScript() {
def errorOccured = false
try {
stepRule.step.handlePipelineStepErrors([
stepName: 'test',
stepParameters: [jenkinsUtilsStub: jenkinsUtils],
failOnError: false
]) {
throw new AbortException('TestError')
}
} catch (err) {
errorOccured = true
}
assertThat(errorOccured, is(false))
}
} }

View File

@ -5,12 +5,21 @@ import com.sap.piper.ConfigurationHelper
import groovy.text.SimpleTemplateEngine import groovy.text.SimpleTemplateEngine
import groovy.transform.Field import groovy.transform.Field
import hudson.AbortException
@Field STEP_NAME = getClass().getName() @Field STEP_NAME = getClass().getName()
@Field Set GENERAL_CONFIG_KEYS = [] @Field Set GENERAL_CONFIG_KEYS = []
@Field Set STEP_CONFIG_KEYS = [] @Field Set STEP_CONFIG_KEYS = GENERAL_CONFIG_KEYS.plus([
@Field Set PARAMETER_KEYS = [ /**
* 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 a list of mandatory steps (step names) which have to be successful (=stop the pipeline), even if `failOnError: false` */
'mandatorySteps'
])
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS.plus([
/** /**
* If it is set to true details will be output to the console. See example below. * If it is set to true details will be output to the console. See example below.
* @possibleValues `true`, `false` * @possibleValues `true`, `false`
@ -20,13 +29,13 @@ import groovy.transform.Field
'libraryDocumentationUrl', 'libraryDocumentationUrl',
/** Defines the url of the library's repository that will be used to generate the corresponding links to the step implementation.*/ /** Defines the url of the library's repository that will be used to generate the corresponding links to the step implementation.*/
'libraryRepositoryUrl', 'libraryRepositoryUrl',
/** Defines the name of the step executed that will be shown in the console output.*/ /** Defines the name of the step for which the error handling is active. It will be shown in the console log.*/
'stepName', 'stepName',
/** */ /** Defines the documented step, in case the documentation reference should point to a different step. */
'stepNameDoc', 'stepNameDoc',
/** Defines the parameters from the step to be executed. The list of parameters is then shown in the console output.*/ /** 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' 'stepParameters'
] ])
/** /**
* Used by other steps to make error analysis easier. Lists parameters and other data available to the step in which the error occurs. * Used by other steps to make error analysis easier. Lists parameters and other data available to the step in which the error occurs.
@ -34,8 +43,12 @@ import groovy.transform.Field
@GenerateDocumentation @GenerateDocumentation
void call(Map parameters = [:], body) { void call(Map parameters = [:], body) {
// load default & individual configuration // load default & individual configuration
def cpe = parameters.stepParameters?.script?.commonPipelineEnvironment ?: commonPipelineEnvironment
Map config = ConfigurationHelper.newInstance(this) Map config = ConfigurationHelper.newInstance(this)
.loadStepDefaults() .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) .mixin(parameters, PARAMETER_KEYS)
.withMandatoryProperty('stepParameters') .withMandatoryProperty('stepParameters')
.withMandatoryProperty('stepName') .withMandatoryProperty('stepName')
@ -48,6 +61,19 @@ void call(Map parameters = [:], body) {
echo "--- Begin library step of: ${config.stepName} ---" echo "--- Begin library step of: ${config.stepName} ---"
body() body()
} catch (AbortException ae) {
if (config.echoDetails)
message += formatErrorMessage(config, ae)
writeErrorToInfluxData(config, ae)
if (config.failOnError || config.stepName in config.mandatorySteps) {
throw ae
}
if (config.stepParameters?.script) {
config.stepParameters?.script.currentBuild.result = 'UNSTABLE'
} else {
currentBuild.result = 'UNSTABLE'
}
} catch (Throwable error) { } catch (Throwable error) {
if (config.echoDetails) if (config.echoDetails)
message += formatErrorMessage(config, error) message += formatErrorMessage(config, error)