Ajouter les dependances causales des groupes
This commit is contained in:
parent
df70c580bd
commit
1d25338d26
12 changed files with 326 additions and 21 deletions
|
|
@ -297,6 +297,7 @@ Quand un nouveau groupe opérationnel est ajouté :
|
|||
|
||||
- créer le playbook homonyme dans `playbooks/groupes/` ;
|
||||
- documenter son intention opérationnelle ;
|
||||
- déclarer ses dépendances causales dans `docs/dependances-groupes.yml` si son exécution requiert un autre service actif ;
|
||||
- prévoir les rôles nécessaires ;
|
||||
- valider au minimum sa syntaxe ;
|
||||
- l'ajouter aux facilités d'exploitation si l'opérateur doit l'utiliser directement.
|
||||
|
|
@ -367,6 +368,14 @@ Grafana → datasources et dashboards côté plateforme
|
|||
|
||||
Quand un nouveau service central est ajouté, prévoir aussi le ou les rôles clients nécessaires pour intégrer les VM existantes et futures.
|
||||
|
||||
Les dépendances entre groupes doivent être déclarées dans :
|
||||
|
||||
```text
|
||||
docs/dependances-groupes.yml
|
||||
```
|
||||
|
||||
Ces dépendances servent à refuser un déploiement lorsque les prérequis actifs sont absents et à préparer les futurs checks de supervision.
|
||||
|
||||
Les intégrations clientes doivent être :
|
||||
|
||||
- idempotentes ;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
### Ajouté
|
||||
- Ajout de `make hote-planifier` pour renseigner un hôte, son VMID et ses groupes sans création ni déploiement immédiat.
|
||||
- Ajout de `scripts/inventory_rules.py` pour centraliser les règles partagées entre la gestion CLI et l'interface locale d'inventaire.
|
||||
- Ajout de `docs/dependances-groupes.yml` comme registre exploitable des dépendances causales entre groupes.
|
||||
- Ajout du contrat `serveurs_powerdns` afin de représenter la capacité DNS centrale requise par `clients_dns`.
|
||||
|
||||
### Modifié
|
||||
- Renforcement de la validation d'inventaire :
|
||||
|
|
@ -16,6 +18,7 @@
|
|||
- Alignement de l'aide Makefile et de la documentation sur le flux opérateur : planifier, activer/créer, déployer.
|
||||
- Retrait des groupes génériques obsolètes `serveurs_bases_donnees` et `serveurs_supervision` au profit des groupes de services précis.
|
||||
- Consolidation des groupes Icinga en `serveurs_icinga` afin de représenter une seule capacité de supervision serveur sur `mon-01`.
|
||||
- Validation des prérequis actifs avant déploiement d'un groupe, déploiement d'un hôte ou sauvegarde active via l'interface d'inventaire.
|
||||
|
||||
## 2026-06-21
|
||||
|
||||
|
|
|
|||
12
Makefile
12
Makefile
|
|
@ -8,6 +8,7 @@ export ANSIBLE_SSH_ARGS ?= -F /dev/null -o ControlMaster=no
|
|||
INVENTAIRE_LAB ?= inventories/lab/hosts.yml
|
||||
INVENTAIRE_PRODUCTION ?= inventories/production/hosts.yml
|
||||
FICHIER_INVENTAIRE ?= $(INVENTAIRE_PRODUCTION)
|
||||
FICHIER_DEPENDANCES ?= docs/dependances-groupes.yml
|
||||
GROUPE_MODELE ?= modeles_vm
|
||||
GROUPE_DEBIAN ?= serveurs_debian
|
||||
GROUPE_HOTES_ACTIFS ?= hotes_actifs
|
||||
|
|
@ -141,7 +142,7 @@ aide:
|
|||
@printf '%s\n' 'Variables frequentes'
|
||||
@printf '%s\n' ' HOTE=web-01 GROUPE=serveurs_debian GROUPES="serveurs_debian serveurs_durcis"'
|
||||
@printf '%s\n' ' VMID=101 VLAN=12 ADRESSE_IP=192.168.12.101 PASSERELLE=192.168.12.1'
|
||||
@printf '%s\n' ' FICHIER_INVENTAIRE=inventories/production/hosts.yml CONFIRMER=true'
|
||||
@printf '%s\n' ' FICHIER_INVENTAIRE=inventories/production/hosts.yml FICHIER_DEPENDANCES=docs/dependances-groupes.yml CONFIRMER=true'
|
||||
|
||||
.PHONY: lint
|
||||
lint: ansible-runtime
|
||||
|
|
@ -187,7 +188,7 @@ hote-ajouter: ansible-runtime
|
|||
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) --vmid "$(VMID)" --groupes "$(GROUPES)" --dossier-playbooks $(DOSSIER_PLAYBOOKS_GROUPES)
|
||||
python3 scripts/inventory_host.py --inventaire $(FICHIER_INVENTAIRE) --dependances $(FICHIER_DEPENDANCES) ajouter --hote $(HOTE) --adresse-ip $(ADRESSE_IP) --utilisateur-ansible $(UTILISATEUR_ANSIBLE) --vmid "$(VMID)" --groupes "$(GROUPES)" --dossier-playbooks $(DOSSIER_PLAYBOOKS_GROUPES)
|
||||
$(MAKE) inventaire-verifier
|
||||
$(MAKE) hote-afficher HOTE="$(HOTE)" FICHIER_INVENTAIRE="$(FICHIER_INVENTAIRE)"
|
||||
|
||||
|
|
@ -196,7 +197,7 @@ hote-planifier: 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) ajouter --hote $(HOTE) --vmid "$(VMID)" --groupes "$(GROUPES)" --dossier-playbooks $(DOSSIER_PLAYBOOKS_GROUPES)
|
||||
python3 scripts/inventory_host.py --inventaire $(FICHIER_INVENTAIRE) --dependances $(FICHIER_DEPENDANCES) ajouter --hote $(HOTE) --vmid "$(VMID)" --groupes "$(GROUPES)" --dossier-playbooks $(DOSSIER_PLAYBOOKS_GROUPES)
|
||||
$(MAKE) inventaire-verifier
|
||||
$(MAKE) hote-afficher HOTE="$(HOTE)" FICHIER_INVENTAIRE="$(FICHIER_INVENTAIRE)"
|
||||
|
||||
|
|
@ -205,7 +206,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)" --dossier-playbooks $(DOSSIER_PLAYBOOKS_GROUPES)
|
||||
python3 scripts/inventory_host.py --inventaire $(FICHIER_INVENTAIRE) --dependances $(FICHIER_DEPENDANCES) definir-groupes --hote $(HOTE) --groupes "$(GROUPES)" --dossier-playbooks $(DOSSIER_PLAYBOOKS_GROUPES)
|
||||
$(MAKE) inventaire-verifier
|
||||
$(MAKE) hote-afficher HOTE="$(HOTE)" FICHIER_INVENTAIRE="$(FICHIER_INVENTAIRE)"
|
||||
|
||||
|
|
@ -225,6 +226,7 @@ appliquer: ansible-runtime
|
|||
printf '%s\n' 'Refus: aucun playbook pour ce groupe: $(DOSSIER_PLAYBOOKS_GROUPES)/$(GROUPE).yml'; \
|
||||
exit 2; \
|
||||
fi
|
||||
python3 scripts/inventory_host.py --inventaire $(INVENTAIRE_PRODUCTION) --dependances $(FICHIER_DEPENDANCES) verifier-dependances-groupe --groupe $(GROUPE)
|
||||
ansible-playbook -i $(INVENTAIRE_PRODUCTION) "$(DOSSIER_PLAYBOOKS_GROUPES)/$(GROUPE).yml" --limit '$(GROUPE):&$(GROUPE_HOTES_ACTIFS)'
|
||||
|
||||
deployer:
|
||||
|
|
@ -234,6 +236,7 @@ deployer:
|
|||
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)
|
||||
@python3 scripts/inventory_host.py --inventaire $(FICHIER_INVENTAIRE) --dependances $(FICHIER_DEPENDANCES) verifier-dependances-hote --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 $$?; \
|
||||
|
|
@ -322,6 +325,7 @@ 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)
|
||||
python3 scripts/inventory_host.py --inventaire $(INVENTAIRE_PRODUCTION) --dependances $(FICHIER_DEPENDANCES) verifier-dependances --dossier-playbooks $(DOSSIER_PLAYBOOKS_GROUPES)
|
||||
|
||||
inventaire-lister: ansible-runtime
|
||||
ansible-inventory -i $(FICHIER_INVENTAIRE) --list
|
||||
|
|
|
|||
|
|
@ -87,6 +87,8 @@ Les groupes opérationnels avec des hôtes, comme `serveurs_web` ou `clients_dns
|
|||
|
||||
Le catalogue des services et intégrations prévus est dans `docs/catalogue-services.md`.
|
||||
|
||||
Les dépendances causales entre groupes sont dans `docs/dependances-groupes.yml`.
|
||||
|
||||
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 :
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ Un service central peut partager un hôte avec d'autres services du même domain
|
|||
| 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` |
|
||||
| PowerDNS | `serveurs_powerdns` | `playbooks/groupes/serveurs_powerdns.yml` | `powerdns` |
|
||||
| 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` |
|
||||
|
|
@ -40,6 +41,7 @@ Un service central peut partager un hôte avec d'autres services du même domain
|
|||
| `infra-pki-01` | step-ca |
|
||||
| `infra-edge-01` | NGINX WAF et reverse proxy |
|
||||
| `infra-mail-01` | Sendmail MTA |
|
||||
| `infra-dns-01` | PowerDNS |
|
||||
| `idm-01` | OpenLDAP, Keycloak |
|
||||
| `data-01` | PostgreSQL, Redis |
|
||||
| `obs-01` | Prometheus, Loki, Grafana |
|
||||
|
|
@ -65,58 +67,63 @@ L'ordre ci-dessous privilégie les dépendances structurantes avant les applicat
|
|||
|
||||
### Phase 1 - Fondations transversales
|
||||
|
||||
1. `serveurs_step_ca`
|
||||
1. `serveurs_powerdns`
|
||||
- Service central : DNS interne autoritaire et/ou résolution interne selon le design retenu.
|
||||
- Intégration à prévoir : `clients_dns`.
|
||||
- Raison : les autres intégrations auront besoin de noms stables plutôt que d'adresses IP.
|
||||
|
||||
2. `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`
|
||||
3. `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`
|
||||
4. `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`
|
||||
5. `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`
|
||||
6. `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`
|
||||
7. `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`
|
||||
8. `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`
|
||||
9. `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`
|
||||
10. `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_icinga`
|
||||
11. `serveurs_icinga`
|
||||
- Service central : supervision active, interface web et vues métiers Icinga.
|
||||
- Composants prévus : Icinga 2, Icinga Web 2, Icinga BPM.
|
||||
- Intégrations à prévoir : `clients_supervision`, PostgreSQL, NGINX, Keycloak si retenu.
|
||||
|
|
@ -124,22 +131,22 @@ L'ordre ci-dessous privilégie les dépendances structurantes avant les applicat
|
|||
|
||||
### Phase 5 - Services applicatifs internes
|
||||
|
||||
11. `serveurs_redis`
|
||||
12. `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.
|
||||
|
||||
12. `serveurs_forgejo`
|
||||
13. `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é.
|
||||
|
||||
13. `serveurs_nextcloud`
|
||||
14. `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.
|
||||
|
||||
14. `serveurs_collabora`
|
||||
15. `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.
|
||||
|
|
@ -154,3 +161,14 @@ L'ordre ci-dessous privilégie les dépendances structurantes avant les applicat
|
|||
- 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.
|
||||
|
||||
## Dépendances causales
|
||||
|
||||
Les dépendances exécutables sont déclarées dans `docs/dependances-groupes.yml`.
|
||||
|
||||
Ce fichier sert à deux usages :
|
||||
|
||||
- bloquer le déploiement d'un groupe tant que ses prérequis actifs ne sont pas présents ;
|
||||
- fournir une source exploitable pour les futurs modèles de supervision.
|
||||
|
||||
Un hôte peut porter un groupe client à l'état planifié. Le déploiement est refusé tant que le groupe serveur requis n'a pas au moins un hôte actif.
|
||||
|
|
|
|||
86
docs/dependances-groupes.yml
Normal file
86
docs/dependances-groupes.yml
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
---
|
||||
groupes:
|
||||
clients_dns:
|
||||
requiert_groupes_actifs:
|
||||
- serveurs_powerdns
|
||||
raison: "Les resolvers clients ne doivent pas pointer vers un service DNS absent."
|
||||
surveillance: "Verifier resolution interne et disponibilite du service DNS."
|
||||
|
||||
clients_pki:
|
||||
requiert_groupes_actifs:
|
||||
- serveurs_step_ca
|
||||
raison: "La confiance CA et ACME client dependent de l'autorite interne."
|
||||
surveillance: "Verifier validite CA, emission ACME et expiration des certificats."
|
||||
|
||||
clients_ldap:
|
||||
requiert_groupes_actifs:
|
||||
- serveurs_openldap
|
||||
raison: "La configuration NSS/PAM/SSSD depend de l'annuaire LDAP."
|
||||
surveillance: "Verifier bind LDAP, latence et expiration des certificats LDAP."
|
||||
|
||||
clients_supervision:
|
||||
requiert_groupes_actifs:
|
||||
- serveurs_icinga
|
||||
raison: "Les agents ou checks clients doivent se rattacher a une plateforme Icinga active."
|
||||
surveillance: "Verifier enregistrement des hotes, fraicheur des checks et notifications."
|
||||
|
||||
clients_metriques:
|
||||
requiert_groupes_actifs:
|
||||
- serveurs_prometheus
|
||||
raison: "Les exporters clients doivent etre collectes par Prometheus."
|
||||
surveillance: "Verifier targets Prometheus, scrape duration et erreurs de collecte."
|
||||
|
||||
clients_journaux:
|
||||
requiert_groupes_actifs:
|
||||
- serveurs_loki
|
||||
raison: "L'expedition des journaux depend du collecteur central Loki."
|
||||
surveillance: "Verifier ingestion Loki, retard et volume de journaux."
|
||||
|
||||
clients_smtp:
|
||||
requiert_groupes_actifs:
|
||||
- serveurs_sendmail
|
||||
raison: "Les notifications locales doivent relayer vers un MTA actif."
|
||||
surveillance: "Verifier file d'attente, relais SMTP et echecs de livraison."
|
||||
|
||||
serveurs_keycloak:
|
||||
requiert_groupes_actifs:
|
||||
- serveurs_postgresql
|
||||
raison: "Keycloak doit utiliser une base PostgreSQL geree."
|
||||
surveillance: "Verifier connexion base, etat realm et disponibilite OIDC."
|
||||
|
||||
serveurs_grafana:
|
||||
requiert_groupes_actifs:
|
||||
- serveurs_prometheus
|
||||
- serveurs_loki
|
||||
raison: "Grafana est utile comme interface aux metriques et journaux centraux."
|
||||
surveillance: "Verifier datasources Prometheus/Loki et authentification."
|
||||
|
||||
serveurs_icinga:
|
||||
requiert_groupes_actifs:
|
||||
- serveurs_postgresql
|
||||
raison: "La plateforme Icinga Web/BPM depend d'une base relationnelle."
|
||||
surveillance: "Verifier moteur Icinga, base, interface web et notifications."
|
||||
|
||||
serveurs_forgejo:
|
||||
requiert_groupes_actifs:
|
||||
- serveurs_postgresql
|
||||
- serveurs_nginx
|
||||
- serveurs_sendmail
|
||||
raison: "Forgejo depend d'une base, d'une publication HTTP(S) et du courriel."
|
||||
surveillance: "Verifier HTTP(S), base, files Git et envoi courriel."
|
||||
|
||||
serveurs_nextcloud:
|
||||
requiert_groupes_actifs:
|
||||
- serveurs_postgresql
|
||||
- serveurs_redis
|
||||
- serveurs_nginx
|
||||
- serveurs_sendmail
|
||||
raison: "Nextcloud depend d'une base, d'un cache, d'un frontal web et du courriel."
|
||||
surveillance: "Verifier jobs, base, Redis, HTTP(S), stockage et courriel."
|
||||
|
||||
serveurs_collabora:
|
||||
requiert_groupes_actifs:
|
||||
- serveurs_nextcloud
|
||||
- serveurs_nginx
|
||||
raison: "Collabora est expose via le frontal web et integre a Nextcloud."
|
||||
surveillance: "Verifier connectivite Nextcloud, websocket et disponibilite HTTP(S)."
|
||||
|
|
@ -18,6 +18,7 @@ Exemples :
|
|||
infra-pki-01
|
||||
infra-edge-01
|
||||
infra-mail-01
|
||||
infra-dns-01
|
||||
idm-01
|
||||
data-01
|
||||
obs-01
|
||||
|
|
@ -35,6 +36,7 @@ Les noms courts historiques `web-01` et `web-02` restent valides pour les deux p
|
|||
| `infra-pki-01` | `serveurs_step_ca` |
|
||||
| `infra-edge-01` | `serveurs_nginx` |
|
||||
| `infra-mail-01` | `serveurs_sendmail` |
|
||||
| `infra-dns-01` | `serveurs_powerdns` |
|
||||
| `idm-01` | `serveurs_openldap`, `serveurs_keycloak` |
|
||||
| `data-01` | `serveurs_postgresql`, `serveurs_redis` |
|
||||
| `obs-01` | `serveurs_prometheus`, `serveurs_loki`, `serveurs_grafana` |
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ all:
|
|||
proxmox_vmid: 91201
|
||||
infra-mail-01:
|
||||
proxmox_vmid: 91301
|
||||
infra-dns-01:
|
||||
proxmox_vmid: 91401
|
||||
idm-01:
|
||||
proxmox_vmid: 92101
|
||||
data-01:
|
||||
|
|
@ -43,6 +45,7 @@ all:
|
|||
infra-pki-01: {}
|
||||
infra-edge-01: {}
|
||||
infra-mail-01: {}
|
||||
infra-dns-01: {}
|
||||
idm-01: {}
|
||||
data-01: {}
|
||||
obs-01: {}
|
||||
|
|
@ -60,6 +63,7 @@ all:
|
|||
infra-pki-01: {}
|
||||
infra-edge-01: {}
|
||||
infra-mail-01: {}
|
||||
infra-dns-01: {}
|
||||
idm-01: {}
|
||||
data-01: {}
|
||||
obs-01: {}
|
||||
|
|
@ -104,6 +108,9 @@ all:
|
|||
serveurs_step_ca:
|
||||
hosts:
|
||||
infra-pki-01: {}
|
||||
serveurs_powerdns:
|
||||
hosts:
|
||||
infra-dns-01: {}
|
||||
serveurs_redis:
|
||||
hosts:
|
||||
data-01: {}
|
||||
|
|
|
|||
10
playbooks/groupes/serveurs_powerdns.yml
Normal file
10
playbooks/groupes/serveurs_powerdns.yml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
- name: Appliquer le groupe serveurs_powerdns
|
||||
hosts: serveurs_powerdns
|
||||
become: true
|
||||
gather_facts: true
|
||||
|
||||
tasks:
|
||||
- name: Indiquer que le service PowerDNS reste à définir
|
||||
ansible.builtin.debug:
|
||||
msg: "Aucun rôle n'est encore associé au groupe serveurs_powerdns."
|
||||
|
|
@ -15,6 +15,7 @@ from inventory_rules import (
|
|||
GROUPE_HOTES_ACTIFS,
|
||||
GROUPE_HOTES_PLANIFIES,
|
||||
GROUPES_ETAT_HOTE,
|
||||
charger_dependances,
|
||||
est_groupe_operationnel,
|
||||
groupes_operationnels_connus,
|
||||
)
|
||||
|
|
@ -22,6 +23,7 @@ from inventory_rules import (
|
|||
RACINE = Path(__file__).resolve().parents[1]
|
||||
INVENTAIRE_DEFAUT = RACINE / "inventories/production/hosts.yml"
|
||||
DOSSIER_PLAYBOOKS_GROUPES = RACINE / "playbooks/groupes"
|
||||
FICHIER_DEPENDANCES = RACINE / "docs/dependances-groupes.yml"
|
||||
|
||||
|
||||
def charger_yaml(path: Path) -> dict:
|
||||
|
|
@ -99,9 +101,11 @@ def liste_hotes(data: dict) -> list[dict]:
|
|||
|
||||
def inventaire_api(path: Path) -> dict:
|
||||
data = charger_yaml(path)
|
||||
dependencies = charger_dependances(FICHIER_DEPENDANCES)
|
||||
return {
|
||||
"inventaire": str(path.relative_to(RACINE)),
|
||||
"groupes": groupes_disponibles(data),
|
||||
"dependances": dependencies,
|
||||
"hotes": liste_hotes(data),
|
||||
}
|
||||
|
||||
|
|
@ -145,7 +149,30 @@ def construire_inventaire(payload: dict, groupes_connus: list[str]) -> dict:
|
|||
return {"all": {"children": enfants_data}}
|
||||
|
||||
|
||||
def valider_payload(payload: dict, groupes_connus: list[str]) -> None:
|
||||
def valider_dependances_payload(payload: dict, dependencies: dict) -> None:
|
||||
hotes = payload.get("hotes", [])
|
||||
active_groups: set[str] = set()
|
||||
|
||||
for hote in hotes:
|
||||
if hote.get("etat") != "actif":
|
||||
continue
|
||||
active_groups.update(hote.get("groupes", []))
|
||||
|
||||
missing: list[str] = []
|
||||
for hote in hotes:
|
||||
if hote.get("etat") != "actif":
|
||||
continue
|
||||
nom = str(hote.get("nom", "")).strip()
|
||||
for groupe in hote.get("groupes", []):
|
||||
for required_group in dependencies.get(groupe, {}).get("requiert_groupes_actifs", []):
|
||||
if required_group not in active_groups:
|
||||
missing.append(f"{nom}: {groupe} requiert {required_group} actif")
|
||||
|
||||
if missing:
|
||||
raise ValueError("Dependances manquantes: " + "; ".join(missing))
|
||||
|
||||
|
||||
def valider_payload(payload: dict, groupes_connus: list[str], dependencies: dict) -> None:
|
||||
noms: set[str] = set()
|
||||
vmids: dict[str, str] = {}
|
||||
for hote in payload.get("hotes", []):
|
||||
|
|
@ -170,6 +197,8 @@ def valider_payload(payload: dict, groupes_connus: list[str]) -> None:
|
|||
raise ValueError(f"VMID en double: {vmid} pour {vmids[vmid]} et {nom}")
|
||||
vmids[vmid] = nom
|
||||
|
||||
valider_dependances_payload(payload, dependencies)
|
||||
|
||||
|
||||
HTML = r"""<!doctype html>
|
||||
<html lang="fr">
|
||||
|
|
@ -242,6 +271,7 @@ HTML = r"""<!doctype html>
|
|||
</main>
|
||||
<script>
|
||||
let groupes = [];
|
||||
let dependances = {};
|
||||
let hotes = [];
|
||||
|
||||
function message(texte, erreur=false) {
|
||||
|
|
@ -254,6 +284,7 @@ HTML = r"""<!doctype html>
|
|||
const reponse = await fetch('/api/inventaire');
|
||||
const data = await reponse.json();
|
||||
groupes = data.groupes;
|
||||
dependances = data.dependances || {};
|
||||
hotes = data.hotes;
|
||||
document.getElementById('inventaire').textContent = data.inventaire;
|
||||
dessiner();
|
||||
|
|
@ -295,7 +326,7 @@ HTML = r"""<!doctype html>
|
|||
<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>
|
||||
<label title="${echapper(titreGroupe(groupe))}"><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>
|
||||
`;
|
||||
|
|
@ -307,6 +338,13 @@ HTML = r"""<!doctype html>
|
|||
return valeur.replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
||||
}
|
||||
|
||||
function titreGroupe(groupe) {
|
||||
const config = dependances[groupe] || {};
|
||||
const requis = config.requiert_groupes_actifs || [];
|
||||
if (!requis.length) return 'Aucun prérequis actif déclaré.';
|
||||
return `Prérequis actif: ${requis.join(', ')}`;
|
||||
}
|
||||
|
||||
async function sauvegarder() {
|
||||
const reponse = await fetch('/api/inventaire', {
|
||||
method: 'POST',
|
||||
|
|
@ -318,6 +356,7 @@ HTML = r"""<!doctype html>
|
|||
message(data.erreur || 'Sauvegarde refusée.', true);
|
||||
return;
|
||||
}
|
||||
dependances = data.dependances || {};
|
||||
groupes = data.groupes;
|
||||
hotes = data.hotes;
|
||||
dessiner();
|
||||
|
|
@ -366,7 +405,8 @@ class Gestionnaire(BaseHTTPRequestHandler):
|
|||
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)
|
||||
dependencies = charger_dependances(FICHIER_DEPENDANCES)
|
||||
valider_payload(payload, groupes_connus, dependencies)
|
||||
nouveau = construire_inventaire(payload, groupes_connus)
|
||||
ecrire_yaml(self.inventaire, nouveau)
|
||||
self.repondre_json(200, inventaire_api(self.inventaire))
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ from inventory_rules import (
|
|||
GROUPE_HOTES_ACTIFS,
|
||||
GROUPE_HOTES_PLANIFIES,
|
||||
GROUPES_ETAT_HOTE,
|
||||
charger_dependances,
|
||||
est_groupe_operationnel,
|
||||
)
|
||||
|
||||
|
|
@ -181,6 +182,58 @@ def verifier_hote_actif(data: dict, host: str) -> None:
|
|||
raise ValueError(f"Hote non actif dans l'inventaire: {host}")
|
||||
|
||||
|
||||
def groupe_a_hote_actif(data: dict, group: str) -> bool:
|
||||
active_hosts = set(enfants(data).get(GROUPE_HOTES_ACTIFS, {}).get("hosts", {}))
|
||||
group_hosts = set(enfants(data).get(group, {}).get("hosts", {}))
|
||||
return bool(active_hosts & group_hosts)
|
||||
|
||||
|
||||
def verifier_dependances_groupes(data: dict, dependencies: dict, groups: list[str]) -> None:
|
||||
missing: list[str] = []
|
||||
|
||||
for group in sorted(set(groups)):
|
||||
for required_group in dependencies.get(group, {}).get("requiert_groupes_actifs", []):
|
||||
if not groupe_a_hote_actif(data, required_group):
|
||||
missing.append(f"{group} requiert {required_group} actif")
|
||||
|
||||
if missing:
|
||||
raise ValueError("Dependances manquantes: " + "; ".join(missing))
|
||||
|
||||
|
||||
def exiger_dependances(dependencies: dict, path: Path | None) -> None:
|
||||
if not path:
|
||||
raise ValueError("Fichier de dependances requis: utiliser --dependances.")
|
||||
if not dependencies:
|
||||
raise ValueError(f"Aucune dependance chargee depuis {path}.")
|
||||
|
||||
|
||||
def verifier_dependances_hote(data: dict, dependencies: dict, host: str) -> None:
|
||||
groups = [group for group in groupes_hote(data, host) if est_groupe_operationnel(group)]
|
||||
verifier_dependances_groupes(data, dependencies, groups)
|
||||
|
||||
|
||||
def verifier_dependances_actives(data: dict, dependencies: dict) -> None:
|
||||
active_hosts = set(enfants(data).get(GROUPE_HOTES_ACTIFS, {}).get("hosts", {}))
|
||||
active_groups: set[str] = set()
|
||||
|
||||
for group, group_data in enfants(data).items():
|
||||
if not est_groupe_operationnel(group):
|
||||
continue
|
||||
if active_hosts & set(group_data.get("hosts", {})):
|
||||
active_groups.add(group)
|
||||
|
||||
verifier_dependances_groupes(data, dependencies, sorted(active_groups))
|
||||
|
||||
|
||||
def verifier_dependances_connues(data: dict, dependencies: dict, playbook_dir: Path | None) -> None:
|
||||
for group, config in dependencies.items():
|
||||
if playbook_dir and not (playbook_dir / f"{group}.yml").exists():
|
||||
raise ValueError(f"Playbook de groupe manquant pour dependance: {group}")
|
||||
for required_group in config.get("requiert_groupes_actifs", []):
|
||||
if playbook_dir and not (playbook_dir / f"{required_group}.yml").exists():
|
||||
raise ValueError(f"Playbook de groupe manquant pour prerequis: {required_group}")
|
||||
|
||||
|
||||
def verifier_coherence_hotes(data: dict) -> None:
|
||||
vmids: dict[str, str] = {}
|
||||
|
||||
|
|
@ -234,6 +287,7 @@ def verifier_playbooks_groupes(data: dict, playbook_dir: Path) -> None:
|
|||
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)
|
||||
parser.add_argument("--dependances", type=Path)
|
||||
|
||||
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||
|
||||
|
|
@ -260,16 +314,35 @@ def main() -> int:
|
|||
active_parser = subparsers.add_parser("verifier-actif", help="Verifie qu'un hote est actif.")
|
||||
active_parser.add_argument("--hote", required=True)
|
||||
|
||||
host_dependencies_parser = subparsers.add_parser(
|
||||
"verifier-dependances-hote",
|
||||
help="Verifie les prerequis actifs requis par les groupes d'un hote.",
|
||||
)
|
||||
host_dependencies_parser.add_argument("--hote", required=True)
|
||||
|
||||
group_dependencies_parser = subparsers.add_parser(
|
||||
"verifier-dependances-groupe",
|
||||
help="Verifie les prerequis actifs requis par un groupe.",
|
||||
)
|
||||
group_dependencies_parser.add_argument("--groupe", 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)
|
||||
|
||||
dependencies_parser = subparsers.add_parser(
|
||||
"verifier-dependances",
|
||||
help="Verifie le registre de dependances et les prerequis des groupes actifs.",
|
||||
)
|
||||
dependencies_parser.add_argument("--dossier-playbooks", required=True, type=Path)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
data = charger_inventaire(args.inventaire)
|
||||
dependencies = charger_dependances(args.dependances)
|
||||
|
||||
if args.command == "ajouter":
|
||||
ajouter_hote(
|
||||
|
|
@ -282,11 +355,17 @@ def main() -> int:
|
|||
)
|
||||
if args.dossier_playbooks:
|
||||
verifier_playbooks_groupes(data, args.dossier_playbooks)
|
||||
if args.dependances:
|
||||
verifier_dependances_connues(data, dependencies, args.dossier_playbooks)
|
||||
verifier_dependances_actives(data, dependencies)
|
||||
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)
|
||||
if args.dependances:
|
||||
verifier_dependances_connues(data, dependencies, args.dossier_playbooks)
|
||||
verifier_dependances_actives(data, dependencies)
|
||||
ecrire_inventaire(args.inventaire, data)
|
||||
elif args.command == "afficher":
|
||||
afficher_hote(data, args.hote)
|
||||
|
|
@ -294,8 +373,18 @@ def main() -> int:
|
|||
afficher_playbooks_hote(data, args.hote, args.dossier_playbooks)
|
||||
elif args.command == "verifier-actif":
|
||||
verifier_hote_actif(data, args.hote)
|
||||
elif args.command == "verifier-dependances-hote":
|
||||
exiger_dependances(dependencies, args.dependances)
|
||||
verifier_dependances_hote(data, dependencies, args.hote)
|
||||
elif args.command == "verifier-dependances-groupe":
|
||||
exiger_dependances(dependencies, args.dependances)
|
||||
verifier_dependances_groupes(data, dependencies, [args.groupe])
|
||||
elif args.command == "verifier-playbooks":
|
||||
verifier_playbooks_groupes(data, args.dossier_playbooks)
|
||||
elif args.command == "verifier-dependances":
|
||||
exiger_dependances(dependencies, args.dependances)
|
||||
verifier_dependances_connues(data, dependencies, args.dossier_playbooks)
|
||||
verifier_dependances_actives(data, dependencies)
|
||||
except Exception as exc:
|
||||
print(f"erreur: {exc}", file=sys.stderr)
|
||||
return 2
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ from __future__ import annotations
|
|||
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
|
||||
GROUPES_OPERATIONNELS_PREFIXES = ("serveurs_", "clients_")
|
||||
GROUPE_HOTES_ACTIFS = "hotes_actifs"
|
||||
|
|
@ -20,3 +22,36 @@ def groupes_operationnels_connus(enfants: dict, dossier_playbooks: Path) -> list
|
|||
groupes = set(enfants)
|
||||
groupes.update(playbook.stem for playbook in dossier_playbooks.glob("*.yml"))
|
||||
return sorted(groupe for groupe in groupes if est_groupe_operationnel(groupe))
|
||||
|
||||
|
||||
def charger_dependances(path: Path | None) -> dict:
|
||||
if not path:
|
||||
return {}
|
||||
if not path.exists():
|
||||
raise ValueError(f"Fichier de dependances introuvable: {path}")
|
||||
|
||||
with path.open("r", encoding="utf-8") as dependencies_file:
|
||||
data = yaml.safe_load(dependencies_file) or {}
|
||||
|
||||
if not isinstance(data, dict):
|
||||
raise ValueError(f"{path} ne contient pas une table YAML.")
|
||||
|
||||
groups = data.get("groupes", {})
|
||||
if not isinstance(groups, dict):
|
||||
raise ValueError(f"{path} doit contenir une cle 'groupes' de type table.")
|
||||
|
||||
for group, config in groups.items():
|
||||
if not est_groupe_operationnel(group):
|
||||
raise ValueError(f"Groupe de dependance non operationnel: {group}")
|
||||
if not isinstance(config, dict):
|
||||
raise ValueError(f"Dependance invalide pour {group}: table attendue.")
|
||||
required = config.get("requiert_groupes_actifs", [])
|
||||
if required is None:
|
||||
required = []
|
||||
if not isinstance(required, list) or not all(isinstance(item, str) for item in required):
|
||||
raise ValueError(f"requiert_groupes_actifs doit etre une liste de groupes pour {group}.")
|
||||
for required_group in required:
|
||||
if not est_groupe_operationnel(required_group):
|
||||
raise ValueError(f"Prerequis non operationnel pour {group}: {required_group}")
|
||||
|
||||
return groups
|
||||
|
|
|
|||
Reference in a new issue