--- # node_health — read-only hardware health inspection. See defaults/main.yml for the why. # # ORDER OF OPERATIONS # 1. Collect everything readable WITHOUT root (one script, one round trip). # 2. Probe whether privilege escalation actually works on this host. # 3. Only if it does, and only for what the first pass could NOT read: re-run the same # collector with --privileged (kernel ring buffer, SMBIOS DIMM inventory). # 4. Only if it does AND smartmontools is installed: read SMART. # 5. Turn facts into a list of explicit PASS/WARN/FAIL/SKIP checks. # 6. Render the human summary + write the JSON artifact. # # BECOME HANDLING — the subtle bit, read before editing. # `ansible_become` set as an inventory/group var (which is exactly what # inventory/group_vars/k3s_prod/vars.yml does for prod) is a CONNECTION VARIABLE, and # connection variables OUTRANK the `become:` play/task keyword. Verified on this repo's # ansible-core 2.20: a play or task saying `become: false` still tries to sudo, and on # prod (no NOPASSWD, vaulted password) dies with "Missing sudo password" before the # module ever runs. So the unprivileged tasks here must override the VARIABLE, not the # keyword — hence `vars: ansible_become: false` on the include below. That makes the role # self-contained: it behaves identically whatever the calling play sets. # # Equally verified: `failed_when: false` does NOT catch a missing-sudo-password error # (it is raised by the become plugin before the task's failure expression is evaluated). # `ignore_errors: true` DOES, and still populates the registered variable. That is why # the escalation probe below uses ignore_errors — anything else turns "no vault yet" into # a hard, misleading play failure. - name: Initialise the health record ansible.builtin.set_fact: node_health_checks: [] node_health_smart: [] node_health_root_available: false node_health_root_skip_reason: "privileged checks not attempted (node_health_try_privileged=false)" vars: ansible_become: false - name: Collect unprivileged hardware facts ansible.builtin.include_tasks: collect_unprivileged.yml # Applies to every task in the included file (dynamic includes pass their vars down). vars: ansible_become: false - name: Probe privilege escalation ansible.builtin.include_tasks: probe_privilege.yml when: node_health_try_privileged | bool # Only when the unprivileged pass actually failed to read one of them. On a node with # kernel.dmesg_restrict=0 the first pass already has the kernel log and this is skipped # entirely — "everything readable WITHOUT root is read without root" (defaults/main.yml). - name: Re-read the root-only facts with escalation (kernel log, SMBIOS DIMMs) ansible.builtin.include_tasks: collect_privileged.yml when: - node_health_root_available | bool - (not (node_health_facts.klog.available | bool)) or (not (node_health_facts.memory.devices.available | bool)) - name: Read SMART data (privileged) ansible.builtin.include_tasks: collect_smart.yml when: - node_health_root_available | bool - node_health_facts.tools.smartctl is truthy - name: Assess collected facts into explicit checks ansible.builtin.include_tasks: assess.yml vars: ansible_become: false - name: Report (human summary + JSON artifact) ansible.builtin.include_tasks: report.yml vars: ansible_become: false