How CI Pipeline Scripts and Exit Codes Interact

What is An Exit Code?

An exit code, sometimes called a return code, is a number returned by a shell command or script. It tells the caller the status of the command execution. A successful command call usually returns an exit code of 0 and an exit code of any other value indicates an error.

How CI Pipeline Scripts Work

On the GitLab CI pipeline, each job has a script section with a command or an array of commands in that will be run in a shell by the pipeline runner. If any of the commands returns a non-zero exit code, the job fails and the rest of the commands will not be executed.

Determine Pipeline Job Results With Exit Codes

By using the characteristic of the script section, we can decide to pass or fail a CI pipeline from our own scripts by returning an exit code. For example, if you want to fail a pipeline due to the absence of a directory or the number of high severity warnings from static code analysis, all you have to do is to return a non-zero exit code from a script. The following bash script will fail the pipeline if the directory doesn’t exist, or continue on if it does.

#!/bin/bash

# If a directory exists
if [ -d "/path/to/dir" ]; then
	echo "Directory exists."
	exit 0
else
	echo "ERROR: Directory does not exist."
	exit 1
fi

The following Python script will fail the pipeline if there is at least one high severity warning from a code analyzer we have used.

#!/usr/bin/env python3

# If there is at least one high severity warning
if num_of_high_severity_warn > 1:
	print("Number of high severity warnings: {}".format(num_of_high_severity_warn))
	sys.exit(1)
else:
	print("No high severity warnings")
	sys.exit(0)

When a Non-Zero Exit Code Doesn’t Mean The Job Failed

There are times when scripts will return a non-zero value that shouldn’t cause your pipeline to fail. I found out about this when I had to move files and folders in a repository on a Windows machine. The script was running on Powershell and using Robocopy.

I added a Robocopy operation to my script and the pipeline failed every time it was called. I was puzzled because it worked just fine earlier when I tested the script outside of the pipeline job. I decided to capture the exit code to investigate what happened to the Robocopy call. It works, but the exit code is… 3?

# Powershell
PS> robocopy /MOVE /S /path/to/source /path/to/destination
PS> echo $?
False
PS> robocopy /MOVE /S /path/to/source /path/to/destination
PS> echo $lastexitcode
3

By referring to the Robocopy documentation, I found out that any Robocopy exit code that is less than 8 is considered a success and describes what operations occurred. In order to pass the pipeline, I have to let the pipeline knows that it is fine even if the Robocopy command returns a non-zero exit code.

( robocopy <Source> <Destination> [<Options>]  );if ($lastexitcode -lt 8){write-host 0}else{write-host $lastexitcode};

To do this, you need two commands in a single line so that the pipeline job script sees it as one command. The first command calls the Robocopy operation and the second command checks if the exit code is less than 8, returns 0 if true and the exit code if false because an error actually happened. With the single-line command hack above, the pipeline was able to continue after calling the Robocopy command because the non-zero value is never returned to the pipeline if the command was successful.

And this isn’t just for jumping through hoops to work with Windows, either! You may have a command that isn’t critical to the success of the pipeline job, or have an intentionally incorrect command to test and log the output, but not necessarily fail the job. In Linux, you can do a similar exit code capture to prevent a job from failing by doing something similar to this command:

<command> <arg> || exit_code=$?; if [ $exit_code -ne 0 ]; then echo "The exit code of the previous command is $exit_code"; fi;

By understanding how exit codes and pipelines interact, you now have the tools to customize your next CI pipeline.

Want more tips, hacks, and great conversation on embed ops? We host a weekly Lunch and Learn on a variety of topics from embedded on cellular, deep learning with robots, and more! Check out our current list of events and sign up for your seats – always no cost!

Posted in