From 9aad42a4c2c683f7def1abdc1d862a2e57a4a2c1 Mon Sep 17 00:00:00 2001 From: Sandwich Date: Mon, 5 Oct 2026 16:54:28 +0200 Subject: [PATCH] refactor(global_defaults): derive the primary user once for every consumer --- README.md | 2 +- main.yml | 8 +++----- roles/global_defaults/tasks/main.yml | 19 ++++++++++++++----- roles/virtualization/tasks/proxmox.yml | 4 ++-- 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 975efee..53d849d 100644 --- a/README.md +++ b/README.md @@ -217,7 +217,7 @@ When `interfaces` is empty, the flat fields (`bridge`, `ip`, `prefix`, `gateway` #### `system.users` -Dict keyed by username. At least one user must have a `password` (used for SSH access during bootstrap). Users without a password get locked accounts (key-only auth). +Dict keyed by username. At least one user must have a non-empty `password`: the first such user, in declaration order, is the account bootstrap logs in as after the reboot and the cloud-init and Proxmox `ciuser` primary user. Users without a password get locked accounts (key-only auth). ```yaml system: diff --git a/main.yml b/main.yml index baecbce..b1fa5d3 100644 --- a/main.yml +++ b/main.yml @@ -119,15 +119,13 @@ when: - post_reboot_can_connect | bool no_log: true - vars: - _primary: "{{ (system_cfg.users | dict2items | selectattr('value.password', 'defined') | first) }}" ansible.builtin.set_fact: ansible_connection: ssh ansible_host: "{{ system_cfg.network.ip }}" ansible_port: 22 - ansible_user: "{{ _primary.key }}" - ansible_password: "{{ _primary.value.password }}" - ansible_become_password: "{{ _primary.value.password }}" + ansible_user: "{{ bootstrap_primary_user.name }}" + ansible_password: "{{ bootstrap_primary_user.password }}" + ansible_become_password: "{{ bootstrap_primary_user.password }}" ansible_ssh_extra_args: "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" ansible_python_interpreter: /usr/bin/python3 diff --git a/roles/global_defaults/tasks/main.yml b/roles/global_defaults/tasks/main.yml index 03e5133..e123d20 100644 --- a/roles/global_defaults/tasks/main.yml +++ b/roles/global_defaults/tasks/main.yml @@ -47,6 +47,17 @@ ansible.builtin.set_fact: system_cfg: "{{ system_cfg | combine({'path': hypervisor_cfg.folder | string}, recursive=True) }}" +# Guarded so a malformed users input reaches the validation message, not a filter error. +- name: Pick the primary user (first with a password) + vars: + _first: >- + {{ (system_cfg.users if system_cfg.users is mapping else {}) | dict2items + | selectattr('value', 'mapping') | selectattr('value.password', 'defined') + | rejectattr('value.password', 'equalto', '') | first | default({}) }} + ansible.builtin.set_fact: + bootstrap_primary_user: "{{ {'name': _first.key, 'password': _first.value.password} if _first else {} }}" + no_log: true + - name: Validate variables ansible.builtin.include_tasks: validation.yml @@ -89,14 +100,12 @@ when: - system_cfg.type == "virtual" - hypervisor_type != "vmware" - vars: - _primary: "{{ (system_cfg.users | dict2items | selectattr('value.password', 'defined') | first) }}" ansible.builtin.set_fact: ansible_host: "{{ system_cfg.network.ip }}" ansible_port: 22 - ansible_user: "{{ _primary.key }}" - ansible_password: "{{ _primary.value.password }}" - ansible_become_password: "{{ _primary.value.password }}" + ansible_user: "{{ bootstrap_primary_user.name }}" + ansible_password: "{{ bootstrap_primary_user.password }}" + ansible_become_password: "{{ bootstrap_primary_user.password }}" ansible_ssh_extra_args: "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" no_log: true diff --git a/roles/virtualization/tasks/proxmox.yml b/roles/virtualization/tasks/proxmox.yml index f49af93..ae17fb5 100644 --- a/roles/virtualization/tasks/proxmox.yml +++ b/roles/virtualization/tasks/proxmox.yml @@ -35,8 +35,8 @@ {%- endfor -%} {{ out }} community.proxmox.proxmox_kvm: - ciuser: "{{ (system_cfg.users | dict2items | selectattr('value.password', 'defined') | first).key }}" - cipassword: "{{ (system_cfg.users | dict2items | selectattr('value.password', 'defined') | first).value.password }}" + ciuser: "{{ bootstrap_primary_user.name }}" + cipassword: "{{ bootstrap_primary_user.password }}" ciupgrade: false vmid: "{{ system_cfg.id }}" name: "{{ hostname }}"