Set-OPS-Public/playbooks/proxmox/supprimer_vm_debian.yml
Daniel Allaire 3698152b6a frontière nord/sud : devis dérivé, lien de transit et les deux routes
La bordure devient un artefact dérivé, comme le devis switch — et le chemin
qui y mène est enfin déclaré.

`make devis-opnsense` (+ preuve P24) dérive la politique de bordure du
registre des flux : les flux `pair: externe`, que `resoudre_flux.py` saute
volontairement parce qu'ils relèvent de la frontière et non du pare-feu
d'hôte. Aucun port, aucune adresse, aucun nom d'hôte dans le générateur.

Le lien manquait dans tous les fichiers : le devis switch ne contenait pas
une seule `ip route`. Un réseau underlay portant `passerelle_sortie` le
déclare — il vit dans l'underlay et non dans un tenant parce que la
frontière route vers TOUS les supernets tenants par le même saut, donc il
ne peut dériver d'aucun `index`. `devis-reseau` en tire deux routes :
l'aller (sortie générale) et le retour vers l'administration, dont l'absence
a coûté la passe de déploiement du 2026-07-29 — la réponse revient au
pare-feu par une autre interface que celle où l'état a été créé, et se fait
jeter en silence.

Les réseaux d'administration viennent de l'intrant `nftables_admin_ssh` :
même source unique que la garde anti-lockout des nftables et l'alias
SETOPS_ADMIN. Les trois pare-feux et les routes ne peuvent plus diverger.

La frontière est réglable depuis la console (section « Frontière » du
panneau Intrants) ; les identifiants d'API restent interdits d'écriture par
le GUI et vivent dans la voûte.

Correctifs de la même passe :
- le panneau refusait d'enregistrer les intrants de la frontière : le
  garde-fou confondait une référence de voûte `{{ vault_* }}` préservée
  avec un secret soumis. Il regarde désormais la valeur, pas le nom.
- `supprimer_vm_debian.yml` ne chargeait que `proxmox.vault.yml` pour ses
  secrets ; retirer ce reliquat aurait cassé `make detruire`. Aligné sur le
  playbook de clonage, voûte unique en dernier.
- documentation : la voûte est unique, `proxmox.vault.yml` n'est qu'un
  reliquat de compatibilité.

Preuves : 24 OK, 0 échec. Cas de rejet du validateur d'underlay exercés un
par un ; résolution du jeton Proxmox vérifiée en exécution réelle.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 19:32:04 -04:00

119 lines
4.9 KiB
YAML

---
# DESTRUCTIF : supprime des VM Proxmox par VMID. Réservé à un from-zero explicite.
# Sécurités : n'agit QUE sur les VMID passés en `cibles_vmid` ET présents dans le
# cluster ; refuse si le modèle (proxmox_clone_vmid_modele) est dans la liste.
- name: Supprimer des VM Debian du cluster Proxmox (from-zero)
hosts: localhost
connection: local
gather_facts: false
become: false
vars:
setops_inventaires: [lab, principal, production]
# Voute Proxmox : la voute UNIQUE de l'instance (all/vault.yml) est autoritaire ;
# `proxmox.vault.yml` reste accepte en compat legacy pour les instances qui en
# ont encore une. all/vault.yml en dernier => il l'emporte sur les memes cles.
setops_gv_proxmox: [proxmox.yml, proxmox.vault.yml, proxmox.local.yml, all/vault.yml]
# cibles_vmid : liste d'entiers, passée en -e (sinon dérivée des hôtes actifs).
cibles_vmid: "{{ groups['hotes_actifs'] | map('extract', hostvars, 'proxmox_vmid') | map('int') | list }}"
tasks:
- name: Localiser les fichiers de variables Proxmox
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
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: Charger les secrets Proxmox (Vault)
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
- name: Résoudre les identifiants d'API
ansible.builtin.set_fact:
api_host: "{{ proxmox_api_host | default(lookup('env', 'PROXMOX_API_HOST'), true) }}"
api_port: "{{ proxmox_api_port | default('8006', true) }}"
api_user: "{{ proxmox_api_user | default(lookup('env', 'PROXMOX_API_USER'), true) }}"
api_tid: "{{ proxmox_api_token_id | default(lookup('env', 'PROXMOX_API_TOKEN_ID'), true) }}"
api_secret: "{{ proxmox_api_token_secret | default(lookup('env', 'PROXMOX_API_TOKEN_SECRET'), true) }}"
no_log: true
- name: Interroger l'inventaire du cluster
ansible.builtin.uri:
url: "https://{{ api_host }}:{{ api_port }}/api2/json/cluster/resources?type=vm"
method: GET
headers:
Authorization: "PVEAPIToken={{ api_user }}!{{ api_tid }}={{ api_secret }}"
validate_certs: "{{ proxmox_validate_certs | default(false) | bool }}"
register: cluster_vms
check_mode: false
no_log: true
- name: Établir la carte VMID -> nœud pour nos cibles présentes
ansible.builtin.set_fact:
vm_a_supprimer: >-
{{ cluster_vms.json.data
| selectattr('vmid', 'in', cibles_vmid | map('int') | list)
| list }}
- name: "SÉCURITÉ — le modèle n'est PAS ciblé (VMID {{ proxmox_clone_vmid_modele }})"
ansible.builtin.assert:
that: "(proxmox_clone_vmid_modele | int) not in (cibles_vmid | map('int') | list)"
fail_msg: "REFUS : le VMID du modèle est dans la liste à supprimer."
- name: Récapitulatif avant suppression
ansible.builtin.debug:
msg: >-
Suppression de {{ vm_a_supprimer | length }} VM :
{{ vm_a_supprimer | map(attribute='name') | list }}
(VMID {{ vm_a_supprimer | map(attribute='vmid') | list }})
- name: Arrêter chaque VM (force)
community.general.proxmox_kvm:
api_host: "{{ api_host }}"
api_port: "{{ api_port | int }}"
api_user: "{{ api_user }}"
api_token_id: "{{ api_tid }}"
api_token_secret: "{{ api_secret }}"
validate_certs: "{{ proxmox_validate_certs | default(false) | bool }}"
node: "{{ item.node }}"
vmid: "{{ item.vmid | int }}"
state: stopped
force: true
timeout: 120
loop: "{{ vm_a_supprimer }}"
loop_control:
label: "{{ item.name }} ({{ item.vmid }})"
no_log: true
failed_when: false
- name: Supprimer chaque VM
community.general.proxmox_kvm:
api_host: "{{ api_host }}"
api_port: "{{ api_port | int }}"
api_user: "{{ api_user }}"
api_token_id: "{{ api_tid }}"
api_token_secret: "{{ api_secret }}"
validate_certs: "{{ proxmox_validate_certs | default(false) | bool }}"
node: "{{ item.node }}"
vmid: "{{ item.vmid | int }}"
state: absent
timeout: 120
loop: "{{ vm_a_supprimer }}"
loop_control:
label: "{{ item.name }} ({{ item.vmid }})"
no_log: true