Preparer la gestion planifiee des services

This commit is contained in:
Daniel Allaire 2026-06-21 20:35:03 -04:00
parent 61e2c7ca67
commit f941b66d39
32 changed files with 1073 additions and 7 deletions

View file

@ -52,6 +52,14 @@
- Modification de `make deployer HOTE=...` pour appliquer les playbooks correspondant aux groupes de l'hôte.
- Ajout d'une vérification explicite de la bibliothèque Python `proxmoxer` avant les appels API Proxmox.
- Retrait de la taille disque clone des paramètres Proxmox persistés afin de ne pas tenter de réduire un disque hérité du modèle.
- Ajout de la validation inventaire/playbooks pour empêcher un groupe opérationnel avec hôtes sans playbook homonyme.
- Ajout du playbook de groupe `serveurs_web` comme point d'ancrage explicite avant l'ajout d'un rôle web.
- Ajout d'un catalogue de services pour préparer Keycloak, OpenLDAP, PostgreSQL, Loki, Icinga, Prometheus, Grafana, Forgejo, Sendmail, step-ca, Redis, NGINX, Nextcloud et Collabora.
- Ajout des groupes et playbooks homonymes neutres pour les prochains services centraux et intégrations clientes.
- Documentation de l'ordre d'implémentation recommandé des services selon leurs dépendances et intégrations.
- Ajout d'une nomenclature VM/VMID et distinction entre hôtes planifiés et hôtes actifs dans l'inventaire.
- Ajout d'une interface web locale `make inventaire-ui` pour éditer et sauvegarder l'inventaire sans lancer de déploiement.
- Ajustement de la nomenclature planifiée vers des noms d'hôtes par domaine afin de permettre la cohabitation de plusieurs services sur une même VM.
## 2026-06-19

View file

@ -10,6 +10,7 @@ INVENTAIRE_PRODUCTION ?= inventories/production/hosts.yml
FICHIER_INVENTAIRE ?= $(INVENTAIRE_PRODUCTION)
GROUPE_MODELE ?= modeles_vm
GROUPE_DEBIAN ?= serveurs_debian
GROUPE_HOTES_ACTIFS ?= hotes_actifs
LIMITE ?= $(GROUPE_DEBIAN)
HOTE ?=
ADRESSE_IP ?=
@ -122,6 +123,8 @@ aide:
@printf '%s\n' ' make inventaire-verifier'
@printf '%s\n' ' Lister les donnees brutes:'
@printf '%s\n' ' make inventaire-lister'
@printf '%s\n' ' Interface locale de gestion:'
@printf '%s\n' ' make inventaire-ui'
@printf '%s\n' ''
@printf '%s\n' 'Modele Debian 13 Proxmox'
@printf '%s\n' ' Construire et verifier:'
@ -168,20 +171,23 @@ syntaxe-proxmox: ansible-runtime
ansible-playbook -i localhost, $(PLAYBOOK_PROXMOX_CLONER_VM) --syntax-check
.PHONY: verifier
verifier: lint syntaxe
verifier: lint inventaire-verifier syntaxe
.PHONY: inventaire hote-ajouter hote-groupes hote-afficher appliquer deployer deployer-groupe cloner-vm creer-vm config inventaire-verifier inventaire-lister inventaire-graphe inventaire-hote inventaire-lab inventaire-production
.PHONY: inventaire hote-ajouter hote-groupes hote-afficher appliquer deployer deployer-groupe cloner-vm creer-vm config inventaire-ui inventaire-verifier inventaire-lister inventaire-graphe inventaire-hote inventaire-lab inventaire-production
inventaire: inventaire-production
config:
python3 scripts/config_proxmox.py
inventaire-ui:
python3 scripts/inventory_gui.py --inventaire $(FICHIER_INVENTAIRE)
hote-ajouter: ansible-runtime
@if [[ -z "$(HOTE)" || -z "$(ADRESSE_IP)" ]]; then \
printf '%s\n' 'Refus: relancer avec HOTE=nom_hote ADRESSE_IP=adresse_ip.'; \
exit 2; \
fi
python3 scripts/inventory_host.py --inventaire $(FICHIER_INVENTAIRE) ajouter --hote $(HOTE) --adresse-ip $(ADRESSE_IP) --utilisateur-ansible $(UTILISATEUR_ANSIBLE) --groupes "$(GROUPES)"
python3 scripts/inventory_host.py --inventaire $(FICHIER_INVENTAIRE) ajouter --hote $(HOTE) --adresse-ip $(ADRESSE_IP) --utilisateur-ansible $(UTILISATEUR_ANSIBLE) --groupes "$(GROUPES)" --dossier-playbooks $(DOSSIER_PLAYBOOKS_GROUPES)
$(MAKE) inventaire-verifier
$(MAKE) hote-afficher HOTE="$(HOTE)" FICHIER_INVENTAIRE="$(FICHIER_INVENTAIRE)"
@ -190,7 +196,7 @@ hote-groupes: ansible-runtime
printf '%s\n' 'Refus: relancer avec HOTE=nom_hote GROUPES="groupe1 groupe2".'; \
exit 2; \
fi
python3 scripts/inventory_host.py --inventaire $(FICHIER_INVENTAIRE) definir-groupes --hote $(HOTE) --groupes "$(GROUPES)"
python3 scripts/inventory_host.py --inventaire $(FICHIER_INVENTAIRE) definir-groupes --hote $(HOTE) --groupes "$(GROUPES)" --dossier-playbooks $(DOSSIER_PLAYBOOKS_GROUPES)
$(MAKE) inventaire-verifier
$(MAKE) hote-afficher HOTE="$(HOTE)" FICHIER_INVENTAIRE="$(FICHIER_INVENTAIRE)"
@ -210,13 +216,15 @@ appliquer: ansible-runtime
printf '%s\n' 'Refus: aucun playbook pour ce groupe: $(DOSSIER_PLAYBOOKS_GROUPES)/$(GROUPE).yml'; \
exit 2; \
fi
ansible-playbook -i $(INVENTAIRE_PRODUCTION) "$(DOSSIER_PLAYBOOKS_GROUPES)/$(GROUPE).yml" --limit $(GROUPE)
ansible-playbook -i $(INVENTAIRE_PRODUCTION) "$(DOSSIER_PLAYBOOKS_GROUPES)/$(GROUPE).yml" --limit '$(GROUPE):&$(GROUPE_HOTES_ACTIFS)'
deployer:
@if [[ -z "$(HOTE)" ]]; then \
printf '%s\n' 'Refus: relancer avec HOTE=nom_hote.'; \
exit 2; \
fi
@python3 scripts/inventory_host.py --inventaire $(FICHIER_INVENTAIRE) playbooks --hote $(HOTE) --dossier-playbooks $(DOSSIER_PLAYBOOKS_GROUPES) > /dev/null
@python3 scripts/inventory_host.py --inventaire $(FICHIER_INVENTAIRE) verifier-actif --hote $(HOTE)
$(MAKE) _verifier-acces-hote LIMITE="$(HOTE)"
$(MAKE) _verifier-privileges-hote LIMITE="$(HOTE)"
@playbooks="$$(python3 scripts/inventory_host.py --inventaire $(FICHIER_INVENTAIRE) playbooks --hote $(HOTE) --dossier-playbooks $(DOSSIER_PLAYBOOKS_GROUPES))" || exit $$?; \
@ -304,6 +312,7 @@ creer-vm:
inventaire-verifier: ansible-runtime
ansible-inventory -i $(INVENTAIRE_LAB) --list > /dev/null
ansible-inventory -i $(INVENTAIRE_PRODUCTION) --list > /dev/null
python3 scripts/inventory_host.py --inventaire $(INVENTAIRE_PRODUCTION) verifier-playbooks --dossier-playbooks $(DOSSIER_PLAYBOOKS_GROUPES)
inventaire-lister: ansible-runtime
ansible-inventory -i $(FICHIER_INVENTAIRE) --list

View file

@ -60,6 +60,12 @@ make inventaire
make hote-afficher HOTE=web-01
```
Ouvrir l'interface locale de gestion d'inventaire :
```bash
make inventaire-ui
```
Ajouter une VM à l'inventaire production et l'associer aux groupes qui détermineront sa configuration :
```bash
@ -71,6 +77,12 @@ Chaque groupe opérationnel doit avoir son playbook homonyme dans `playbooks/gro
La conformité normale des VM passe par ces playbooks de groupes. Les rôles de socle et de durcissement sont appliqués par `serveurs_debian` et `serveurs_durcis`, pas par des playbooks de couches séparés.
Les groupes opérationnels avec des hôtes, comme `serveurs_web` ou `clients_dns`, sont validés contre `playbooks/groupes/` par les commandes d'inventaire.
Le catalogue des services et intégrations prévus est dans `docs/catalogue-services.md`.
La nomenclature des noms de VM et des VMID est dans `docs/nomenclature-vm.md`.
Créer un clone depuis le modèle Debian 13 via l'API Proxmox :
```bash

167
docs/catalogue-services.md Normal file
View file

@ -0,0 +1,167 @@
# Catalogue des services Chezlepro
Ce document fixe les noms de groupes et de playbooks pour les prochains services.
La règle reste :
```text
groupe opérationnel -> playbooks/groupes/<groupe>.yml
```
Les playbooks ajoutés maintenant sont des points d'ancrage. Ils ne doivent pas installer un service tant que le rôle correspondant n'existe pas et que ses variables, secrets et prérequis sont documentés.
La nomenclature des VM et des VMID est documentée dans `docs/nomenclature-vm.md`.
Un service central peut partager un hôte avec d'autres services du même domaine opérationnel. La relation stricte est entre groupe et playbook, pas entre groupe et VM.
## Services centraux
| Service | Groupe | Playbook | Rôle futur |
| --- | --- | --- | --- |
| Keycloak | `serveurs_keycloak` | `playbooks/groupes/serveurs_keycloak.yml` | `keycloak` |
| OpenLDAP | `serveurs_openldap` | `playbooks/groupes/serveurs_openldap.yml` | `openldap` |
| PostgreSQL | `serveurs_postgresql` | `playbooks/groupes/serveurs_postgresql.yml` | `postgresql` |
| Loki | `serveurs_loki` | `playbooks/groupes/serveurs_loki.yml` | `loki` |
| Icinga 2 | `serveurs_icinga2` | `playbooks/groupes/serveurs_icinga2.yml` | `icinga2` |
| Icinga Web 2 | `serveurs_icingaweb2` | `playbooks/groupes/serveurs_icingaweb2.yml` | `icingaweb2` |
| Icinga BPM | `serveurs_icinga_bpm` | `playbooks/groupes/serveurs_icinga_bpm.yml` | `icinga_bpm` |
| Prometheus | `serveurs_prometheus` | `playbooks/groupes/serveurs_prometheus.yml` | `prometheus` |
| Grafana | `serveurs_grafana` | `playbooks/groupes/serveurs_grafana.yml` | `grafana` |
| Forgejo | `serveurs_forgejo` | `playbooks/groupes/serveurs_forgejo.yml` | `forgejo` |
| Sendmail MTA | `serveurs_sendmail` | `playbooks/groupes/serveurs_sendmail.yml` | `sendmail` |
| step-ca | `serveurs_step_ca` | `playbooks/groupes/serveurs_step_ca.yml` | `step_ca` |
| Redis | `serveurs_redis` | `playbooks/groupes/serveurs_redis.yml` | `redis` |
| NGINX WAF et reverse proxy | `serveurs_nginx` | `playbooks/groupes/serveurs_nginx.yml` | `nginx` |
| Nextcloud | `serveurs_nextcloud` | `playbooks/groupes/serveurs_nextcloud.yml` | `nextcloud` |
| Collabora | `serveurs_collabora` | `playbooks/groupes/serveurs_collabora.yml` | `collabora` |
## Hôtes planifiés
| Hôte | Services |
| --- | --- |
| `infra-pki-01` | step-ca |
| `infra-edge-01` | NGINX WAF et reverse proxy |
| `infra-mail-01` | Sendmail MTA |
| `idm-01` | OpenLDAP, Keycloak |
| `data-01` | PostgreSQL, Redis |
| `obs-01` | Prometheus, Loki, Grafana |
| `mon-01` | Icinga 2, Icinga Web 2, Icinga BPM |
| `forge-01` | Forgejo |
| `collab-01` | Nextcloud, Collabora |
## Intégrations clientes
| Intégration | Groupe | Playbook | Rôle futur |
| --- | --- | --- | --- |
| Résolution DNS interne | `clients_dns` | `playbooks/groupes/clients_dns.yml` | `client_dns` |
| Confiance PKI / ACME | `clients_pki` | `playbooks/groupes/clients_pki.yml` | `client_pki` |
| Authentification LDAP | `clients_ldap` | `playbooks/groupes/clients_ldap.yml` | `client_ldap` |
| Supervision Icinga | `clients_supervision` | `playbooks/groupes/clients_supervision.yml` | `client_supervision` |
| Métriques Prometheus | `clients_metriques` | `playbooks/groupes/clients_metriques.yml` | `client_metriques` |
| Journaux vers Loki | `clients_journaux` | `playbooks/groupes/clients_journaux.yml` | `client_journaux` |
| Relais SMTP | `clients_smtp` | `playbooks/groupes/clients_smtp.yml` | `client_smtp` |
## Ordre d'implémentation recommandé
L'ordre ci-dessous privilégie les dépendances structurantes avant les applications.
### Phase 1 - Fondations transversales
1. `serveurs_step_ca`
- Service central : autorité de certification interne et ACME.
- Intégration à prévoir : `clients_pki`.
- Raison : les autres services auront besoin de certificats fiables avant d'être exposés proprement.
2. `serveurs_nginx`
- Service central : reverse proxy, terminaison TLS, publication HTTP(S), WAF.
- Intégration à prévoir : publication des services HTTP derrière le proxy.
- Raison : plusieurs services seront consommés par navigateur ou API et doivent passer par un point d'entrée cohérent.
3. `serveurs_sendmail`
- Service central : relais SMTP sortant.
- Intégration à prévoir : `clients_smtp`.
- Raison : les notifications, réinitialisations de mot de passe et alertes doivent fonctionner tôt.
### Phase 2 - Données et identité
4. `serveurs_postgresql`
- Service central : base de données relationnelle partagée.
- Intégration à prévoir : bases dédiées par application, comptes applicatifs et sauvegardes.
- Raison : Keycloak, Grafana, Icinga Web 2, Forgejo et Nextcloud peuvent dépendre de PostgreSQL.
5. `serveurs_openldap`
- Service central : annuaire interne.
- Intégration à prévoir : `clients_ldap`.
- Raison : l'identité Unix et l'annuaire doivent exister avant les intégrations d'authentification avancées.
6. `serveurs_keycloak`
- Service central : SSO/OIDC/SAML.
- Intégration à prévoir : applications web derrière `serveurs_nginx`.
- Raison : les applications devraient être branchées au SSO dès leur arrivée plutôt qu'après coup.
### Phase 3 - Observabilité minimale
7. `serveurs_prometheus`
- Service central : métriques.
- Intégration à prévoir : `clients_metriques`.
- Raison : les prochains services doivent être mesurables dès leur déploiement.
8. `serveurs_loki`
- Service central : journaux centralisés.
- Intégration à prévoir : `clients_journaux`.
- Raison : les journaux centralisés accélèrent le diagnostic des services suivants.
9. `serveurs_grafana`
- Service central : tableaux de bord.
- Intégration à prévoir : datasources Prometheus et Loki, authentification Keycloak.
- Raison : Grafana consolide les métriques et journaux après leur mise en place.
### Phase 4 - Supervision active
10. `serveurs_icinga2`
- Service central : supervision active, checks, alertes.
- Intégration à prévoir : `clients_supervision`.
- Raison : Icinga doit surveiller l'infrastructure déjà instrumentée.
11. `serveurs_icingaweb2`
- Service central : interface web Icinga.
- Intégration à prévoir : PostgreSQL, NGINX, Keycloak si retenu.
- Raison : l'interface dépend du moteur Icinga et de la couche web.
12. `serveurs_icinga_bpm`
- Service central : vues métiers Icinga.
- Intégration à prévoir : modèles de dépendances des services.
- Raison : BPM devient utile quand les premiers services critiques existent.
### Phase 5 - Services applicatifs internes
13. `serveurs_redis`
- Service central : cache et files internes.
- Intégration à prévoir : Nextcloud et autres applications qui en ont besoin.
- Raison : Redis est une dépendance applicative, pas une fondation globale.
14. `serveurs_forgejo`
- Service central : forge Git.
- Intégration à prévoir : PostgreSQL, NGINX, Keycloak, SMTP, sauvegardes.
- Raison : la forge devient plus utile après SSO, TLS, SMTP et observabilité.
15. `serveurs_nextcloud`
- Service central : collaboration fichiers.
- Intégration à prévoir : PostgreSQL, Redis, NGINX, Keycloak, SMTP, sauvegardes.
- Raison : Nextcloud dépend de plusieurs fondations et doit arriver après elles.
16. `serveurs_collabora`
- Service central : édition documentaire en ligne.
- Intégration à prévoir : Nextcloud, NGINX, certificats.
- Raison : Collabora est une extension de Nextcloud et doit venir après lui.
## Règles d'intégration
- Tout service exposé en HTTP(S) doit prévoir son intégration avec `serveurs_nginx`.
- Tout service avec authentification humaine doit prévoir son intégration avec `serveurs_keycloak`, sauf justification contraire.
- Tout service générant des alertes ou notifications doit prévoir `clients_smtp`.
- Toute VM de service doit rejoindre `clients_metriques`, `clients_journaux` et `clients_supervision` quand les services centraux correspondants existent.
- Tout service utilisant un certificat interne doit dépendre de `clients_pki`.
- Tout rôle serveur doit documenter ses ports, secrets, sauvegardes, dépendances et groupes clients associés.
Les groupes clients peuvent être ajoutés aux VM existantes quand le service central correspondant est réellement disponible.

