#!/usr/bin/env python3 import argparse import os import re import subprocess import sys import textwrap from pathlib import Path ROOT = Path(__file__).resolve().parent KEY = re.compile(r"^(?P[ \t]*)(?:-\s+)?(?P[\w.-]+):\s*!vault\s*\|-?\s*$") HEADER = "$ANSIBLE_VAULT" def password_file(): override = os.environ.get("ANSIBLE_VAULT_PASSWORD_FILE") path = Path(override) if override else ROOT / ".vault-password" if not path.is_file(): sys.exit(f"vault password file not found: {path}") return str(path) def decrypt(blob, pw): result = subprocess.run( ["ansible-vault", "decrypt", "--vault-password-file", pw, "--output", "-"], input=blob, capture_output=True, text=True, ) if result.returncode != 0: return None, result.stderr.strip() return result.stdout, None def extract(text): lines = text.splitlines() found = [] i = 0 while i < len(lines): match = KEY.match(lines[i]) if not match: i += 1 continue indent = len(match.group("indent").expandtabs(8)) body = [] i += 1 while i < len(lines): line = lines[i] if not line.strip(): body.append("") i += 1 continue width = len(line.expandtabs(8)) - len(line.expandtabs(8).lstrip()) if width <= indent: break body.append(line) i += 1 blob = textwrap.dedent("\n".join(body)).strip() if blob.startswith(HEADER): found.append((match.group("name"), blob)) return found def show(name, value): value = value.rstrip("\n") if "\n" in value: print(f"{name}:") print(textwrap.indent(value, " ")) else: print(f"{name}: {value}") def main(): parser = argparse.ArgumentParser( description="Decrypt inline !vault variables in a playbook or vars file." ) parser.add_argument("file", help="path to the file, or - to read a blob from stdin") parser.add_argument("names", nargs="*", help="only show these variable names") args = parser.parse_args() pw = password_file() if args.file == "-": blob = textwrap.dedent(sys.stdin.read()).strip() if not blob.startswith(HEADER): sys.exit("stdin does not contain a vault blob") value, error = decrypt(blob, pw) if error: sys.exit(error) print(value.rstrip("\n")) return path = Path(args.file) text = path.read_text() if text.lstrip().startswith(HEADER): value, error = decrypt(text.strip(), pw) if error: sys.exit(error) sys.stdout.write(value) return variables = extract(text) if args.names: variables = [(n, b) for n, b in variables if n in args.names] if not variables: sys.exit(f"no inline vault variables found in {path}") failures = 0 for name, blob in variables: value, error = decrypt(blob, pw) if error: print(f"{name}: ", file=sys.stderr) failures += 1 continue show(name, value) sys.exit(1 if failures else 0) if __name__ == "__main__": main()