1

I've a Jenkinsfile, which has a two different stages: Pre-Build and Build. The Pre-Build is executing pylint and uses the warnings-ng-plugin to report that back to Jenkins.

Something like that:

stages {
        stage('Pre-build') {
            steps {
                script {
                    sh """#!/usr/bin/env bash
                            pip install .
                            pylint --exit-zero --output-format=parseable --reports=n myProject > reports/pylint.log
                            """
                }
            }

            post {
                always {
                    recordIssues(
                            enabledForFailure: true,
                            tool: pyLint(pattern: '**/pylint.log'),
                            unstableTotalAll: 20,
                            failedTotalAll: 30,
                    )
                }
                failure {
                    cleanWs()
                }
            }
        }

        stage('Build') {
            steps {
                script {
                    sh """#!/usr/bin/env bash
                            set -e
                            echo 'I AM STAGE TWO AND I SHOULD NOT BE EXECUTED'
                            """
                }
            }
            post {
                always {
                    cleanWs()
                }
            }
        }
    }

I'm running into a couple of issues here. Currently I'm setting pylint to --exit-zero, as I want the warnings-ng plugin decide if it is good to go or not, based on the report.

Currently this is set to fail at a total of 30 issues. Now, myProject has 45 issues and I want to prevent that the next stage, Build is entered. But currently I can't seem to be able to prevent this behaviour, as it always continuous to the Build stage.

The build is flagged as failure, because of the results determined within recordIssues, but it doesn't abort the job.

I've found a ticket on https://issues.jenkins-ci.org (Ticket), but I can't seem to make sense out of all of this.

tschaka1904
  • 1,303
  • 2
  • 17
  • 39
  • I'm having the same issue. The aforementioned Jira ticket is about the fact, that warnings-ng plugin didn't set the stage result to "FAILED" and also the whole build to FAILED before. Right now it is setting it correctly to FAILED, but it will not stop next stages from executing. – borievka May 13 '20 at 11:53

3 Answers3

3

I've found a solution to your problem and I think that this is a bug in the pipeline workflow. The warnings-ng correctly sets build status to failed, but next stages are started despite the status in ${currentBuild.currentResult} variable.

You can use when { expression { return currentBuild.currentResult == "SUCCESS" } } to skip later stages, or throwing an error. But I think this should be default behaviour. Your file then should like:

stages {
        stage('Pre-build') {
            steps {
                script {
                    sh """#!/usr/bin/env bash
                            pip install .
                            pylint --exit-zero --output-format=parseable --reports=n myProject > reports/pylint.log
                            """
                }
            }

            post {
                always {
                    recordIssues(
                            enabledForFailure: true,
                            tool: pyLint(pattern: '**/pylint.log'),
                            unstableTotalAll: 20,
                            failedTotalAll: 30,
                    )
                }
            }
        }

        stage('Build') {
            when { expression { return currentBuild.currentResult == "SUCCESS" } }

            steps {
                script {
                    echo "currentResult: ${currentBuild.currentResult}"
                    sh """#!/usr/bin/env bash
                            set -e
                            echo 'I AM STAGE TWO AND I SHOULD NOT BE EXECUTED'
                            """
                }
            }

        }
        post {
            always {
                cleanWs()
            }
        }
    }

I've created an issue in their Jira.

My environment:

Jenkins ver.: 2.222.1

warnings-ng ver.: 8.1

worfklow-api ver.: 2.40

borievka
  • 636
  • 5
  • 13
  • Thanks for the solution, it's super-useful. I'm able to skep the deployment stage with `when { expression { return currentBuild.result == "SUCCESS" } }` but after that for some reason the build is marked as succeeded. Have you experienced that at some point? – Uko Sep 22 '20 at 09:21
  • 1
    Yes, I have. But I was not able to find a root cause. There are two similar variables: `currentBuild.result` and `currentBuild.currentResult`. I was not able to tell the difference between them since there is currently no official documentation for `currentResult`, but I saw [JENKINS-56536](https://issues.jenkins-ci.org/browse/JENKINS-56536) and now we are just using `currentBuild.currentResult` and everything works as expected. – borievka Sep 22 '20 at 12:00
  • Good to know. I also looked at your code in the Jenkins issue report and came up with a slightly different solution. You can check my answer. I'm just explicitly executing `error()` if the build is marked as failed. This way you don't have to think about which stages you should fix. – Uko Sep 22 '20 at 12:38
1
  1. You have used post 2 times which is wrong implementation as post is designed to get executed only once after all stages are done. It should be written after all the stages just before end of pipeline.
  2. To stop or skip the execution of 2nd Build stage, you can create global varaible at the top, capture the output of pylint in that and use if or when condition at start of stage. Something similar to --
pipeline {
def result

stages {
        stage('Pre-build') {
            steps {
                script {
                    sh """#!/usr/bin/env bash
                            pip install .
                            pylint --exit-zero --output-format=parseable --reports=n myProject > reports/pylint.log
                            """
                   }
                }
            }
        }
        stage('Pylint result') {   // Not sure how recordIssue works. This just an example.
         result =  recordIssues(
                            enabledForFailure: true,
                            tool: pyLint(pattern: '**/pylint.log'),
                            unstableTotalAll: 20,
                            failedTotalAll: 30,
                    )
        }
        stage('Build') {
          if ( result == "pass") {
            steps {
                script {
                    sh """#!/usr/bin/env bash
                            set -e
                            echo 'I AM STAGE TWO AND I SHOULD NOT BE EXECUTED'
                            """
                }
            }
         } 
       }
    }
   post {   // this should be used after stages
                always {
                    cleanWs()
                }   
            failure {
                    cleanWs()
                }
}

Also, stages are designed in such a way that if they fail, next stage will not be executed so it's a good idea to have the pylint to be executed inside a stage instead of post condition.

Note: The code above is just an example. Please modify it according to your need.

Harsha
  • 87
  • 6
  • Wait, so the post condition is not a stage post conditions, but a global post condition? – Uko Sep 22 '20 at 08:54
  • It depends, where you define it. In my answer, I have one post condition for the stage 'pre-build' and one post condition for the whole build. Useful to read: https://www.jenkins.io/doc/book/pipeline/syntax/#post – borievka Sep 22 '20 at 11:55
0

One option that you may consider is to fail the build explicitly with the following code:

post {
    always {
        recordIssues(
            enabledForFailure: true,
            tool: pyLint(pattern: '**/pylint.log'),
            unstableTotalAll: 20,
            failedTotalAll: 30
        )

        script {
            if (currentBuild.currentResult == 'FAILURE') {
                error('Ensure that the build fails if the quality gates fail')
            }
        }
    }
}

Here, after you record the issues, you also check if the value of currentBuild.currentResult is FAILURE and in that case you explicitly call the error() function which fails the build correctly.

Uko
  • 13,134
  • 6
  • 58
  • 106