70
docs/nomenclature-vm.md Normal file
View file

@ -0,0 +1,70 @@
# Nomenclature des VM
La nomenclature doit rendre lisible le domaine opérationnel d'une VM sans l'enfermer dans un seul service.
Un hôte peut porter plusieurs groupes Ansible. Le nom de VM doit donc représenter une capacité ou un domaine, pas forcément un produit unique.
## Noms de VM
Format recommandé :
```text
<domaine>-<numero>
```
Exemples :
```text
infra-pki-01
infra-edge-01
infra-mail-01
idm-01
data-01
obs-01
mon-01
forge-01
collab-01
```
Les noms courts historiques `web-01` et `web-02` restent valides pour les deux premières VM déjà créées.
## Regroupements recommandés
| Hôte | Groupes prévus |
| --- | --- |
| `infra-pki-01` | `serveurs_step_ca` |
| `infra-edge-01` | `serveurs_nginx` |
| `infra-mail-01` | `serveurs_sendmail` |
| `idm-01` | `serveurs_openldap`, `serveurs_keycloak` |
| `data-01` | `serveurs_postgresql`, `serveurs_redis` |
| `obs-01` | `serveurs_prometheus`, `serveurs_loki`, `serveurs_grafana` |
| `mon-01` | `serveurs_icinga2`, `serveurs_icingaweb2`, `serveurs_icinga_bpm` |
| `forge-01` | `serveurs_forgejo` |
| `collab-01` | `serveurs_nextcloud`, `serveurs_collabora` |
Les groupes restent fins et composables. La cohabitation se fait en associant plusieurs groupes au même hôte.
## Plages VMID
| Plage | Usage |
| --- | --- |
| `91xxx` | fondations transversales : PKI, reverse proxy, SMTP |
| `92xxx` | identité : LDAP, SSO |
| `93xxx` | données et cache : PostgreSQL, Redis |
| `94xxx` | observabilité et supervision |
| `95xxx` | applications internes |
| `99xxx` | modèles, essais initiaux ou exceptions documentées |
## Hôtes planifiés
Les hôtes prévus mais non encore déployés sont placés dans `hotes_planifies`.
Les hôtes réellement joignables par Ansible sont placés dans `hotes_actifs`.
Les déploiements groupés limitent automatiquement l'exécution à :
```text
<groupe demandé> & hotes_actifs
```
Cela permet de renseigner l'inventaire complet sans tenter de configurer une VM qui n'existe pas encore.

