Skip to content

Instantly share code, notes, and snippets.

@depau
Last active May 31, 2025 22:37
Show Gist options
  • Save depau/44ac1e67ea9939dd1770907ea5231a6f to your computer and use it in GitHub Desktop.
Save depau/44ac1e67ea9939dd1770907ea5231a6f to your computer and use it in GitHub Desktop.
Homebrew "ask vault pass" for Semaphore UI with Ansible

Re-implementing "ask vault pass" with Semaphore UI surveys

This simple Ansible client script allows you to input the vault password from Semaphore's UI when launching the task template.

Set up

  1. Drop the Python script into your repo and make it executable
  2. Add a survey variable named vault#VAULT-ID (where VAULT-ID is your vault ID; use default if you are using only one vault without an ID); make it required
  3. Add a vault, setting the type to "Client script" and the path to the script path in your repo

License

MIT license

#!/usr/bin/env python3
import argparse
import json
import os
import sys
from typing import List, Optional
def parse_args() -> argparse.Namespace:
p = argparse.ArgumentParser(
description="Fetch vault password from Semaphore survey JSON")
p.add_argument(
"--vault-id",
dest="vault_id",
required=True,
help="The vault-id supplied by Ansible (e.g. 'prod' in --vault-id prod)",
)
return p.parse_args()
def read_cmdline(pid: int) -> List[str]:
"""Return the argv of *pid* as a list, using /proc."""
path = f"/proc/{pid}/cmdline"
try:
with open(path, "rb") as fh:
raw = fh.read()
except FileNotFoundError:
sys.stderr.write(f"[vault-client] Parent process {pid} no longer exists\n")
sys.exit(1)
argv = raw.decode(errors="replace").split("\0")
if argv and argv[-1] == "":
argv.pop() # final empty after last NUL
return argv
def iter_extra_vars(argv: List[str]):
"""Yield every --extra-vars value (handles spaced and = variants)."""
i = 0
while i < len(argv):
tok = argv[i]
if tok == "--extra-vars":
if i + 1 < len(argv):
yield argv[i + 1]
i += 2
continue
if tok.startswith("--extra-vars="):
yield tok.split("=", 1)[1]
i += 1
def maybe_password(extra: str, vault_key: str) -> Optional[str]:
"""Return password if *extra* is JSON containing *vault_key*, else None."""
if extra.startswith("@"):
return None # file reference; skip
if not extra.lstrip().startswith("{"):
return None # not JSON; skip
try:
data = json.loads(extra)
except json.JSONDecodeError:
return None
return data.get(vault_key)
def main() -> int:
args = parse_args()
vault_key = f"vault#{args.vault_id}"
ppid = os.getppid()
argv = read_cmdline(ppid)
for extra in iter_extra_vars(argv):
pwd = maybe_password(extra, vault_key)
if pwd is not None:
# Ansible accepts either with or without the trailing newline.
print(pwd)
return 0
sys.stderr.write(
f"[vault-client] Key '{vault_key}' not found in parent's --extra-vars\n"
)
return 1
if __name__ == "__main__":
sys.exit(main())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment