|
| 1 | +import difflib |
| 2 | +from itertools import islice |
| 3 | +import re |
| 4 | +import subprocess |
| 5 | + |
| 6 | +from ansible.utils.vault import VaultLib |
| 7 | + |
| 8 | +from utils import get_vault_password, green, red, cyan, intense |
| 9 | + |
| 10 | + |
| 11 | +def get_parts(git_diff_output): |
| 12 | + r = re.compile(r"^diff --git", re.MULTILINE) |
| 13 | + parts = [] |
| 14 | + locations = [i.start() for i in r.finditer(git_diff_output)] |
| 15 | + for i, location in enumerate(locations): |
| 16 | + is_last_item = i + 1 == len(locations) |
| 17 | + next_location = None if is_last_item else locations[i + 1] |
| 18 | + parts.append(git_diff_output[location:next_location]) |
| 19 | + return parts |
| 20 | + |
| 21 | + |
| 22 | +def get_old_sha(diff_part): |
| 23 | + """ |
| 24 | + Returns the SHA for the original file that was changed in a diff part. |
| 25 | + """ |
| 26 | + r = re.compile(r'index ([a-fA-F\d]*)') |
| 27 | + return r.search(diff_part).groups()[0] |
| 28 | + |
| 29 | + |
| 30 | +def get_old_filename(diff_part): |
| 31 | + """ |
| 32 | + Returns the filename for the original file that was changed in a diff part. |
| 33 | + """ |
| 34 | + r = re.compile(r'^--- a/(.*)', re.MULTILINE) |
| 35 | + return r.search(diff_part).groups()[0] |
| 36 | + |
| 37 | + |
| 38 | +def get_old_contents(sha, filename): |
| 39 | + return subprocess.check_output(['git', 'show', sha, '--', filename]) |
| 40 | + |
| 41 | + |
| 42 | +def get_new_filename(diff_part): |
| 43 | + """ |
| 44 | + Returns the filename for the updated file in a diff part. |
| 45 | + """ |
| 46 | + r = re.compile(r'^\+\+\+ b/(.*)', re.MULTILINE) |
| 47 | + return r.search(diff_part).groups()[0] |
| 48 | + |
| 49 | + |
| 50 | +def get_new_contents(filename): |
| 51 | + with open(filename, 'rb') as f: |
| 52 | + return f.read() |
| 53 | + |
| 54 | + |
| 55 | +def get_head(diff_part): |
| 56 | + """ |
| 57 | + Returns the pre-content, non-chunk headers of a diff part. |
| 58 | +
|
| 59 | + E.g. |
| 60 | +
|
| 61 | + diff --git a/group_vars/foo b/group_vars/foo |
| 62 | + index 6b9eef7..eb9fb09 100644 |
| 63 | + --- a/group_vars/foo |
| 64 | + +++ b/group_vars/foo |
| 65 | + """ |
| 66 | + return '\n'.join(diff_part.split('\n')[:4]) + '\n' |
| 67 | + |
| 68 | + |
| 69 | +def get_contents(diff_part): |
| 70 | + """ |
| 71 | + Returns a tuple of old content and new content. |
| 72 | + """ |
| 73 | + old_sha = get_old_sha(diff_part) |
| 74 | + old_filename = get_old_filename(diff_part) |
| 75 | + old_contents = get_old_contents(old_sha, old_filename) |
| 76 | + new_filename = get_new_filename(diff_part) |
| 77 | + new_contents = get_new_contents(new_filename) |
| 78 | + return old_contents, new_contents |
| 79 | + |
| 80 | + |
| 81 | +def decrypt_diff(diff_part, password_file=None): |
| 82 | + """ |
| 83 | + Diff part is a string in the format: |
| 84 | +
|
| 85 | + diff --git a/group_vars/foo b/group_vars/foo |
| 86 | + index c09080b..0d803bb 100644 |
| 87 | + --- a/group_vars/foo |
| 88 | + +++ b/group_vars/foo |
| 89 | + @@ -1,32 +1,33 @@ |
| 90 | + $ANSIBLE_VAULT;1.1;AES256 |
| 91 | + -61316662363730313230626432303662316330323064373866616436623565613033396539366263 |
| 92 | + -383632656663356364656531653039333965 |
| 93 | + +30393563383639396563623339383936613866326332383162306532653239636166633162323236 |
| 94 | + +62376161626137626133 |
| 95 | +
|
| 96 | + Returns a tuple of decrypted old contents and decrypted new contents. |
| 97 | + """ |
| 98 | + vault = VaultLib(get_vault_password(password_file)) |
| 99 | + old_contents, new_contents = get_contents(diff_part) |
| 100 | + if vault.is_encrypted(old_contents): |
| 101 | + old_contents = vault.decrypt(old_contents) |
| 102 | + if vault.is_encrypted(new_contents): |
| 103 | + new_contents = vault.decrypt(new_contents) |
| 104 | + return old_contents, new_contents |
| 105 | + |
| 106 | + |
| 107 | +def show_unencrypted_diff(diff_part, password_file=None): |
| 108 | + intense(get_head(diff_part).strip()) |
| 109 | + old, new = decrypt_diff(diff_part, password_file) |
| 110 | + diff = difflib.unified_diff(old.split('\n'), new.split('\n'), lineterm='') |
| 111 | + # ... we'll take the git filenames from git's diff output rather than |
| 112 | + # ... difflib |
| 113 | + for line in islice(diff, 2, None): |
| 114 | + if line.startswith('-'): |
| 115 | + red(line) |
| 116 | + elif line.startswith('+'): |
| 117 | + green(line) |
| 118 | + elif line.startswith('@@'): |
| 119 | + cyan(line) |
| 120 | + else: |
| 121 | + print line |
| 122 | + |
| 123 | + |
| 124 | +def show_unencrypted_diffs(git_diff_output, password_file=None): |
| 125 | + parts = get_parts(git_diff_output) |
| 126 | + for part in parts: |
| 127 | + show_unencrypted_diff(part, password_file) |
0 commit comments