View file

@ -2,6 +2,36 @@ all:
children:
modeles_vm:
hosts: {}
hotes_actifs:
hosts:
web-01:
ansible_host: 192.168.12.101
ansible_user: ansible
proxmox_vmid: 99101
web-02:
ansible_host: 192.168.12.102
ansible_user: ansible
proxmox_vmid: 99102
hotes_planifies:
hosts:
infra-pki-01:
proxmox_vmid: 91101
infra-edge-01:
proxmox_vmid: 91201
infra-mail-01:
proxmox_vmid: 91301
idm-01:
proxmox_vmid: 92101
data-01:
proxmox_vmid: 93101
obs-01:
proxmox_vmid: 94101
mon-01:
proxmox_vmid: 94401
forge-01:
proxmox_vmid: 95101
collab-01:
proxmox_vmid: 95201
serveurs_debian:
hosts:
web-01:
@ -10,6 +40,15 @@ all:
web-02:
ansible_host: 192.168.12.102
ansible_user: ansible
infra-pki-01: {}
infra-edge-01: {}
infra-mail-01: {}
idm-01: {}
data-01: {}
obs-01: {}
mon-01: {}
forge-01: {}
collab-01: {}
serveurs_durcis:
hosts:
web-01:
@ -18,11 +57,88 @@ all:
web-02:
ansible_host: 192.168.12.102
ansible_user: ansible
infra-pki-01: {}
infra-edge-01: {}
infra-mail-01: {}
idm-01: {}
data-01: {}
obs-01: {}
mon-01: {}
forge-01: {}
collab-01: {}
serveurs_web:
hosts: {}
hosts:
web-01:
ansible_host: 192.168.12.101
ansible_user: ansible
web-02:
ansible_host: 192.168.12.102
ansible_user: ansible
serveurs_bases_donnees:
hosts: {}
serveurs_supervision:
hosts: {}
serveurs_keycloak:
hosts:
idm-01: {}
serveurs_openldap:
hosts:
idm-01: {}
serveurs_postgresql:
hosts:
data-01: {}
serveurs_loki:
hosts:
obs-01: {}
serveurs_icinga2:
hosts:
mon-01: {}
serveurs_icingaweb2:
hosts:
mon-01: {}
serveurs_icinga_bpm:
hosts:
mon-01: {}
serveurs_prometheus:
hosts:
obs-01: {}
serveurs_grafana:
hosts:
obs-01: {}
serveurs_forgejo:
hosts:
forge-01: {}
serveurs_sendmail:
hosts:
infra-mail-01: {}
serveurs_step_ca:
hosts:
infra-pki-01: {}
serveurs_redis:
hosts:
data-01: {}
serveurs_nginx:
hosts:
infra-edge-01: {}
serveurs_nextcloud:
hosts:
collab-01: {}
serveurs_collabora:
hosts:
collab-01: {}
clients_dns:
hosts: {}
clients_pki:
hosts: {}
clients_ldap:
hosts: {}
clients_supervision:
hosts: {}
clients_metriques:
hosts: {}
clients_journaux:
hosts: {}
clients_smtp:
hosts: {}
hotes_proxmox:
hosts: {}

View file

@ -0,0 +1,10 @@
---
- name: Appliquer le groupe clients_dns
hosts: clients_dns
become: true
gather_facts: true
tasks:
- name: Indiquer que l'intégration cliente DNS reste à définir
ansible.builtin.debug:
msg: "Aucun rôle n'est encore associé au groupe clients_dns."

View file

@ -0,0 +1,10 @@
---
- name: Appliquer le groupe clients_journaux
hosts: clients_journaux
become: true
gather_facts: true
tasks:
- name: Indiquer que l'intégration cliente journaux reste à définir
ansible.builtin.debug:
msg: "Aucun rôle n'est encore associé au groupe clients_journaux."

View file

@ -0,0 +1,10 @@
---
- name: Appliquer le groupe clients_ldap
hosts: clients_ldap
become: true
gather_facts: true
tasks:
- name: Indiquer que l'intégration cliente LDAP reste à définir
ansible.builtin.debug:
msg: "Aucun rôle n'est encore associé au groupe clients_ldap."

View file

@ -0,0 +1,10 @@
---
- name: Appliquer le groupe clients_metriques
hosts: clients_metriques
become: true
gather_facts: true
tasks:
- name: Indiquer que l'intégration cliente métriques reste à définir
ansible.builtin.debug:
msg: "Aucun rôle n'est encore associé au groupe clients_metriques."

View file

@ -0,0 +1,10 @@
---
- name: Appliquer le groupe clients_pki
hosts: clients_pki
become: true
gather_facts: true
tasks:
- name: Indiquer que l'intégration cliente PKI reste à définir
ansible.builtin.debug:
msg: "Aucun rôle n'est encore associé au groupe clients_pki."

View file

@ -0,0 +1,10 @@
---
- name: Appliquer le groupe clients_smtp
hosts: clients_smtp
become: true
gather_facts: true
tasks:
- name: Indiquer que l'intégration cliente SMTP reste à définir
ansible.builtin.debug:
msg: "Aucun rôle n'est encore associé au groupe clients_smtp."

