I am trying to generate a badge from PyLint output in a Gitlab CI script. Eventually, the job should fail if PyLint has a non-zero exit code. But before it does so, I want the badge to be created. So I have tried the following:
before_script:
- [...]
- mkdir -p public
script:
- pylint lib --disable R,missing-docstring,wrong-import-order --reports=y | tee public/pylint-report.txt
- export SUCCESS=${PIPESTATUS[0]}
- SCORE=$(tail -n 2 public/pylint-report.txt | grep -o -P "\d\d?\.\d+\/\d*" | head -1)
- echo "PyLint score ${SCORE}"
- python3.6 -m pybadges --left-text=PyLint --right-text=${SCORE} > public/pylint.svg
- exit ${SUCCESS}
artifacts:
when: always
[...]
This works fine if the PyLint exit code is 0:
$ mkdir -p public
$ pylint lib --disable R,missing-docstring,wrong-import-order --reports=y | tee public/pylint-report.txt; export SUCCESS=${PIPESTATUS[0]}
[Pylint report output]
$ SCORE=$(tail -n 2 public/pylint-report.txt | grep -o -P "\d\d?\.\d+\/\d*" | head -1)
$ echo "PyLint score ${SCORE}"
PyLint score 10.00/10
$ python3.6 -m pybadges --left-text=PyLint --right-text=${SCORE} > public/pylint.svg
$ exit ${SUCCESS}
Uploading artifacts...
public/pylint-report.txt: found 1 matching files
public/pylint.svg: found 1 matching files
Uploading artifacts to coordinator... ok id=XXX responseStatus=201 Created token=XXX
Job succeeded
However, when PyLint exits with non-zero, the script is aborted after the first line:
$ mkdir -p public
$ pylint lib --disable R,missing-docstring,wrong-import-order --reports=y | tee public/pylint-report.txt
[Pylint report output]
Uploading artifacts...
public/pylint-report.txt: found 1 matching files
WARNING: public/pylint.svg: no matching files
Uploading artifacts to coordinator... ok id=XXX responseStatus=201 Created token=XXX
ERROR: Job failed: exit code 1
To clarify: I want the job to fail, but I want to make sure the script always runs all the lines. Only the exit
command in the last line should determine the job status.
This runs in a container that uses the Bash.
I expected the tee
command to always exit with 0 so that the first script line should never fail. But that does not seem to be the case.
I have tried appending a || true
call to the first line, but then the following line, SUCCESS=${PIPESTATUS[0]}
, is always 0; perhaps this refers to the root cause.
Also, I have tried to append the export
call (now second line) to the first line, separated by a semicolon. Again, no difference even though I also expected the export
call always to exit with 0.
My question is hence: why can the first line of the script exit with a non-zero code? How do I prevent this?
Or maybe: is there an easier way to achieve the same goal?
Gitlab sets a bunch of "helpful" shell options you don't actually want. Among these is errexit
, aka set -e
, and pipefail
(which is generally a good idea, but in conjunction with set -e
means your script exits if any component of a pipeline fails).
To work around this one:
{ SUCCESS=0; pylint lib ...args... || SUCCESS=$?; } > >(tee public/pylint-report.txt)
We're sitting SUCCESS
directly here (no need for export
), so you don't need to refer to PIPESTATUS
later. Branching on the return value of a command marks that command as "checked", so it isn't treated as a failure for purposes of errexit
.
BTW, for background on set -e
and why it's something you really don't want, see BashFAQ #105.
As another aside, all-caps variable names are used for variables meaningful to the shell or POSIX-specified tools, whereas names with at least one lowercase character are reserved for application use and guaranteed not to collide. See https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html, keeping in mind that setting a shell variable will overwrite any preexisting like-named environment variable.