102

In a project I'm working on, we are using shell scripts to execute different tasks. Some are sh/bash scripts that run rsync, and some are PHP scripts. One of the PHP scripts is running some integration tests that output to JUnit XML, code coverage reports, and similar.

Jenkins is able to mark the jobs as successful / failed based on exit status. In PHP, the script exits with 1 if it has detected that the tests failed during the run. The other shell scripts run commands and use the exit codes from those to mark a build as failed.

// :: End of PHP script:
// If any tests have failed, fail the build
if ($build_error) exit(1);

In Jenkins Terminology, an unstable build is defined as:

A build is unstable if it was built successfully and one or more publishers report it unstable. For example if the JUnit publisher is configured and a test fails then the build will be marked unstable.

How can I get Jenkins to mark a build as unstable instead of only success / failed when running shell scripts?

Pang
  • 9,564
  • 146
  • 81
  • 122
HNygard
  • 4,526
  • 6
  • 32
  • 40
  • I achived it running diffrent job steps and using jenkins plugins http://stackoverflow.com/questions/25442343/failure-unstable-via-script-return-code/28966322#28966322 – Fantastory Mar 10 '15 at 16:59

16 Answers16

76

Modern Jenkins versions (since 2.26, October 2016) solved this: it's just an advanced option for the Execute shell build step!

exit code for build

You can just choose and set an arbitrary exit value; if it matches, the build will be unstable. Just pick a value which is unlikely to be launched by a real process in your build.

Alan Franzoni
  • 3,041
  • 1
  • 23
  • 35
  • I like this option since it doesn't require you to install any additional plugins – mattherman May 04 '18 at 16:06
  • 2
    Since this is implemented in latest Jenkins - this should be an accepted answer – smoke_lp Aug 07 '18 at 09:24
  • 3
    "Modern Jenkins versions" means Jenkins 2.26 or newer. See https://issues.jenkins-ci.org/browse/JENKINS-23786. – Blue Aug 27 '18 at 12:18
  • 6
    Is it possible to specify this via code when using the `sh` step command in a `Jenkinsfile`? Where is the setting located in the GUI? I cannot find it. – bluenote10 Apr 02 '19 at 16:40
  • 2
    I had to click open the "Advanced..." button under the build step to expose this. It's not very useful to hide a single (and not particularly advanced) option behind a "click here to do things" collapser but that's how it is. – tripleee Aug 01 '19 at 11:07
  • This seems to no longer be available. – Kissaki Jun 27 '22 at 14:40
  • @Kissaki which version? In the current LTS (2.346.2) it's available and working. – Alan Franzoni Aug 01 '22 at 06:01
61

It can be done without printing magic strings and using TextFinder. Here's some info on it.

Basically you need a .jar file from http://yourserver.com/cli available in shell scripts, then you can use the following command to mark a build unstable:

java -jar jenkins-cli.jar set-build-result unstable

To mark build unstable on error, you can use:

failing_cmd cmd_args || java -jar jenkins-cli.jar set-build-result unstable

The problem is that jenkins-cli.jar has to be available from shell script. You can either put it in easy-to-access path, or download in via job's shell script:

wget ${JENKINS_URL}jnlpJars/jenkins-cli.jar
binaryLV
  • 9,002
  • 2
  • 40
  • 42
  • 2
    I really like this solution, I implemented an ruby class for this for easy reuse in my rakefiles. :) – Shire Mar 16 '12 at 22:20
  • 3
    +1 - this is a better solution than the accepted answer because the Text Finder can only search for one string per job, so you can only set the build status to one of two values. – gareth_bowles Nov 15 '13 at 23:31
  • 4
    Interesting solution. But if your Jenkins requires authentication, you will need to set up public key authentication in its config or any jenkins-cli command will fail with an AccessDeniedException. – Tom De Leu Feb 11 '14 at 11:26
  • 2
    This won't work if you're using a slave that has no web access to the master. For example, if the Jenkins slave cannot create an HTTP or HTTPS connection back to the server. – Steve HHH Jul 05 '15 at 17:39
  • 3
    I wanted to use this solution, but `set-build-result` has been deprecated in the `jenkins-cli`. – DrLime2k10 Mar 26 '18 at 11:51
  • @DrLime2k10 I guess you can use `help` command to see if there's any command that would replace `set-build-result`. I don't have access to Jenkins anymore, so I can't really help here. – binaryLV Mar 27 '18 at 08:05
  • Thanks @binaryLV but there isn't. It's just deprecated. I imagine something broke and they dropped support for it; nowadays they just want to support and you to use pipeline. – DrLime2k10 Mar 27 '18 at 11:52
