Initial commit — agent_ansible v2.0

This commit is contained in:
2026-03-09 09:01:34 +00:00
commit f2b0dad2d2
14 changed files with 723 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
venv/
__pycache__/
*.pyc
*.pyo
*.db
*.log
data/
*.egg-info/
.vault_pass
config/config.json
+87
View File
@@ -0,0 +1,87 @@
#!/usr/bin/env python3
"""
Agent Ansible — Automatisation d'infrastructure via Ansible.
Gère playbooks, commandes ad-hoc, inventaire, galaxy, vault.
"""
import os
import sys
import logging
sys.path.insert(0, "/opt")
from agents_core import BaseAgent, AgentContext, Message, MessageType
logger = logging.getLogger(__name__)
class AgentAnsible(BaseAgent):
AGENT_TYPE = "ansible"
DESCRIPTION = (
"Automatisation infrastructure via Ansible : "
"playbooks, commandes ad-hoc, gestion inventaire, galaxy, vault"
)
DEFAULT_CONFIG_PATH = "/opt/agent_ansible/config/config.json"
def get_skills_dir(self) -> str:
return os.path.join(os.path.dirname(__file__), "skills")
def on_start(self):
self.mqtt.send_to("nexus", f"Agent Ansible ({self.agent_id}) en ligne.")
def setup_extra_subscriptions(self):
self.mqtt.subscribe(
f"agents/{self.agent_id}/control",
self._on_control,
)
def _on_control(self, msg, topic: str):
from agents_core.message_bus import Message as Msg
payload = msg.payload if isinstance(msg, Msg) else str(msg)
result = self._handle_system_command(payload)
if result and isinstance(msg, Msg):
self.mqtt.reply(msg, result)
def handle_custom_command(self, cmd: str, args: str, source_msg=None):
if cmd == "report":
return self._build_report()
if cmd == "update":
return self._self_update()
return f"Commande inconnue : /{cmd}"
def on_broadcast(self, msg: Message):
if "status" in str(msg.payload).lower():
self.mqtt.reply(msg, self._build_report())
def _build_report(self) -> str:
import subprocess
stats = self.queue.daily_stats()
lines = [f"── Rapport {self.agent_id} ──"]
lines.append(
f"Tâches : {stats['total']} total / "
f"{stats['completed']} OK / {stats['failed']} erreurs / "
f"durée moy. {stats['avg_duration_s']}s"
)
try:
version = subprocess.check_output(
"ansible --version | head -1", shell=True, text=True
).strip()
lines.append(f"Ansible : {version}")
except Exception:
pass
return "\n".join(lines)
def _self_update(self) -> str:
import subprocess
try:
out = subprocess.check_output(
"cd /opt/agent_ansible && git pull",
shell=True, text=True, stderr=subprocess.STDOUT
)
subprocess.Popen(["systemctl", "restart", self.agent_id])
return f"Mise à jour :\n{out}\nRedémarrage..."
except subprocess.CalledProcessError as e:
return f"Erreur : {e.output}"
if __name__ == "__main__":
AgentAnsible().run()
+18
View File
@@ -0,0 +1,18 @@
[Unit]
Description=Agent Ansible — Automatisation infrastructure
After=network.target mosquitto.service
Wants=mosquitto.service
[Service]
Type=simple
User=root
WorkingDirectory=/opt/agent_ansible
ExecStart=/opt/agent_ansible/venv/bin/python /opt/agent_ansible/agent_ansible.py
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=agent-ansible
[Install]
WantedBy=multi-user.target
+15
View File
@@ -0,0 +1,15 @@
[defaults]
inventory = /opt/agent_ansible/inventory/hosts
roles_path = /opt/agent_ansible/roles
host_key_checking = False
retry_files_enabled = False
stdout_callback = yaml
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /opt/agent_ansible/data/facts_cache
fact_caching_timeout = 86400
forks = 10
[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
pipelining = True
+31
View File
@@ -0,0 +1,31 @@
Tu es un agent d'automatisation Ansible. Tu gères l'infrastructure via des playbooks et des commandes ad-hoc.
Tu reçois des instructions via MQTT (depuis Nexus) ou XMPP.
## Tes skills et quand les utiliser
- **playbook** : exécuter, créer, vérifier des playbooks Ansible
- **adhoc** : commandes ad-hoc rapides sur un ou plusieurs hôtes
- **inventory** : gérer l'inventaire (ajouter/supprimer hôtes et groupes, vérifier la connectivité)
- **galaxy** : installer des rôles et collections depuis Ansible Galaxy
- **vault** : chiffrer/déchiffrer des secrets Ansible
- **shell** : fallback pour commandes ansible ou bash directes
- **mqtt_send** : envoyer un résultat ou message à un autre agent
## Workflow typique
1. Vérifier la connectivité : SKILL:adhoc ARGS:all ping
2. Exécuter un playbook : SKILL:playbook ARGS:run site.yml
3. Résultat → renvoyé automatiquement à Nexus
## Règles
- Toujours faire un `check` (dry-run) avant un playbook destructif
- Vérifier la connectivité des hôtes avant d'exécuter
- Pour les secrets, utiliser Ansible Vault
- Réponds en français, sois concis
- Si un playbook échoue, analyse la sortie et propose une correction
## Communication MQTT
Tu peux envoyer des messages à d'autres agents :
SKILL:mqtt_send ARGS:agents/nexus/inbox | résultat ou information importante
+13
View File
@@ -0,0 +1,13 @@
# Inventaire Ansible
# Ajouter vos hôtes ici ou via : SKILL:inventory ARGS:add-host <ip> <groupe>
[local]
localhost ansible_connection=local
# Exemple :
# [webservers]
# 192.168.1.10 ansible_user=root
# 192.168.1.11 ansible_user=root
#
# [dbservers]
# 192.168.1.20 ansible_user=root ansible_ssh_private_key_file=/root/.ssh/id_rsa
+3
View File
@@ -0,0 +1,3 @@
agents_core @ file:///opt/agents_core
ansible>=8.0
requests>=2.28
+76
View File
@@ -0,0 +1,76 @@
"""
Skill ADHOC — commandes Ansible ad-hoc sur l'inventaire.
Usage LLM :
SKILL:adhoc ARGS:<hôtes> <module> [args]
SKILL:adhoc ARGS:all ping
SKILL:adhoc ARGS:webservers shell cmd="uptime"
SKILL:adhoc ARGS:192.168.1.10 command cmd="df -h"
SKILL:adhoc ARGS:all setup filter="ansible_memory_mb"
SKILL:adhoc ARGS:all apt name=nginx state=present
SKILL:adhoc ARGS:all service name=nginx state=restarted
"""
import os
import subprocess
DESCRIPTION = "Commandes Ansible ad-hoc sur les hôtes de l'inventaire"
USAGE = "SKILL:adhoc ARGS:<hôtes> <module> [args] — Ex: all ping | webservers shell cmd='uptime' | all apt name=nginx state=present"
INVENTORY = "/opt/agent_ansible/inventory/hosts"
ANSIBLE_CFG = "/opt/agent_ansible/ansible.cfg"
# Modules courants et leurs alias simplifiés
MODULE_ALIASES = {
"ping": ("ping", ""),
"uptime": ("shell", "cmd='uptime'"),
"df": ("shell", "cmd='df -h'"),
"free": ("shell", "cmd='free -h'"),
"whoami": ("shell", "cmd='whoami'"),
"reboot": ("reboot", ""),
"gather": ("setup", ""),
}
def _run(cmd: str, timeout: int = 120) -> str:
env = os.environ.copy()
env["ANSIBLE_FORCE_COLOR"] = "0"
env["ANSIBLE_HOST_KEY_CHECKING"] = "False"
if os.path.exists(ANSIBLE_CFG):
env["ANSIBLE_CONFIG"] = ANSIBLE_CFG
try:
result = subprocess.run(
cmd, shell=True, text=True,
capture_output=True, timeout=timeout, env=env
)
out = (result.stdout + result.stderr).strip()
return out[:5000] if out else "(aucune sortie)"
except subprocess.TimeoutExpired:
return f"Timeout ({timeout}s)"
except Exception as e:
return str(e)
def run(args: str, context) -> str:
parts = args.strip().split(None, 2)
if len(parts) < 2:
return (
"Format : SKILL:adhoc ARGS:<hôtes> <module> [args]\n"
"Exemple : all ping | webservers shell cmd='df -h'"
)
hosts = parts[0]
module = parts[1].lower()
margs = parts[2] if len(parts) > 2 else ""
# Alias pratiques
if module in MODULE_ALIASES and not margs:
module, margs = MODULE_ALIASES[module]
inv_flag = f"-i {INVENTORY}" if os.path.exists(INVENTORY) else "-i localhost,"
# Construction de la commande
cmd = f"ansible {hosts} {inv_flag} -m {module}"
if margs:
cmd += f" -a \"{margs}\""
return _run(cmd)
+83
View File
@@ -0,0 +1,83 @@
"""
Skill GALAXY — gestion des rôles et collections Ansible Galaxy.
Usage LLM :
SKILL:galaxy ARGS:install <role_ou_collection>
SKILL:galaxy ARGS:list
SKILL:galaxy ARGS:remove <role>
SKILL:galaxy ARGS:search <terme>
SKILL:galaxy ARGS:info <role>
SKILL:galaxy ARGS:init <nom_role> (crée un squelette de rôle)
SKILL:galaxy ARGS:collection install <nom>
SKILL:galaxy ARGS:collection list
"""
import subprocess
import os
DESCRIPTION = "Gestion des rôles et collections Ansible Galaxy"
USAGE = "SKILL:galaxy ARGS:install <role> | list | remove <role> | search <terme> | info <role> | init <nom> | collection install <nom>"
ROLES_DIR = "/opt/agent_ansible/roles"
def _run(cmd: str, timeout: int = 60) -> str:
env = os.environ.copy()
env["ANSIBLE_FORCE_COLOR"] = "0"
try:
result = subprocess.run(
cmd, shell=True, text=True,
capture_output=True, timeout=timeout, env=env
)
out = (result.stdout + result.stderr).strip()
return out[:4000] if out else "(aucune sortie)"
except subprocess.TimeoutExpired:
return f"Timeout ({timeout}s)"
except Exception as e:
return str(e)
def run(args: str, context) -> str:
parts = args.strip().split(None, 1)
action = parts[0].lower() if parts else "list"
rest = parts[1] if len(parts) > 1 else ""
if action == "install":
if not rest:
return "Précise le rôle ou la collection à installer."
return _run(f"ansible-galaxy install {rest} --roles-path {ROLES_DIR}", timeout=120)
if action == "list":
return _run(f"ansible-galaxy list --roles-path {ROLES_DIR}")
if action == "remove":
if not rest:
return "Précise le rôle à supprimer."
return _run(f"ansible-galaxy remove {rest} --roles-path {ROLES_DIR}")
if action == "search":
if not rest:
return "Précise le terme de recherche."
return _run(f"ansible-galaxy search {rest} | head -30")
if action == "info":
if not rest:
return "Précise le rôle."
return _run(f"ansible-galaxy info {rest}")
if action == "init":
if not rest:
return "Précise le nom du rôle à créer."
os.makedirs(ROLES_DIR, exist_ok=True)
return _run(f"ansible-galaxy init {rest} --init-path {ROLES_DIR}")
if action == "collection":
parts2 = rest.split(None, 1)
sub = parts2[0].lower() if parts2 else "list"
carg = parts2[1] if len(parts2) > 1 else ""
if sub == "install":
return _run(f"ansible-galaxy collection install {carg}", timeout=120)
if sub == "list":
return _run("ansible-galaxy collection list")
return f"Sous-commande inconnue : collection {sub}"
return "Action inconnue. Disponible : install, list, remove, search, info, init, collection"
+146
View File
@@ -0,0 +1,146 @@
"""
Skill INVENTORY — gestion de l'inventaire Ansible.
Usage LLM :
SKILL:inventory ARGS:show
SKILL:inventory ARGS:list (hôtes avec variables)
SKILL:inventory ARGS:hosts (liste des hôtes)
SKILL:inventory ARGS:groups (liste des groupes)
SKILL:inventory ARGS:add-host <ip> [groupe] [vars...]
SKILL:inventory ARGS:add-group <nom>
SKILL:inventory ARGS:remove-host <ip_ou_nom>
SKILL:inventory ARGS:ping-all (teste la connectivité)
SKILL:inventory ARGS:facts <hôte> (collecte les facts)
"""
import os
import re
import subprocess
DESCRIPTION = "Gestion de l'inventaire Ansible (hôtes, groupes, facts)"
USAGE = "SKILL:inventory ARGS:show | list | hosts | groups | add-host <ip> [groupe] | remove-host <hôte> | ping-all | facts <hôte>"
INVENTORY_FILE = "/opt/agent_ansible/inventory/hosts"
ANSIBLE_CFG = "/opt/agent_ansible/ansible.cfg"
def _run(cmd: str, timeout: int = 60) -> str:
env = os.environ.copy()
env["ANSIBLE_FORCE_COLOR"] = "0"
env["ANSIBLE_HOST_KEY_CHECKING"] = "False"
if os.path.exists(ANSIBLE_CFG):
env["ANSIBLE_CONFIG"] = ANSIBLE_CFG
try:
result = subprocess.run(
cmd, shell=True, text=True,
capture_output=True, timeout=timeout, env=env
)
out = (result.stdout + result.stderr).strip()
return out[:4000] if out else "(aucune sortie)"
except subprocess.TimeoutExpired:
return f"Timeout ({timeout}s)"
except Exception as e:
return str(e)
def _read_inventory() -> str:
if not os.path.exists(INVENTORY_FILE):
return ""
with open(INVENTORY_FILE) as f:
return f.read()
def _write_inventory(content: str):
os.makedirs(os.path.dirname(INVENTORY_FILE), exist_ok=True)
with open(INVENTORY_FILE, "w") as f:
f.write(content)
def run(args: str, context) -> str:
parts = args.strip().split(None, 1)
action = parts[0].lower() if parts else "show"
rest = parts[1] if len(parts) > 1 else ""
if action == "show":
content = _read_inventory()
return content if content else f"Inventaire vide ou inexistant : {INVENTORY_FILE}"
if action == "list":
return _run(f"ansible-inventory -i {INVENTORY_FILE} --list 2>/dev/null || cat {INVENTORY_FILE}")
if action == "hosts":
return _run(f"ansible-inventory -i {INVENTORY_FILE} --list-hosts all 2>/dev/null")
if action == "groups":
return _run(f"ansible-inventory -i {INVENTORY_FILE} --list 2>/dev/null | python3 -c \"import json,sys; d=json.load(sys.stdin); print('\\n'.join(k for k in d if not k.startswith('_')))\"")
if action == "add-host":
rparts = rest.split()
if not rparts:
return "Précise l'IP ou le nom d'hôte."
host = rparts[0]
group = rparts[1] if len(rparts) > 1 else "all"
vars_ = " ".join(rparts[2:]) if len(rparts) > 2 else ""
content = _read_inventory()
lines = content.splitlines() if content else []
# Vérifie si le groupe existe déjà
group_header = f"[{group}]"
if group_header not in content:
lines.append(f"\n{group_header}")
# Ajoute l'hôte sous le groupe
entry = f"{host} {vars_}".strip()
if entry in content:
return f"Hôte '{host}' déjà dans l'inventaire."
# Insère après l'en-tête de groupe
new_lines = []
inserted = False
for line in lines:
new_lines.append(line)
if line.strip() == group_header and not inserted:
new_lines.append(entry)
inserted = True
if not inserted:
new_lines.append(entry)
_write_inventory("\n".join(new_lines) + "\n")
return f"Hôte '{host}' ajouté au groupe '{group}'."
if action == "add-group":
group = rest.strip()
if not group:
return "Précise le nom du groupe."
content = _read_inventory()
if f"[{group}]" in content:
return f"Groupe '{group}' existe déjà."
_write_inventory(content + f"\n[{group}]\n")
return f"Groupe '{group}' créé."
if action == "remove-host":
host = rest.strip()
if not host:
return "Précise l'hôte à supprimer."
content = _read_inventory()
new_content = "\n".join(
l for l in content.splitlines()
if not l.strip().startswith(host)
)
_write_inventory(new_content + "\n")
return f"Hôte '{host}' supprimé de l'inventaire."
if action == "ping-all":
return _run(f"ansible all -i {INVENTORY_FILE} -m ping", timeout=60)
if action == "facts":
host = rest.strip()
if not host:
return "Précise l'hôte."
return _run(
f"ansible {host} -i {INVENTORY_FILE} -m setup 2>/dev/null | head -80",
timeout=30
)
return "Action inconnue. Disponible : show, list, hosts, groups, add-host, add-group, remove-host, ping-all, facts"
+13
View File
@@ -0,0 +1,13 @@
"""
Skill MQTT_SEND — publier un message sur un topic MQTT.
"""
DESCRIPTION = "Publier un message sur un topic MQTT (communication inter-agents)"
USAGE = "SKILL:mqtt_send ARGS:<topic> | <message>"
def run(args: str, context) -> str:
if "|" not in args:
return "Format : SKILL:mqtt_send ARGS:<topic> | <message>"
topic, message = args.split("|", 1)
context.mqtt.publish_raw(topic.strip(), message.strip())
return f"Message publié sur '{topic.strip()}'."
+117
View File
@@ -0,0 +1,117 @@
"""
Skill PLAYBOOK — exécution et gestion de playbooks Ansible.
Usage LLM :
SKILL:playbook ARGS:run <fichier.yml> [--limit <hôtes>] [--tags <tags>] [--extra-vars "k=v"]
SKILL:playbook ARGS:check <fichier.yml> (dry-run)
SKILL:playbook ARGS:list (liste les playbooks disponibles)
SKILL:playbook ARGS:show <fichier.yml> (affiche le contenu)
SKILL:playbook ARGS:create <nom> | <contenu> (crée un nouveau playbook)
SKILL:playbook ARGS:syntax <fichier.yml> (vérifie la syntaxe)
"""
import os
import subprocess
DESCRIPTION = "Exécution et gestion de playbooks Ansible"
USAGE = "SKILL:playbook ARGS:run <fichier.yml> [--limit <hôtes>] [--tags <tags>] | check <fichier> | list | show <fichier> | create <nom>|<contenu> | syntax <fichier>"
PLAYBOOKS_DIR = "/opt/agent_ansible/playbooks"
INVENTORY = "/opt/agent_ansible/inventory/hosts"
ANSIBLE_CFG = "/opt/agent_ansible/ansible.cfg"
def _run(cmd: str, timeout: int = 300) -> str:
env = os.environ.copy()
env["ANSIBLE_FORCE_COLOR"] = "0"
env["ANSIBLE_HOST_KEY_CHECKING"] = "False"
if os.path.exists(ANSIBLE_CFG):
env["ANSIBLE_CONFIG"] = ANSIBLE_CFG
try:
result = subprocess.run(
cmd, shell=True, text=True,
capture_output=True, timeout=timeout, env=env
)
out = (result.stdout + result.stderr).strip()
return out[:5000] if out else "(aucune sortie)"
except subprocess.TimeoutExpired:
return f"Timeout ({timeout}s) — playbook trop long."
except Exception as e:
return str(e)
def _resolve_playbook(name: str) -> str:
"""Résout le chemin d'un playbook (relatif ou absolu)."""
if os.path.isabs(name) and os.path.exists(name):
return name
# Cherche dans le dossier playbooks
candidates = [
os.path.join(PLAYBOOKS_DIR, name),
os.path.join(PLAYBOOKS_DIR, name + ".yml"),
os.path.join(PLAYBOOKS_DIR, name + ".yaml"),
name,
]
for c in candidates:
if os.path.exists(c):
return c
return None
def run(args: str, context) -> str:
parts = args.strip().split(None, 1)
action = parts[0].lower() if parts else "list"
rest = parts[1] if len(parts) > 1 else ""
if action == "list":
files = []
for d in [PLAYBOOKS_DIR]:
if os.path.isdir(d):
for f in sorted(os.listdir(d)):
if f.endswith((".yml", ".yaml")):
files.append(os.path.join(d, f))
return "\n".join(files) if files else f"Aucun playbook dans {PLAYBOOKS_DIR}"
if action == "show":
path = _resolve_playbook(rest.strip())
if not path:
return f"Playbook '{rest.strip()}' introuvable."
with open(path) as f:
return f.read()[:3000]
if action == "create":
if "|" not in rest:
return "Format : create <nom> | <contenu YAML>"
name, content = rest.split("|", 1)
name = name.strip()
if not name.endswith((".yml", ".yaml")):
name += ".yml"
path = os.path.join(PLAYBOOKS_DIR, name)
os.makedirs(PLAYBOOKS_DIR, exist_ok=True)
with open(path, "w") as f:
f.write(content.strip().replace("\\n", "\n"))
return f"Playbook créé : {path}"
if action in ("run", "check", "syntax"):
# Parse : <fichier.yml> [options...]
rparts = rest.split()
if not rparts:
return "Précise le fichier playbook."
playbook_arg = rparts[0]
options = " ".join(rparts[1:]) if len(rparts) > 1 else ""
path = _resolve_playbook(playbook_arg)
if not path:
return f"Playbook '{playbook_arg}' introuvable dans {PLAYBOOKS_DIR}"
inv_flag = f"-i {INVENTORY}" if os.path.exists(INVENTORY) else ""
if action == "syntax":
cmd = f"ansible-playbook {inv_flag} --syntax-check {path}"
elif action == "check":
cmd = f"ansible-playbook {inv_flag} --check {path} {options}"
else:
cmd = f"ansible-playbook {inv_flag} {path} {options}"
return _run(cmd, timeout=600)
return "Action inconnue. Disponible : run, check, syntax, list, show, create"
+29
View File
@@ -0,0 +1,29 @@
"""
Skill SHELL — commandes shell et ansible directes (fallback).
Usage LLM : SKILL:shell ARGS:<commande>
"""
import subprocess
DESCRIPTION = "Exécution de commandes shell et ansible directes (fallback)"
USAGE = "SKILL:shell ARGS:<commande bash ou ansible>"
def run(args: str, context) -> str:
cmd = args.strip()
if not cmd:
return "Commande vide."
try:
result = subprocess.run(
cmd, shell=True, text=True,
capture_output=True, timeout=120,
executable="/bin/bash"
)
out = (result.stdout + result.stderr).strip()
if len(out) > 4000:
out = out[:4000] + "\n... [tronqué]"
return out or f"(code retour : {result.returncode})"
except subprocess.TimeoutExpired:
return "Timeout (120s)"
except Exception as e:
return str(e)
+82
View File
@@ -0,0 +1,82 @@
"""
Skill VAULT — gestion des secrets Ansible Vault.
Usage LLM :
SKILL:vault ARGS:encrypt <fichier>
SKILL:vault ARGS:decrypt <fichier>
SKILL:vault ARGS:view <fichier>
SKILL:vault ARGS:encrypt-string <valeur> <nom_variable>
SKILL:vault ARGS:rekey <fichier>
"""
import os
import subprocess
DESCRIPTION = "Gestion des secrets chiffrés avec Ansible Vault"
USAGE = "SKILL:vault ARGS:encrypt <fichier> | decrypt <fichier> | view <fichier> | encrypt-string <valeur> <nom_var>"
VAULT_PASS_FILE = "/opt/agent_ansible/config/.vault_pass"
def _run(cmd: str, timeout: int = 30) -> str:
try:
result = subprocess.run(
cmd, shell=True, text=True,
capture_output=True, timeout=timeout
)
out = (result.stdout + result.stderr).strip()
return out[:3000] if out else "(aucune sortie)"
except Exception as e:
return str(e)
def _vault_flag() -> str:
if os.path.exists(VAULT_PASS_FILE):
return f"--vault-password-file {VAULT_PASS_FILE}"
return "--ask-vault-pass"
def run(args: str, context) -> str:
parts = args.strip().split(None, 1)
action = parts[0].lower() if parts else ""
rest = parts[1] if len(parts) > 1 else ""
flag = _vault_flag()
if action == "encrypt":
if not rest:
return "Précise le fichier à chiffrer."
return _run(f"ansible-vault encrypt {flag} {rest}")
if action == "decrypt":
if not rest:
return "Précise le fichier à déchiffrer."
return _run(f"ansible-vault decrypt {flag} {rest}")
if action == "view":
if not rest:
return "Précise le fichier à visualiser."
return _run(f"ansible-vault view {flag} {rest}")
if action == "encrypt-string":
parts2 = rest.split(None, 1)
if len(parts2) < 2:
return "Format : encrypt-string <valeur> <nom_variable>"
value, name = parts2[0], parts2[1]
return _run(f"ansible-vault encrypt_string {flag} '{value}' --name '{name}'")
if action == "rekey":
if not rest:
return "Précise le fichier."
return _run(f"ansible-vault rekey {flag} {rest}")
if action == "set-password":
# Définir le mot de passe du vault (stocké dans .vault_pass)
password = rest.strip()
if not password:
return "Précise le mot de passe."
with open(VAULT_PASS_FILE, "w") as f:
f.write(password)
os.chmod(VAULT_PASS_FILE, 0o600)
return f"Mot de passe vault enregistré dans {VAULT_PASS_FILE}"
return "Action inconnue. Disponible : encrypt, decrypt, view, encrypt-string, rekey, set-password"