Une convention de nommage n’existe que si quelqu’un vérifie qu’elle est tenue. Un audit par API compare chaque nom de VM à la règle, puis à ses catégories, et sort la liste des écarts, sans rien corriger tout seul.
Contexte
Les catégories rangent les VM (le concept est détaillé dans ce billet). Quand elles sont posées par des playbooks qui lisent le nom, une règle de nommage floue donne des tags faux ou absents. Le rattrapage par phases et les défauts de playbooks sont racontés ici ; ce billet porte sur l’audit lui-même.
Écrire la règle en regex
Une regex à groupes nommés décrit la convention : les groupes entité et environnement donnent directement les valeurs de catégorie attendues.
import re
REGLE = re.compile(
r"^(?P<entite>[a-z]{2})(?P<env>\d{2})(?P<fonction>[a-z]+)(?P<site>[a-z])(?P<instance>\d{3})$"
)
ENV = {"31": "Production", "32": "Preproduction", "33": "Recette"} # table de la convention
Trois leçons terrain. D’abord, fixer la position : une regex de playbook en ^.{0,2}<code> laissait le code tomber au mauvais endroit, elle a été durcie en ^.{2}<code>.+[0-9]{3}$. Ensuite, la centaine du numéro d’instance (xy31webb203) n’est pas un environnement : c’est un numéro d’instance ou de réplica. Enfin, l’environnement qui fait foi reste la catégorie posée ; le nom n’est qu’un indice fort.
Collecter par l’API v4
L’audit d’origine passait par l’API v3 ; la collecte v4 ci-dessous, écrite d’après la doc, n’a pas été rejouée sur le terrain. Deux listes suffisent. Côté VM, GET /api/vmm/v4.0/ahv/config/vms (chemin d’un exemple nutanix.dev) : d’après la doc de list_vms dans le SDK, categories fait partie des champs de $select, pas de ceux de $filter. Chaque entrée est un CategoryReference qui ne porte qu’un extId. Côté catégories, la liste v4 (GET /api/prism/v4.0/config/categories, utilisée sur le terrain lors d’un renommage) donne extId, key et value : on en fait un dictionnaire. On indexe tout par extId, jamais par nom (les homonymes existent).
import csv
import os
import requests
PC = os.environ["PC_HOST"]
AUTH = (os.environ["PC_USER"], os.environ["PC_PASSWORD"])
VERIFY = os.environ.get("PC_CA_BUNDLE") or True # bundle CA ; variable vide = vérification par défaut
def lister(chemin, select):
page, sortie = 0, []
while True:
r = requests.get(
f"https://{PC}:9440/api/{chemin}?$page={page}&$limit=100&$select={select}",
auth=AUTH, verify=VERIFY, timeout=30,
)
r.raise_for_status()
corps = r.json()
data = corps.get("data") or []
sortie += data
if not data or len(sortie) >= corps["metadata"]["totalAvailableResults"]:
return sortie
page += 1
CATS = {c["extId"]: (c["key"], c["value"])
for c in lister("prism/v4.0/config/categories", "extId,key,value")}
VMS = lister("vmm/v4.0/ahv/config/vms", "extId,name,categories")
La pagination ($page à index zéro, $limit à 100 au plus) et totalAvailableResults sont détaillés dans le billet OData.
Classer chaque VM
def classer(vm):
nom = vm["name"].lower()
if nom.endswith("_old"):
return "EXCLUE", ""
m = REGLE.match(nom)
if not m or m["env"] not in ENV:
return "NON_DERIVABLE", ""
attendu = ENV[m["env"]]
poses = {CATS.get(c["extId"], (None, None)) for c in vm.get("categories") or []}
actuel = {v for k, v in poses if k == "Env"}
if not actuel:
return "A_TAGUER", attendu
if attendu not in actuel:
return "CONFLIT", f"tag {sorted(actuel)}, nom {attendu}"
return "OK", ""
with open("audit_nomenclature.csv", "w", newline="", encoding="utf-8-sig") as f:
w = csv.writer(f, delimiter=";") # s'ouvre tel quel dans un tableur français
w.writerow(["extId", "nom", "statut", "detail"])
for vm in VMS:
w.writerow([vm["extId"], vm["name"], *classer(vm)])
Le statut CONFLIT est le plus précieux : le nom dit une chose, le tag une autre, et seul un humain sait lequel a raison. Au premier audit terrain, il n’y en avait aucun sur l’entité et l’environnement ; le vrai sujet était les VM sans tag.
Lire les non dérivables
Ne les rangez pas d’office en « traitement manuel ». Sur le terrain, environ 80 % des noms non dérivables suivaient une variante (une lettre puis le code d’environnement), avec un code valide en position 2-3 dans tous les cas. L’environnement devenait dérivable ; l’entité, non, faute de confirmation de la convention. Une proposition a été faite, sans que rien ne soit tagué avant décision. Le reste, noms libres et copies, part en revue humaine.
Dernier contrôle utile : les VM créées récemment, conformes mais sans tag. Le terrain n’en a trouvé aucune après correction des playbooks, et c’est ce qui a montré qu’ils fonctionnaient. Le champ createTime figure parmi les champs de $select.
Ce qu’il faut retenir
- La règle de nommage s’écrit en regex à groupes nommés, position du code fixée.
categoriesse lit via$selectsur la liste v4 des VM, puis se traduit par un dictionnaireextIdvers clé:valeur.- Cinq statuts : conforme, à taguer, conflit, non dérivable, exclue. Le conflit se tranche à la main.
- L’audit ne corrige rien ; l’application est une phase séparée, après revue.
- Décomposez les non dérivables : une variante peut rester partiellement dérivable.
In English
A regex for the naming rule, two v4 lists for VMs and categories, one status per VM and a CSV of discrepancies. The script observes, the human decides what gets fixed. The v4 VM list accepts categories in $select but not in $filter, and the regex must pin the position of the environment code, since the hundreds digit of the instance number is not one.
Sources & vérification
Testé sur : Prism Central de production, août 2026 (audit d'origine via l'API v3)
- Nutanix API User Guide (nutanix.dev) nutanix.dev
- Nutanix v4 API Batch Operations Part 2, ACTION nutanix.dev
- Updating VMs with the Nutanix v4 APIs and PowerShell (nutanix.dev) nutanix.dev
- VMM v4.0 Python SDK, VmApi (list_vms, champs $select) developers.nutanix.com
- VMM v4.0 Python SDK, modèle Vm developers.nutanix.com
- VMM v4.0 Python SDK, modèle CategoryReference developers.nutanix.com
- Prism v4.0 Java SDK, CategoriesApi developers.nutanix.com
- Expérience directe de l’auteur, en production (versions ci-dessus)
