Skip to content

Instantly share code, notes, and snippets.

@princebot
Last active October 28, 2016 18:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save princebot/760f6cbdbb2dc4021d3021a1dcf2f910 to your computer and use it in GitHub Desktop.
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.
#!/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