View file

@ -0,0 +1,10 @@
---
- name: Appliquer le groupe clients_supervision
hosts: clients_supervision
become: true
gather_facts: true
tasks:
- name: Indiquer que l'intégration cliente supervision reste à définir
ansible.builtin.debug:
msg: "Aucun rôle n'est encore associé au groupe clients_supervision."

View file

@ -0,0 +1,10 @@
---
- name: Appliquer le groupe serveurs_collabora
hosts: serveurs_collabora
become: true
gather_facts: true
tasks:
- name: Indiquer que le service Collabora reste à définir
ansible.builtin.debug:
msg: "Aucun rôle n'est encore associé au groupe serveurs_collabora."

View file

@ -0,0 +1,10 @@
---
- name: Appliquer le groupe serveurs_forgejo
hosts: serveurs_forgejo
become: true
gather_facts: true
tasks:
- name: Indiquer que le service Forgejo reste à définir
ansible.builtin.debug:
msg: "Aucun rôle n'est encore associé au groupe serveurs_forgejo."

View file

@ -0,0 +1,10 @@
---
- name: Appliquer le groupe serveurs_grafana
hosts: serveurs_grafana
become: true
gather_facts: true
tasks:
- name: Indiquer que le service Grafana reste à définir
ansible.builtin.debug:
msg: "Aucun rôle n'est encore associé au groupe serveurs_grafana."

View file

@ -0,0 +1,10 @@
---
- name: Appliquer le groupe serveurs_icinga2
hosts: serveurs_icinga2
become: true
gather_facts: true
tasks:
- name: Indiquer que le service Icinga 2 reste à définir
ansible.builtin.debug:
msg: "Aucun rôle n'est encore associé au groupe serveurs_icinga2."

View file

@ -0,0 +1,10 @@
---
- name: Appliquer le groupe serveurs_icinga_bpm
hosts: serveurs_icinga_bpm
become: true
gather_facts: true
tasks:
- name: Indiquer que le service Icinga BPM reste à définir
ansible.builtin.debug:
msg: "Aucun rôle n'est encore associé au groupe serveurs_icinga_bpm."

View file

@ -0,0 +1,10 @@
---
- name: Appliquer le groupe serveurs_icingaweb2
hosts: serveurs_icingaweb2
become: true
gather_facts: true
tasks:
- name: Indiquer que le service Icinga Web 2 reste à définir
ansible.builtin.debug:
msg: "Aucun rôle n'est encore associé au groupe serveurs_icingaweb2."

View file

@ -0,0 +1,10 @@
---
- name: Appliquer le groupe serveurs_keycloak
hosts: serveurs_keycloak
become: true
gather_facts: true
tasks:
- name: Indiquer que le service Keycloak reste à définir
ansible.builtin.debug:
msg: "Aucun rôle n'est encore associé au groupe serveurs_keycloak."

View file

@ -0,0 +1,10 @@
---
- name: Appliquer le groupe serveurs_loki
hosts: serveurs_loki
become: true
gather_facts: true
tasks:
- name: Indiquer que le service Loki reste à définir
ansible.builtin.debug:
msg: "Aucun rôle n'est encore associé au groupe serveurs_loki."

View file

@ -0,0 +1,10 @@
---
- name: Appliquer le groupe serveurs_nextcloud
hosts: serveurs_nextcloud
become: true
gather_facts: true
tasks:
- name: Indiquer que le service Nextcloud reste à définir
ansible.builtin.debug:
msg: "Aucun rôle n'est encore associé au groupe serveurs_nextcloud."

View file

@ -0,0 +1,10 @@
---
- name: Appliquer le groupe serveurs_nginx
hosts: serveurs_nginx
become: true
gather_facts: true
tasks:
- name: Indiquer que le service NGINX reste à définir
ansible.builtin.debug:
msg: "Aucun rôle n'est encore associé au groupe serveurs_nginx."

View file

@ -0,0 +1,10 @@
---
- name: Appliquer le groupe serveurs_openldap
hosts: serveurs_openldap
become: true
gather_facts: true
tasks:
- name: Indiquer que le service OpenLDAP reste à définir
ansible.builtin.debug:
msg: "Aucun rôle n'est encore associé au groupe serveurs_openldap."

View file

@ -0,0 +1,10 @@
---
- name: Appliquer le groupe serveurs_postgresql
hosts: serveurs_postgresql
become: true
gather_facts: true
tasks:
- name: Indiquer que le service PostgreSQL reste à définir
ansible.builtin.debug:
msg: "Aucun rôle n'est encore associé au groupe serveurs_postgresql."

View file

@ -0,0 +1,10 @@
---
- name: Appliquer le groupe serveurs_prometheus
hosts: serveurs_prometheus
become: true
gather_facts: true
tasks:
- name: Indiquer que le service Prometheus reste à définir
ansible.builtin.debug:
msg: "Aucun rôle n'est encore associé au groupe serveurs_prometheus."

View file

@ -0,0 +1,10 @@
---
- name: Appliquer le groupe serveurs_redis
hosts: serveurs_redis
become: true
gather_facts: true
tasks:
- name: Indiquer que le service Redis reste à définir
ansible.builtin.debug:
msg: "Aucun rôle n'est encore associé au groupe serveurs_redis."

View file

@ -0,0 +1,10 @@
---
- name: Appliquer le groupe serveurs_sendmail
hosts: serveurs_sendmail
become: true
gather_facts: true
tasks:
- name: Indiquer que le service Sendmail reste à définir
ansible.builtin.debug:
msg: "Aucun rôle n'est encore associé au groupe serveurs_sendmail."

View file

@ -0,0 +1,10 @@
---
- name: Appliquer le groupe serveurs_step_ca
hosts: serveurs_step_ca
become: true
gather_facts: true
tasks:
- name: Indiquer que le service step-ca reste à définir
ansible.builtin.debug:
msg: "Aucun rôle n'est encore associé au groupe serveurs_step_ca."

View file

@ -0,0 +1,17 @@
---
- name: Appliquer le groupe serveurs_web
hosts: serveurs_web
become: true
gather_facts: true
pre_tasks:
- name: Vérifier que la cible est Debian
ansible.builtin.assert:
that:
- ansible_facts.distribution == "Debian"
fail_msg: "Ce playbook est prévu pour Debian."
tasks:
- name: Indiquer que le rôle web reste à définir
ansible.builtin.debug:
msg: "Aucun rôle web n'est encore associé au groupe serveurs_web."

375
scripts/inventory_gui.py Normal file
View file

@ -0,0 +1,375 @@
#!/usr/bin/env python3
"""Interface web locale pour gerer un inventaire Set-OPS."""
from __future__ import annotations
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
import argparse
import json
from pathlib import Path
from urllib.parse import urlparse
import yaml
RACINE = Path(__file__).resolve().parents[1]
INVENTAIRE_DEFAUT = RACINE / "inventories/production/hosts.yml"
DOSSIER_PLAYBOOKS_GROUPES = RACINE / "playbooks/groupes"
GROUPES_OPERATIONNELS_PREFIXES = ("serveurs_", "clients_")
GROUPES_ETAT = ("hotes_actifs", "hotes_planifies")
def charger_yaml(path: Path) -> dict:
if not path.exists():
return {"all": {"children": {}}}
with path.open("r", encoding="utf-8") as fichier:
data = yaml.safe_load(fichier) or {}
if not isinstance(data, dict):
raise ValueError(f"{path} ne contient pas une table YAML.")
data.setdefault("all", {})
data["all"].setdefault("children", {})
return data
def ecrire_yaml(path: Path, data: dict) -> None:
with path.open("w", encoding="utf-8") as fichier:
yaml.safe_dump(data, fichier, default_flow_style=False, sort_keys=False, allow_unicode=True)
def enfants(data: dict) -> dict:
return data.setdefault("all", {}).setdefault("children", {})
def groupes_disponibles(data: dict) -> list[str]:
groupes = set(enfants(data))
groupes.update(playbook.stem for playbook in DOSSIER_PLAYBOOKS_GROUPES.glob("*.yml"))
return sorted(group for group in groupes if group.startswith(GROUPES_OPERATIONNELS_PREFIXES))
def variables_hote(data: dict, hote: str) -> dict:
resultat: dict = {}
for groupe_data in enfants(data).values():
hosts = groupe_data.get("hosts", {})
valeurs = hosts.get(hote)
if isinstance(valeurs, dict):
resultat.update(valeurs)
return resultat
def liste_hotes(data: dict) -> list[dict]:
noms = set()
for groupe_data in enfants(data).values():
noms.update(groupe_data.get("hosts", {}))
hotes = []
for nom in sorted(noms):
groupes = []
etat = "planifie"
for groupe, groupe_data in enfants(data).items():
if nom not in groupe_data.get("hosts", {}):
continue
if groupe == "hotes_actifs":
etat = "actif"
elif groupe in GROUPES_ETAT:
continue
elif groupe.startswith(GROUPES_OPERATIONNELS_PREFIXES):
groupes.append(groupe)
variables = variables_hote(data, nom)
hotes.append(
{
"nom": nom,
"etat": etat,
"adresse_ip": variables.get("ansible_host", ""),
"utilisateur_ansible": variables.get("ansible_user", "ansible"),
"vmid": variables.get("proxmox_vmid", ""),
"groupes": sorted(groupes),
}
)
return hotes
def inventaire_api(path: Path) -> dict:
data = charger_yaml(path)
return {
"inventaire": str(path.relative_to(RACINE)),
"groupes": groupes_disponibles(data),
"hotes": liste_hotes(data),
}
def donnees_hote(hote: dict) -> dict:
variables: dict = {}
adresse_ip = str(hote.get("adresse_ip", "")).strip()
utilisateur = str(hote.get("utilisateur_ansible", "")).strip()
vmid = str(hote.get("vmid", "")).strip()
if adresse_ip:
variables["ansible_host"] = adresse_ip
if utilisateur:
variables["ansible_user"] = utilisateur
if vmid:
variables["proxmox_vmid"] = int(vmid) if vmid.isdigit() else vmid
return variables
def construire_inventaire(payload: dict, groupes_connus: list[str]) -> dict:
enfants_data: dict = {
"modeles_vm": {"hosts": {}},
"hotes_actifs": {"hosts": {}},
"hotes_planifies": {"hosts": {}},
}
for groupe in groupes_connus:
enfants_data.setdefault(groupe, {"hosts": {}})
enfants_data.setdefault("hotes_proxmox", {"hosts": {}})
for hote in payload.get("hotes", []):
nom = str(hote.get("nom", "")).strip()
if not nom:
continue
variables = donnees_hote(hote)
groupe_etat = "hotes_actifs" if hote.get("etat") == "actif" else "hotes_planifies"
enfants_data[groupe_etat]["hosts"][nom] = variables
for groupe in hote.get("groupes", []):
if groupe not in groupes_connus:
continue
enfants_data[groupe]["hosts"][nom] = {}
return {"all": {"children": enfants_data}}
def valider_payload(payload: dict, groupes_connus: list[str]) -> None:
noms: set[str] = set()
for hote in payload.get("hotes", []):
nom = str(hote.get("nom", "")).strip()
if not nom:
raise ValueError("Un hote a un nom vide.")
if nom in noms:
raise ValueError(f"Hote en double: {nom}")
noms.add(nom)
groupes = hote.get("groupes", [])
if not groupes:
raise ValueError(f"Aucun groupe operationnel pour: {nom}")
inconnus = sorted(set(groupes) - set(groupes_connus))
if inconnus:
raise ValueError(f"Groupes inconnus pour {nom}: {', '.join(inconnus)}")
HTML = r"""<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Set-OPS - Inventaire</title>
<style>
:root { color-scheme: light; --fond:#f7f7f4; --texte:#202124; --muted:#63665f; --ligne:#d8d9d2; --accent:#276749; --accent2:#1f4e79; --danger:#9b2c2c; }
* { box-sizing: border-box; }
body { margin: 0; font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: var(--fond); color: var(--texte); }
header { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 18px 24px; border-bottom: 1px solid var(--ligne); background: #fff; position: sticky; top: 0; z-index: 3; }
h1 { margin: 0; font-size: 20px; font-weight: 700; letter-spacing: 0; }
main { padding: 18px 24px 32px; }
.barre { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
button { border: 1px solid var(--ligne); background: #fff; color: var(--texte); padding: 8px 11px; border-radius: 6px; cursor: pointer; font-weight: 650; }
button.primaire { background: var(--accent); color: #fff; border-color: var(--accent); }
button.danger { color: var(--danger); }
button:disabled { opacity: .55; cursor: not-allowed; }
input, select { width: 100%; border: 1px solid var(--ligne); border-radius: 5px; padding: 7px 8px; background: #fff; color: var(--texte); font: inherit; }
table { width: 100%; border-collapse: collapse; background: #fff; border: 1px solid var(--ligne); }
th, td { border-bottom: 1px solid var(--ligne); padding: 8px; text-align: left; vertical-align: top; }
th { font-size: 12px; text-transform: uppercase; color: var(--muted); background: #fbfbf8; position: sticky; top: 65px; z-index: 2; }
td.nom { min-width: 170px; }
td.ip { min-width: 150px; }
td.vmid { width: 100px; }
td.etat { width: 115px; }
td.groupes { min-width: 360px; }
.groupes-liste { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 5px 10px; }
label { display: inline-flex; align-items: center; gap: 6px; color: var(--texte); }
label input { width: auto; }
.meta { color: var(--muted); font-size: 13px; }
.message { min-height: 22px; color: var(--accent2); font-size: 14px; }
.erreur { color: var(--danger); }
@media (max-width: 900px) {
header { align-items: flex-start; flex-direction: column; }
main { padding: 12px; overflow-x: auto; }
th { top: 115px; }
}
</style>
</head>
<body>
<header>
<div>
<h1>Inventaire Set-OPS</h1>
<div class="meta" id="inventaire"></div>
</div>
<div class="barre">
<button type="button" onclick="ajouterHote()">Ajouter</button>
<button type="button" onclick="charger()">Recharger</button>
<button type="button" class="primaire" onclick="sauvegarder()">Sauvegarder</button>
</div>
</header>
<main>
<div class="message" id="message"></div>
<table>
<thead>
<tr>
<th>Hôte</th>
<th>État</th>
<th>Adresse IP</th>
<th>Utilisateur</th>
<th>VMID</th>
<th>Groupes</th>
<th></th>
</tr>
</thead>
<tbody id="lignes"></tbody>
</table>
</main>
<script>
let groupes = [];
let hotes = [];
function message(texte, erreur=false) {
const el = document.getElementById('message');
el.textContent = texte;
el.className = erreur ? 'message erreur' : 'message';
}
async function charger() {
const reponse = await fetch('/api/inventaire');
const data = await reponse.json();
groupes = data.groupes;
hotes = data.hotes;
document.getElementById('inventaire').textContent = data.inventaire;
dessiner();
message('Inventaire chargé.');
}
function ajouterHote() {
hotes.push({nom:'', etat:'planifie', adresse_ip:'', utilisateur_ansible:'ansible', vmid:'', groupes:['serveurs_debian','serveurs_durcis']});
dessiner();
}
function supprimerHote(index) {
hotes.splice(index, 1);
dessiner();
}
function definir(index, champ, valeur) {
hotes[index][champ] = valeur;
}
function basculerGroupe(index, groupe, actif) {
const ensemble = new Set(hotes[index].groupes || []);
if (actif) ensemble.add(groupe); else ensemble.delete(groupe);
hotes[index].groupes = Array.from(ensemble).sort();
}
function dessiner() {
const tbody = document.getElementById('lignes');
tbody.innerHTML = '';
hotes.forEach((hote, index) => {
const tr = document.createElement('tr');
tr.innerHTML = `
<td class="nom"><input value="${echapper(hote.nom || '')}" oninput="definir(${index}, 'nom', this.value)"></td>
<td class="etat"><select onchange="definir(${index}, 'etat', this.value)">
<option value="actif" ${hote.etat === 'actif' ? 'selected' : ''}>actif</option>
<option value="planifie" ${hote.etat !== 'actif' ? 'selected' : ''}>planifié</option>
</select></td>
<td class="ip"><input value="${echapper(hote.adresse_ip || '')}" oninput="definir(${index}, 'adresse_ip', this.value)"></td>
<td><input value="${echapper(hote.utilisateur_ansible || '')}" oninput="definir(${index}, 'utilisateur_ansible', this.value)"></td>
<td class="vmid"><input value="${echapper(String(hote.vmid || ''))}" oninput="definir(${index}, 'vmid', this.value)"></td>
<td class="groupes"><div class="groupes-liste">${groupes.map(groupe => `
<label><input type="checkbox" ${(hote.groupes || []).includes(groupe) ? 'checked' : ''} onchange="basculerGroupe(${index}, '${groupe}', this.checked)"> ${groupe}</label>
`).join('')}</div></td>
<td><button type="button" class="danger" onclick="supprimerHote(${index})">Retirer</button></td>
`;
tbody.appendChild(tr);
});
}
function echapper(valeur) {
return valeur.replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
}
async function sauvegarder() {
const reponse = await fetch('/api/inventaire', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({hotes})
});
const data = await reponse.json();
if (!reponse.ok) {
message(data.erreur || 'Sauvegarde refusée.', true);
return;
}
groupes = data.groupes;
hotes = data.hotes;
dessiner();
message('Inventaire sauvegardé.');
}
charger().catch(err => message(err.message, true));
</script>
</body>
</html>
"""
class Gestionnaire(BaseHTTPRequestHandler):
inventaire: Path = INVENTAIRE_DEFAUT
def repondre(self, status: int, contenu: bytes, content_type: str) -> None:
self.send_response(status)
self.send_header("Content-Type", content_type)
self.send_header("Content-Length", str(len(contenu)))
self.end_headers()
self.wfile.write(contenu)
def repondre_json(self, status: int, data: dict) -> None:
self.repondre(status, json.dumps(data, ensure_ascii=False).encode("utf-8"), "application/json; charset=utf-8")
def do_GET(self) -> None:
chemin = urlparse(self.path).path
try:
if chemin == "/":
self.repondre(200, HTML.encode("utf-8"), "text/html; charset=utf-8")
elif chemin == "/api/inventaire":
self.repondre_json(200, inventaire_api(self.inventaire))
else:
self.repondre_json(404, {"erreur": "Introuvable"})
except Exception as exc:
self.repondre_json(500, {"erreur": str(exc)})
def do_POST(self) -> None:
chemin = urlparse(self.path).path
try:
if chemin != "/api/inventaire":
self.repondre_json(404, {"erreur": "Introuvable"})
return
longueur = int(self.headers.get("Content-Length", "0"))
payload = json.loads(self.rfile.read(longueur).decode("utf-8"))
courant = charger_yaml(self.inventaire)
groupes_connus = groupes_disponibles(courant)
valider_payload(payload, groupes_connus)
nouveau = construire_inventaire(payload, groupes_connus)
ecrire_yaml(self.inventaire, nouveau)
self.repondre_json(200, inventaire_api(self.inventaire))
except Exception as exc:
self.repondre_json(400, {"erreur": str(exc)})
def main() -> int:
parser = argparse.ArgumentParser(description="Interface web locale pour gerer un inventaire Set-OPS.")
parser.add_argument("--inventaire", type=Path, default=INVENTAIRE_DEFAUT)
parser.add_argument("--hote", default="127.0.0.1")
parser.add_argument("--port", type=int, default=8765)
args = parser.parse_args()
Gestionnaire.inventaire = args.inventaire.resolve()
serveur = ThreadingHTTPServer((args.hote, args.port), Gestionnaire)
print(f"Interface inventaire: http://{args.hote}:{args.port}/")
print(f"Inventaire: {Gestionnaire.inventaire}")
serveur.serve_forever()
return 0
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -10,6 +10,12 @@ import sys
import yaml
GROUPES_OPERATIONNELS_PREFIXES = ("serveurs_", "clients_")
GROUPE_HOTES_ACTIFS = "hotes_actifs"
GROUPE_HOTES_PLANIFIES = "hotes_planifies"
GROUPES_ETAT_HOTE = {GROUPE_HOTES_ACTIFS, GROUPE_HOTES_PLANIFIES}
def charger_inventaire(path: Path) -> dict:
if not path.exists():
return {"all": {"children": {}}}
@ -78,12 +84,20 @@ def ajouter_hote(data: dict, host: str, groups: list[str], ip: str | None, ansib
for group in groups:
assurer_groupe(data, group)["hosts"][host] = dict(vars_for_host)
if ip:
assurer_groupe(data, GROUPE_HOTES_ACTIFS)["hosts"][host] = dict(vars_for_host)
assurer_groupe(data, GROUPE_HOTES_PLANIFIES)["hosts"].pop(host, None)
def definir_groupes_hote(data: dict, host: str, groups: list[str]) -> None:
vars_for_host = variables_hote(data, host)
if not groupes_hote(data, host):
current_groups = groupes_hote(data, host)
if not current_groups:
raise ValueError(f"Hote absent de l'inventaire: {host}")
retirer_hote_de_tous_les_groupes(data, host)
for group in current_groups:
if group in GROUPES_ETAT_HOTE:
assurer_groupe(data, group)["hosts"][host] = dict(vars_for_host)
for group in groups:
assurer_groupe(data, group)["hosts"][host] = dict(vars_for_host)
@ -122,6 +136,8 @@ def afficher_playbooks_hote(data: dict, host: str, playbook_dir: Path) -> None:
playbooks: list[Path] = []
for group in groups:
if not group.startswith(GROUPES_OPERATIONNELS_PREFIXES):
continue
playbook = playbook_dir / f"{group}.yml"
if playbook.exists():
playbooks.append(playbook)
@ -132,10 +148,37 @@ def afficher_playbooks_hote(data: dict, host: str, playbook_dir: Path) -> None:
missing = ", ".join(missing_playbooks)
raise ValueError(f"Playbook de groupe manquant pour: {missing}")
if not playbooks:
raise ValueError(f"Aucun playbook de groupe opérationnel pour: {host}")
for playbook in playbooks:
print(playbook)
def verifier_hote_actif(data: dict, host: str) -> None:
hosts = enfants(data).get(GROUPE_HOTES_ACTIFS, {}).get("hosts", {})
if host not in hosts:
raise ValueError(f"Hote non actif dans l'inventaire: {host}")
def verifier_playbooks_groupes(data: dict, playbook_dir: Path) -> None:
missing_playbooks: list[str] = []
for group, group_data in enfants(data).items():
hosts = group_data.get("hosts", {})
if not hosts:
continue
if not group.startswith(GROUPES_OPERATIONNELS_PREFIXES):
continue
playbook = playbook_dir / f"{group}.yml"
if not playbook.exists():
missing_playbooks.append(group)
if missing_playbooks:
missing = ", ".join(sorted(missing_playbooks))
raise ValueError(f"Playbook de groupe manquant pour: {missing}")
def main() -> int:
parser = argparse.ArgumentParser(description="Gere les hotes d'un inventaire Ansible statique Set-OPS.")
parser.add_argument("--inventaire", required=True, type=Path)
@ -147,10 +190,12 @@ def main() -> int:
add_parser.add_argument("--groupes", required=True)
add_parser.add_argument("--adresse-ip")
add_parser.add_argument("--utilisateur-ansible")
add_parser.add_argument("--dossier-playbooks", type=Path)
groups_parser = subparsers.add_parser("definir-groupes", help="Remplace l'appartenance aux groupes pour un hote.")
groups_parser.add_argument("--hote", required=True)
groups_parser.add_argument("--groupes", required=True)
groups_parser.add_argument("--dossier-playbooks", type=Path)
show_parser = subparsers.add_parser("afficher", help="Affiche les groupes et variables d'un hote.")
show_parser.add_argument("--hote", required=True)
@ -159,6 +204,15 @@ def main() -> int:
playbooks_parser.add_argument("--hote", required=True)
playbooks_parser.add_argument("--dossier-playbooks", required=True, type=Path)
active_parser = subparsers.add_parser("verifier-actif", help="Verifie qu'un hote est actif.")
active_parser.add_argument("--hote", required=True)
verify_parser = subparsers.add_parser(
"verifier-playbooks",
help="Verifie que les groupes operationnels avec hotes ont un playbook homonyme.",
)
verify_parser.add_argument("--dossier-playbooks", required=True, type=Path)
args = parser.parse_args()
try:
@ -172,14 +226,22 @@ def main() -> int:
args.adresse_ip,
args.utilisateur_ansible,
)
if args.dossier_playbooks:
verifier_playbooks_groupes(data, args.dossier_playbooks)
ecrire_inventaire(args.inventaire, data)
elif args.command == "definir-groupes":
definir_groupes_hote(data, args.hote, analyser_groupes(args.groupes))
if args.dossier_playbooks:
verifier_playbooks_groupes(data, args.dossier_playbooks)
ecrire_inventaire(args.inventaire, data)
elif args.command == "afficher":
afficher_hote(data, args.hote)
elif args.command == "playbooks":
afficher_playbooks_hote(data, args.hote, args.dossier_playbooks)
elif args.command == "verifier-actif":
verifier_hote_actif(data, args.hote)
elif args.command == "verifier-playbooks":
verifier_playbooks_groupes(data, args.dossier_playbooks)
except Exception as exc:
print(f"erreur: {exc}", file=sys.stderr)
return 2