60

Use the Text-finder plugin.

Instead of exiting with status 1 (which would fail the build), do:

if ($build_error) print("TESTS FAILED!");

Than in the post-build actions enable the Text Finder, set the regular expression to match the message you printed (TESTS FAILED!) and check the "Unstable if found" checkbox under that entry.

Jan Hudec
  • 73,652
  • 13
  • 125
  • 172
  • 2
    See the answer below for an option without installing a plugin, since jenkins version 2.26: https://stackoverflow.com/a/49676269/1347649 – JSoet Oct 18 '18 at 10:56
42

You should use Jenkinsfile to wrap your build script and simply mark the current build as UNSTABLE by using currentBuild.result = "UNSTABLE".

   stage {
      status = /* your build command goes here */
      if (status === "MARK-AS-UNSTABLE") {
        currentBuild.result = "UNSTABLE"
      }
   }
poussma
  • 7,033
  • 3
  • 43
  • 68
  • 4
    Why doesn't this answer has more upvote? Is there something wrong with it (except the use of the "magic" string `UNSTABLE`)? It seems more straightforward than the other answers. – Kevin Jul 15 '19 at 11:13
  • 6
    The question was asking about freestyle jobs, while this answer is about Pipeline jobs. The Pipeline answer does not apply to freestyle jobs – Mark Waite Jul 25 '19 at 17:13
  • How does this even work? I get an error:`Expected one of "steps", "stages", or "parallel" for stage` when I try to set currentBuild.result directly inside a stage. – dokaspar Jun 09 '20 at 10:33
  • @dokaspar There likely need to be `steps {}` and possibly `script {}` blocks inside the `stage {}` before you can put this arbitrary code with `if` blocks. – ErikE Sep 22 '22 at 21:02
  • i added it to my pipeline script but it didn't help (the goal- if the tests failed the build status will be SUCCESS if the tests passed the status will be FAILED and if its mixed pass+failed, the status will be SUCCESS: stage('Tests') { steps { script{ //running the tests sh "${mvnHome}/bin/mvn clean test -e -Dgroups=categories.knownBug" currentBuild.currentResult = "SUCCESS" } } – Noy M Nov 21 '22 at 13:42
12

you should also be able to use groovy and do what textfinder did

marking a build as un-stable with groovy post-build plugin

if(manager.logContains("Could not login to FTP server")) {
    manager.addWarningBadge("FTP Login Failure")
    manager.createSummary("warning.gif").appendText("<h1>Failed to login to remote FTP Server!</h1>", false, false, false, "red")
    manager.buildUnstable()
}

Also see Groovy Postbuild Plugin

Kalpesh Soni
  • 6,879
  • 2
  • 56
  • 59
7

In my job script, I have the following statements (this job only runs on the Jenkins master):

# This is the condition test I use to set the build status as UNSTABLE
if [ ${PERCENTAGE} -gt 80 -a ${PERCENTAGE} -lt 90 ]; then
  echo WARNING: disc usage percentage above 80%

  # Download the Jenkins CLI JAR:
  curl -o jenkins-cli.jar ${JENKINS_URL}/jnlpJars/jenkins-cli.jar

  # Set build status to unstable
  java -jar jenkins-cli.jar -s ${JENKINS_URL}/ set-build-result unstable

fi

You can see this and a lot more information about setting build statuses on the Jenkins wiki: https://wiki.jenkins-ci.org/display/JENKINS/Jenkins+CLI

sorin
  • 161,544
  • 178
  • 535
  • 806
Steve HHH
  • 12,947
  • 6
  • 68
  • 71
