Some checks are pending
verifier / verifier (push) Waiting to run
Chezlepro detruite (15 VM, disques compris) et refaite depuis le gabarit minimal. 15/15 hotes, 0 echec ; make valider passe, test de restitution compris. Aucun defaut ne venait de la flotte ni du gabarit. 1. Un avertissement n est pas un echec. Proxmox rend WARNINGS: n pour une tache ABOUTIE ; la garde n acceptait que OK et declarait perdues cinq VM clonees a 100 pourcent. Le message parlait d etat stopped — celui de la TACHE, pas de la VM. 2. client_artefacts se contredisait : son commentaire disait de degrader, son code arretait. L autorite monte en premier, donc avant le cache du locataire : aucun ordre ne pouvait satisfaire la garde. 3. harden-below-nxdomain etendait le NXDOMAIN signe de la racine pour le TLD internal a toute la zone du site, sans jamais interroger l autoritatif. Declencheur : toute question sur un nom absent sous internal, y compris la zone d un autre locataire. Le cache contenait la bonne reponse ET un message negatif ; c est le negatif qui etait servi. aggressive-nsec: no avait semble marcher — c est le redemarrage qui vidait le cache, pas le reglage. 4. Un locataire doit savoir a qui demander la zone de son hebergeur, sans quoi il ne peut plus nommer son depot de sauvegarde. La derivation prenait dns_amorcage pour le resolveur du site : faux chez Technolibre, dont l amorcage est 9.9.9.9. P03 l a attrape avant tout deploiement. Au passage : instancier tentait encore le mot de passe unique d avant la separation des voutes ; comparer echouait en exit 4. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
729 lines
40 KiB
YAML
729 lines
40 KiB
YAML
---
|
|
- 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]
|
|
# 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]
|
|
# 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"
|
|
# LA VOUTE DE L'UNDERLAY — les secrets du MONDE PHYSIQUE (jeton d'API du cluster).
|
|
# Meme derivation que le fichier ci-dessus : le symlink `underlay.yml` designe deja
|
|
# l'hebergeur, on n'a rien a redeclarer. Depuis le 2026-08-22 ces secrets ne vivent
|
|
# plus dans la voute d'un tenant : recopies chez chacun, ils ne pouvaient plus etre
|
|
# revoques isolement (docs/frontiere-physique-virtuel.md).
|
|
setops_voute_underlay: "{{ (playbook_dir ~ '/../../underlay.yml') | realpath | dirname }}/underlay.vault.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')
|
|
|
|
- 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: Verifier la voute de l'underlay
|
|
ansible.builtin.stat:
|
|
path: "{{ setops_voute_underlay }}"
|
|
register: proxmox_voute_underlay
|
|
|
|
- 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
|
|
|
|
# APRES celle du tenant, donc elle GAGNE : quand les deux portent la cle, c'est
|
|
# celle de l'hebergeur qui fait foi. C'est le sens de la frontiere entre les deux
|
|
# mondes, et ca rend la migration d'un site sans coupure.
|
|
- name: Charger les secrets du monde physique (voute de l'underlay)
|
|
ansible.builtin.include_vars:
|
|
file: "{{ setops_voute_underlay }}"
|
|
when: proxmox_voute_underlay.stat.exists
|
|
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) }}"
|
|
# `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
|
|
|
|
# LA CLE D'AMORCAGE S'AJOUTE, ELLE NE REMPLACE PAS.
|
|
#
|
|
# L'exploitant doit garder son acces a la machine qu'il vient de creer : ecraser sa
|
|
# cle par celle du runner rendrait le runner seul maitre du terrain, ce qui est
|
|
# exactement l'inverse de la doctrine (c'est l'humain qui arme, cf.
|
|
# docs/filiation-emancipation.md). Deux cles, deux lignes, et cloud-init les pose
|
|
# toutes les deux.
|
|
#
|
|
# Elle n'arrive ICI que pour le runner d'un tenant : `inventory_host.py` la rend vide
|
|
# partout ailleurs, et cette tache ne se declenche donc pas.
|
|
#
|
|
# DEUX PIEGES, MESURES LE 2026-08-28, ET LE MEME MESSAGE POUR LES DEUX :
|
|
# 500 Internal Server Error: SSH public key validation error
|
|
#
|
|
# 1. LE SEPARATEUR. Un scalaire PLIE (`>-`) ne produit pas de saut de ligne : `'\n'`
|
|
# y reste deux caracteres, une barre et un « n ». Seul le scalaire YAML entre
|
|
# GUILLEMETS le convertit avant que Jinja ne le lise (verifie en base64 sur les
|
|
# trois formes candidates).
|
|
#
|
|
# 2. LA LISTE PEUT N'AVOIR QU'UN ELEMENT — et c'est le cas courant. Sur cette flotte,
|
|
# `CLE_SSH_PUBLIQUE` est vide : la cle de l'exploitant vient du GABARIT DORE, pas
|
|
# de cloud-init. Une simple concatenation rendait donc une valeur commencant par
|
|
# un saut de ligne, que Proxmox refuse tout autant. On assemble une LISTE et on
|
|
# laisse `select` retirer les vides — la forme est juste qu'il y ait zero, une ou
|
|
# deux cles.
|
|
- name: Ajouter la cle d'amorcage du runner de SITE (insemination)
|
|
ansible.builtin.set_fact:
|
|
proxmox_clone_sshkeys: "{{ [proxmox_clone_sshkeys | default('') | trim, proxmox_clone_cles_amorcage | trim] | select | join('\n') }}"
|
|
when:
|
|
- proxmox_clone_cles_amorcage is defined
|
|
- proxmox_clone_cles_amorcage | 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
|
|
|
|
- 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
|
|
|
|
# LE CLONE SE CIBLE PAR VMID, JAMAIS PAR NOM — et c'est tout l'objet de ces trois
|
|
# taches. `community.general.proxmox_kvm` cherche d'abord une VM portant le `name`
|
|
# demande ; s'il en trouve une, il conclut « elle existe deja », rend `ok` et ne
|
|
# clone RIEN. Aucune tache n'apparait meme cote cluster.
|
|
#
|
|
# Or les noms courts sont VOLONTAIREMENT identiques d'un tenant a l'autre (meme
|
|
# fonction, meme nom) : c'est le pool qui restitue l'appartenance. Le premier clone
|
|
# de Technolibre — `backup-01` — est donc tombe sur le `backup-01` de Chezlepro et
|
|
# n'a rien fait, puis l'attente a expire sur une configuration qui n'existerait
|
|
# jamais. Mesure du 2026-08-10 : zero VM creee, zero tache `qmclone` au cluster.
|
|
#
|
|
# Le defaut etait invisible tant qu'un seul tenant existait. C'est exactement ce
|
|
# qu'une epreuve de portabilite doit trouver.
|
|
- name: Recenser les VM du cluster (pour cibler par VMID)
|
|
vars:
|
|
proxmox_api_racine: "https://{{ proxmox_api_host_effectif }}:{{ proxmox_api_port_effectif | default(8006, true) }}"
|
|
ansible.builtin.uri:
|
|
url: "{{ proxmox_api_racine }}/api2/json/cluster/resources?type=vm"
|
|
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_inventaire_vm
|
|
changed_when: false
|
|
no_log: true
|
|
|
|
- name: Retenir si le VMID cible est deja pris
|
|
ansible.builtin.set_fact:
|
|
proxmox_clone_deja_la: >-
|
|
{{ (proxmox_inventaire_vm.json.data | default([]))
|
|
| selectattr('vmid', 'defined')
|
|
| selectattr('vmid', 'equalto', proxmox_clone_vmid | int)
|
|
| list | length > 0 }}
|
|
|
|
# OU VIT LE GABARIT — et ce n'est pas forcement ou va la VM (2026-08-25).
|
|
#
|
|
# L'URL de clonage visait `nodes/{{ proxmox_clone_noeud }}`, c'est-a-dire le noeud de
|
|
# DESTINATION. Or l'API veut le noeud qui DETIENT le modele ; la destination se dit
|
|
# par `target`. Cloner vers gandalf un gabarit qui vit sur asgard rendait donc :
|
|
#
|
|
# 500 Configuration file 'nodes/gandalf/qemu-server/99998.conf' does not exist
|
|
#
|
|
# Ca n'etait jamais apparu parce que toutes les VM naissaient sur le noeud du gabarit
|
|
# et migraient ensuite. La premiere machine placee ailleurs l'a revele.
|
|
- name: Localiser le noeud qui detient le gabarit
|
|
ansible.builtin.set_fact:
|
|
proxmox_noeud_gabarit: >-
|
|
{{ ((proxmox_inventaire_vm.json.data | default([]))
|
|
| selectattr('vmid', 'defined')
|
|
| selectattr('vmid', 'equalto', proxmox_clone_vmid_modele | int)
|
|
| map(attribute='node') | list | first) | default('', true) }}
|
|
|
|
- name: "Un noeud de la grappe detient bien le gabarit {{ proxmox_clone_vmid_modele }}"
|
|
ansible.builtin.assert:
|
|
that: proxmox_noeud_gabarit | length > 0
|
|
fail_msg: >-
|
|
Aucun noeud de la grappe ne detient le gabarit
|
|
{{ proxmox_clone_vmid_modele }}. Sans lui, rien a cloner.
|
|
|
|
- name: Cloner la VM depuis le modele (API directe, cible par VMID)
|
|
vars:
|
|
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_noeud_gabarit }}/qemu/{{ proxmox_clone_vmid_modele | int }}/clone"
|
|
method: POST
|
|
headers:
|
|
Authorization: >-
|
|
PVEAPIToken={{ proxmox_api_user_effectif }}!{{ proxmox_api_token_id_effectif }}={{ proxmox_api_token_secret_effectif }}
|
|
body_format: form-urlencoded
|
|
# Corps ecrit en MAPPING YAML, pas assemble en Jinja : une expression `>-` rend
|
|
# une CHAINE, et le premier jet a ainsi perdu le `pool` en route — la VM est
|
|
# nee hors de son pool, sans un mot. `omit` retire proprement les cles vides.
|
|
body:
|
|
newid: "{{ proxmox_clone_vmid | int }}"
|
|
name: "{{ proxmox_clone_nom }}"
|
|
full: "{{ 1 if (proxmox_clone_complet | default(true) | bool) else 0 }}"
|
|
storage: "{{ proxmox_clone_stockage | default(omit, true) }}"
|
|
format: "{{ proxmox_clone_format | default(omit, true) }}"
|
|
# A la CREATION seulement : l'API ne sait pas changer le pool d'une VM
|
|
# existante par cet appel (il faut passer par le membre du pool).
|
|
pool: "{{ proxmox_clone_pool | default(omit, true) }}"
|
|
# LA DESTINATION SE DIT ICI, pas dans l'URL. Omise quand elle coincide avec le
|
|
# noeud du gabarit : l'API refuse un `target` inutile sur stockage non partage.
|
|
target: "{{ proxmox_clone_noeud if proxmox_clone_noeud != proxmox_noeud_gabarit else omit }}"
|
|
validate_certs: "{{ proxmox_validate_certs | default(false) | bool }}"
|
|
status_code: 200
|
|
register: proxmox_clone_lance
|
|
# Le corps ne porte aucun secret, mais l'en-tete si : `no_log` masquerait alors
|
|
# l'erreur de l'API, qui est justement ce qu'on veut lire. On ne journalise donc
|
|
# que l'ECHEC, et sans les en-tetes.
|
|
failed_when: false
|
|
no_log: true
|
|
when: not proxmox_clone_deja_la
|
|
|
|
# L'ECHEC DU POST ETAIT AVALE. `failed_when: false` protege du 500 « VMID deja pris »,
|
|
# mais il avalait AUSSI un refus reel — et la tache d'attente qui suit se bloquait
|
|
# alors sur un clonage qui n'avait jamais commence. Un blocage sans message, la panne
|
|
# la plus chere a diagnostiquer. On garde le masque pour l'en-tete, et on releve
|
|
# l'echec ici, message de l'API compris.
|
|
- name: "Le clonage a bien ete accepte par l'API"
|
|
ansible.builtin.assert:
|
|
that: (proxmox_clone_lance.status | default(0)) == 200
|
|
fail_msg: >-
|
|
L'API a refuse le clonage
|
|
({{ proxmox_clone_lance.status | default('aucune reponse') }}) :
|
|
{{ proxmox_clone_lance.json.message
|
|
| default(proxmox_clone_lance.msg | default('sans message')) }}
|
|
— gabarit {{ proxmox_clone_vmid_modele }} sur « {{ proxmox_noeud_gabarit }} »,
|
|
destination « {{ proxmox_clone_noeud }} ».
|
|
when: not proxmox_clone_deja_la
|
|
|
|
# ATTENDRE QUE LE CLONE SOIT REELLEMENT FINI, pas seulement demande.
|
|
#
|
|
# `POST .../clone` rend un UPID et la main IMMEDIATEMENT : Proxmox copie le disque en
|
|
# tache de fond. `proxmox_kvm`, qu'on a remplace, attendait la fin (`timeout: 600`) — la
|
|
# reecriture par API avait perdu cette attente sans que ca se voie, parce qu'en
|
|
# sequentiel l'attente de SSH qui suit l'absorbait.
|
|
#
|
|
# En PARALLELE, c'est tout autre chose : `make creer-vm` rend la main pendant que le
|
|
# disque se copie, la limite de concurrence ne retient plus que des processus vides, et
|
|
# les clones s'empilent. Mesure du 2026-08-10 : limite a 4, DOUZE copies integrales du
|
|
# gabarit simultanees sur le meme stockage. Le CPU de l'hyperviseur a 2 %, et pourtant
|
|
# tout ramait — ce n'etait pas la machine, c'etait TrueNAS.
|
|
- name: Attendre la fin reelle du clonage
|
|
vars:
|
|
proxmox_api_racine: "https://{{ proxmox_api_host_effectif }}:{{ proxmox_api_port_effectif | default(8006, true) }}"
|
|
ansible.builtin.uri:
|
|
# LE NOEUD VIENT DE L'UPID LUI-MEME, et de nulle part ailleurs (2026-08-25).
|
|
#
|
|
# Cette attente interrogeait `nodes/{{ proxmox_clone_noeud }}` — la DESTINATION.
|
|
# Or un clonage inter-noeuds s'execute sur le noeud du GABARIT : l'UPID n'existait
|
|
# pas la ou on le cherchait, l'API repondait en erreur, `failed_when: false`
|
|
# l'avalait, et `until` n'etait jamais satisfait. Resultat : soixante tentatives a
|
|
# dix secondes pour un clonage deja termine en 87 s — dix minutes d'attente
|
|
# immobile, puis la suite qui reprend sans un mot.
|
|
#
|
|
# Un UPID s'ecrit `UPID:<noeud>:<pid>:...`. Le noeud y est deja ; le redemander a
|
|
# une variable, c'est se donner une chance de se tromper.
|
|
url: >-
|
|
{{ proxmox_api_racine }}/api2/json/nodes/{{
|
|
proxmox_clone_lance.json.data.split(':')[1]
|
|
}}/tasks/{{ proxmox_clone_lance.json.data | urlencode }}/status
|
|
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_tache
|
|
until: (proxmox_clone_tache.json.data.status | default('running')) == 'stopped'
|
|
retries: "{{ ((proxmox_clone_timeout | default(600) | int) / 10) | round | int }}"
|
|
delay: 10
|
|
changed_when: false
|
|
failed_when: false
|
|
no_log: true
|
|
when:
|
|
- not proxmox_clone_deja_la
|
|
- proxmox_clone_lance.json.data is defined
|
|
|
|
# UN AVERTISSEMENT N'EST PAS UN ECHEC — ET IL NE DOIT PAS DISPARAITRE NON PLUS.
|
|
#
|
|
# Proxmox rend trois formes d'`exitstatus` : `OK`, `WARNINGS: <n>` pour une tache
|
|
# QUI A ABOUTI en signalant quelque chose, et un texte d'erreur pour un vrai echec.
|
|
# La garde n'acceptait que `OK` : elle declarait donc perdus des clonages termines.
|
|
#
|
|
# MESURE DU 2026-09-01, RECONSTRUCTION DE CHEZLEPRO. Cinq VM sur quinze refusees,
|
|
# toutes avec `WARNINGS: 1`. Le journal de la tache disait :
|
|
#
|
|
# transferred 16.0 GiB of 16.0 GiB (100.00%)
|
|
# can't deactivate LV 'vm-9006-disk-1': Logical volume in use.
|
|
# WARN: volume deactivation failed
|
|
#
|
|
# Le disque etait transfere INTEGRALEMENT. Proxmox essaie ensuite de desactiver le
|
|
# volume DU GABARIT, et n'y parvient pas tant qu'un autre clonage parallele s'en
|
|
# sert. C'est la consequence NORMALE de cloner un meme gabarit en parallele — la
|
|
# condition meme que `flotte-creer` recherche pour aller vite.
|
|
#
|
|
# Ce que ca coutait : la reconstruction s'arretait sur cinq VM parfaitement clonees,
|
|
# et le message parlait d'« etat stopped » — l'etat de la TACHE, qui signifie
|
|
# « terminee ». On lit « la VM est arretee » et on cherche un probleme de demarrage
|
|
# qui n'existe pas. Le message dit desormais de quoi il parle.
|
|
- name: Le clonage a-t-il signale quelque chose en aboutissant ?
|
|
ansible.builtin.debug:
|
|
msg: >-
|
|
Clonage de {{ proxmox_clone_nom }} abouti AVEC AVERTISSEMENT
|
|
({{ proxmox_clone_tache.json.data.exitstatus }}). Le plus courant est
|
|
« volume deactivation failed » sur le disque du gabarit : un autre clonage
|
|
parallele s'en servait encore. Le journal complet de la tache est sur
|
|
« {{ proxmox_clone_lance.json.data.split(':')[1] | default('?') }} ».
|
|
when:
|
|
- not proxmox_clone_deja_la
|
|
- (proxmox_clone_tache.json.data.status | default('')) == 'stopped'
|
|
- (proxmox_clone_tache.json.data.exitstatus | default('')) is match('WARNINGS')
|
|
|
|
- name: Dire si le clonage ne s'est pas termine correctement
|
|
ansible.builtin.fail:
|
|
msg: >-
|
|
Le clonage de {{ proxmox_clone_nom }} (VMID {{ proxmox_clone_vmid }}) ne s'est pas
|
|
termine correctement : la TACHE est « {{ proxmox_clone_tache.json.data.status
|
|
| default('?') }} » (« stopped » = terminee, ce n'est pas l'etat de la VM) et sa
|
|
sortie est « {{ proxmox_clone_tache.json.data.exitstatus | default('?') }} ».
|
|
Regarder la tache sur
|
|
« {{ proxmox_clone_lance.json.data.split(':')[1] | default('?') }} ».
|
|
when:
|
|
- not proxmox_clone_deja_la
|
|
# `exitstatus | default('OK')` faisait passer pour un SUCCES une attente qui
|
|
# n'avait jamais rien observe : quand l'API repondait en erreur — le cas du
|
|
# clonage inter-noeuds interroge sur le mauvais noeud — il n'y avait aucun
|
|
# `json.data`, donc aucun `exitstatus`, donc « OK » par defaut. On exige
|
|
# desormais d'avoir VU la tache s'arreter, et bien s'arreter.
|
|
# `OK` et `WARNINGS: <n>` disent tous deux que la tache a ABOUTI. Tout autre
|
|
# texte est un vrai echec, et l'absence de sortie signifie qu'on n'a jamais vu
|
|
# la tache s'arreter — le cas que la note ci-dessus decrit.
|
|
- >-
|
|
(proxmox_clone_tache.json.data.status | default('')) != 'stopped'
|
|
or not ((proxmox_clone_tache.json.data.exitstatus | default('')) == 'OK'
|
|
or (proxmox_clone_tache.json.data.exitstatus | default('')) is match('WARNINGS'))
|
|
|
|
- name: Dire pourquoi le clonage a echoue, s'il a echoue
|
|
ansible.builtin.fail:
|
|
msg: >-
|
|
Le clonage de {{ proxmox_clone_nom }} (VMID {{ proxmox_clone_vmid }}) depuis le
|
|
gabarit {{ proxmox_clone_vmid_modele }} a ete refuse par l'API Proxmox
|
|
(HTTP {{ proxmox_clone_lance.status | default('?') }}).
|
|
Verifier que le gabarit existe sur {{ proxmox_clone_noeud }}, que le pool
|
|
{{ proxmox_clone_pool | default('(aucun)', true) }} existe, et que le VMID
|
|
cible est libre.
|
|
when:
|
|
- not proxmox_clone_deja_la
|
|
- proxmox_clone_lance.status | default(0) != 200
|
|
|
|
# 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
|
|
failed_when: false
|
|
no_log: true
|
|
|
|
# `no_log` masque la cause au moment ou on en a besoin : le 2026-08-10, l'echec de
|
|
# cette attente s'est lu « the output has been hidden », et il a fallu interroger le
|
|
# cluster a la main pour comprendre que le clone n'avait jamais eu lieu. On rend
|
|
# donc le verdict lisible ici, sans reveler l'en-tete d'autorisation.
|
|
- name: Dire ce que l'attente du clone a constate
|
|
ansible.builtin.fail:
|
|
msg: >-
|
|
La configuration de {{ proxmox_clone_nom }} (VMID {{ proxmox_clone_vmid }})
|
|
n'existe toujours pas apres {{ 30 * 5 }} s sur {{ proxmox_clone_noeud }}.
|
|
Le clonage n'a donc pas abouti — verifier les taches du cluster
|
|
(`/cluster/tasks`) : si aucune tache de clonage n'apparait, c'est que rien
|
|
n'a ete demande a l'API.
|
|
when: proxmox_clone_conf.status | default(0) != 200
|
|
|
|
- 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
|
|
# Le noeud vient de finir un clone COMPLET : son API repond encore lentement, et
|
|
# la lecture expire a 5 s. Mesure du 2026-08-10 : « Read timed out » sur le tout
|
|
# premier clone, VM creee mais laissee aux valeurs du gabarit — 2 coeurs / 2 Go au
|
|
# lieu du plan. Une VM sous-dimensionnee en silence est pire qu'un echec franc.
|
|
register: proxmox_gabarit_calcul
|
|
until: proxmox_gabarit_calcul is succeeded
|
|
retries: 6
|
|
delay: 10
|
|
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
|
|
|
|
# Un desarmement doit se VOIR. Sans ce message, l'ecart entre ce que l'ecosysteme
|
|
# declare et ce que la VM recoit resterait muet — et c'est precisement le genre de
|
|
# silence qui a coute une soiree de diagnostic le 2026-08-25.
|
|
- name: Dire que le pare-feu Proxmox reste desarme (pont classique, pas un VNet SDN)
|
|
ansible.builtin.debug:
|
|
msg: >-
|
|
`proxmox_clone_parefeu_interface` vaut vrai, mais {{ proxmox_clone_nom }} nait
|
|
sur `{{ proxmox_clone_pont }}` — un pont Linux, pas un VNet SDN. Le pare-feu
|
|
Proxmox y jetterait le retour des flux en epingle (`ctstate INVALID`) : il reste
|
|
desarme. La frontiere continue de policer ce segment.
|
|
when:
|
|
- proxmox_clone_parefeu_interface | default(false) | bool
|
|
- proxmox_clone_pont is defined
|
|
- proxmox_clone_pont is match('^vmbr[0-9]+$')
|
|
|
|
- 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 }}"
|
|
# LE PARE-FEU PROXMOX NE S'ARME QUE SUR UN VNET SDN (2026-08-25).
|
|
#
|
|
# `firewall=1` fait passer tout le trafic ponte par conntrack. Sur un VNET SDN
|
|
# c'est sans consequence : en EVPN le routage inter-VNet se fait dans le VRF, SUR
|
|
# LE NOEUD. Le flux ne quitte jamais l'hyperviseur, conntrack en voit un chemin
|
|
# coherent, et le devis est-ouest (`devis_proxmox_fw.py`) peut le policer.
|
|
#
|
|
# Sur un PONT CLASSIQUE route par une frontiere externe, le meme reglage devient
|
|
# un piege. Deux VM du meme noeud dans deux VLAN differents ne se parlent qu'en
|
|
# EPINGLE : la trame sort par le lien physique, la frontiere la route, et elle
|
|
# revient sur LE MEME PONT. La meme table conntrack voit alors les deux moities de
|
|
# la connexion, classe le retour `INVALID`, et `PVEFW-FORWARD` le jette.
|
|
#
|
|
# MESURE DU 2026-08-25, sur le site decoupe en quatre zones : `site-ops-01` et
|
|
# `site-pki-01` (asgard), `site-dns-01` et `site-cache-01` (gandalf) ne se
|
|
# joignaient plus — 0/8 — alors que TOUTES les paires entre noeuds differents
|
|
# etaient a 8/8. Douze tentatives faisaient monter le compteur `ctstate INVALID`
|
|
# de +112 sur asgard et +116 sur gandalf ; `firewall=0` pose, il ne bouge plus.
|
|
#
|
|
# Le defaut se deguise en panne reseau : la poignee TCP aboutit (SYN/SYN-ACK
|
|
# creent l'etat), et ce sont les paquets de DONNEES qui disparaissent. On accuse
|
|
# le MTU, la frontiere, la zone. Rien de tout cela n'y est pour quelque chose.
|
|
#
|
|
# L'INTENTION DECLAREE NE SUFFIT DONC PAS : un tenant declare
|
|
# `proxmox_clone_parefeu_interface: true` avec `proxmox_clone_pont: vmbr1` comme
|
|
# valeur PAR DEFAUT, chaque hote la remplacant par son VNet derive. L'hote qui
|
|
# retombe sur `vmbr1` naitrait arme sur un pont classique. On croise donc
|
|
# l'intention avec le pont REELLEMENT utilise. Un pont Linux se nomme `vmbrN` ;
|
|
# un VNet SDN porte le nom derive de sa zone (`t29serv`).
|
|
firewall: "{{ (proxmox_clone_parefeu_interface | default(false) | bool) and (proxmox_clone_pont is not match('^vmbr[0-9]+$')) }}"
|
|
# `mtu: 1` est la valeur Proxmox pour « HERITE DU PONT » (virtio uniquement).
|
|
#
|
|
# Sans elle, l'invite nait a 1500 quel que soit le pont. En SDN EVPN la zone est a
|
|
# 1450 (VXLAN coute 50 octets) : la VM emet donc des trames que son propre chemin
|
|
# ne peut pas encapsuler. Ca ne casse pas franchement — la connexion s'etablit,
|
|
# les petites requetes passent, les grosses reponses restent suspendues. C'est
|
|
# exactement la panne que le registre des flux decrit comme « la plus couteuse a
|
|
# diagnostiquer », et pourquoi il declare l'ICMP « fragmentation necessaire ».
|
|
#
|
|
# Mesure du 2026-08-10 : zones `t11` et `t17` a 1450 sur le cluster, les 14 invites
|
|
# a 1500. Invisible tant que toutes les VM vivent sur le MEME hyperviseur — elles
|
|
# communiquent alors par le pont local, sans encapsulation. La panne apparaitrait
|
|
# au premier eclatement de la flotte sur plusieurs noeuds.
|
|
#
|
|
# HERITER plutot qu'ecrire 1450 : hors SDN le pont est a 1500 et la VM suit. Une
|
|
# seule source de verite — celle du pont auquel elle est reellement attachee — et
|
|
# la valeur reste juste le jour ou la fabric passera aux trames jumbo.
|
|
#
|
|
# Le GABARIT le porte aussi (`net0 ... mtu=1`), ce qui couvre les clonages qui ne
|
|
# passent pas par ce playbook — un clone fait a la main dans l'interface, par
|
|
# exemple. On garde neanmoins la ligne ici : un gabarit se RECAPTURE (fait le
|
|
# 2026-08-09), et ce qui n'est pas versionne se perd en silence. Le playbook est
|
|
# la garantie qui survit a la recapture ; `make mtu-mesurer` verifie le resultat.
|
|
#
|
|
# Cette tache s'execute AVANT le demarrage du clone (voir « Demarrer le clone »
|
|
# plus bas) : aucun risque de rebranchement a chaud. Applique a une VM EN MARCHE,
|
|
# le meme changement detache et rebranche la carte sans que l'invite reconfigure
|
|
# son interface — 14 machines coupees d'un coup le 2026-08-10, et il a fallu les
|
|
# redemarrer. Sur une VM deja en service : poser la config, puis redemarrer.
|
|
mtu: 1
|
|
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"
|
|
|
|
# LE DEMARRAGE DECLARAIT EN PANNE CE QUI FONCTIONNAIT (2026-08-29).
|
|
#
|
|
# Quinze VM creees trois a la fois : l'une d'elles a depasse le delai du module
|
|
# PENDANT QUE PROXMOX GENERAIT ENCORE SON ISO CLOUD-INIT.
|
|
#
|
|
# fatal: Reached timeout while waiting for starting VM.
|
|
# Last line in task before timeout: 'generating cloud-init ISO'
|
|
#
|
|
# Elle a demarre juste apres. Le module avait abandonne, pas l'hyperviseur — et
|
|
# `flotte-creer` a rendu « au moins une VM n'a pas ete creee » alors que les quinze
|
|
# tournaient. CE FAUX ECHEC ARRETE UNE RECONSTRUCTION : `reconstruire` enchaine
|
|
# `flotte-creer` puis `deployer-tout`, et se serait arrete la.
|
|
#
|
|
# DEUX CORRECTIONS, ET LA SECONDE EST LA VRAIE. Un delai plus genereux (le defaut du
|
|
# module est court, et la generation d'ISO sur un stockage partage se met en file
|
|
# quand trois clonages tombent ensemble). Mais un delai reste un pari : on ne fait
|
|
# donc plus de son expiration un verdict. C'EST L'HYPERVISEUR QUI JUGE, en repondant
|
|
# ce qu'il fait tourner — meme principe que P52, ou l'agent invite juge la
|
|
# materialisation a la place d'une reponse SSH.
|
|
- 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
|
|
timeout: "{{ proxmox_clone_delai_demarrage | default(180) | int }}"
|
|
register: proxmox_clone_demarrage
|
|
failed_when: false
|
|
when: proxmox_clone_demarrer | default(true) | bool
|
|
|
|
# L'HYPERVISEUR EST LE JUGE. On lui demande ce qu'il fait tourner, on ne deduit rien
|
|
# du sort de la tache precedente. Les tentatives couvrent le cas ou il finissait
|
|
# encore son travail au moment ou le module a renonce.
|
|
- name: Confirmer aupres de l'hyperviseur que la VM tourne
|
|
community.general.proxmox_vm_info:
|
|
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 }}"
|
|
type: qemu
|
|
register: proxmox_clone_etat
|
|
until: (proxmox_clone_etat.proxmox_vms | default([])
|
|
| map(attribute='status') | first | default('')) == 'running'
|
|
retries: 12
|
|
delay: 10
|
|
changed_when: false
|
|
when: proxmox_clone_demarrer | default(true) | bool
|
|
|
|
- name: Refuser si la VM ne tourne toujours pas
|
|
ansible.builtin.assert:
|
|
that:
|
|
- (proxmox_clone_etat.proxmox_vms | default([])
|
|
| map(attribute='status') | first | default('')) == 'running'
|
|
fail_msg: >-
|
|
{{ proxmox_clone_nom }} (VMID {{ proxmox_clone_vmid }}) ne tourne pas apres
|
|
deux minutes d'attente. Ce n'est plus un delai trop court : l'hyperviseur lui-meme
|
|
la rapporte
|
|
« {{ proxmox_clone_etat.proxmox_vms | default([]) | map(attribute='status')
|
|
| first | default('introuvable') }} ».
|
|
success_msg: >-
|
|
{{ proxmox_clone_nom }} tourne
|
|
{{ '(le module avait renonce avant l hyperviseur)'
|
|
if proxmox_clone_demarrage.failed | default(false) else '' }}
|
|
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 }})"
|
|
- "Pool: {{ proxmox_clone_pool | default('(aucun)', true) }}"
|
|
- "Cloud-Init ipconfig0: {{ proxmox_clone_ipconfig0 }}"
|