playbooks/validate.yml (make validate): reads pct config for every service in homelab_services and fails if hostname, IP, cores, memory or swap disagree with the registry. Read-only; meant as a pre/post gate around any inventory or provisioning change. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012uoq5AVK8mkBgg83Mq6o5V
86 lines
4.6 KiB
YAML
86 lines
4.6 KiB
YAML
---
|
||
# Дрейф между реестром homelab_services и фактическим состоянием Proxmox.
|
||
#
|
||
# ЗАЧЕМ
|
||
# Реестр — источник правды, но программно его потребляют лишь несколько мест.
|
||
# VMID, адрес и ресурсы легко разъезжаются с реальностью, и заметить это
|
||
# раньше было нечем: `make status` показывает состояние, но не сверяет его
|
||
# с задекларированным, и его exit code намеренно не является gate'ом.
|
||
#
|
||
# ОТЛИЧИЕ ОТ status.yml
|
||
# Этот плейбук — именно gate: при любом расхождении он завершается ошибкой.
|
||
# Годится для cron и CI. Строго read-only: только `pct config`.
|
||
#
|
||
# make validate
|
||
# make validate EXTRA="--limit cloud-pc"
|
||
|
||
- name: Read actual LXC configuration from the Proxmox nodes
|
||
hosts: pve_nodes
|
||
gather_facts: false
|
||
tasks:
|
||
- name: Read pct config for the services declared on this node
|
||
ansible.builtin.command: "pct config {{ item.value.vmid }}"
|
||
loop: >-
|
||
{{ homelab_services | dict2items
|
||
| selectattr('value.node', 'eq', inventory_hostname) | list }}
|
||
loop_control:
|
||
label: "{{ item.key }} ({{ item.value.vmid }})"
|
||
register: validate_pct
|
||
changed_when: false
|
||
failed_when: false
|
||
check_mode: false
|
||
|
||
# Сравниваются только однозначные поля. rootfs намеренно пропущен: в реестре
|
||
# он записан как "data:32", а pct отдаёт "data:vm-141-disk-0,size=32G" —
|
||
# это разные представления, и их сверка требует отдельного парсера.
|
||
- name: Collect drift for this node
|
||
ansible.builtin.set_fact:
|
||
validate_drift: "{{ validate_drift | default([]) + item_drift }}"
|
||
loop: "{{ validate_pct.results }}"
|
||
loop_control:
|
||
label: "{{ item.item.key }}"
|
||
vars:
|
||
svc: "{{ item.item.value }}"
|
||
sname: "{{ item.item.key }}"
|
||
gone: "{{ item.rc | default(1) != 0 }}"
|
||
out: "{{ item.stdout | default('') }}"
|
||
a_host: "{{ out | regex_search('(?m)^hostname: (\\S+)', '\\1') | default([''], true) | first }}"
|
||
a_cores: "{{ out | regex_search('(?m)^cores: (\\d+)', '\\1') | default([''], true) | first }}"
|
||
a_mem: "{{ out | regex_search('(?m)^memory: (\\d+)', '\\1') | default([''], true) | first }}"
|
||
a_swap: "{{ out | regex_search('(?m)^swap: (\\d+)', '\\1') | default([''], true) | first }}"
|
||
a_ip: "{{ out | regex_search('ip=([0-9.]+)', '\\1') | default([''], true) | first }}"
|
||
item_drift: >-
|
||
{{ ([sname ~ ': VMID ' ~ svc.vmid ~ ' отсутствует на узле ' ~ inventory_hostname] if gone else [])
|
||
+ ([sname ~ ': hostname=' ~ a_host ~ ', в реестре ' ~ svc.hostname]
|
||
if (not gone and a_host != svc.hostname) else [])
|
||
+ ([sname ~ ': ip=' ~ a_ip ~ ', в реестре ' ~ svc.ip]
|
||
if (not gone and a_ip != svc.ip) else [])
|
||
+ ([sname ~ ': cores=' ~ a_cores ~ ', в реестре ' ~ svc.lxc.cores]
|
||
if (not gone and svc.lxc.cores is not none and a_cores | string != svc.lxc.cores | string) else [])
|
||
+ ([sname ~ ': memory=' ~ a_mem ~ ', в реестре ' ~ svc.lxc.memory]
|
||
if (not gone and svc.lxc.memory is not none and a_mem | string != svc.lxc.memory | string) else [])
|
||
+ ([sname ~ ': swap=' ~ a_swap ~ ', в реестре ' ~ svc.lxc.swap]
|
||
if (not gone and svc.lxc.swap is not none and a_swap | string != svc.lxc.swap | string) else []) }}
|
||
|
||
- name: Report registry drift
|
||
hosts: pve_nodes
|
||
gather_facts: false
|
||
run_once: true
|
||
tasks:
|
||
- name: Fail when the registry disagrees with Proxmox
|
||
ansible.builtin.assert:
|
||
that:
|
||
- all_drift | length == 0
|
||
success_msg: >-
|
||
Реестр совпадает с Proxmox: проверено сервисов —
|
||
{{ homelab_services | dict2items
|
||
| selectattr('value.node', 'in', groups['pve_nodes']) | list | length }}.
|
||
fail_msg: >-
|
||
{{ ['Реестр разошёлся с Proxmox:']
|
||
+ (all_drift | map('regex_replace', '^', ' - ') | list) }}
|
||
vars:
|
||
all_drift: >-
|
||
{{ groups['pve_nodes']
|
||
| map('extract', hostvars, 'validate_drift')
|
||
| select('defined') | flatten }}
|