4
  1. Configure PHP build to produce xml junit report

    <phpunit bootstrap="tests/bootstrap.php" colors="true" >
       <logging>
           <log type="junit" target="build/junit.xml" 
               logIncompleteSkipped="false" title="Test Results"/>
       </logging>
    
       ....
    
     </phpunit>
    
  2. Finish build script with status 0

    ...
    exit 0;
    
  3. Add post-build action Publish JUnit test result report for Test report XMLs. This plugin will change Stable build to Unstable when test are failing.

    **/build/junit.xml
    
  4. Add Jenkins Text Finder plugin with console output scanning and unchecked options. This plugin fail whole build on fatal error.

    PHP Fatal error:
    
MariuszS
  • 30,646
  • 12
  • 114
  • 155
4

Duplicating my answer from here because I spent some time looking for this:

This is now possible in newer versions of Jenkins, you can do something like this:

#!/usr/bin/env groovy

properties([
  parameters([string(name: 'foo', defaultValue: 'bar', description: 'Fails job if not bar (unstable if bar)')]),
])


stage('Stage 1') {
  node('parent'){
    def ret = sh(
      returnStatus: true, // This is the key bit!
      script: '''if [ "$foo" = bar ]; then exit 2; else exit 1; fi'''
    )
    // ret can be any number/range, does not have to be 2.
    if (ret == 2) {
      currentBuild.result = 'UNSTABLE'
    } else if (ret != 0) {
      currentBuild.result = 'FAILURE'
      // If you do not manually error the status will be set to "failed", but the
      // pipeline will still run the next stage.
      error("Stage 1 failed with exit code ${ret}")
    }
  }
}

The Pipeline Syntax generator shows you this in the advanced tab:

Pipeline Syntax Example

gib
  • 1,951
  • 1
  • 21
  • 24
3

I find the most flexible way to do this is by reading a file in the groovy post build plugin. enter image description here

import hudson.FilePath
import java.io.InputStream

def build = Thread.currentThread().executable

String unstable = null
if(build.workspace.isRemote()) {
    channel = build.workspace.channel;
    fp = new FilePath(channel, build.workspace.toString() + "/build.properties")
    InputStream is = fp.read()
    unstable = is.text.trim()
} else {
    fp = new FilePath(new File(build.workspace.toString() + "/build.properties"))
    InputStream is = fp.read()
    unstable = is.text.trim()
}

manager.listener.logger.println("Build status file: " + unstable)
if (unstable.equalsIgnoreCase('true')) {
    manager.listener.logger.println('setting build to unstable')
    manager.buildUnstable()
}

If the file contents are 'true' the build will be set to unstable. This will work on the local master and on any slaves you run the job on, and for any kind of scripts that can write to disk.

jeremyjjbrown
  • 7,772
  • 5
  • 43
  • 55
  • I'm assuming this really says "if there is a file in the workspace named build.properties" mark as unstable. is that right? I'm new to Groovy, Would you mind breaking this explanation down a bit more ? – uchuugaka Oct 31 '16 at 08:23
  • @uchuugaka yeah, if there is a file and it has those contents. The file name and contents are arbitrary. Use whatever fits your case. – jeremyjjbrown Oct 31 '16 at 16:11
  • Thanks! very helpful. Groovy Postbuild is fairly indirect, and Groovy sucks in such a vast amount of stuff from Java and adds more... it's a new trick for me. – uchuugaka Nov 01 '16 at 08:46
  • @uchuugaka I don't think that's a problem with groovy :) – jeremyjjbrown Nov 01 '16 at 09:30
  • Not a problem at all. Just a challenge to lean into ! – uchuugaka Nov 01 '16 at 09:49
3

I thought I would post another answer for people that might be looking for something similar.

In our build job we have cases where we would want the build to continue, but be marked as unstable. For ours it's relating to version numbers.

So, I wanted to set a condition on the build and set the build to unstable if that condition is met.

I used the Conditional step (single) option as a build step.

Then I used Execute system Groovy script as the build step that would run when that condition is met.

I used Groovy Command and set the script the following

import hudson.model.*

def build = Thread.currentThread().executable
build.@result = hudson.model.Result.UNSTABLE

return

That seems to work quite well.

I stumbled upon the solution here

