refactor(users): change system.users from list to dict keyed by username
This commit is contained in:
52
main.yml
52
main.yml
@@ -39,33 +39,24 @@
|
|||||||
no_log: true
|
no_log: true
|
||||||
vars:
|
vars:
|
||||||
system_input: "{{ system | default({}) }}"
|
system_input: "{{ system | default({}) }}"
|
||||||
system_users_input: "{{ system_input.users | default([]) }}"
|
system_users_input: "{{ system_input.users | default({}) }}"
|
||||||
system_first_user: >-
|
_first_entry: "{{ system_users_input | dict2items | first | default({'key': '', 'value': {}}) }}"
|
||||||
{{
|
_first_name: "{{ _first_entry.key }}"
|
||||||
system_users_input[0]
|
_first_attrs: "{{ _first_entry.value if _first_entry.value is mapping else {} }}"
|
||||||
if (system_users_input is iterable and system_users_input is not string
|
|
||||||
and system_users_input is not mapping and system_users_input | length > 0)
|
|
||||||
else {}
|
|
||||||
}}
|
|
||||||
system_root_input: "{{ (system_input.root | default({})) if (system_input.root is mapping) else {} }}"
|
system_root_input: "{{ (system_input.root | default({})) if (system_input.root is mapping) else {} }}"
|
||||||
prompt_user_name: "{{ user_name | default(system_user_name | default(''), true) | string }}"
|
prompt_user_name: "{{ user_name | default(system_user_name | default(''), true) | string }}"
|
||||||
prompt_user_key: "{{ user_public_key | default(user_key | default(system_user_key | default(''), true), true) | string | trim }}"
|
prompt_user_key: "{{ user_public_key | default(user_key | default(system_user_key | default(''), true), true) | string | trim }}"
|
||||||
prompt_user_password: "{{ user_password | default(system_user_password | default(''), true) | string }}"
|
prompt_user_password: "{{ user_password | default(system_user_password | default(''), true) | string }}"
|
||||||
prompt_root_password: "{{ root_password | default(system_root_password | default(''), true) | string }}"
|
prompt_root_password: "{{ root_password | default(system_root_password | default(''), true) | string }}"
|
||||||
resolved_user:
|
resolved_name: "{{ _first_name if (_first_name | length > 0) else prompt_user_name }}"
|
||||||
name: >-
|
resolved_attrs:
|
||||||
{{
|
|
||||||
system_first_user.name | string
|
|
||||||
if (system_first_user.name | default('') | string | length) > 0
|
|
||||||
else prompt_user_name
|
|
||||||
}}
|
|
||||||
keys: >-
|
keys: >-
|
||||||
{{
|
{{
|
||||||
system_first_user['keys']
|
_first_attrs['keys']
|
||||||
if (system_first_user['keys'] is defined
|
if (_first_attrs['keys'] is defined
|
||||||
and system_first_user['keys'] is iterable
|
and _first_attrs['keys'] is iterable
|
||||||
and system_first_user['keys'] is not string
|
and _first_attrs['keys'] is not string
|
||||||
and system_first_user['keys'] | length > 0)
|
and _first_attrs['keys'] | length > 0)
|
||||||
else (
|
else (
|
||||||
[prompt_user_key]
|
[prompt_user_key]
|
||||||
if (prompt_user_key | length > 0)
|
if (prompt_user_key | length > 0)
|
||||||
@@ -74,8 +65,8 @@
|
|||||||
}}
|
}}
|
||||||
password: >-
|
password: >-
|
||||||
{{
|
{{
|
||||||
system_first_user.password | string
|
_first_attrs.password | string
|
||||||
if (system_first_user.password | default('') | string | length) > 0
|
if (_first_attrs.password | default('') | string | length) > 0
|
||||||
else prompt_user_password
|
else prompt_user_password
|
||||||
}}
|
}}
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
@@ -84,14 +75,7 @@
|
|||||||
system_input
|
system_input
|
||||||
| combine(
|
| combine(
|
||||||
{
|
{
|
||||||
'users': (
|
'users': system_users_input | combine({resolved_name: (_first_attrs | combine(resolved_attrs, recursive=True))}),
|
||||||
[resolved_user]
|
|
||||||
+ (system_users_input[1:]
|
|
||||||
if (system_users_input is sequence
|
|
||||||
and system_users_input is not string
|
|
||||||
and system_users_input | length > 1)
|
|
||||||
else [])
|
|
||||||
),
|
|
||||||
'root': {
|
'root': {
|
||||||
'password': (
|
'password': (
|
||||||
(system_root_input.password | default('') | string | length) > 0
|
(system_root_input.password | default('') | string | length) > 0
|
||||||
@@ -206,10 +190,12 @@
|
|||||||
when:
|
when:
|
||||||
- post_reboot_can_connect | bool
|
- post_reboot_can_connect | bool
|
||||||
no_log: true
|
no_log: true
|
||||||
|
vars:
|
||||||
|
_primary: "{{ (system_cfg.users | dict2items | selectattr('value.password', 'defined') | first) }}"
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
ansible_user: "{{ system_cfg.users[0].name }}"
|
ansible_user: "{{ _primary.key }}"
|
||||||
ansible_password: "{{ system_cfg.users[0].password }}"
|
ansible_password: "{{ _primary.value.password }}"
|
||||||
ansible_become_password: "{{ system_cfg.users[0].password }}"
|
ansible_become_password: "{{ _primary.value.password }}"
|
||||||
ansible_ssh_extra_args: "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
|
ansible_ssh_extra_args: "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
|
||||||
ansible_python_interpreter: /usr/bin/python3
|
ansible_python_interpreter: /usr/bin/python3
|
||||||
|
|
||||||
|
|||||||
@@ -15,15 +15,15 @@
|
|||||||
validate: /usr/sbin/visudo --check --file=%s
|
validate: /usr/sbin/visudo --check --file=%s
|
||||||
|
|
||||||
- name: Deploy per-user sudoers rules
|
- name: Deploy per-user sudoers rules
|
||||||
when: item.sudo | default(false)
|
when: item.value.sudo | default(false)
|
||||||
vars:
|
vars:
|
||||||
configuration_sudoers_rule: >-
|
configuration_sudoers_rule: >-
|
||||||
{{ item.sudo if item.sudo is string else 'ALL=(ALL) NOPASSWD: ALL' }}
|
{{ item.value.sudo if item.value.sudo is string else 'ALL=(ALL) NOPASSWD: ALL' }}
|
||||||
ansible.builtin.copy:
|
ansible.builtin.copy:
|
||||||
content: "{{ item.name }} {{ configuration_sudoers_rule }}\n"
|
content: "{{ item.key }} {{ configuration_sudoers_rule }}\n"
|
||||||
dest: "/mnt/etc/sudoers.d/{{ item.name }}"
|
dest: "/mnt/etc/sudoers.d/{{ item.key }}"
|
||||||
mode: "0440"
|
mode: "0440"
|
||||||
validate: /usr/sbin/visudo --check --file=%s
|
validate: /usr/sbin/visudo --check --file=%s
|
||||||
loop: "{{ system_cfg.users }}"
|
loop: "{{ system_cfg.users | dict2items }}"
|
||||||
loop_control:
|
loop_control:
|
||||||
label: "{{ item.name }}"
|
label: "{{ item.key }}"
|
||||||
|
|||||||
@@ -26,44 +26,43 @@
|
|||||||
- name: Create user accounts
|
- name: Create user accounts
|
||||||
vars:
|
vars:
|
||||||
configuration_user_group: "{{ _configuration_platform.user_group }}"
|
configuration_user_group: "{{ _configuration_platform.user_group }}"
|
||||||
# UID starts at 1000; safe for fresh installs only
|
|
||||||
configuration_useradd_cmd: >-
|
configuration_useradd_cmd: >-
|
||||||
{{ chroot_command }} /usr/sbin/useradd --create-home --user-group
|
{{ chroot_command }} /usr/sbin/useradd --create-home --user-group
|
||||||
--uid {{ 1000 + ansible_loop.index0 }}
|
--uid {{ 1000 + _idx }}
|
||||||
--groups {{ configuration_user_group }} {{ item.name }}
|
--groups {{ configuration_user_group }} {{ item.key }}
|
||||||
--password {{ item.password | password_hash('sha512') }} --shell {{ item.shell | default('/bin/bash') }}
|
{{ ('--password ' ~ (item.value.password | password_hash('sha512'))) if (item.value.password | default('') | string | length > 0) else '' }}
|
||||||
|
--shell {{ item.value.shell | default('/bin/bash') }}
|
||||||
ansible.builtin.command: "{{ configuration_useradd_cmd }}"
|
ansible.builtin.command: "{{ configuration_useradd_cmd }}"
|
||||||
loop: "{{ system_cfg.users }}"
|
loop: "{{ system_cfg.users | dict2items }}"
|
||||||
loop_control:
|
loop_control:
|
||||||
extended: true
|
index_var: _idx
|
||||||
label: "{{ item.name }}"
|
label: "{{ item.key }}"
|
||||||
register: configuration_user_result
|
register: configuration_user_result
|
||||||
changed_when: configuration_user_result.rc == 0
|
changed_when: configuration_user_result.rc == 0
|
||||||
no_log: true
|
no_log: true
|
||||||
|
|
||||||
- name: Ensure .ssh directory exists
|
- name: Ensure .ssh directory exists
|
||||||
when: "'keys' in item and item['keys'] is iterable and item['keys'] is not string and item['keys'] | length > 0"
|
when: (item.value['keys'] | default([]) | length) > 0
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
path: "/mnt/home/{{ item.name }}/.ssh"
|
path: "/mnt/home/{{ item.key }}/.ssh"
|
||||||
state: directory
|
state: directory
|
||||||
owner: "{{ 1000 + ansible_loop.index0 }}"
|
owner: "{{ 1000 + _idx }}"
|
||||||
group: "{{ 1000 + ansible_loop.index0 }}"
|
group: "{{ 1000 + _idx }}"
|
||||||
mode: "0700"
|
mode: "0700"
|
||||||
loop: "{{ system_cfg.users }}"
|
loop: "{{ system_cfg.users | dict2items }}"
|
||||||
loop_control:
|
loop_control:
|
||||||
extended: true
|
index_var: _idx
|
||||||
label: "{{ item.name }}"
|
label: "{{ item.key }}"
|
||||||
|
|
||||||
- name: Add SSH public keys to authorized_keys
|
- name: Deploy SSH authorized_keys
|
||||||
vars:
|
when: (item.value['keys'] | default([]) | length) > 0
|
||||||
configuration_uid: "{{ 1000 + (system_cfg.users | map(attribute='name') | list).index(item.0.name) }}"
|
ansible.builtin.copy:
|
||||||
ansible.builtin.lineinfile:
|
content: "{{ item.value['keys'] | join('\n') }}\n"
|
||||||
path: "/mnt/home/{{ item.0.name }}/.ssh/authorized_keys"
|
dest: "/mnt/home/{{ item.key }}/.ssh/authorized_keys"
|
||||||
line: "{{ item.1 }}"
|
owner: "{{ 1000 + _idx }}"
|
||||||
owner: "{{ configuration_uid }}"
|
group: "{{ 1000 + _idx }}"
|
||||||
group: "{{ configuration_uid }}"
|
|
||||||
mode: "0600"
|
mode: "0600"
|
||||||
create: true
|
loop: "{{ system_cfg.users | dict2items }}"
|
||||||
loop: "{{ system_cfg.users | subelements('keys', skip_missing=True) }}"
|
|
||||||
loop_control:
|
loop_control:
|
||||||
label: "{{ item.0.name }}: {{ item.1[:40] }}..."
|
index_var: _idx
|
||||||
|
label: "{{ item.key }}"
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ system_defaults:
|
|||||||
mirror: ""
|
mirror: ""
|
||||||
packages: []
|
packages: []
|
||||||
disks: []
|
disks: []
|
||||||
users: []
|
users: {}
|
||||||
root:
|
root:
|
||||||
password: ""
|
password: ""
|
||||||
shell: "/bin/bash"
|
shell: "/bin/bash"
|
||||||
|
|||||||
@@ -96,7 +96,7 @@
|
|||||||
}}
|
}}
|
||||||
# --- Storage & accounts ---
|
# --- Storage & accounts ---
|
||||||
disks: "{{ system_raw.disks | default([]) }}"
|
disks: "{{ system_raw.disks | default([]) }}"
|
||||||
users: "{{ system_raw.users | default([]) }}"
|
users: "{{ system_raw.users | default({}) }}"
|
||||||
root:
|
root:
|
||||||
password: "{{ system_raw.root.password | string }}"
|
password: "{{ system_raw.root.password | string }}"
|
||||||
shell: "{{ system_raw.root.shell | default('/bin/bash') | string }}"
|
shell: "{{ system_raw.root.shell | default('/bin/bash') | string }}"
|
||||||
|
|||||||
@@ -25,17 +25,17 @@
|
|||||||
quiet: true
|
quiet: true
|
||||||
|
|
||||||
- name: Validate system.users entries
|
- name: Validate system.users entries
|
||||||
when: system.users is defined and system.users | length > 0
|
when: system.users is defined and system.users is mapping and system.users | length > 0
|
||||||
ansible.builtin.assert:
|
ansible.builtin.assert:
|
||||||
that:
|
that:
|
||||||
- item is mapping
|
- item.value is mapping
|
||||||
- item.name is defined and (item.name | string | length) > 0
|
- item.key | string | length > 0
|
||||||
- item['keys'] is not defined or (item['keys'] is iterable and item['keys'] is not string)
|
- item.value['keys'] is not defined or (item.value['keys'] is iterable and item.value['keys'] is not string)
|
||||||
fail_msg: "Each system.users[] entry must be a dict with 'name'; 'keys' must be a list."
|
fail_msg: "Each system.users entry must be a dict keyed by username; 'keys' must be a list."
|
||||||
quiet: true
|
quiet: true
|
||||||
loop: "{{ system.users }}"
|
loop: "{{ system.users | dict2items }}"
|
||||||
loop_control:
|
loop_control:
|
||||||
label: "{{ item.name | default('(unnamed)') }}"
|
label: "{{ item.key }}"
|
||||||
|
|
||||||
- name: Validate system features input types
|
- name: Validate system features input types
|
||||||
when: system.features is defined
|
when: system.features is defined
|
||||||
|
|||||||
@@ -81,10 +81,12 @@
|
|||||||
when:
|
when:
|
||||||
- system_cfg.type == "virtual"
|
- system_cfg.type == "virtual"
|
||||||
- hypervisor_type != "vmware"
|
- hypervisor_type != "vmware"
|
||||||
|
vars:
|
||||||
|
_primary: "{{ (system_cfg.users | dict2items | selectattr('value.password', 'defined') | first) }}"
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
ansible_user: "{{ system_cfg.users[0].name }}"
|
ansible_user: "{{ _primary.key }}"
|
||||||
ansible_password: "{{ system_cfg.users[0].password }}"
|
ansible_password: "{{ _primary.value.password }}"
|
||||||
ansible_become_password: "{{ system_cfg.users[0].password }}"
|
ansible_become_password: "{{ _primary.value.password }}"
|
||||||
ansible_ssh_extra_args: "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
|
ansible_ssh_extra_args: "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
|
||||||
no_log: true
|
no_log: true
|
||||||
|
|
||||||
|
|||||||
@@ -261,13 +261,16 @@
|
|||||||
fail_msg: "Invalid system sizing. Check system.cpus, system.memory, and system.disks[0].size."
|
fail_msg: "Invalid system sizing. Check system.cpus, system.memory, and system.disks[0].size."
|
||||||
quiet: true
|
quiet: true
|
||||||
|
|
||||||
- name: Validate at least one user is defined
|
- name: Validate at least one user with a password is defined
|
||||||
|
vars:
|
||||||
|
_pw_users: "{{ system_cfg.users | dict2items | selectattr('value.password', 'defined') | list }}"
|
||||||
ansible.builtin.assert:
|
ansible.builtin.assert:
|
||||||
that:
|
that:
|
||||||
- system_cfg.users | default([]) | length > 0
|
- system_cfg.users | default({}) | length > 0
|
||||||
- system_cfg.users[0].name is defined and (system_cfg.users[0].name | string | length) > 0
|
- _pw_users | length > 0
|
||||||
- system_cfg.users[0].password is defined and (system_cfg.users[0].password | string | length) > 0
|
- _pw_users[0].key | string | length > 0
|
||||||
fail_msg: "At least one user with a name and password must be defined in system.users[]."
|
- _pw_users[0].value.password | string | length > 0
|
||||||
|
fail_msg: "At least one user with a password must be defined in system.users."
|
||||||
quiet: true
|
quiet: true
|
||||||
no_log: true
|
no_log: true
|
||||||
|
|
||||||
|
|||||||
@@ -35,8 +35,8 @@
|
|||||||
{%- endfor -%}
|
{%- endfor -%}
|
||||||
{{ out }}
|
{{ out }}
|
||||||
community.proxmox.proxmox_kvm:
|
community.proxmox.proxmox_kvm:
|
||||||
ciuser: "{{ system_cfg.users[0].name }}"
|
ciuser: "{{ (system_cfg.users | dict2items | selectattr('value.password', 'defined') | first).key }}"
|
||||||
cipassword: "{{ system_cfg.users[0].password }}"
|
cipassword: "{{ (system_cfg.users | dict2items | selectattr('value.password', 'defined') | first).value.password }}"
|
||||||
ciupgrade: false
|
ciupgrade: false
|
||||||
vmid: "{{ system_cfg.id }}"
|
vmid: "{{ system_cfg.id }}"
|
||||||
name: "{{ hostname }}"
|
name: "{{ hostname }}"
|
||||||
|
|||||||
Reference in New Issue
Block a user