-1

I'm in the process of implementing a little pre-commit hook that calls gitleaks protect prior to every commit.

This works well in a terminal but when trying to commit from within VSCode, a non-descriptive "Git: O" is returned (I assume this is simply the first line of gitleaks, part of its ascii logo).

As you can tell, I've tried multiple ways to have VSCode's Git module return a proper message upon exit of the submodule. However, nothing seems to work in that regard.

def eprint(*args, **kwargs):
    print(*args, file=sys.stderr, **kwargs)

exit_code = subprocess.run("gitleaks protect -v --staged -c gitleaks.toml",shell=True)
if exit_code.returncode == 1:
    eprint("This is a test")
    sys.exit("TEST")

How do I return an alert window in VSCode that displays a message whenever the subprocess is exiting with exit code 1?

EDIT:

Ok. This works somehow, but it fails in so far as that subprocess.run("gitleaks version", shell=True, stdout=dev_null, stderr=dev_null) only works with my WSL Bash whereas subprocess.run("gitleaks version", stdout=dev_null, stderr=dev_null) (without the shell=True) only works for my VSCode with Windows Git Bash.

Any way to make this portable, so FileNotFoundError is correctly thrown on both systems?

#!/usr/bin/env python3
# pylint: disable=C0116,W0613

import sys
import warnings
import subprocess

dev_null = subprocess.DEVNULL

def eprint(*args, **kwargs):
    print(*args, file=sys.stderr, **kwargs)

def gitleaks_installed():
    try:
        subprocess.run("gitleaks version", shell=True, stdout=dev_null, stderr=dev_null)
        return True
    except FileNotFoundError:
        return False

if gitleaks_installed():
    exit_code = subprocess.run("gitleaks protect -v --staged -c gitleaks.toml", shell=True, stdout=dev_null, stderr=dev_null)
    if exit_code.returncode == 1:
        eprint("gitleaks has detected sensitive information in your changes. Commit aborted.")
        subprocess.run("gitleaks protect -v --staged -c gitleaks.toml", shell=True)
        sys.exit(1)
else:
    eprint("gitleaks is not installed or in the PATH.")
    sys.exit(1)

EDIT2: NVM. The gitleaks_installed part doesn't work at all under WSL Bash. It either always True or always False, depending on whether I include shell=True.

Is there a better way to detect whether gitleaks is installed/in the PATH or not?

user237251
  • 211
  • 1
  • 10

2 Answers2

0

The object returned by subprocess.run is a CompletedProcess object, not the return code. You have to access its .returncode attribute to examine what it returned.

However, you might instead add check=True to have Python throw an error on failure.

You should also almost certainly get rid of the superfluous shell=True by parsing the command line into tokens yourself.

try:
    subprocess.run(
        ["gitleaks", "protect", "-v", "--staged", "-c", "gitleaks.toml"],
        check=True)
except subprocess.CalledProcessError:
    eprint("gitleaks has detected sensitive information in your changes. Commit aborted.")
    sys.exit(1)
except FileNotFoundError:
    eprint("gitleaks is not installed or in the PATH.")
    sys.exit(1)

This also avoids running the process a second time just to obtain the output. If you want to force your own message to go on top, capture the output and print it after your message (add capture_output=True and text=True).

You might also want to replace eprint with logging.warn, and perhaps return different exit codes in the different error scenarios. (Bash conventionally returns 127 when it can't find a binary, but that's just Bash.)

Running subprocess.run on a string, instead of a list, without shell=True weirdly happens to work on Windows, but my recommendation would be to always avoid trying to exploit that for convenience. Perhaps see also Actual meaning of shell=True in subprocess

tripleee
  • 175,061
  • 34
  • 275
  • 318
  • I'm aware I have to access the `.returncode` attribute. – user237251 Jan 20 '22 at 14:20
  • I'll check out your solution now. Thank you. – user237251 Jan 20 '22 at 14:21
  • I tested your solution. It works fine (I'll probably use <3.6 idioms though. As silly as that sort of backwards compatibility may seem). Now how do I get access to the original stdout though, after I captured the output? Since `subprocess.run` is in the try block, I can't access it from the except block. – user237251 Jan 20 '22 at 15:25
  • I figured it out. I can do `except subprocess.CalledProcessError as e:` then print via `print(e.output)`. – user237251 Jan 20 '22 at 16:11
  • Sorry about that and thanks for the comments. You will perhaps want to examine `e.stderr` too, or instead. – tripleee Jan 20 '22 at 18:22
-1

Not the most elegant solution, but this works. Instead of the FileNotFoundError exception, we're using the subprocess.run returncode.

#!/usr/bin/env python3
# pylint: disable=C0116,W0613

import sys
import subprocess

dev_null = subprocess.DEVNULL

def eprint(*args, **kwargs):
    print(*args, file=sys.stderr, **kwargs)

def gitleaks_installed():
    exit_code = subprocess.run("gitleaks version", shell=True, stdout=dev_null, stderr=dev_null)

    if exit_code.returncode == 0:
        return True
    else:
        return False

if gitleaks_installed():
    exit_code = subprocess.run("gitleaks protect -v --staged -c gitleaks.toml", shell=True, stdout=dev_null, stderr=dev_null)

    if exit_code.returncode == 1:
        eprint("gitleaks has detected sensitive information in your changes. Commit aborted.")
        subprocess.run("gitleaks protect -v --staged -c gitleaks.toml", shell=True)
        sys.exit(1)
else:
    eprint("gitleaks is not installed or in the PATH.")
    sys.exit(1)
user237251
  • 211
  • 1
  • 10