Last active
October 28, 2016 18:32
-
-
Save princebot/760f6cbdbb2dc4021d3021a1dcf2f910 to your computer and use it in GitHub Desktop.
(python) A Git pre-commit hook that rejects commits to Ansible projects that contain unencrypted Ansible Vault files.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# -*- coding: utf-8 -*- | |
"""Reject commits to Ansible projects that contain unencrypted vault files. | |
I realized pretty quick that a simple shell script could so the same thing (see | |
https://gist.github.com/princebot/2bd84b3b344168db22ce1259241f4a88) — but I | |
finished this anyway for funsies. | |
Also for funsies, I wanted to see what static classes in Python might look like. | |
Verdict? | |
Unpythonic as fuck. :-D | |
""" | |
from __future__ import (absolute_import, division, print_function, | |
unicode_literals) | |
import os | |
import sys | |
class UnencryptedFileError(Exception): | |
def __str__(self): | |
path = self.args[0] if self.args else 'file' | |
return '{} is not encrypted with ansible-vault'.format(path) | |
class Tattle(object): | |
"""Static class for finding unencrypted vault files.""" | |
VAULT_FILE_HEADER = '$ANSIBLE_VAULT' | |
def __init__(self): | |
raise TypeError('static class Tattle cannot be instantiated') | |
@classmethod | |
def tattle(cls, path): | |
"""Recursively search a git repo for unencrypted vault files. | |
If path is a file, this calls check_file; if path is a directory, this | |
calls itself again for each entry in that directory. | |
Args: | |
path: a file or directory path | |
Raises: | |
CommitError: A file named `vault` is not encrypted. | |
""" | |
path = os.path.relpath(path) | |
if os.path.isfile(path): | |
cls.check_file(path) | |
if os.path.basename(path) == '.git': | |
return | |
for name in os.listdir(path): | |
relpath = os.path.join(path, name) | |
if os.path.isdir(relpath): | |
if not os.path.islink(relpath): | |
cls.tattle(relpath) | |
continue | |
cls.check_file(relpath) | |
@classmethod | |
def check_file(cls, filepath): | |
"""Verify a file named `vault` is encrypted with ansible-vault. | |
This assumes the project follows a common best practice where regular | |
var files point to encrypted var files (see the documentation at | |
https://docs.ansible.com/ansible/playbooks_best_practices.html). | |
Args: | |
filepath: the file to check | |
Raises: | |
CommitError: A file named `vault` is not encrypted. | |
""" | |
if os.path.basename(filepath) != 'vault': | |
return | |
with open(filepath) as f: | |
for line in f: | |
if not line.strip(): | |
continue | |
if not line.startswith(cls.VAULT_FILE_HEADER): | |
raise UnencryptedFileError(filepath) | |
if __name__ == '__main__': | |
try: | |
Tattle.tattle('.') | |
except UnencryptedFileError as e: | |
sys.exit('Commit rejected: {}'.format(e)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment