Preparer la gestion planifiee des services
This commit is contained in:
parent
61e2c7ca67
commit
f941b66d39
32 changed files with 1073 additions and 7 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
19
Makefile
19
Makefile
|
|
@ -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
|
||||
|
|
|
|||
12
README.md
12
README.md
|
|
@ -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
167
docs/catalogue-services.md
Normal 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
70
docs/nomenclature-vm.md
Normal 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.
|
||||
|
|
@ -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: {}
|
||||
|
|
|
|||
10
playbooks/groupes/clients_dns.yml
Normal file
10
playbooks/groupes/clients_dns.yml
Normal 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."
|
||||
10
playbooks/groupes/clients_journaux.yml
Normal file
10
playbooks/groupes/clients_journaux.yml
Normal 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."
|
||||
10
playbooks/groupes/clients_ldap.yml
Normal file
10
playbooks/groupes/clients_ldap.yml
Normal 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."
|
||||
10
playbooks/groupes/clients_metriques.yml
Normal file
10
playbooks/groupes/clients_metriques.yml
Normal 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."
|
||||
10
playbooks/groupes/clients_pki.yml
Normal file
10
playbooks/groupes/clients_pki.yml
Normal 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."
|
||||
10
playbooks/groupes/clients_smtp.yml
Normal file
10
playbooks/groupes/clients_smtp.yml
Normal 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."
|
||||
10
playbooks/groupes/clients_supervision.yml
Normal file
10
playbooks/groupes/clients_supervision.yml
Normal 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."
|
||||
10
playbooks/groupes/serveurs_collabora.yml
Normal file
10
playbooks/groupes/serveurs_collabora.yml
Normal 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."
|
||||
10
playbooks/groupes/serveurs_forgejo.yml
Normal file
10
playbooks/groupes/serveurs_forgejo.yml
Normal 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."
|
||||
10
playbooks/groupes/serveurs_grafana.yml
Normal file
10
playbooks/groupes/serveurs_grafana.yml
Normal 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."
|
||||
10
playbooks/groupes/serveurs_icinga2.yml
Normal file
10
playbooks/groupes/serveurs_icinga2.yml
Normal 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."
|
||||
10
playbooks/groupes/serveurs_icinga_bpm.yml
Normal file
10
playbooks/groupes/serveurs_icinga_bpm.yml
Normal 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."
|
||||
10
playbooks/groupes/serveurs_icingaweb2.yml
Normal file
10
playbooks/groupes/serveurs_icingaweb2.yml
Normal 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."
|
||||
10
playbooks/groupes/serveurs_keycloak.yml
Normal file
10
playbooks/groupes/serveurs_keycloak.yml
Normal 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."
|
||||
10
playbooks/groupes/serveurs_loki.yml
Normal file
10
playbooks/groupes/serveurs_loki.yml
Normal 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."
|
||||
10
playbooks/groupes/serveurs_nextcloud.yml
Normal file
10
playbooks/groupes/serveurs_nextcloud.yml
Normal 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."
|
||||
10
playbooks/groupes/serveurs_nginx.yml
Normal file
10
playbooks/groupes/serveurs_nginx.yml
Normal 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."
|
||||
10
playbooks/groupes/serveurs_openldap.yml
Normal file
10
playbooks/groupes/serveurs_openldap.yml
Normal 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."
|
||||
10
playbooks/groupes/serveurs_postgresql.yml
Normal file
10
playbooks/groupes/serveurs_postgresql.yml
Normal 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."
|
||||
10
playbooks/groupes/serveurs_prometheus.yml
Normal file
10
playbooks/groupes/serveurs_prometheus.yml
Normal 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."
|
||||
10
playbooks/groupes/serveurs_redis.yml
Normal file
10
playbooks/groupes/serveurs_redis.yml
Normal 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."
|
||||
10
playbooks/groupes/serveurs_sendmail.yml
Normal file
10
playbooks/groupes/serveurs_sendmail.yml
Normal 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."
|
||||
10
playbooks/groupes/serveurs_step_ca.yml
Normal file
10
playbooks/groupes/serveurs_step_ca.yml
Normal 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."
|
||||
17
playbooks/groupes/serveurs_web.yml
Normal file
17
playbooks/groupes/serveurs_web.yml
Normal 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
375
scripts/inventory_gui.py
Normal 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 => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[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())
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Reference in a new issue