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 "dd?.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 "dd?.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?
Advertisement
Answer
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.