-
New Feature
-
Resolution: Unresolved
-
Major
-
Jenkins 2.234
pipeline-model-definition-plugin 1.6.0
workflow-step-api-plugin 2.22
With the use of declarative pipeline, it is impossible to add logic to a post condition to determine or perform logic on the cause of the failure. We only have access to the current build result.
I am trying to differentiate when the build was interrupted because of a timeout from when a build was aborted for other reasons such as a manual intervention, an upstream or downstream build that was aborted, etc.
I initially tried to use the build actions and the InterruptedBuildAction class, but it seems like this action won't be set when we are in the post conditions. The handle method https://github.com/jenkinsci/workflow-step-api-plugin/blob/master/src/main/java/org/jenkinsci/plugins/workflow/steps/FlowInterruptedException.java seems to be called after the whole build. https://github.com/jenkinsci/workflow-job-plugin/blob/master/src/main/java/org/jenkinsci/plugins/workflow/job/WorkflowRun.java#L604
The only possible way I found to work around this problem is to convert to scripted pipeline and wrap the whole pipeline in a try/catch to catch FlowInterruptedException and perform the logic on it. Another way with declarative pipelines would be to use catchError and a try/catch inside each stage to set a global error variable that could be accessed in the post condition.
Possible fix ideas:
- In workflow-step-api-plugin or in pipeline-model-definition-plugin, make sure the InterruptedBuildAction is added before the execution of the post build actions.
- In pipeline-model-definition-plugin, make the stageError available in the closure. It could also be part of currentBuild. (https://github.com/jenkinsci/pipeline-model-definition-plugin/blob/master/pipeline-model-definition/src/main/resources/org/jenkinsci/plugins/pipeline/modeldefinition/ModelInterpreter.groovy#L741)
In the code below, I would like to be able to differentiate a DownstreamFailureCause from a TimeoutStepExecution$ExceededTimeout in the post condition.
pipeline { options { timeout(time: 5, unit: 'SECONDS') skipDefaultCheckout() } agent { label 'master' } stages { stage("Build Failure or Timeout") { steps { script { build( job: 'failing-job', parameters: [], propagate: true, wait: true ) } } post { unsuccessful { script { getInterruptionCause(currentBuild) } } } } } } def getInterruptionCause(def build) { def directCause = null def rawBuild = build if (build instanceof org.jenkinsci.plugins.workflow.support.steps.build.RunWrapper) { rawBuild = build.getRawBuild() } def actions = rawBuild.getActions(jenkins.model.InterruptedBuildAction) for (action in actions) { def causes = action.getCauses() for (cause in causes) { // Print the causes and their root causes BFS style. println "Interruption Cause: " + cause.getShortDescription() directCause = cause // Received when the upstream build was cancelled. if(cause instanceof org.jenkinsci.plugins.workflow.support.steps.build.BuildTriggerCancelledCause) { def upstreamCause = build.getCause(hudson.model.Cause$UpstreamCause) if (upstreamCause) { getInterruptionCause(upstreamCause.getUpstreamRun()) } } // Received when a job triggered a build that failed with propagate: true. if(cause instanceof org.jenkinsci.plugins.workflow.support.steps.build.DownstreamFailureCause) { getInterruptionCause(cause.getDownstreamBuild()) } } } return directCause }