http://tech.akom.net/archives/112-Marking-Jenkins-build-UNSTABLE-from-environment-inject-groovy-script.html

adprocas
  • 1,863
  • 1
  • 14
  • 31
3

In addition to all others answers, jenkins also allows the use of the unstable() method (which is in my opinion clearer). This method can be used with a message parameter which describe why the build is unstable.

In addition of this, you can use the returnStatus of your shell script (bat or sh) to enable this.

For example:

def status = bat(script: "<your command here>", returnStatus: true)
if (status != 0) {
    unstable("unstable build because script failed")
}

Of course, you can make something with more granularity depending on your needs and the return status.


Furthermore, for raising error, you can also use warnError() in place of unstable(). It will indicate your build as failed instead of unstable, but the syntax is same.

Kaz
  • 1,047
  • 8
  • 18
2

The TextFinder is good only if the job status hasn't been changed from SUCCESS to FAILED or ABORTED. For such cases, use a groovy script in the PostBuild step:

errpattern = ~/TEXT-TO-LOOK-FOR-IN-JENKINS-BUILD-OUTPUT.*/;
manager.build.logFile.eachLine{ line ->
    errmatcher=errpattern.matcher(line)
    if (errmatcher.find()) {
        manager.build.@result = hudson.model.Result.NEW-STATUS-TO-SET
    }
 }

See more details in a post I've wrote about it: http://www.tikalk.com/devops/JenkinsJobStatusChange/

yorammi
  • 6,272
  • 1
  • 28
  • 34
1

As a lighter alternative to the existing answers, you can set the build result with a simple HTTP POST to access the Groovy script console REST API:

    curl -X POST \
     --silent \
     --user "$YOUR_CREDENTIALS" \
     --data-urlencode "script=Jenkins.instance.getItemByFullName( '$JOB_NAME' ).getBuildByNumber( $BUILD_NUMBER ).setResult( hudson.model.Result.UNSTABLE )" $JENKINS_URL/scriptText

Advantages:

  • no need to download and run a huge jar file
  • no kludges for setting and reading some global state (console text, files in workspace)
  • no plugins required (besides Groovy)
  • no need to configure an extra build step that is superfluous in the PASSED or FAILURE cases.

For this solution, your environment must meet these conditions:

  • Jenkins REST API can be accessed from slave
  • Slave must have access to credentials that allows to access the Jenkins Groovy script REST API.
Alex O
  • 7,746
  • 2
  • 25
  • 38
1

If you want to use a declarative approach I suggest you to use code like this.

pipeline {
    stages {
        // create separate stage only for problematic command
        stage("build") {
            steps {
                sh "command"
            }
            post {
                failure {
                    // set status
                    unstable 'rsync was unsuccessful'
                }
                always {
                    echo "Do something at the end of stage"
                }
            }
        }
    }

    post {
        always {
            echo "Do something at the end of pipeline"
        }
    }
}

In case you want to keep everything in one stage use catchError

pipeline {
    stages {
        // create separate stage only for problematic command
        stage("build") {
            steps {
                catchError(stageResult: 'UNSTABLE') {
                    sh "command"
                }
                sh "other command"
            }
        }
    }
}
Dumam
  • 11
  • 2
-1

One easy way to set a build as unstable, is in your "execute shell" block, run exit 13

-4

You can just call "exit 1", and the build will fail at that point and not continue. I wound up making a passthrough make function to handle it for me, and call safemake instead of make for building:

function safemake {
  make "$@"
  if [ "$?" -ne 0 ]; then
    echo "ERROR: BUILD FAILED"
    exit 1
  else
    echo "BUILD SUCCEEDED"
  fi
}
jessebs
  • 517
  • 3
  • 14
  • 13
    exit 1, will as far as I know only make the build fail. I do not want the build to fail, I want it to be marked unstable. – HNygard Nov 08 '12 at 00:48
  • 1
    See also https://stackoverflow.com/questions/36313216/why-is-testing-to-see-if-a-command-succeeded-or-not-an-anti-pattern -- the simple solution is just `if make "$@"; then echo "BUILD SUCCEEDED"; else rc=$?; echo "BUILD FAILED"; exit $rc; fi` – tripleee Jul 24 '19 at 17:31