Initial commit

This commit is contained in:
2026-04-30 14:55:20 +02:00
commit 50b3e867f9
3 changed files with 309 additions and 0 deletions
+189
View File
@@ -0,0 +1,189 @@
#!/usr/bin/env python3
import os
import sys
import tempfile
import subprocess
from pathlib import Path
import hvac
from git import Repo
from dotenv import load_dotenv
from prompt_toolkit.shortcuts import checkboxlist_dialog, radiolist_dialog, message_dialog
# from ansible.parsing.dataloader import DataLoader
# from ansible.inventory.manager import InventoryManager
VERSION = "0.0.14"
# ================= LOAD ENV =================
load_dotenv()
VAULT_ADDR = os.getenv("VAULT_ADDR", "https://vault.sectorq.eu")
VAULT_TOKEN = os.getenv("VAULT_TOKEN") or os.environ["VAULT_TOKEN"]
GITLAB_URL = os.getenv("GITLAB_URL", "https://gitlab.sectorq.eu/jaydee/ansible.git")
GITLAB_BRANCH = os.getenv("GITLAB_BRANCH", "main")
VAULT_GITLAB_PATH = os.getenv("VAULT_GITLAB_PATH", "secret/data/gitlab")
VAULT_GITLAB_FIELD = os.getenv("VAULT_GITLAB_FIELD", "token")
VAULT_ANSIBLE_PATH = os.getenv("VAULT_ANSIBLE_SECRET", "secret/data/ansible")
VAULT_ANSIBLE_VAULT_FIELD = os.getenv("VAULT_ANSIBLE_VAULT_FIELD", "vault")
VAULT_ANSIBLE_SSH_FIELD = os.getenv("VAULT_ANSIBLE_SSH_FIELD", "ssh_key")
CLONE_DIR = Path(os.getenv("CLONE_DIR", "/tmp/ansible-repo"))
# ================= VAULT =================
def get_vault_client():
if not VAULT_ADDR or not VAULT_TOKEN:
sys.exit("Vault config missing in .env")
client = hvac.Client(url=VAULT_ADDR, token=VAULT_TOKEN)
if not client.is_authenticated():
sys.exit("Vault authentication failed")
return client
def read_kv2_secret(client, path, field):
clean_path = path.replace("secret/data/", "")
secret = client.secrets.kv.v2.read_secret_version(path=clean_path)
return secret["data"]["data"][field]
# ================= GIT =================
def clone_or_update_repo(repo_url, branch, token):
repo_url_auth = repo_url.replace("https://", f"https://oauth2:{token}@")
if CLONE_DIR.exists():
print("Updating repository...")
repo = Repo(CLONE_DIR)
repo.git.checkout(branch)
repo.remotes.origin.pull()
else:
print("Cloning repository...")
Repo.clone_from(repo_url_auth, CLONE_DIR, branch=branch)
# ================= MENU =================
def select_inventory():
inventories = ["hosts_init.yml", "hosts_roles.yml"]
result = radiolist_dialog(
title=f"Inventory Selection - Version {VERSION}",
text="Select inventory file:",
values=[(inv, inv) for inv in inventories],
).run()
if not result:
sys.exit(0)
return result
def select_roles():
roles_path = CLONE_DIR / "roles"
if not roles_path.exists():
return []
roles = sorted([r.name for r in roles_path.iterdir() if r.is_dir()])
result = checkboxlist_dialog(
title="Role Selection",
text="Select roles to execute (for info/log only):",
values=[(r, r) for r in roles],
).run()
return result or []
def select_limit():
# Path to your inventory file
inventory_path = f"{CLONE_DIR}/hosts_roles.yml"
# loader = DataLoader()
# inventory = InventoryManager(loader=loader, sources=[inventory_path])
# Get all hosts
# hosts = inventory.get_hosts()
# for host in hosts:
# print(host.name)
limits = [
("all", "All hosts"),
("m-server.home.lan", "m-server"),
("morefine.home.lan", "morefine"),
("asus.home.lan", "asus"),
("nas.home.lan", "nas"),
("rpi4.home.lan", "rpi4"),
("rpi5.home.lan", "rpi5"),
("debian13", "Debian 13"),
("ubuntu24", "Ubuntu 24"),
("ubuntu24s", "Ubuntu 24 Server"),
("ubuntu26s", "Ubuntu 26 Server"),
("rocky9", "Rocky 9"),
("rocky10", "Rocky 10"),
("alma10", "AlmaLinux 10"),
("custom", "Custom hosts (comma-separated)"),
]
result = checkboxlist_dialog(
title="Inventory Limit",
text="Select host limit:",
values=limits,
).run()
if result and "custom" in result:
custom_hosts = input("Enter custom hosts (comma-separated): ")
result = [r for r in result if r != "custom"] + [custom_hosts]
if not result:
sys.exit(0)
return result
# ================= RUN ANSIBLE =================
def run_ansible_playbook(inventory, limit, roles=None, vault_password=None, ssh_key=None):
# create ssh key file
content = "Hello world"
file_path = f"{CLONE_DIR}/ssh_key.pem"
# create file
with open(file_path, "w") as f:
f.write(ssh_key + "\n")
# set permissions (rw-r--r--)
os.chmod(file_path, 0o600)
playbook_file = CLONE_DIR / "all.yml"
if not playbook_file.exists():
sys.exit(f"Playbook {playbook_file} not found!")
# Write vault password to temp file
with tempfile.NamedTemporaryFile(mode="w", delete=False) as tmp:
tmp.write(vault_password)
tmp.flush()
vault_file = tmp.name
cmd = [
"ansible-playbook",
"-i", inventory,
str(playbook_file),
"--vault-password-file", vault_file,
]
if limit != "all":
cmd.extend(["--limit", ",".join(limit)])
if roles:
cmd.extend(["--tags", ",".join(roles)])
print(cmd)
try:
subprocess.run(cmd, cwd=CLONE_DIR, check=True)
except:
print("not all jobs finished")
finally:
os.unlink(vault_file)
# ================= MAIN =================
def main():
print("Connecting to Vault...")
client = get_vault_client()
print("Retrieving secrets...")
gitlab_token = read_kv2_secret(client, VAULT_GITLAB_PATH, VAULT_GITLAB_FIELD)
ansible_vault_pass = read_kv2_secret(client, VAULT_ANSIBLE_PATH, VAULT_ANSIBLE_VAULT_FIELD)
ansible_ssh_key = read_kv2_secret(client, VAULT_ANSIBLE_PATH, VAULT_ANSIBLE_SSH_FIELD)
clone_or_update_repo(GITLAB_URL, GITLAB_BRANCH, gitlab_token)
inventory = select_inventory()
roles = select_roles() # purely informational
limit = select_limit()
print(f"Selected roles: {roles}")
print(f"Running playbook all.yml with inventory {inventory} and limit {limit}")
run_ansible_playbook(inventory, limit, roles, ansible_vault_pass, ansible_ssh_key)
print("Execution finished successfully.")
if __name__ == "__main__":
main()