Set-OPS-Public/playbooks/proxmox/cloner_vm_debian.yml

309 lines
16 KiB
YAML
Raw Permalink Normal View History

---
- name: Cloner une VM Debian depuis le modele Proxmox
hosts: localhost
connection: local
gather_facts: false
become: false
vars:
# Config Proxmox de l'instance : on cherche dans l'inventaire de l'instance,
# quel que soit son nom (lab > principal > production). Compatible « par instance ».
setops_inventaires: [lab, principal, production]
proxmox : pools créés, jeton normalisé, reliquat de voûte supprimé Reconnaissance en lecture seule de l'API du cluster, avec le jeton de la voûte. Trois valeurs devinées étaient fausses, et deux défauts bloquants sont apparus. Corrigé d'après le cluster - stockages : truenas-dbsql manquait ; le catalogue ne garde que ceux qui portent `images` (PBS, cephFS, local et truenas iSCSI n'accueillent pas de disque de VM) ; - ponts : vmbr0 avait été omis, et l'uniformité sur les trois nœuds n'avait pas été vérifiée — un pont partiel empêche la VM de démarrer sur certains nœuds. Pools Chezlepro-17 et Technolibre-11 créés, dérivés comme le reste. Les pools Env.Tenant antérieurs (Prod.Chezlepro, Lab.KBR...) sont l'ancien monde : on n'y touche pas et on n'y verse pas la flotte générée. Diff réel : 2 pools ajoutés, 0 retiré, 1 VM sur 38 déplacée — infra-pki-01, qui n'avait aucun pool. Défaut bloquant : make creer-vm aurait échoué en 401 proxmoxer recompose `utilisateur!nom` à partir d'api_user et d'api_token_id. La voûte stocke la forme complète, que les playbooks passaient telle quelle, d'où un 401 muet — alors que le même jeton fonctionne en curl. Mesuré des deux côtés avec un module en lecture seule : forme complète = 401, forme courte = OK. Normalisation par split('!') | last, qui accepte les deux écritures. Reliquat proxmox.vault.yml supprimé Toléré « en compatibilité », il restait le seul porteur du jeton chez Technolibre — et comme *.vault.yml est gitignoré, ce jeton ne voyageait avec aucun dépôt : une voûte unique (D-19) qui ne l'était pas. Migration faite en mémoire, avec relecture et aller-retour de chiffrement vérifiés avant écriture ; fichier supprimé, listes de chargement des playbooks nettoyées, validé par un appel API réel ne chargeant que all/vault.yml. La garde qui manquait voute.py verifier ne comparait que le gabarit — il disait « complet » pendant qu'un secret vivait ailleurs. Il contrôle maintenant aussi la voûte réelle quand ANSIBLE_VAULT_PASSWORD_FILE la rend déchiffrable : noms de clés seulement, jamais de valeur, et vérification sautée sans mot de passe. Elle a trouvé un second trou dès son premier passage : la voûte réelle de Technolibre n'a ni vault_nextcloud_admin ni vault_nextcloud_oidc, que le plan exige. Non corrigé — générer ces secrets est une décision, et celui d'OIDC doit correspondre à ce que Keycloak connaîtra. 27 preuves OK. --syntax-check et ansible-lint (production) sur les playbooks. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 15:52:44 -04:00
# Voute UNIQUE de l'instance : all/vault.yml porte TOUS les secrets, jeton
# d'hyperviseur compris (D-19). `proxmox.vault.yml` a ete retire le 2026-08-03 :
# tolere "en compatibilite", il restait le SEUL porteur du jeton chez un tenant,
# et il etait gitignore — une voute unique qui ne l'etait pas, dans un fichier
# qui ne voyageait avec aucun depot. P18 ne pouvait pas le voir : il verifie le
# gabarit, pas le contenu reel.
# `proxmox.local.yml` reste une surcharge locale volontaire, non versionnee.
setops_gv_proxmox: [proxmox.yml, proxmox.local.yml, all/vault.yml]
intégrations : le rôle déclare sa politique ; le cluster passe à l'hébergeur Deux corrections de propriété, l'une dans le plan, l'autre dans les intrants. 1. Intégrations universelles (D-33/D-34, P26) Le plan portait 57 lignes d'intégration écrites à la main, dont 28 disaient oui à quelque chose de vrai pour tous les hôtes. Elles n'existaient que pour être oubliées — et elles l'avaient été : dans Chezlepro, backup-01 et infra-pki-01 n'étaient ni supervisés, ni journalisés, ni certifiés. Le rôle déclare désormais sa politique une fois, dans meta/integration.yml ; le plan ne garde que les vrais choix et refuse la recopie. Les exemptions se dérivent du service rendu (sauf_role), jamais d'un nom d'hôte : l'AC ne s'enrôle pas auprès d'elle-même, et l'exemption suit step-ca si on le déplace. Une seule fonction de résolution — integrations_de() — lue par l'inventaire, la voûte et le panneau. Sans le passage par la voûte, les secrets des intégrations universelles auraient cessé d'être exigés et P18 serait passé au vert sur une voûte incomplète. Vérifié : diff vide sur Technolibre (la politique reproduit exactement les 41 lignes retirées) ; sur Chezlepro, exactement les groupes manquants, et pas client_pki sur infra-pki-01. 2. Vue Intégrations : la matrice La fiche montrait les intégrations d'UN serveur ; le trou de Chezlepro n'a pas été trouvé par le panneau mais par le devis de pare-feu. Matrice serveurs x intégrations : colonnes de politique en lecture seule, facultatives cochables sur place, ligne de couverture n/N qui rend le motif visible sans le juger. 3. Propriété des intrants (D-35/D-36, P27) Le cluster Proxmox appartient à l'hébergeur, comme sa fabric et sa frontière. Recopié chez chaque tenant, son inventaire avait déjà divergé : deux listes de stockages contradictoires pour le même matériel. API/nœuds/stockages/ponts vont dans proxmox-hebergeur.yml, à côté d'underlay.yml, dont le chemin se dérive — l'hébergeur reste non déclaré (D-17). Restent au tenant son golden template et ses défauts de placement. Le panneau nomme désormais le propriétaire de chaque section : éditer une section « hébergeur » vaut pour tous ses tenants, et l'écran ne le disait pas. 26 preuves OK, 0 échec. --syntax-check des deux playbooks Proxmox. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 14:09:22 -04:00
# Le CLUSTER appartient a l'HEBERGEUR (API, noeuds, stockages, ponts). Son fichier
# vit dans le depot de l'hebergeur, que le symlink `underlay.yml` designe deja
# (D-14/D-17) : on en DERIVE le chemin plutot que de redeclarer l'hebergeur. Absent
# (depot sans underlay monte), le `stat` echoue simplement et rien n'est charge.
setops_proxmox_hebergeur: "{{ (playbook_dir ~ '/../../underlay.yml') | realpath | dirname }}/proxmox-hebergeur.yml"
tasks:
- name: Verifier les fichiers de variables Proxmox optionnels
ansible.builtin.stat:
path: "{{ playbook_dir }}/../../instance/inventories/{{ item.0 }}/group_vars/{{ item.1 }}"
loop: "{{ setops_inventaires | product(setops_gv_proxmox) | list }}"
loop_control:
label: "{{ item.0 }}/{{ item.1 }}"
register: proxmox_fichiers_variables
- name: Charger les variables Proxmox non sensibles disponibles
ansible.builtin.include_vars:
file: "{{ item.stat.path }}"
loop: "{{ proxmox_fichiers_variables.results }}"
when:
- item.stat.exists
- item.stat.path is not search('vault')
intégrations : le rôle déclare sa politique ; le cluster passe à l'hébergeur Deux corrections de propriété, l'une dans le plan, l'autre dans les intrants. 1. Intégrations universelles (D-33/D-34, P26) Le plan portait 57 lignes d'intégration écrites à la main, dont 28 disaient oui à quelque chose de vrai pour tous les hôtes. Elles n'existaient que pour être oubliées — et elles l'avaient été : dans Chezlepro, backup-01 et infra-pki-01 n'étaient ni supervisés, ni journalisés, ni certifiés. Le rôle déclare désormais sa politique une fois, dans meta/integration.yml ; le plan ne garde que les vrais choix et refuse la recopie. Les exemptions se dérivent du service rendu (sauf_role), jamais d'un nom d'hôte : l'AC ne s'enrôle pas auprès d'elle-même, et l'exemption suit step-ca si on le déplace. Une seule fonction de résolution — integrations_de() — lue par l'inventaire, la voûte et le panneau. Sans le passage par la voûte, les secrets des intégrations universelles auraient cessé d'être exigés et P18 serait passé au vert sur une voûte incomplète. Vérifié : diff vide sur Technolibre (la politique reproduit exactement les 41 lignes retirées) ; sur Chezlepro, exactement les groupes manquants, et pas client_pki sur infra-pki-01. 2. Vue Intégrations : la matrice La fiche montrait les intégrations d'UN serveur ; le trou de Chezlepro n'a pas été trouvé par le panneau mais par le devis de pare-feu. Matrice serveurs x intégrations : colonnes de politique en lecture seule, facultatives cochables sur place, ligne de couverture n/N qui rend le motif visible sans le juger. 3. Propriété des intrants (D-35/D-36, P27) Le cluster Proxmox appartient à l'hébergeur, comme sa fabric et sa frontière. Recopié chez chaque tenant, son inventaire avait déjà divergé : deux listes de stockages contradictoires pour le même matériel. API/nœuds/stockages/ponts vont dans proxmox-hebergeur.yml, à côté d'underlay.yml, dont le chemin se dérive — l'hébergeur reste non déclaré (D-17). Restent au tenant son golden template et ses défauts de placement. Le panneau nomme désormais le propriétaire de chaque section : éditer une section « hébergeur » vaut pour tous ses tenants, et l'écran ne le disait pas. 26 preuves OK, 0 échec. --syntax-check des deux playbooks Proxmox. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 14:09:22 -04:00
- name: Localiser le fichier Proxmox de l'hebergeur
ansible.builtin.stat:
path: "{{ setops_proxmox_hebergeur }}"
register: proxmox_fichier_hebergeur
- name: Charger le cluster de l'hebergeur (autoritaire sur ses propres cles)
ansible.builtin.include_vars:
file: "{{ setops_proxmox_hebergeur }}"
when: proxmox_fichier_hebergeur.stat.exists
- name: Charger les secrets Proxmox Vault
block:
- name: Charger les secrets de la voute Proxmox
ansible.builtin.include_vars:
file: "{{ item.stat.path }}"
loop: "{{ proxmox_fichiers_variables.results }}"
when:
- item.stat.exists
- item.stat.path is search('vault')
no_log: true
rescue:
- name: Expliquer l echec de chargement du Vault
ansible.builtin.fail:
msg: >-
Impossible de charger la voute Proxmox (group_vars/all/vault.yml ou
proxmox.vault.yml). Si elle est chiffree avec Ansible Vault, fournir son
mot de passe ou definir ANSIBLE_VAULT_PASSWORD_FILE avant de relancer make creer-vm.
- name: Appliquer les valeurs Proxmox avec repli environnement
ansible.builtin.set_fact:
proxmox_api_host_effectif: "{{ proxmox_api_host | default(lookup('env', 'PROXMOX_API_HOST'), true) }}"
proxmox_api_user_effectif: "{{ proxmox_api_user | default(lookup('env', 'PROXMOX_API_USER'), true) }}"
proxmox : pools créés, jeton normalisé, reliquat de voûte supprimé Reconnaissance en lecture seule de l'API du cluster, avec le jeton de la voûte. Trois valeurs devinées étaient fausses, et deux défauts bloquants sont apparus. Corrigé d'après le cluster - stockages : truenas-dbsql manquait ; le catalogue ne garde que ceux qui portent `images` (PBS, cephFS, local et truenas iSCSI n'accueillent pas de disque de VM) ; - ponts : vmbr0 avait été omis, et l'uniformité sur les trois nœuds n'avait pas été vérifiée — un pont partiel empêche la VM de démarrer sur certains nœuds. Pools Chezlepro-17 et Technolibre-11 créés, dérivés comme le reste. Les pools Env.Tenant antérieurs (Prod.Chezlepro, Lab.KBR...) sont l'ancien monde : on n'y touche pas et on n'y verse pas la flotte générée. Diff réel : 2 pools ajoutés, 0 retiré, 1 VM sur 38 déplacée — infra-pki-01, qui n'avait aucun pool. Défaut bloquant : make creer-vm aurait échoué en 401 proxmoxer recompose `utilisateur!nom` à partir d'api_user et d'api_token_id. La voûte stocke la forme complète, que les playbooks passaient telle quelle, d'où un 401 muet — alors que le même jeton fonctionne en curl. Mesuré des deux côtés avec un module en lecture seule : forme complète = 401, forme courte = OK. Normalisation par split('!') | last, qui accepte les deux écritures. Reliquat proxmox.vault.yml supprimé Toléré « en compatibilité », il restait le seul porteur du jeton chez Technolibre — et comme *.vault.yml est gitignoré, ce jeton ne voyageait avec aucun dépôt : une voûte unique (D-19) qui ne l'était pas. Migration faite en mémoire, avec relecture et aller-retour de chiffrement vérifiés avant écriture ; fichier supprimé, listes de chargement des playbooks nettoyées, validé par un appel API réel ne chargeant que all/vault.yml. La garde qui manquait voute.py verifier ne comparait que le gabarit — il disait « complet » pendant qu'un secret vivait ailleurs. Il contrôle maintenant aussi la voûte réelle quand ANSIBLE_VAULT_PASSWORD_FILE la rend déchiffrable : noms de clés seulement, jamais de valeur, et vérification sautée sans mot de passe. Elle a trouvé un second trou dès son premier passage : la voûte réelle de Technolibre n'a ni vault_nextcloud_admin ni vault_nextcloud_oidc, que le plan exige. Non corrigé — générer ces secrets est une décision, et celui d'OIDC doit correspondre à ce que Keycloak connaîtra. 27 preuves OK. --syntax-check et ansible-lint (production) sur les playbooks. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 15:52:44 -04:00
# `split('!')[-1]` : proxmoxer recompose lui-meme `utilisateur!nom` a partir de
# `api_user` et `api_token_id`. Lui passer la forme COMPLETE produit
# `ansible@pve!ansible@pve!nom` et un 401 muet — le meme jeton fonctionne en
# HTTP direct, ce qui rend le diagnostic trompeur. Verifie contre le cluster le
# 2026-08-03 : forme complete = 401, forme courte = OK. Sans effet si la voute
# stocke deja le nom seul, donc les deux ecritures restent acceptees.
proxmox_api_token_id_effectif: "{{ (proxmox_api_token_id | default(lookup('env', 'PROXMOX_API_TOKEN_ID'), true)) | string | split('!') | last }}"
proxmox_api_token_secret_effectif: "{{ proxmox_api_token_secret | default(lookup('env', 'PROXMOX_API_TOKEN_SECRET'), true) }}"
proxmox_api_port_effectif: "{{ proxmox_api_port | default(lookup('env', 'PROXMOX_API_PORT'), true) }}"
no_log: true
- name: Valider les parametres obligatoires Proxmox
ansible.builtin.assert:
that:
- proxmox_api_host_effectif | length > 0
- proxmox_api_user_effectif | length > 0
- proxmox_api_token_id_effectif | length > 0
- proxmox_api_token_secret_effectif | length > 0
- proxmox_clone_nom is defined
- proxmox_clone_nom | length > 0
- proxmox_clone_vmid_modele is defined
- proxmox_clone_vmid is defined
- proxmox_clone_noeud is defined
- proxmox_clone_noeud | length > 0
- proxmox_clone_ipconfig0 is defined
- proxmox_clone_ipconfig0 | length > 0
fail_msg: "Parametres Proxmox incomplets. Verifier proxmox.yml, la voute (all/vault.yml ou proxmox.vault.yml) et les variables make propres a la VM."
- name: Lire la cle publique SSH depuis le fichier fourni
ansible.builtin.set_fact:
proxmox_clone_sshkeys: "{{ lookup('ansible.builtin.file', proxmox_clone_cle_publique_fichier) }}"
when:
- proxmox_clone_cle_publique is not defined
- proxmox_clone_cle_publique_fichier is defined
- proxmox_clone_cle_publique_fichier | length > 0
- name: Utiliser la cle publique SSH fournie directement
ansible.builtin.set_fact:
proxmox_clone_sshkeys: "{{ proxmox_clone_cle_publique }}"
when:
- proxmox_clone_cle_publique is defined
- proxmox_clone_cle_publique | length > 0
- name: Construire les serveurs DNS Cloud-Init
ansible.builtin.set_fact:
proxmox_clone_nameservers: >-
{{
(proxmox_clone_dns | default('') | split(','))
| map('trim')
| reject('equalto', '')
| list
}}
- name: Verifier la bibliotheque Python proxmoxer locale
ansible.builtin.command:
cmd: "{{ ansible_playbook_python }} -c 'import proxmoxer'"
changed_when: false
failed_when: false
register: proxmoxer_verification
- name: Refuser si proxmoxer est absent
ansible.builtin.fail:
msg: >-
La bibliotheque Python proxmoxer est absente pour {{ ansible_playbook_python }}.
Installer le paquet systeme approprie, par exemple
sudo apt install python3-proxmoxer, ou installer proxmoxer dans
l'environnement Python utilise par Ansible, puis relancer make creer-vm.
when: proxmoxer_verification.rc != 0
pools Proxmox : un par tenant, dérivé de l'index (D-37, P28) Onze des quatorze serveurs portent le même nom court chez Chezlepro et chez Technolibre. Vérifié un par un, ce n'est pas un problème technique : tout le reste dérive du seed et diverge (10.27.19.21 contre 10.21.19.21, VMID 117402101 contre 111402101, VLAN 1174 contre 1114, deux domaines internes), et rien n'est indexé sur le nom court — les opérations Proxmox portent toutes un vmid, les certificats un FQDN, et client_backup_repo vise backup-01.{{ domaine_interne }}. Le coût est humain : la console Proxmox affiche le nom, et deux infra-pki-01 y sont indiscernables à l'œil. Le VMID porte le tenant, encore faut-il connaître le codage. Un pool par tenant, dérivé du dossier d'instance et de l'index — déjà unique par P21, donc aucun registre de plus : Chezlepro-17, Technolibre-11. make devis-proxmox-pools rattrape la flotte existante (création du pool, puis affectation des VM actives). Les VM créées ensuite entrent d'elles-mêmes : make creer-vm dérive le pool par la même fonction et le passe à la création. Le playbook crée le pool au préalable — proxmox_kvm échoue sur un pool inconnu, et l'API ne sait pas changer le pool d'une VM existante ; c'est aussi pourquoi le rattrapage passe par les membres. P28 garde deux collisions : même nom de pool entre tenants, et surtout même VMID — une machine appartenant à deux tenants serait pire qu'une homonymie. Rien n'est renommé : les homonymes sont la preuve que la nomenclature est un vrai gabarit. Le devis ne lit pas le cluster, il dit l'état cible et non l'écart. 27 preuves OK, 0 échec. --syntax-check du playbook de clonage. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 15:15:14 -04:00
- name: S'assurer que le pool du tenant existe
# Un pool par tenant : la console Proxmox affiche le NOM, et onze serveurs
# portent le meme d'un tenant a l'autre. Le pool restitue l'appartenance sans
# rien renommer. Il doit exister AVANT le clone — `proxmox_kvm` echoue sur un
# pool inconnu, et l'API ne sait pas changer le pool d'une VM deja creee.
community.general.proxmox_pool:
api_host: "{{ proxmox_api_host_effectif }}"
api_port: "{{ proxmox_api_port_effectif | int if proxmox_api_port_effectif | length > 0 else omit }}"
api_user: "{{ proxmox_api_user_effectif }}"
api_token_id: "{{ proxmox_api_token_id_effectif }}"
api_token_secret: "{{ proxmox_api_token_secret_effectif }}"
validate_certs: "{{ proxmox_validate_certs | default(false) | bool }}"
poolid: "{{ proxmox_clone_pool }}"
comment: "Tenant Set-OPS — genere, ne pas renommer a la main"
state: present
when: proxmox_clone_pool | default('', true) | length > 0
- name: Cloner la VM depuis le modele
community.general.proxmox_kvm:
api_host: "{{ proxmox_api_host_effectif }}"
api_port: "{{ proxmox_api_port_effectif | int if proxmox_api_port_effectif | length > 0 else omit }}"
api_user: "{{ proxmox_api_user_effectif }}"
api_token_id: "{{ proxmox_api_token_id_effectif }}"
api_token_secret: "{{ proxmox_api_token_secret_effectif }}"
validate_certs: "{{ proxmox_validate_certs | default(false) | bool }}"
node: "{{ proxmox_clone_noeud }}"
clone: "{{ proxmox_clone_source_nom | default('modele-debian13') }}"
vmid: "{{ proxmox_clone_vmid_modele | int }}"
newid: "{{ proxmox_clone_vmid | int }}"
name: "{{ proxmox_clone_nom }}"
cores: "{{ proxmox_clone_coeurs | int if proxmox_clone_coeurs is defined and proxmox_clone_coeurs | string | length > 0 else omit }}"
memory: "{{ proxmox_clone_memoire | int if proxmox_clone_memoire is defined and proxmox_clone_memoire | string | length > 0 else omit }}"
storage: "{{ proxmox_clone_stockage | default(omit, true) }}"
format: "{{ proxmox_clone_format | default(omit, true) }}"
full: "{{ proxmox_clone_complet | default(true) | bool }}"
pools Proxmox : un par tenant, dérivé de l'index (D-37, P28) Onze des quatorze serveurs portent le même nom court chez Chezlepro et chez Technolibre. Vérifié un par un, ce n'est pas un problème technique : tout le reste dérive du seed et diverge (10.27.19.21 contre 10.21.19.21, VMID 117402101 contre 111402101, VLAN 1174 contre 1114, deux domaines internes), et rien n'est indexé sur le nom court — les opérations Proxmox portent toutes un vmid, les certificats un FQDN, et client_backup_repo vise backup-01.{{ domaine_interne }}. Le coût est humain : la console Proxmox affiche le nom, et deux infra-pki-01 y sont indiscernables à l'œil. Le VMID porte le tenant, encore faut-il connaître le codage. Un pool par tenant, dérivé du dossier d'instance et de l'index — déjà unique par P21, donc aucun registre de plus : Chezlepro-17, Technolibre-11. make devis-proxmox-pools rattrape la flotte existante (création du pool, puis affectation des VM actives). Les VM créées ensuite entrent d'elles-mêmes : make creer-vm dérive le pool par la même fonction et le passe à la création. Le playbook crée le pool au préalable — proxmox_kvm échoue sur un pool inconnu, et l'API ne sait pas changer le pool d'une VM existante ; c'est aussi pourquoi le rattrapage passe par les membres. P28 garde deux collisions : même nom de pool entre tenants, et surtout même VMID — une machine appartenant à deux tenants serait pire qu'une homonymie. Rien n'est renommé : les homonymes sont la preuve que la nomenclature est un vrai gabarit. Le devis ne lit pas le cluster, il dit l'état cible et non l'écart. 27 preuves OK, 0 échec. --syntax-check du playbook de clonage. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 15:15:14 -04:00
# A la CREATION seulement : l'API ne permet pas de le changer ensuite.
pool: "{{ proxmox_clone_pool | default(omit, true) }}"
timeout: "{{ proxmox_clone_timeout | default(600) | int }}"
# L'API de CLONAGE ignore `cores` et `memory` : le clone herite du gabarit, et le
# plan est contredit sans un mot. Mesure du 2026-08-06 : plan 1 coeur / 1024 Mo,
# VM creee avec 2 / 2048 — ceux du gabarit. Il faut donc les reposer APRES le clone.
# Le clone COMPLET rend la main avant que sa configuration ne soit ecrite : sur
# un stockage lent, `qm clone` retourne, et le `.conf` n'existe pas encore. La
# tache suivante echouait alors sur « Configuration file ... does not exist ».
# Observe deux fois sur quinze clones (2026-08-07) — une course, pas un hasard.
- name: Attendre que la configuration du clone existe
vars:
# Assemblee dans une variable : un `>-` replie les retours en ESPACES, ce qui
# inserait une espace au milieu de l'URL. Le lint passait, la requete non.
proxmox_api_racine: "https://{{ proxmox_api_host_effectif }}:{{ proxmox_api_port_effectif | default(8006, true) }}"
ansible.builtin.uri:
url: "{{ proxmox_api_racine }}/api2/json/nodes/{{ proxmox_clone_noeud }}/qemu/{{ proxmox_clone_vmid | int }}/config"
method: GET
headers:
Authorization: >-
PVEAPIToken={{ proxmox_api_user_effectif }}!{{ proxmox_api_token_id_effectif }}={{ proxmox_api_token_secret_effectif }}
validate_certs: "{{ proxmox_validate_certs | default(false) | bool }}"
status_code: 200
register: proxmox_clone_conf
until: proxmox_clone_conf.status == 200
retries: 30
delay: 5
changed_when: false
no_log: true
- name: Appliquer le gabarit de calcul du plan (le clonage ne le fait pas)
community.general.proxmox_kvm:
api_host: "{{ proxmox_api_host_effectif }}"
api_port: "{{ proxmox_api_port_effectif | int if proxmox_api_port_effectif | length > 0 else omit }}"
api_user: "{{ proxmox_api_user_effectif }}"
api_token_id: "{{ proxmox_api_token_id_effectif }}"
api_token_secret: "{{ proxmox_api_token_secret_effectif }}"
validate_certs: "{{ proxmox_validate_certs | default(false) | bool }}"
node: "{{ proxmox_clone_noeud }}"
vmid: "{{ proxmox_clone_vmid | int }}"
cores: "{{ proxmox_clone_coeurs | int }}"
memory: "{{ proxmox_clone_memoire | int }}"
update: true
when:
- proxmox_clone_coeurs is defined and proxmox_clone_coeurs | string | length > 0
- proxmox_clone_memoire is defined and proxmox_clone_memoire | string | length > 0
- name: Ajuster le reseau de la VM clonee
community.general.proxmox_nic:
api_host: "{{ proxmox_api_host_effectif }}"
api_port: "{{ proxmox_api_port_effectif | int if proxmox_api_port_effectif | length > 0 else omit }}"
api_user: "{{ proxmox_api_user_effectif }}"
api_token_id: "{{ proxmox_api_token_id_effectif }}"
api_token_secret: "{{ proxmox_api_token_secret_effectif }}"
validate_certs: "{{ proxmox_validate_certs | default(false) | bool }}"
vmid: "{{ proxmox_clone_vmid | int }}"
interface: "{{ proxmox_clone_interface | default('net0') }}"
model: virtio
bridge: "{{ proxmox_clone_pont }}"
tag: "{{ proxmox_clone_vlan | int if proxmox_clone_vlan is defined and proxmox_clone_vlan | string | length > 0 else omit }}"
firewall: "{{ proxmox_clone_parefeu_interface | default(false) | bool }}"
state: present
when:
- proxmox_clone_pont is defined
- proxmox_clone_pont | length > 0
- name: Agrandir le disque principal du clone (grow-only)
community.general.proxmox_disk:
api_host: "{{ proxmox_api_host_effectif }}"
api_port: "{{ proxmox_api_port_effectif | int if proxmox_api_port_effectif | length > 0 else omit }}"
api_user: "{{ proxmox_api_user_effectif }}"
api_token_id: "{{ proxmox_api_token_id_effectif }}"
api_token_secret: "{{ proxmox_api_token_secret_effectif }}"
validate_certs: "{{ proxmox_validate_certs | default(false) | bool }}"
vmid: "{{ proxmox_clone_vmid | int }}"
disk: "{{ proxmox_clone_disque | default('scsi0') }}"
size: "{{ proxmox_clone_taille_disque }}"
state: resized
register: proxmox_redim_disque
# Le disque derive du plan est un MINIMUM. Si le template est deja plus grand,
# Proxmox refuse de retrecir : on tolere ce cas (grow-only), la VM garde le
# disque du template. Toute autre erreur reste bloquante.
failed_when:
- proxmox_redim_disque is failed
- "'shrinking disks is not supported' not in (proxmox_redim_disque.msg | default(''))"
when:
- proxmox_clone_taille_disque is defined
- proxmox_clone_taille_disque | length > 0
- name: Configurer Cloud-Init sur le clone
community.general.proxmox_kvm:
api_host: "{{ proxmox_api_host_effectif }}"
api_port: "{{ proxmox_api_port_effectif | int if proxmox_api_port_effectif | length > 0 else omit }}"
api_user: "{{ proxmox_api_user_effectif }}"
api_token_id: "{{ proxmox_api_token_id_effectif }}"
api_token_secret: "{{ proxmox_api_token_secret_effectif }}"
validate_certs: "{{ proxmox_validate_certs | default(false) | bool }}"
node: "{{ proxmox_clone_noeud }}"
vmid: "{{ proxmox_clone_vmid | int }}"
name: "{{ proxmox_clone_nom }}"
update: true
ciuser: "{{ proxmox_clone_ciuser | default(omit, true) }}"
sshkeys: "{{ proxmox_clone_sshkeys | default(omit) }}"
ipconfig:
ipconfig0: "{{ proxmox_clone_ipconfig0 }}"
nameservers: "{{ proxmox_clone_nameservers if proxmox_clone_nameservers | length > 0 else omit }}"
searchdomains: "{{ proxmox_clone_domaines_recherche | default(omit, true) }}"
agent: "enabled=1"
- name: Demarrer le clone
community.general.proxmox_kvm:
api_host: "{{ proxmox_api_host_effectif }}"
api_port: "{{ proxmox_api_port_effectif | int if proxmox_api_port_effectif | length > 0 else omit }}"
api_user: "{{ proxmox_api_user_effectif }}"
api_token_id: "{{ proxmox_api_token_id_effectif }}"
api_token_secret: "{{ proxmox_api_token_secret_effectif }}"
validate_certs: "{{ proxmox_validate_certs | default(false) | bool }}"
node: "{{ proxmox_clone_noeud }}"
vmid: "{{ proxmox_clone_vmid | int }}"
name: "{{ proxmox_clone_nom }}"
state: started
when: proxmox_clone_demarrer | default(true) | bool
- name: Afficher le resume du clone
ansible.builtin.debug:
msg:
- "VM creee: {{ proxmox_clone_nom }} (VMID {{ proxmox_clone_vmid }})"
pools Proxmox : un par tenant, dérivé de l'index (D-37, P28) Onze des quatorze serveurs portent le même nom court chez Chezlepro et chez Technolibre. Vérifié un par un, ce n'est pas un problème technique : tout le reste dérive du seed et diverge (10.27.19.21 contre 10.21.19.21, VMID 117402101 contre 111402101, VLAN 1174 contre 1114, deux domaines internes), et rien n'est indexé sur le nom court — les opérations Proxmox portent toutes un vmid, les certificats un FQDN, et client_backup_repo vise backup-01.{{ domaine_interne }}. Le coût est humain : la console Proxmox affiche le nom, et deux infra-pki-01 y sont indiscernables à l'œil. Le VMID porte le tenant, encore faut-il connaître le codage. Un pool par tenant, dérivé du dossier d'instance et de l'index — déjà unique par P21, donc aucun registre de plus : Chezlepro-17, Technolibre-11. make devis-proxmox-pools rattrape la flotte existante (création du pool, puis affectation des VM actives). Les VM créées ensuite entrent d'elles-mêmes : make creer-vm dérive le pool par la même fonction et le passe à la création. Le playbook crée le pool au préalable — proxmox_kvm échoue sur un pool inconnu, et l'API ne sait pas changer le pool d'une VM existante ; c'est aussi pourquoi le rattrapage passe par les membres. P28 garde deux collisions : même nom de pool entre tenants, et surtout même VMID — une machine appartenant à deux tenants serait pire qu'une homonymie. Rien n'est renommé : les homonymes sont la preuve que la nomenclature est un vrai gabarit. Le devis ne lit pas le cluster, il dit l'état cible et non l'écart. 27 preuves OK, 0 échec. --syntax-check du playbook de clonage. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 15:15:14 -04:00
- "Pool: {{ proxmox_clone_pool | default('(aucun)', true) }}"
- "Cloud-Init ipconfig0: {{ proxmox_clone_ipconfig0 }}"