Uploaded image for project: 'Jenkins'
  1. Jenkins
  2. JENKINS-62257

FlowInterruptedException cause is not available in post condition

    XMLWordPrintable

    Details

    • Similar Issues:

      Description

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

        Attachments

          Activity

          agaudreault Alexandre Gaudreault created issue -
          agaudreault Alexandre Gaudreault made changes -
          Field Original Value New Value
          Summary InterruptionError is not available in post condition FlowInterruptedException cause is not available in post condition
          agaudreault Alexandre Gaudreault made changes -
          Description 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 wont be set when we are in the post conditions. The handle method seems to be called after: [https://github.com/jenkinsci/workflow-step-api-plugin/blob/master/src/main/java/org/jenkinsci/plugins/workflow/steps/FlowInterruptedException.java]

          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])

           
          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 wont be set when we are in the post conditions. The handle method seems to be called after: [https://github.com/jenkinsci/workflow-step-api-plugin/blob/master/src/main/java/org/jenkinsci/plugins/workflow/steps/FlowInterruptedException.java]

          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])

           ```
          pipeline {
          options {
            timestamps()
            skipStagesAfterUnstable()
            ansiColor('xterm')
            timeout(time: 5, unit: 'SECONDS')
            durabilityHint('PERFORMANCE_OPTIMIZED')
            disableResume()
            buildDiscarder(logRotator(daysToKeepStr: '10', numToKeepStr: '30'))
            skipDefaultCheckout()
          }

          agent { label 'master' }
              stages {
                  stage("abort older") {
                      steps {
                          milestone label: 'Last milestone of previous build', ordinal: Integer.parseInt(env.BUILD_NUMBER) -1
                          milestone label: 'Flag this build as newer and cancel previous', ordinal: Integer.parseInt(env.BUILD_NUMBER)
                      }
                  }
                  stage("Print Params") {
                      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
          }
          ```
          agaudreault Alexandre Gaudreault made changes -
          Description 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 wont be set when we are in the post conditions. The handle method seems to be called after: [https://github.com/jenkinsci/workflow-step-api-plugin/blob/master/src/main/java/org/jenkinsci/plugins/workflow/steps/FlowInterruptedException.java]

          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])

           ```
          pipeline {
          options {
            timestamps()
            skipStagesAfterUnstable()
            ansiColor('xterm')
            timeout(time: 5, unit: 'SECONDS')
            durabilityHint('PERFORMANCE_OPTIMIZED')
            disableResume()
            buildDiscarder(logRotator(daysToKeepStr: '10', numToKeepStr: '30'))
            skipDefaultCheckout()
          }

          agent { label 'master' }
              stages {
                  stage("abort older") {
                      steps {
                          milestone label: 'Last milestone of previous build', ordinal: Integer.parseInt(env.BUILD_NUMBER) -1
                          milestone label: 'Flag this build as newer and cancel previous', ordinal: Integer.parseInt(env.BUILD_NUMBER)
                      }
                  }
                  stage("Print Params") {
                      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
          }
          ```
          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 wont be set when we are in the post conditions. The handle method seems to be called after: [https://github.com/jenkinsci/workflow-step-api-plugin/blob/master/src/main/java/org/jenkinsci/plugins/workflow/steps/FlowInterruptedException.java]

          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])

          {code:groovy}
          pipeline {
          options {
            timestamps()
            skipStagesAfterUnstable()
            ansiColor('xterm')
            timeout(time: 5, unit: 'SECONDS')
            durabilityHint('PERFORMANCE_OPTIMIZED')
            disableResume()
            buildDiscarder(logRotator(daysToKeepStr: '10', numToKeepStr: '30'))
            skipDefaultCheckout()
          }

          agent { label 'master' }
              stages {
                  stage("abort older") {
                      steps {
                          milestone label: 'Last milestone of previous build', ordinal: Integer.parseInt(env.BUILD_NUMBER) -1
                          milestone label: 'Flag this build as newer and cancel previous', ordinal: Integer.parseInt(env.BUILD_NUMBER)
                      }
                  }
                  stage("Print Params") {
                      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
          }
          {code}
          agaudreault Alexandre Gaudreault made changes -
          Description 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 wont be set when we are in the post conditions. The handle method seems to be called after: [https://github.com/jenkinsci/workflow-step-api-plugin/blob/master/src/main/java/org/jenkinsci/plugins/workflow/steps/FlowInterruptedException.java]

          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])

          {code:groovy}
          pipeline {
          options {
            timestamps()
            skipStagesAfterUnstable()
            ansiColor('xterm')
            timeout(time: 5, unit: 'SECONDS')
            durabilityHint('PERFORMANCE_OPTIMIZED')
            disableResume()
            buildDiscarder(logRotator(daysToKeepStr: '10', numToKeepStr: '30'))
            skipDefaultCheckout()
          }

          agent { label 'master' }
              stages {
                  stage("abort older") {
                      steps {
                          milestone label: 'Last milestone of previous build', ordinal: Integer.parseInt(env.BUILD_NUMBER) -1
                          milestone label: 'Flag this build as newer and cancel previous', ordinal: Integer.parseInt(env.BUILD_NUMBER)
                      }
                  }
                  stage("Print Params") {
                      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
          }
          {code}
          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 wont be set when we are in the post conditions. The handle method seems to be called after: [https://github.com/jenkinsci/workflow-step-api-plugin/blob/master/src/main/java/org/jenkinsci/plugins/workflow/steps/FlowInterruptedException.java]

          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.

          {code:groovy}
          pipeline {
          options {
            timestamps()
            skipStagesAfterUnstable()
            ansiColor('xterm')
            timeout(time: 5, unit: 'SECONDS')
            durabilityHint('PERFORMANCE_OPTIMIZED')
            disableResume()
            buildDiscarder(logRotator(daysToKeepStr: '10', numToKeepStr: '30'))
            skipDefaultCheckout()
          }

          agent { label 'master' }
              stages {
                  stage("abort older") {
                      steps {
                          milestone label: 'Last milestone of previous build', ordinal: Integer.parseInt(env.BUILD_NUMBER) -1
                          milestone label: 'Flag this build as newer and cancel previous', ordinal: Integer.parseInt(env.BUILD_NUMBER)
                      }
                  }
                  stage("Print Params") {
                      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
          }
          {code}
          agaudreault Alexandre Gaudreault made changes -
          Description 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 wont be set when we are in the post conditions. The handle method seems to be called after: [https://github.com/jenkinsci/workflow-step-api-plugin/blob/master/src/main/java/org/jenkinsci/plugins/workflow/steps/FlowInterruptedException.java]

          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.

          {code:groovy}
          pipeline {
          options {
            timestamps()
            skipStagesAfterUnstable()
            ansiColor('xterm')
            timeout(time: 5, unit: 'SECONDS')
            durabilityHint('PERFORMANCE_OPTIMIZED')
            disableResume()
            buildDiscarder(logRotator(daysToKeepStr: '10', numToKeepStr: '30'))
            skipDefaultCheckout()
          }

          agent { label 'master' }
              stages {
                  stage("abort older") {
                      steps {
                          milestone label: 'Last milestone of previous build', ordinal: Integer.parseInt(env.BUILD_NUMBER) -1
                          milestone label: 'Flag this build as newer and cancel previous', ordinal: Integer.parseInt(env.BUILD_NUMBER)
                      }
                  }
                  stage("Print Params") {
                      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
          }
          {code}
          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 wont be set when we are in the post conditions. The handle method seems to be called after: [https://github.com/jenkinsci/workflow-step-api-plugin/blob/master/src/main/java/org/jenkinsci/plugins/workflow/steps/FlowInterruptedException.java]

          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.

          {code:groovy}
          pipeline {
          options {
            timestamps()
            skipStagesAfterUnstable()
            ansiColor('xterm')
            timeout(time: 5, unit: 'SECONDS')
            durabilityHint('PERFORMANCE_OPTIMIZED')
            disableResume()
            buildDiscarder(logRotator(daysToKeepStr: '10', numToKeepStr: '30'))
            skipDefaultCheckout()
          }

          agent { label 'master' }
              stages {
                  stage("abort older") {
                      steps {
                          milestone label: 'Last milestone of previous build', ordinal: Integer.parseInt(env.BUILD_NUMBER) -1
                          milestone label: 'Flag this build as newer and cancel previous', ordinal: Integer.parseInt(env.BUILD_NUMBER)
                      }
                  }
                  stage("Print Params") {
                      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
          }
          {code}
          agaudreault Alexandre Gaudreault made changes -
          Description 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 wont be set when we are in the post conditions. The handle method seems to be called after: [https://github.com/jenkinsci/workflow-step-api-plugin/blob/master/src/main/java/org/jenkinsci/plugins/workflow/steps/FlowInterruptedException.java]

          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.

          {code:groovy}
          pipeline {
          options {
            timestamps()
            skipStagesAfterUnstable()
            ansiColor('xterm')
            timeout(time: 5, unit: 'SECONDS')
            durabilityHint('PERFORMANCE_OPTIMIZED')
            disableResume()
            buildDiscarder(logRotator(daysToKeepStr: '10', numToKeepStr: '30'))
            skipDefaultCheckout()
          }

          agent { label 'master' }
              stages {
                  stage("abort older") {
                      steps {
                          milestone label: 'Last milestone of previous build', ordinal: Integer.parseInt(env.BUILD_NUMBER) -1
                          milestone label: 'Flag this build as newer and cancel previous', ordinal: Integer.parseInt(env.BUILD_NUMBER)
                      }
                  }
                  stage("Print Params") {
                      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
          }
          {code}
          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 seems to be called after: [https://github.com/jenkinsci/workflow-step-api-plugin/blob/master/src/main/java/org/jenkinsci/plugins/workflow/steps/FlowInterruptedException.java]

          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.

          {code:groovy}
          pipeline {
          options {
            timestamps()
            skipStagesAfterUnstable()
            ansiColor('xterm')
            timeout(time: 5, unit: 'SECONDS')
            durabilityHint('PERFORMANCE_OPTIMIZED')
            disableResume()
            buildDiscarder(logRotator(daysToKeepStr: '10', numToKeepStr: '30'))
            skipDefaultCheckout()
          }

          agent { label 'master' }
              stages {
                  stage("abort older") {
                      steps {
                          milestone label: 'Last milestone of previous build', ordinal: Integer.parseInt(env.BUILD_NUMBER) -1
                          milestone label: 'Flag this build as newer and cancel previous', ordinal: Integer.parseInt(env.BUILD_NUMBER)
                      }
                  }
                  stage("Print Params") {
                      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
          }
          {code}
          agaudreault Alexandre Gaudreault made changes -
          Description 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 seems to be called after: [https://github.com/jenkinsci/workflow-step-api-plugin/blob/master/src/main/java/org/jenkinsci/plugins/workflow/steps/FlowInterruptedException.java]

          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.

          {code:groovy}
          pipeline {
          options {
            timestamps()
            skipStagesAfterUnstable()
            ansiColor('xterm')
            timeout(time: 5, unit: 'SECONDS')
            durabilityHint('PERFORMANCE_OPTIMIZED')
            disableResume()
            buildDiscarder(logRotator(daysToKeepStr: '10', numToKeepStr: '30'))
            skipDefaultCheckout()
          }

          agent { label 'master' }
              stages {
                  stage("abort older") {
                      steps {
                          milestone label: 'Last milestone of previous build', ordinal: Integer.parseInt(env.BUILD_NUMBER) -1
                          milestone label: 'Flag this build as newer and cancel previous', ordinal: Integer.parseInt(env.BUILD_NUMBER)
                      }
                  }
                  stage("Print Params") {
                      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
          }
          {code}
          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 seems to be called after: [https://github.com/jenkinsci/workflow-step-api-plugin/blob/master/src/main/java/org/jenkinsci/plugins/workflow/steps/FlowInterruptedException.java]

          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.

          {code:groovy}
          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
          }
          {code}
          agaudreault Alexandre Gaudreault made changes -
          Description 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 seems to be called after: [https://github.com/jenkinsci/workflow-step-api-plugin/blob/master/src/main/java/org/jenkinsci/plugins/workflow/steps/FlowInterruptedException.java]

          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.

          {code:groovy}
          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
          }
          {code}
          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.

          {code:groovy}
          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
          }
          {code}
          agaudreault Alexandre Gaudreault made changes -
          Description 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.

          {code:groovy}
          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
          }
          {code}
          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.

          {code:groovy}
          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
          }
          {code}
          Hide
          agaudreault Alexandre Gaudreault added a comment - - edited

          The "cleanest" workaround I found is to define a method and wrap the script closure in it. This seems to work so far. I have this defined as `vars/interruptibleScript.groovy` in my pipeline library.

          /**
          * interruptibleScript seamlessly catch FlowInterruptionException and handles them so the InterruptedBuildAction is available in the post conditions
          *
          * TimeoutStepExecution$ExceededTimeout and DownstreamFailureCause will never be available in the post conditions otherwise.
          *
          * Required until there is a fix for https://issues.jenkins-ci.org/browse/JENKINS-62257
          *
          * Examples:
             stage('Build') {
               steps {
                 script {
                   interruptibleScript {
                     // Any code here
                   }
                 }
               }
             }
          * Parameters:
          *
          * @param script (Required, Closure) The code to execute.
          *
          * @category step
          * */
          
          def call(Closure script) {
            Exception caught = null
          
            // Keep the status to SUCCESS/SUCCESS. A FlowInterruptionException will update them based on the CauseOfInterruption.
            catchError(buildResult: 'SUCCESS', stageResult: 'SUCCESS', catchInterruptions: true) {
              try {
                script.call()
              } catch (Exception ex) {
                // Save the error in memory and let catchError handle it correctly
                caught = ex
                throw ex;
              }
            }
          
            // rethrow the initial error like nothing happened.
            if (caught) {
              throw caught;
            }
          }
          
          Show
          agaudreault Alexandre Gaudreault added a comment - - edited The "cleanest" workaround I found is to define a method and wrap the script closure in it. This seems to work so far. I have this defined as `vars/interruptibleScript.groovy` in my pipeline library. /** * interruptibleScript seamlessly catch FlowInterruptionException and handles them so the InterruptedBuildAction is available in the post conditions * * TimeoutStepExecution$ExceededTimeout and DownstreamFailureCause will never be available in the post conditions otherwise. * * Required until there is a fix for https: //issues.jenkins-ci.org/browse/JENKINS-62257 * * Examples: stage( 'Build' ) { steps { script { interruptibleScript { // Any code here } } } } * Parameters: * * @param script (Required, Closure) The code to execute. * * @category step * */ def call(Closure script) { Exception caught = null // Keep the status to SUCCESS/SUCCESS. A FlowInterruptionException will update them based on the CauseOfInterruption. catchError(buildResult: 'SUCCESS' , stageResult: 'SUCCESS' , catchInterruptions: true ) { try { script.call() } catch (Exception ex) { // Save the error in memory and let catchError handle it correctly caught = ex throw ex; } } // rethrow the initial error like nothing happened. if (caught) { throw caught; } }
          Hide
          drewctate Andrew added a comment -

          Alexandre Gaudreault thanks for this discussion. This is precisely what we're trying to do, too. Seems like it would be convenient to have a directive added to Jenkins. Something like this maybe?

          options {
              onTimeout myFunction
          }
          

           

          Show
          drewctate Andrew added a comment - Alexandre Gaudreault  thanks for this discussion. This is precisely what we're trying to do, too. Seems like it would be convenient to have a directive added to Jenkins. Something like this maybe? options {     onTimeout myFunction }  
          Hide
          agaudreault Alexandre Gaudreault added a comment - - edited

          I update my previous comment with the updated code I am using. I have this defined as `vars/interruptibleScript.groovy` in my pipeline library.

          Show
          agaudreault Alexandre Gaudreault added a comment - - edited I update my previous comment with the updated code I am using. I have this defined as `vars/interruptibleScript.groovy` in my pipeline library.
          Hide
          drewctate Andrew added a comment -

          Thanks for sharing the updated version!

          Show
          drewctate Andrew added a comment - Thanks for sharing the updated version!

            People

            Assignee:
            Unassigned Unassigned
            Reporter:
            agaudreault Alexandre Gaudreault
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

              Dates

              Created:
              Updated: