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/` ;
|
- créer le playbook homonyme dans `playbooks/groupes/` ;
|
||||||
- documenter son intention opérationnelle ;
|
- 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 ;
|
- prévoir les rôles nécessaires ;
|
||||||
- valider au minimum sa syntaxe ;
|
- valider au minimum sa syntaxe ;
|
||||||
- l'ajouter aux facilités d'exploitation si l'opérateur doit l'utiliser directement.
|
- 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.
|
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 :
|
Les intégrations clientes doivent être :
|
||||||
|
|
||||||
- idempotentes ;
|
- idempotentes ;
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@
|
||||||
### Ajouté
|
### 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 `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 `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é
|
### Modifié
|
||||||
- Renforcement de la validation d'inventaire :
|
- 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.
|
- 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.
|
- 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`.
|
- 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
|
## 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_LAB ?= inventories/lab/hosts.yml
|
||||||
INVENTAIRE_PRODUCTION ?= inventories/production/hosts.yml
|
INVENTAIRE_PRODUCTION ?= inventories/production/hosts.yml
|
||||||
FICHIER_INVENTAIRE ?= $(INVENTAIRE_PRODUCTION)
|
FICHIER_INVENTAIRE ?= $(INVENTAIRE_PRODUCTION)
|
||||||
|
FICHIER_DEPENDANCES ?= docs/dependances-groupes.yml
|
||||||
GROUPE_MODELE ?= modeles_vm
|
GROUPE_MODELE ?= modeles_vm
|
||||||
GROUPE_DEBIAN ?= serveurs_debian
|
GROUPE_DEBIAN ?= serveurs_debian
|
||||||
GROUPE_HOTES_ACTIFS ?= hotes_actifs
|
GROUPE_HOTES_ACTIFS ?= hotes_actifs
|
||||||
|
|
@ -141,7 +142,7 @@ aide:
|
||||||
@printf '%s\n' 'Variables frequentes'
|
@printf '%s\n' 'Variables frequentes'
|
||||||
@printf '%s\n' ' HOTE=web-01 GROUPE=serveurs_debian GROUPES="serveurs_debian serveurs_durcis"'
|
@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' ' 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
|
.PHONY: lint
|
||||||
lint: ansible-runtime
|
lint: ansible-runtime
|
||||||
|
|
@ -187,7 +188,7 @@ hote-ajouter: ansible-runtime
|
||||||
printf '%s\n' 'Refus: relancer avec HOTE=nom_hote ADRESSE_IP=adresse_ip.'; \
|
printf '%s\n' 'Refus: relancer avec HOTE=nom_hote ADRESSE_IP=adresse_ip.'; \
|
||||||
exit 2; \
|
exit 2; \
|
||||||
fi
|
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) inventaire-verifier
|
||||||
$(MAKE) hote-afficher HOTE="$(HOTE)" FICHIER_INVENTAIRE="$(FICHIER_INVENTAIRE)"
|
$(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".'; \
|
printf '%s\n' 'Refus: relancer avec HOTE=nom_hote GROUPES="groupe1 groupe2".'; \
|
||||||
exit 2; \
|
exit 2; \
|
||||||
fi
|
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) inventaire-verifier
|
||||||
$(MAKE) hote-afficher HOTE="$(HOTE)" FICHIER_INVENTAIRE="$(FICHIER_INVENTAIRE)"
|
$(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".'; \
|
printf '%s\n' 'Refus: relancer avec HOTE=nom_hote GROUPES="groupe1 groupe2".'; \
|
||||||
exit 2; \
|
exit 2; \
|
||||||
fi
|
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) inventaire-verifier
|
||||||
$(MAKE) hote-afficher HOTE="$(HOTE)" FICHIER_INVENTAIRE="$(FICHIER_INVENTAIRE)"
|
$(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'; \
|
printf '%s\n' 'Refus: aucun playbook pour ce groupe: $(DOSSIER_PLAYBOOKS_GROUPES)/$(GROUPE).yml'; \
|
||||||
exit 2; \
|
exit 2; \
|
||||||
fi
|
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)'
|
ansible-playbook -i $(INVENTAIRE_PRODUCTION) "$(DOSSIER_PLAYBOOKS_GROUPES)/$(GROUPE).yml" --limit '$(GROUPE):&$(GROUPE_HOTES_ACTIFS)'
|
||||||
|
|
||||||
deployer:
|
deployer:
|
||||||
|
|
@ -234,6 +236,7 @@ deployer:
|
||||||
fi
|
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) 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) 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-acces-hote LIMITE="$(HOTE)"
|
||||||
$(MAKE) _verifier-privileges-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 $$?; \
|
@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_LAB) --list > /dev/null
|
||||||
ansible-inventory -i $(INVENTAIRE_PRODUCTION) --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) 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
|
inventaire-lister: ansible-runtime
|
||||||
ansible-inventory -i $(FICHIER_INVENTAIRE) --list
|
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`.
|
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`.
|
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 :
|
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` |
|
| Forgejo | `serveurs_forgejo` | `playbooks/groupes/serveurs_forgejo.yml` | `forgejo` |
|
||||||
| Sendmail MTA | `serveurs_sendmail` | `playbooks/groupes/serveurs_sendmail.yml` | `sendmail` |
|
| Sendmail MTA | `serveurs_sendmail` | `playbooks/groupes/serveurs_sendmail.yml` | `sendmail` |
|
||||||
| step-ca | `serveurs_step_ca` | `playbooks/groupes/serveurs_step_ca.yml` | `step_ca` |
|
| 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` |
|
| Redis | `serveurs_redis` | `playbooks/groupes/serveurs_redis.yml` | `redis` |
|
||||||
| NGINX WAF et reverse proxy | `serveurs_nginx` | `playbooks/groupes/serveurs_nginx.yml` | `nginx` |
|
| NGINX WAF et reverse proxy | `serveurs_nginx` | `playbooks/groupes/serveurs_nginx.yml` | `nginx` |
|
||||||
| Nextcloud | `serveurs_nextcloud` | `playbooks/groupes/serveurs_nextcloud.yml` | `nextcloud` |
|
| 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-pki-01` | step-ca |
|
||||||
| `infra-edge-01` | NGINX WAF et reverse proxy |
|
| `infra-edge-01` | NGINX WAF et reverse proxy |
|
||||||
| `infra-mail-01` | Sendmail MTA |
|
| `infra-mail-01` | Sendmail MTA |
|
||||||
|
| `infra-dns-01` | PowerDNS |
|
||||||
| `idm-01` | OpenLDAP, Keycloak |
|
| `idm-01` | OpenLDAP, Keycloak |
|
||||||
| `data-01` | PostgreSQL, Redis |
|
| `data-01` | PostgreSQL, Redis |
|
||||||
| `obs-01` | Prometheus, Loki, Grafana |
|
| `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
|
### 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.
|
- Service central : autorité de certification interne et ACME.
|
||||||
- Intégration à prévoir : `clients_pki`.
|
- Intégration à prévoir : `clients_pki`.
|
||||||
- Raison : les autres services auront besoin de certificats fiables avant d'être exposés proprement.
|
- 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.
|
- Service central : reverse proxy, terminaison TLS, publication HTTP(S), WAF.
|
||||||
- Intégration à prévoir : publication des services HTTP derrière le proxy.
|
- 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.
|
- 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.
|
- Service central : relais SMTP sortant.
|
||||||
- Intégration à prévoir : `clients_smtp`.
|
- Intégration à prévoir : `clients_smtp`.
|
||||||
- Raison : les notifications, réinitialisations de mot de passe et alertes doivent fonctionner tôt.
|
- Raison : les notifications, réinitialisations de mot de passe et alertes doivent fonctionner tôt.
|
||||||
|
|
||||||
### Phase 2 - Données et identité
|
### Phase 2 - Données et identité
|
||||||
|
|
||||||
4. `serveurs_postgresql`
|
5. `serveurs_postgresql`
|
||||||
- Service central : base de données relationnelle partagée.
|
- Service central : base de données relationnelle partagée.
|
||||||
- Intégration à prévoir : bases dédiées par application, comptes applicatifs et sauvegardes.
|
- 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.
|
- Raison : Keycloak, Grafana, Icinga Web 2, Forgejo et Nextcloud peuvent dépendre de PostgreSQL.
|
||||||
|
|
||||||
5. `serveurs_openldap`
|
6. `serveurs_openldap`
|
||||||
- Service central : annuaire interne.
|
- Service central : annuaire interne.
|
||||||
- Intégration à prévoir : `clients_ldap`.
|
- Intégration à prévoir : `clients_ldap`.
|
||||||
- Raison : l'identité Unix et l'annuaire doivent exister avant les intégrations d'authentification avancées.
|
- 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.
|
- Service central : SSO/OIDC/SAML.
|
||||||
- Intégration à prévoir : applications web derrière `serveurs_nginx`.
|
- 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.
|
- Raison : les applications devraient être branchées au SSO dès leur arrivée plutôt qu'après coup.
|
||||||
|
|
||||||
### Phase 3 - Observabilité minimale
|
### Phase 3 - Observabilité minimale
|
||||||
|
|
||||||
7. `serveurs_prometheus`
|
8. `serveurs_prometheus`
|
||||||
- Service central : métriques.
|
- Service central : métriques.
|
||||||
- Intégration à prévoir : `clients_metriques`.
|
- Intégration à prévoir : `clients_metriques`.
|
||||||
- Raison : les prochains services doivent être mesurables dès leur déploiement.
|
- Raison : les prochains services doivent être mesurables dès leur déploiement.
|
||||||
|
|
||||||
8. `serveurs_loki`
|
9. `serveurs_loki`
|
||||||
- Service central : journaux centralisés.
|
- Service central : journaux centralisés.
|
||||||
- Intégration à prévoir : `clients_journaux`.
|
- Intégration à prévoir : `clients_journaux`.
|
||||||
- Raison : les journaux centralisés accélèrent le diagnostic des services suivants.
|
- Raison : les journaux centralisés accélèrent le diagnostic des services suivants.
|
||||||
|
|
||||||
9. `serveurs_grafana`
|
10. `serveurs_grafana`
|
||||||
- Service central : tableaux de bord.
|
- Service central : tableaux de bord.
|
||||||
- Intégration à prévoir : datasources Prometheus et Loki, authentification Keycloak.
|
- Intégration à prévoir : datasources Prometheus et Loki, authentification Keycloak.
|
||||||
- Raison : Grafana consolide les métriques et journaux après leur mise en place.
|
- Raison : Grafana consolide les métriques et journaux après leur mise en place.
|
||||||
|
|
||||||
### Phase 4 - Supervision active
|
### Phase 4 - Supervision active
|
||||||
|
|
||||||
10. `serveurs_icinga`
|
11. `serveurs_icinga`
|
||||||
- Service central : supervision active, interface web et vues métiers Icinga.
|
- Service central : supervision active, interface web et vues métiers Icinga.
|
||||||
- Composants prévus : Icinga 2, Icinga Web 2, Icinga BPM.
|
- Composants prévus : Icinga 2, Icinga Web 2, Icinga BPM.
|
||||||
- Intégrations à prévoir : `clients_supervision`, PostgreSQL, NGINX, Keycloak si retenu.
|
- 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
|
### Phase 5 - Services applicatifs internes
|
||||||
|
|
||||||
11. `serveurs_redis`
|
12. `serveurs_redis`
|
||||||
- Service central : cache et files internes.
|
- Service central : cache et files internes.
|
||||||
- Intégration à prévoir : Nextcloud et autres applications qui en ont besoin.
|
- Intégration à prévoir : Nextcloud et autres applications qui en ont besoin.
|
||||||
- Raison : Redis est une dépendance applicative, pas une fondation globale.
|
- Raison : Redis est une dépendance applicative, pas une fondation globale.
|
||||||
|
|
||||||
12. `serveurs_forgejo`
|
13. `serveurs_forgejo`
|
||||||
- Service central : forge Git.
|
- Service central : forge Git.
|
||||||
- Intégration à prévoir : PostgreSQL, NGINX, Keycloak, SMTP, sauvegardes.
|
- Intégration à prévoir : PostgreSQL, NGINX, Keycloak, SMTP, sauvegardes.
|
||||||
- Raison : la forge devient plus utile après SSO, TLS, SMTP et observabilité.
|
- Raison : la forge devient plus utile après SSO, TLS, SMTP et observabilité.
|
||||||
|
|
||||||
13. `serveurs_nextcloud`
|
14. `serveurs_nextcloud`
|
||||||
- Service central : collaboration fichiers.
|
- Service central : collaboration fichiers.
|
||||||
- Intégration à prévoir : PostgreSQL, Redis, NGINX, Keycloak, SMTP, sauvegardes.
|
- Intégration à prévoir : PostgreSQL, Redis, NGINX, Keycloak, SMTP, sauvegardes.
|
||||||
- Raison : Nextcloud dépend de plusieurs fondations et doit arriver après elles.
|
- 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.
|
- Service central : édition documentaire en ligne.
|
||||||
- Intégration à prévoir : Nextcloud, NGINX, certificats.
|
- Intégration à prévoir : Nextcloud, NGINX, certificats.
|
||||||
- Raison : Collabora est une extension de Nextcloud et doit venir après lui.
|
- 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.
|
- 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.
|
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-pki-01
|
||||||
infra-edge-01
|
infra-edge-01
|
||||||
infra-mail-01
|
infra-mail-01
|
||||||
|
infra-dns-01
|
||||||
idm-01
|
idm-01
|
||||||
data-01
|
data-01
|
||||||
obs-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-pki-01` | `serveurs_step_ca` |
|
||||||
| `infra-edge-01` | `serveurs_nginx` |
|
| `infra-edge-01` | `serveurs_nginx` |
|
||||||
| `infra-mail-01` | `serveurs_sendmail` |
|
| `infra-mail-01` | `serveurs_sendmail` |
|
||||||
|
| `infra-dns-01` | `serveurs_powerdns` |
|
||||||
| `idm-01` | `serveurs_openldap`, `serveurs_keycloak` |
|
| `idm-01` | `serveurs_openldap`, `serveurs_keycloak` |
|
||||||
| `data-01` | `serveurs_postgresql`, `serveurs_redis` |
|
| `data-01` | `serveurs_postgresql`, `serveurs_redis` |
|
||||||
| `obs-01` | `serveurs_prometheus`, `serveurs_loki`, `serveurs_grafana` |
|
| `obs-01` | `serveurs_prometheus`, `serveurs_loki`, `serveurs_grafana` |
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ all:
|
||||||
proxmox_vmid: 91201
|
proxmox_vmid: 91201
|
||||||
infra-mail-01:
|
infra-mail-01:
|
||||||
proxmox_vmid: 91301
|
proxmox_vmid: 91301
|
||||||
|
infra-dns-01:
|
||||||
|
proxmox_vmid: 91401
|
||||||
idm-01:
|
idm-01:
|
||||||
proxmox_vmid: 92101
|
proxmox_vmid: 92101
|
||||||
data-01:
|
data-01:
|
||||||
|
|
@ -43,6 +45,7 @@ all:
|
||||||
infra-pki-01: {}
|
infra-pki-01: {}
|
||||||
infra-edge-01: {}
|
infra-edge-01: {}
|
||||||
infra-mail-01: {}
|
infra-mail-01: {}
|
||||||
|
infra-dns-01: {}
|
||||||
idm-01: {}
|
idm-01: {}
|
||||||
data-01: {}
|
data-01: {}
|
||||||
obs-01: {}
|
obs-01: {}
|
||||||
|
|
@ -60,6 +63,7 @@ all:
|
||||||
infra-pki-01: {}
|
infra-pki-01: {}
|
||||||
infra-edge-01: {}
|
infra-edge-01: {}
|
||||||
infra-mail-01: {}
|
infra-mail-01: {}
|
||||||
|
infra-dns-01: {}
|
||||||
idm-01: {}
|
idm-01: {}
|
||||||
data-01: {}
|
data-01: {}
|
||||||
obs-01: {}
|
obs-01: {}
|
||||||
|
|
@ -104,6 +108,9 @@ all:
|
||||||
serveurs_step_ca:
|
serveurs_step_ca:
|
||||||
hosts:
|
hosts:
|
||||||
infra-pki-01: {}
|
infra-pki-01: {}
|
||||||
|
serveurs_powerdns:
|
||||||
|
hosts:
|
||||||
|
infra-dns-01: {}
|
||||||
serveurs_redis:
|
serveurs_redis:
|
||||||
hosts:
|
hosts:
|
||||||
data-01: {}
|
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_ACTIFS,
|
||||||
GROUPE_HOTES_PLANIFIES,
|
GROUPE_HOTES_PLANIFIES,
|
||||||
GROUPES_ETAT_HOTE,
|
GROUPES_ETAT_HOTE,
|
||||||
|
charger_dependances,
|
||||||
est_groupe_operationnel,
|
est_groupe_operationnel,
|
||||||
groupes_operationnels_connus,
|
groupes_operationnels_connus,
|
||||||
)
|
)
|
||||||
|
|
@ -22,6 +23,7 @@ from inventory_rules import (
|
||||||
RACINE = Path(__file__).resolve().parents[1]
|
RACINE = Path(__file__).resolve().parents[1]
|
||||||
INVENTAIRE_DEFAUT = RACINE / "inventories/production/hosts.yml"
|
INVENTAIRE_DEFAUT = RACINE / "inventories/production/hosts.yml"
|
||||||
DOSSIER_PLAYBOOKS_GROUPES = RACINE / "playbooks/groupes"
|
DOSSIER_PLAYBOOKS_GROUPES = RACINE / "playbooks/groupes"
|
||||||
|
FICHIER_DEPENDANCES = RACINE / "docs/dependances-groupes.yml"
|
||||||
|
|
||||||
|
|
||||||
def charger_yaml(path: Path) -> dict:
|
def charger_yaml(path: Path) -> dict:
|
||||||
|
|
@ -99,9 +101,11 @@ def liste_hotes(data: dict) -> list[dict]:
|
||||||
|
|
||||||
def inventaire_api(path: Path) -> dict:
|
def inventaire_api(path: Path) -> dict:
|
||||||
data = charger_yaml(path)
|
data = charger_yaml(path)
|
||||||
|
dependencies = charger_dependances(FICHIER_DEPENDANCES)
|
||||||
return {
|
return {
|
||||||
"inventaire": str(path.relative_to(RACINE)),
|
"inventaire": str(path.relative_to(RACINE)),
|
||||||
"groupes": groupes_disponibles(data),
|
"groupes": groupes_disponibles(data),
|
||||||
|
"dependances": dependencies,
|
||||||
"hotes": liste_hotes(data),
|
"hotes": liste_hotes(data),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -145,7 +149,30 @@ def construire_inventaire(payload: dict, groupes_connus: list[str]) -> dict:
|
||||||
return {"all": {"children": enfants_data}}
|
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()
|
noms: set[str] = set()
|
||||||
vmids: dict[str, str] = {}
|
vmids: dict[str, str] = {}
|
||||||
for hote in payload.get("hotes", []):
|
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}")
|
raise ValueError(f"VMID en double: {vmid} pour {vmids[vmid]} et {nom}")
|
||||||
vmids[vmid] = nom
|
vmids[vmid] = nom
|
||||||
|
|
||||||
|
valider_dependances_payload(payload, dependencies)
|
||||||
|
|
||||||
|
|
||||||
HTML = r"""<!doctype html>
|
HTML = r"""<!doctype html>
|
||||||
<html lang="fr">
|
<html lang="fr">
|
||||||
|
|
@ -242,6 +271,7 @@ HTML = r"""<!doctype html>
|
||||||
</main>
|
</main>
|
||||||
<script>
|
<script>
|
||||||
let groupes = [];
|
let groupes = [];
|
||||||
|
let dependances = {};
|
||||||
let hotes = [];
|
let hotes = [];
|
||||||
|
|
||||||
function message(texte, erreur=false) {
|
function message(texte, erreur=false) {
|
||||||
|
|
@ -254,6 +284,7 @@ HTML = r"""<!doctype html>
|
||||||
const reponse = await fetch('/api/inventaire');
|
const reponse = await fetch('/api/inventaire');
|
||||||
const data = await reponse.json();
|
const data = await reponse.json();
|
||||||
groupes = data.groupes;
|
groupes = data.groupes;
|
||||||
|
dependances = data.dependances || {};
|
||||||
hotes = data.hotes;
|
hotes = data.hotes;
|
||||||
document.getElementById('inventaire').textContent = data.inventaire;
|
document.getElementById('inventaire').textContent = data.inventaire;
|
||||||
dessiner();
|
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><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="vmid"><input value="${echapper(String(hote.vmid || ''))}" oninput="definir(${index}, 'vmid', this.value)"></td>
|
||||||
<td class="groupes"><div class="groupes-liste">${groupes.map(groupe => `
|
<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>
|
`).join('')}</div></td>
|
||||||
<td><button type="button" class="danger" onclick="supprimerHote(${index})">Retirer</button></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]));
|
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() {
|
async function sauvegarder() {
|
||||||
const reponse = await fetch('/api/inventaire', {
|
const reponse = await fetch('/api/inventaire', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|
@ -318,6 +356,7 @@ HTML = r"""<!doctype html>
|
||||||
message(data.erreur || 'Sauvegarde refusée.', true);
|
message(data.erreur || 'Sauvegarde refusée.', true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
dependances = data.dependances || {};
|
||||||
groupes = data.groupes;
|
groupes = data.groupes;
|
||||||
hotes = data.hotes;
|
hotes = data.hotes;
|
||||||
dessiner();
|
dessiner();
|
||||||
|
|
@ -366,7 +405,8 @@ class Gestionnaire(BaseHTTPRequestHandler):
|
||||||
payload = json.loads(self.rfile.read(longueur).decode("utf-8"))
|
payload = json.loads(self.rfile.read(longueur).decode("utf-8"))
|
||||||
courant = charger_yaml(self.inventaire)
|
courant = charger_yaml(self.inventaire)
|
||||||
groupes_connus = groupes_disponibles(courant)
|
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)
|
nouveau = construire_inventaire(payload, groupes_connus)
|
||||||
ecrire_yaml(self.inventaire, nouveau)
|
ecrire_yaml(self.inventaire, nouveau)
|
||||||
self.repondre_json(200, inventaire_api(self.inventaire))
|
self.repondre_json(200, inventaire_api(self.inventaire))
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ from inventory_rules import (
|
||||||
GROUPE_HOTES_ACTIFS,
|
GROUPE_HOTES_ACTIFS,
|
||||||
GROUPE_HOTES_PLANIFIES,
|
GROUPE_HOTES_PLANIFIES,
|
||||||
GROUPES_ETAT_HOTE,
|
GROUPES_ETAT_HOTE,
|
||||||
|
charger_dependances,
|
||||||
est_groupe_operationnel,
|
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}")
|
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:
|
def verifier_coherence_hotes(data: dict) -> None:
|
||||||
vmids: dict[str, str] = {}
|
vmids: dict[str, str] = {}
|
||||||
|
|
||||||
|
|
@ -234,6 +287,7 @@ def verifier_playbooks_groupes(data: dict, playbook_dir: Path) -> None:
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
parser = argparse.ArgumentParser(description="Gere les hotes d'un inventaire Ansible statique Set-OPS.")
|
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("--inventaire", required=True, type=Path)
|
||||||
|
parser.add_argument("--dependances", type=Path)
|
||||||
|
|
||||||
subparsers = parser.add_subparsers(dest="command", required=True)
|
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 = subparsers.add_parser("verifier-actif", help="Verifie qu'un hote est actif.")
|
||||||
active_parser.add_argument("--hote", required=True)
|
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(
|
verify_parser = subparsers.add_parser(
|
||||||
"verifier-playbooks",
|
"verifier-playbooks",
|
||||||
help="Verifie que les groupes operationnels avec hotes ont un playbook homonyme.",
|
help="Verifie que les groupes operationnels avec hotes ont un playbook homonyme.",
|
||||||
)
|
)
|
||||||
verify_parser.add_argument("--dossier-playbooks", required=True, type=Path)
|
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()
|
args = parser.parse_args()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = charger_inventaire(args.inventaire)
|
data = charger_inventaire(args.inventaire)
|
||||||
|
dependencies = charger_dependances(args.dependances)
|
||||||
|
|
||||||
if args.command == "ajouter":
|
if args.command == "ajouter":
|
||||||
ajouter_hote(
|
ajouter_hote(
|
||||||
|
|
@ -282,11 +355,17 @@ def main() -> int:
|
||||||
)
|
)
|
||||||
if args.dossier_playbooks:
|
if args.dossier_playbooks:
|
||||||
verifier_playbooks_groupes(data, 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)
|
ecrire_inventaire(args.inventaire, data)
|
||||||
elif args.command == "definir-groupes":
|
elif args.command == "definir-groupes":
|
||||||
definir_groupes_hote(data, args.hote, analyser_groupes(args.groupes))
|
definir_groupes_hote(data, args.hote, analyser_groupes(args.groupes))
|
||||||
if args.dossier_playbooks:
|
if args.dossier_playbooks:
|
||||||
verifier_playbooks_groupes(data, 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)
|
ecrire_inventaire(args.inventaire, data)
|
||||||
elif args.command == "afficher":
|
elif args.command == "afficher":
|
||||||
afficher_hote(data, args.hote)
|
afficher_hote(data, args.hote)
|
||||||
|
|
@ -294,8 +373,18 @@ def main() -> int:
|
||||||
afficher_playbooks_hote(data, args.hote, args.dossier_playbooks)
|
afficher_playbooks_hote(data, args.hote, args.dossier_playbooks)
|
||||||
elif args.command == "verifier-actif":
|
elif args.command == "verifier-actif":
|
||||||
verifier_hote_actif(data, args.hote)
|
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":
|
elif args.command == "verifier-playbooks":
|
||||||
verifier_playbooks_groupes(data, args.dossier_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:
|
except Exception as exc:
|
||||||
print(f"erreur: {exc}", file=sys.stderr)
|
print(f"erreur: {exc}", file=sys.stderr)
|
||||||
return 2
|
return 2
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ from __future__ import annotations
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
|
||||||
GROUPES_OPERATIONNELS_PREFIXES = ("serveurs_", "clients_")
|
GROUPES_OPERATIONNELS_PREFIXES = ("serveurs_", "clients_")
|
||||||
GROUPE_HOTES_ACTIFS = "hotes_actifs"
|
GROUPE_HOTES_ACTIFS = "hotes_actifs"
|
||||||
|
|
@ -20,3 +22,36 @@ def groupes_operationnels_connus(enfants: dict, dossier_playbooks: Path) -> list
|
||||||
groupes = set(enfants)
|
groupes = set(enfants)
|
||||||
groupes.update(playbook.stem for playbook in dossier_playbooks.glob("*.yml"))
|
groupes.update(playbook.stem for playbook in dossier_playbooks.glob("*.yml"))
|
||||||
return sorted(groupe for groupe in groupes if est_groupe_operationnel(groupe))
|
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