refactor(global_defaults): single source of truth for family-default resolution
This commit is contained in:
@@ -203,6 +203,12 @@ hypervisor_required_fields:
|
|||||||
hypervisor: []
|
hypervisor: []
|
||||||
system: []
|
system: []
|
||||||
|
|
||||||
|
# Family default content mirror URLs, used when content.url is empty.
|
||||||
|
content_mirror_defaults:
|
||||||
|
debian: "https://deb.debian.org/debian/"
|
||||||
|
ubuntu: "http://archive.ubuntu.com/ubuntu/"
|
||||||
|
ubuntu-lts: "http://archive.ubuntu.com/ubuntu/"
|
||||||
|
|
||||||
# Hypervisor-to-disk device prefix mapping for virtual machines.
|
# Hypervisor-to-disk device prefix mapping for virtual machines.
|
||||||
# Physical installs must set system.disks[].device explicitly.
|
# Physical installs must set system.disks[].device explicitly.
|
||||||
hypervisor_disk_device_map:
|
hypervisor_disk_device_map:
|
||||||
|
|||||||
25
roles/global_defaults/tasks/_apply_family_defaults.yml
Normal file
25
roles/global_defaults/tasks/_apply_family_defaults.yml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
# Shared by both the fresh-run path (_normalize_system.yml) and the pre-computed
|
||||||
|
# enrichment path (system.yml) so the family-default rules live in one place.
|
||||||
|
- name: Apply family defaults to system_cfg
|
||||||
|
vars:
|
||||||
|
_os: "{{ system_cfg.os | default('') | string | lower }}"
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
system_cfg: >-
|
||||||
|
{{
|
||||||
|
system_cfg | combine({
|
||||||
|
'content': {
|
||||||
|
'source': system_cfg.content.source
|
||||||
|
if (system_cfg.content.source | default('') | string | trim | length > 0)
|
||||||
|
else ('dvd' if _os == 'rhel' else 'mirror'),
|
||||||
|
'url': system_cfg.content.url
|
||||||
|
if (system_cfg.content.url | default('') | string | trim | length > 0)
|
||||||
|
else (content_mirror_defaults[_os] | default('')),
|
||||||
|
},
|
||||||
|
'features': {'firewall': {'backend':
|
||||||
|
system_cfg.features.firewall.backend
|
||||||
|
if (system_cfg.features.firewall.backend | default('') | string | trim | length > 0)
|
||||||
|
else ('ufw' if _os in os_family_debian else 'firewalld')
|
||||||
|
}},
|
||||||
|
}, recursive=True)
|
||||||
|
}}
|
||||||
@@ -10,29 +10,19 @@
|
|||||||
if (system_raw.name | default('') | string | trim | length) > 0
|
if (system_raw.name | default('') | string | trim | length) > 0
|
||||||
else inventory_hostname
|
else inventory_hostname
|
||||||
}}
|
}}
|
||||||
_mirror_defaults:
|
|
||||||
debian: "https://deb.debian.org/debian/"
|
|
||||||
ubuntu: "http://archive.ubuntu.com/ubuntu/"
|
|
||||||
ubuntu-lts: "http://archive.ubuntu.com/ubuntu/"
|
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
system_cfg:
|
system_cfg:
|
||||||
# --- Identity & platform ---
|
|
||||||
type: "{{ system_type }}"
|
type: "{{ system_type }}"
|
||||||
os: "{{ system_os_input if system_os_input | length > 0 else (physical_default_os if system_type == 'physical' else '') }}"
|
os: "{{ system_os_input if system_os_input | length > 0 else (physical_default_os if system_type == 'physical' else '') }}"
|
||||||
version: "{{ system_raw.version | default('') | string }}"
|
version: "{{ system_raw.version | default('') | string }}"
|
||||||
filesystem: "{{ system_raw.filesystem | default('') | string | lower }}"
|
filesystem: "{{ system_raw.filesystem | default('') | string | lower }}"
|
||||||
name: "{{ system_name }}"
|
name: "{{ system_name }}"
|
||||||
id: "{{ system_raw.id | default('') | string }}"
|
id: "{{ system_raw.id | default('') | string }}"
|
||||||
# --- VM sizing (ignored for physical) ---
|
|
||||||
cpus: "{{ [system_raw.cpus | default(0) | int, 0] | max }}"
|
cpus: "{{ [system_raw.cpus | default(0) | int, 0] | max }}"
|
||||||
memory: "{{ [system_raw.memory | default(0) | int, 0] | max }}"
|
memory: "{{ [system_raw.memory | default(0) | int, 0] | max }}"
|
||||||
balloon: "{{ [system_raw.balloon | default(0) | int, 0] | max }}"
|
balloon: "{{ [system_raw.balloon | default(0) | int, 0] | max }}"
|
||||||
# --- Network ---
|
# Flat fields and interfaces[] describe the same primary NIC: each is
|
||||||
# Flat fields (bridge, ip, etc.) and interfaces[] express the same primary NIC.
|
# backfilled from the other so consumers reading either form still work.
|
||||||
# When only flat fields are set, a synthetic interfaces[] entry is built below.
|
|
||||||
# When interfaces[] is set, the flat ip/prefix/gateway are backfilled from
|
|
||||||
# interfaces[0] so consumers reading the flat fields (e.g. the post-reboot
|
|
||||||
# reconnect block) still work.
|
|
||||||
network:
|
network:
|
||||||
bridge: >-
|
bridge: >-
|
||||||
{{
|
{{
|
||||||
@@ -87,20 +77,13 @@
|
|||||||
else []
|
else []
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
# --- Locale & environment ---
|
|
||||||
timezone: "{{ system_raw.timezone | string }}"
|
timezone: "{{ system_raw.timezone | string }}"
|
||||||
locale: "{{ system_raw.locale | string }}"
|
locale: "{{ system_raw.locale | string }}"
|
||||||
keymap: "{{ system_raw.keymap | string }}"
|
keymap: "{{ system_raw.keymap | string }}"
|
||||||
content:
|
content:
|
||||||
source: >-
|
# Family defaults for empty source/url are applied by _apply_family_defaults.yml.
|
||||||
{%- set s = system_raw.content.source | default('') | string | lower | trim -%}
|
source: "{{ system_raw.content.source | default('') | string | lower | trim }}"
|
||||||
{%- if s | length > 0 -%}{{ s }}
|
url: "{{ system_raw.content.url | default('') | string | trim }}"
|
||||||
{%- elif (system_raw.os | default('') | string | lower) == 'rhel' -%}dvd
|
|
||||||
{%- else -%}mirror{%- endif -%}
|
|
||||||
url: >-
|
|
||||||
{%- set u = system_raw.content.url | default('') | string | trim -%}
|
|
||||||
{%- if u | length > 0 -%}{{ u }}
|
|
||||||
{%- else -%}{{ _mirror_defaults[system_raw.os | default('') | string | lower] | default('') }}{%- endif -%}
|
|
||||||
proxy: "{{ system_raw.content.proxy | default('') | string | trim }}"
|
proxy: "{{ system_raw.content.proxy | default('') | string | trim }}"
|
||||||
gpgcheck: "{{ system_raw.content.gpgcheck | default(true) | bool }}"
|
gpgcheck: "{{ system_raw.content.gpgcheck | default(true) | bool }}"
|
||||||
satellite:
|
satellite:
|
||||||
@@ -129,13 +112,11 @@
|
|||||||
| reject('equalto', '')
|
| reject('equalto', '')
|
||||||
| list
|
| list
|
||||||
}}
|
}}
|
||||||
# --- 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 }}"
|
||||||
# --- LUKS disk encryption ---
|
|
||||||
luks:
|
luks:
|
||||||
enabled: "{{ system_raw.luks.enabled | bool }}"
|
enabled: "{{ system_raw.luks.enabled | bool }}"
|
||||||
passphrase: "{{ system_raw.luks.passphrase | string }}"
|
passphrase: "{{ system_raw.luks.passphrase | string }}"
|
||||||
@@ -153,7 +134,6 @@
|
|||||||
iter: "{{ system_raw.luks.iter | int }}"
|
iter: "{{ system_raw.luks.iter | int }}"
|
||||||
bits: "{{ system_raw.luks.bits | int }}"
|
bits: "{{ system_raw.luks.bits | int }}"
|
||||||
pbkdf: "{{ system_raw.luks.pbkdf | string }}"
|
pbkdf: "{{ system_raw.luks.pbkdf | string }}"
|
||||||
# --- Feature flags ---
|
|
||||||
features:
|
features:
|
||||||
cloud_init: "{{ system_raw.features.cloud_init | default(false) | bool }}"
|
cloud_init: "{{ system_raw.features.cloud_init | default(false) | bool }}"
|
||||||
cis:
|
cis:
|
||||||
@@ -165,10 +145,8 @@
|
|||||||
enabled: "{{ system_raw.features.selinux.enabled | bool }}"
|
enabled: "{{ system_raw.features.selinux.enabled | bool }}"
|
||||||
firewall:
|
firewall:
|
||||||
enabled: "{{ system_raw.features.firewall.enabled | bool }}"
|
enabled: "{{ system_raw.features.firewall.enabled | bool }}"
|
||||||
backend: >-
|
# Empty backend is family-resolved by _apply_family_defaults.yml.
|
||||||
{{ (system_raw.features.firewall.backend | default('') | string | lower | trim)
|
backend: "{{ system_raw.features.firewall.backend | default('') | string | lower | trim }}"
|
||||||
if (system_raw.features.firewall.backend | default('') | string | lower | trim | length > 0)
|
|
||||||
else ('ufw' if (system_raw.os | default('') | string | lower) in ['debian', 'ubuntu', 'ubuntu-lts'] else 'firewalld') }}
|
|
||||||
toolkit: "{{ system_raw.features.firewall.toolkit | string | lower }}"
|
toolkit: "{{ system_raw.features.firewall.toolkit | string | lower }}"
|
||||||
ssh:
|
ssh:
|
||||||
enabled: "{{ system_raw.features.ssh.enabled | bool }}"
|
enabled: "{{ system_raw.features.ssh.enabled | bool }}"
|
||||||
@@ -225,8 +203,7 @@
|
|||||||
if (system_raw.features.peripherals.enabled | string | lower) == 'auto'
|
if (system_raw.features.peripherals.enabled | string | lower) == 'auto'
|
||||||
else (system_raw.features.peripherals.enabled | bool)
|
else (system_raw.features.peripherals.enabled | bool)
|
||||||
}}
|
}}
|
||||||
# fingerprint/camera/audio/bluetooth stay tri-state ('auto'|'true'|'false')
|
# Kept tri-state ('auto'|'true'|'false'): 'auto' resolves at install time from detection.
|
||||||
# because the 'auto' branch is resolved at install time using detection results.
|
|
||||||
fingerprint: >-
|
fingerprint: >-
|
||||||
{{
|
{{
|
||||||
'auto'
|
'auto'
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
label: "system.features.{{ item }}"
|
label: "system.features.{{ item }}"
|
||||||
ansible.builtin.assert:
|
ansible.builtin.assert:
|
||||||
that:
|
that:
|
||||||
- (system.features[item] | default({})) is mapping
|
- (system_defaults.features[item] is not mapping) or ((system.features[item] | default({})) is mapping)
|
||||||
fail_msg: "system.features.{{ item }} must be a dictionary."
|
fail_msg: "system.features.{{ item }} must be a dictionary."
|
||||||
quiet: true
|
quiet: true
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
---
|
---
|
||||||
# Two code paths:
|
# Fresh run normalizes raw `system` input. A pre-computed system_cfg (from the main
|
||||||
# 1. Fresh run (system_cfg undefined): normalize from raw `system` input.
|
# project's deploy_iac) is instead merged with system_defaults to fill the fields
|
||||||
# 2. Pre-computed (system_cfg already set, e.g. from main project's deploy_iac):
|
# bootstrap expects, then convenience facts are derived.
|
||||||
# merge with bootstrap system_defaults to fill missing fields (luks, features,
|
|
||||||
# etc.) that bootstrap expects but the main project doesn't set, then derive
|
|
||||||
# convenience facts (hostname, os, os_version).
|
|
||||||
- name: Normalize system and disk configuration
|
- name: Normalize system and disk configuration
|
||||||
when: system_cfg is not defined
|
when: system_cfg is not defined
|
||||||
block:
|
block:
|
||||||
@@ -50,37 +47,6 @@
|
|||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
system_cfg: "{{ system_defaults | combine(system | default({}), recursive=True) | combine(system_cfg, recursive=True) }}"
|
system_cfg: "{{ system_defaults | combine(system | default({}), recursive=True) | combine(system_cfg, recursive=True) }}"
|
||||||
|
|
||||||
- name: Apply family defaults (content source, firewall backend) for pre-computed system_cfg
|
|
||||||
when:
|
|
||||||
- system_cfg is defined
|
|
||||||
- _bootstrap_needs_enrichment | default(false) | bool
|
|
||||||
vars:
|
|
||||||
# Same family resolution as _normalize_system.yml - kept in sync manually.
|
|
||||||
_mirror_defaults:
|
|
||||||
debian: "https://deb.debian.org/debian/"
|
|
||||||
ubuntu: "http://archive.ubuntu.com/ubuntu/"
|
|
||||||
ubuntu-lts: "http://archive.ubuntu.com/ubuntu/"
|
|
||||||
_os: "{{ system_cfg.os | default('') | string | lower }}"
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
system_cfg: >-
|
|
||||||
{{
|
|
||||||
system_cfg | combine({
|
|
||||||
'content': {
|
|
||||||
'source': system_cfg.content.source
|
|
||||||
if (system_cfg.content.source | default('') | string | trim | length > 0)
|
|
||||||
else ('dvd' if _os == 'rhel' else 'mirror'),
|
|
||||||
'url': system_cfg.content.url
|
|
||||||
if (system_cfg.content.url | default('') | string | trim | length > 0)
|
|
||||||
else (_mirror_defaults[_os] | default('')),
|
|
||||||
},
|
|
||||||
'features': {'firewall': {'backend':
|
|
||||||
system_cfg.features.firewall.backend
|
|
||||||
if (system_cfg.features.firewall.backend | default('') | string | trim | length > 0)
|
|
||||||
else ('ufw' if _os in ['debian', 'ubuntu', 'ubuntu-lts'] else 'firewalld')
|
|
||||||
}},
|
|
||||||
}, recursive=True)
|
|
||||||
}}
|
|
||||||
|
|
||||||
- name: Populate primary network fields from first interface (pre-computed)
|
- name: Populate primary network fields from first interface (pre-computed)
|
||||||
when:
|
when:
|
||||||
- system_cfg is defined
|
- system_cfg is defined
|
||||||
@@ -117,3 +83,8 @@
|
|||||||
- system_cfg is defined
|
- system_cfg is defined
|
||||||
- install_drive is not defined
|
- install_drive is not defined
|
||||||
ansible.builtin.include_tasks: _normalize_disks.yml
|
ansible.builtin.include_tasks: _normalize_disks.yml
|
||||||
|
|
||||||
|
# Runs on every path before validation, so an empty firewall.backend / content.source
|
||||||
|
# resolves to the family default even when system_cfg arrived pre-computed.
|
||||||
|
- name: Apply family defaults (content source, firewall backend)
|
||||||
|
ansible.builtin.include_tasks: _apply_family_defaults.yml
|
||||||
|
|||||||
@@ -96,7 +96,7 @@
|
|||||||
quiet: true
|
quiet: true
|
||||||
|
|
||||||
- name: Validate system.features leaf schemas
|
- name: Validate system.features leaf schemas
|
||||||
loop: "{{ system_defaults.features | dict2items }}"
|
loop: "{{ system_defaults.features | dict2items | selectattr('value', 'mapping') }}"
|
||||||
loop_control:
|
loop_control:
|
||||||
label: "system.features.{{ item.key }}"
|
label: "system.features.{{ item.key }}"
|
||||||
vars:
|
vars:
|
||||||
|
|||||||
Reference in New Issue
Block a user