From d922efd2e4b863f67e4448b10a935346141afc0a Mon Sep 17 00:00:00 2001 From: Sandwich Date: Wed, 27 May 2026 05:15:32 +0200 Subject: [PATCH] feat: uniform system.content source schema across installers and repositories --- README.md | 23 +++++- roles/bootstrap/tasks/archlinux.yml | 50 +++++++++--- roles/bootstrap/tasks/debian.yml | 9 ++- roles/bootstrap/tasks/rhel.yml | 2 + roles/bootstrap/tasks/ubuntu.yml | 9 ++- .../templates/debian.sources.list.j2 | 2 +- .../templates/ubuntu.sources.list.j2 | 2 +- roles/bootstrap/vars/main.yml | 2 +- roles/cleanup/tasks/proxmox.yml | 2 +- roles/cleanup/tasks/vmware.yml | 2 +- roles/configuration/tasks/fstab.yml | 6 +- roles/configuration/tasks/main.yml | 1 - roles/configuration/tasks/repositories.yml | 65 +++++++++++++++- .../tasks/satellite_register.yml | 46 +++++++++++ .../templates/debian.sources.list.j2 | 2 +- .../configuration/templates/el_mirror.repo.j2 | 17 ++++ .../templates/ubuntu.sources.list.j2 | 2 +- roles/environment/templates/rhel.repo.j2 | 15 +++- roles/global_defaults/defaults/main.yml | 21 ++++- .../tasks/_normalize_system.yml | 30 ++++--- roles/global_defaults/tasks/system.yml | 17 ++-- roles/global_defaults/tasks/validation.yml | 4 +- tests/content/_assert_content.yml | 40 ++++++++++ tests/content/content_fixtures.yml | 78 +++++++++++++++++++ tests/content/test_content.yml | 22 ++++++ 25 files changed, 419 insertions(+), 50 deletions(-) create mode 100644 roles/configuration/tasks/satellite_register.yml create mode 100644 roles/configuration/templates/el_mirror.repo.j2 create mode 100644 tests/content/_assert_content.yml create mode 100644 tests/content/content_fixtures.yml create mode 100644 tests/content/test_content.yml diff --git a/README.md b/README.md index e22c770..d921838 100644 --- a/README.md +++ b/README.md @@ -172,7 +172,7 @@ Top-level variables outside `system`/`hypervisor`. | `memory` | int | `0` | Memory in MiB (required for virtual) | | `balloon` | int | `0` | Balloon memory in MiB (Proxmox) | | `path` | string | -- | Hypervisor folder/path (falls back to `hypervisor.folder`) | -| `mirror` | string | per-distro default | Override package mirror (Debian/Ubuntu) | +| `content` | dict | see below | Package content source (mirror/DVD/Satellite, family-resolved) | | `packages` | list | `[]` | Additional packages installed post-reboot | | `network` | dict | see below | Network configuration | | `disks` | list | `[]` | Disk layout (see [Multi-Disk Schema](#46-multi-disk-schema)) | @@ -181,6 +181,25 @@ Top-level variables outside `system`/`hypervisor`. | `luks` | dict | see below | Encryption settings | | `features` | dict | see below | Feature toggles | +#### `system.content` + +Uniform package content source, family-resolved. `source: ''` defaults to `dvd` on EL and `mirror` on Debian/Ubuntu/Arch. Satellite values come from inventory/vault only, never committed code. + +| Key | Type | Default | Description | +| -------------------------- | ------ | -------------- | ----------------------------------------------------------------- | +| `source` | string | family default | `dvd`, `mirror`, `satellite`, or `none` | +| `url` | string | family default | Mirror URL / EL `.repo` baseurl | +| `proxy` | string | -- | `http://host:port` content proxy (dnf/apt/pacman) | +| `gpgcheck` | bool | `true` | Repository GPG checking | +| `satellite.host` | string | -- | EL Katello/Satellite hostname | +| `satellite.ip` | string | -- | Optional `/etc/hosts` entry when DNS does not resolve the host | +| `satellite.org` | string | -- | Organization label | +| `satellite.activation_key` | string | -- | Activation key | +| `satellite.ca_url` | string | derived | Katello CA RPM URL (default `https:///pub/katello-ca-consumer-latest.noarch.rpm`) | +| `satellite.service_level` | string | -- | syspurpose service level | +| `satellite.environment` | string | -- | Lifecycle environment | +| `satellite.install` | bool | `false` | `false`: base from DVD/mirror then register; `true`: install from Satellite | + #### `system.network` | Key | Type | Default | Description | @@ -281,8 +300,6 @@ The bootstrap auto-switches to dracut when `method: tpm2` is set. Override via ` | `banner.sudo` | bool | `true` | Sudo banner | | `chroot.tool` | string | `arch-chroot` | `arch-chroot`, `chroot`, or `systemd-nspawn` | | `initramfs.generator` | string | auto-detected | Override initramfs generator (see below) | -| `rhel_repo.source` | string | `iso` | RHEL post-install repo source: `iso`, `satellite`, or `none` | -| `rhel_repo.url` | string | -- | Satellite/custom repo URL when `source: satellite` | | `secure_boot.enabled` | bool | `false` | Enable Secure Boot (Arch via sbctl, others via shim) | | `secure_boot.method` | string | -- | Arch only: `sbctl` (default) or `uki` | | `desktop.*` | dict | see below | Desktop environment settings (see [4.2.5](#425-systemfeaturesdesktop)) | diff --git a/roles/bootstrap/tasks/archlinux.yml b/roles/bootstrap/tasks/archlinux.yml index 1683d6f..d290dc2 100644 --- a/roles/bootstrap/tasks/archlinux.yml +++ b/roles/bootstrap/tasks/archlinux.yml @@ -1,9 +1,4 @@ --- -- name: Refresh Arch keyring in the live environment - ansible.builtin.command: pacman -Sy --noconfirm archlinux-keyring - register: bootstrap_arch_keyring - changed_when: bootstrap_arch_keyring.rc == 0 - - name: Bootstrap ArchLinux vars: _config: "{{ lookup('vars', bootstrap_var_key) }}" @@ -13,7 +8,44 @@ | reject('equalto', '') | list }} - ansible.builtin.command: >- - pacstrap /mnt {{ bootstrap_archlinux_packages | join(' ') }} - register: bootstrap_result - changed_when: bootstrap_result.rc == 0 + block: + - name: Notify that mirror mode falls back to the public mirrorlist + when: + - system_cfg.content.source == 'mirror' + - system_cfg.content.url | length == 0 + ansible.builtin.debug: + msg: >- + content.source is 'mirror' but content.url is empty: keeping the live + ISO public mirrorlist (refreshed by reflector). Set content.url to pin + a specific mirror. + + - name: Point pacman at the content mirror + when: system_cfg.content.url | length > 0 + ansible.builtin.copy: + dest: /etc/pacman.d/mirrorlist + content: "Server = {{ system_cfg.content.url }}/$repo/os/$arch\n" + mode: "0644" + + - name: Refresh Arch keyring in the live environment + ansible.builtin.command: pacman -Sy --noconfirm archlinux-keyring + environment: + http_proxy: "{{ system_cfg.content.proxy }}" + https_proxy: "{{ system_cfg.content.proxy }}" + register: bootstrap_arch_keyring + changed_when: bootstrap_arch_keyring.rc == 0 + + - name: Install Arch base system + ansible.builtin.command: >- + pacstrap /mnt {{ bootstrap_archlinux_packages | join(' ') }} + environment: + http_proxy: "{{ system_cfg.content.proxy }}" + https_proxy: "{{ system_cfg.content.proxy }}" + register: bootstrap_result + changed_when: bootstrap_result.rc == 0 + + - name: Persist the content mirror in the installed system + when: system_cfg.content.url | length > 0 + ansible.builtin.copy: + dest: /mnt/etc/pacman.d/mirrorlist + content: "Server = {{ system_cfg.content.url }}/$repo/os/$arch\n" + mode: "0644" diff --git a/roles/bootstrap/tasks/debian.yml b/roles/bootstrap/tasks/debian.yml index 012c80a..fee2a5b 100644 --- a/roles/bootstrap/tasks/debian.yml +++ b/roles/bootstrap/tasks/debian.yml @@ -43,7 +43,10 @@ debootstrap --keyring=/usr/share/keyrings/debian-archive-keyring.gpg --include={{ bootstrap_debian_base_csv }} {{ bootstrap_debian_release }} /mnt - {{ system_cfg.mirror | default('http://deb.debian.org/debian', true) }} + {{ system_cfg.content.url }} + environment: + http_proxy: "{{ system_cfg.content.proxy }}" + https_proxy: "{{ system_cfg.content.proxy }}" register: bootstrap_debian_base_result changed_when: bootstrap_debian_base_result.rc == 0 @@ -60,6 +63,10 @@ Acquire::Retries "3"; Acquire::http::Pipeline-Depth "10"; APT::Install-Recommends "false"; + {% if system_cfg.content.proxy | length > 0 %} + Acquire::http::Proxy "{{ system_cfg.content.proxy }}"; + Acquire::https::Proxy "{{ system_cfg.content.proxy }}"; + {% endif %} mode: "0644" - name: Update package lists diff --git a/roles/bootstrap/tasks/rhel.yml b/roles/bootstrap/tasks/rhel.yml index 1461d73..c5dc440 100644 --- a/roles/bootstrap/tasks/rhel.yml +++ b/roles/bootstrap/tasks/rhel.yml @@ -24,12 +24,14 @@ - "'grub2-common' not in (bootstrap_result.stderr | default(''))" - name: Ensure chroot RHEL DVD directory exists + when: system_cfg.content.source != 'mirror' ansible.builtin.file: path: /mnt/usr/local/install/redhat/dvd state: directory mode: "0755" - name: Bind mount RHEL DVD into chroot + when: system_cfg.content.source != 'mirror' ansible.posix.mount: src: /usr/local/install/redhat/dvd path: /mnt/usr/local/install/redhat/dvd diff --git a/roles/bootstrap/tasks/ubuntu.yml b/roles/bootstrap/tasks/ubuntu.yml index aa6a75d..fde7e93 100644 --- a/roles/bootstrap/tasks/ubuntu.yml +++ b/roles/bootstrap/tasks/ubuntu.yml @@ -42,7 +42,10 @@ --keyring=/usr/share/keyrings/ubuntu-archive-keyring.gpg --include={{ bootstrap_ubuntu_base_csv }} {{ bootstrap_ubuntu_release }} /mnt - {{ system_cfg.mirror | default('http://archive.ubuntu.com/ubuntu', true) }} + {{ system_cfg.content.url }} + environment: + http_proxy: "{{ system_cfg.content.proxy }}" + https_proxy: "{{ system_cfg.content.proxy }}" register: bootstrap_ubuntu_base_result changed_when: bootstrap_ubuntu_base_result.rc == 0 @@ -59,6 +62,10 @@ Acquire::Retries "3"; Acquire::http::Pipeline-Depth "10"; APT::Install-Recommends "false"; + {% if system_cfg.content.proxy | length > 0 %} + Acquire::http::Proxy "{{ system_cfg.content.proxy }}"; + Acquire::https::Proxy "{{ system_cfg.content.proxy }}"; + {% endif %} mode: "0644" - name: Update package lists diff --git a/roles/bootstrap/templates/debian.sources.list.j2 b/roles/bootstrap/templates/debian.sources.list.j2 index 8bee0f5..bb14753 100644 --- a/roles/bootstrap/templates/debian.sources.list.j2 +++ b/roles/bootstrap/templates/debian.sources.list.j2 @@ -1,6 +1,6 @@ # Managed by Ansible. {% set release = bootstrap_debian_release %} -{% set mirror = system_cfg.mirror | default('http://deb.debian.org/debian', true) %} +{% set mirror = system_cfg.content.url | default('http://deb.debian.org/debian', true) %} {% set components = 'main contrib non-free non-free-firmware' %} deb {{ mirror }} {{ release }} {{ components }} diff --git a/roles/bootstrap/templates/ubuntu.sources.list.j2 b/roles/bootstrap/templates/ubuntu.sources.list.j2 index f2f14b8..6741414 100644 --- a/roles/bootstrap/templates/ubuntu.sources.list.j2 +++ b/roles/bootstrap/templates/ubuntu.sources.list.j2 @@ -1,6 +1,6 @@ # Managed by Ansible. {% set release = bootstrap_ubuntu_release %} -{% set mirror = system_cfg.mirror %} +{% set mirror = system_cfg.content.url %} {% set components = 'main restricted universe multiverse' %} deb {{ mirror }} {{ release }} {{ components }} diff --git a/roles/bootstrap/vars/main.yml b/roles/bootstrap/vars/main.yml index be20790..3492c29 100644 --- a/roles/bootstrap/vars/main.yml +++ b/roles/bootstrap/vars/main.yml @@ -314,7 +314,6 @@ bootstrap_archlinux: - nfs-utils - ppp - python - - reflector - rsync - sudo - tldr @@ -327,5 +326,6 @@ bootstrap_archlinux: (['openssh'] if system_cfg.features.ssh.enabled | bool else []) + (['iptables-nft'] if system_cfg.features.firewall.toolkit == 'nftables' and system_cfg.features.firewall.enabled | bool else []) + (['sbctl'] if system_cfg.features.secure_boot.enabled | bool else []) + + (['reflector'] if system_cfg.content.url | length == 0 else []) + (bootstrap_common_conditional | reject('equalto', 'nftables') | list) }} diff --git a/roles/cleanup/tasks/proxmox.yml b/roles/cleanup/tasks/proxmox.yml index 57a8b05..9a8a3b3 100644 --- a/roles/cleanup/tasks/proxmox.yml +++ b/roles/cleanup/tasks/proxmox.yml @@ -16,7 +16,7 @@ loop: >- {{ ['ide0', 'ide2'] - + (['ide1'] if not (os == 'rhel' and system_cfg.features.rhel_repo.source == 'iso') else []) + + (['ide1'] if not (os == 'rhel' and system_cfg.content.source == 'dvd') else []) }} failed_when: false no_log: true diff --git a/roles/cleanup/tasks/vmware.yml b/roles/cleanup/tasks/vmware.yml index 2511f86..f7bf56e 100644 --- a/roles/cleanup/tasks/vmware.yml +++ b/roles/cleanup/tasks/vmware.yml @@ -35,7 +35,7 @@ } ] if (rhel_iso is defined and rhel_iso | length > 0 - and not (os == 'rhel' and system_cfg.features.rhel_repo.source == 'iso')) + and not (os == 'rhel' and system_cfg.content.source == 'dvd')) else [] ) }} diff --git a/roles/configuration/tasks/fstab.yml b/roles/configuration/tasks/fstab.yml index bdddaa8..d01a6ae 100644 --- a/roles/configuration/tasks/fstab.yml +++ b/roles/configuration/tasks/fstab.yml @@ -26,7 +26,7 @@ - name: Remove RHEL ISO fstab entry when not using local repo when: - os == "rhel" - - system_cfg.features.rhel_repo.source != "iso" + - system_cfg.content.source != "dvd" ansible.builtin.lineinfile: path: /mnt/etc/fstab regexp: "^.*\\/dvd.*$" @@ -35,7 +35,7 @@ - name: Replace ISO UUID entry with /dev/sr0 in fstab when: - os == "rhel" - - system_cfg.features.rhel_repo.source == "iso" + - system_cfg.content.source == "dvd" vars: configuration_fstab_dvd_line: >- {{ @@ -53,7 +53,7 @@ when: - os == "rhel" - hypervisor_type == "vmware" - - system_cfg.features.rhel_repo.source == "iso" + - system_cfg.content.source == "dvd" ansible.builtin.command: argv: - dd diff --git a/roles/configuration/tasks/main.yml b/roles/configuration/tasks/main.yml index d82761d..422345a 100644 --- a/roles/configuration/tasks/main.yml +++ b/roles/configuration/tasks/main.yml @@ -7,7 +7,6 @@ ansible.builtin.include_tasks: "{{ configuration_task.file }}" loop: - file: repositories.yml - when: "{{ os_family == 'Debian' }}" - file: banner.yml - file: fstab.yml - file: locales.yml diff --git a/roles/configuration/tasks/repositories.yml b/roles/configuration/tasks/repositories.yml index 5802a93..17e9c48 100644 --- a/roles/configuration/tasks/repositories.yml +++ b/roles/configuration/tasks/repositories.yml @@ -1,5 +1,8 @@ --- -- name: Write final sources.list +# Config runs against the chroot, so these write /mnt directly via templates +# rather than apt_repository/yum_repository, which would touch the live host. +- name: Write the apt sources.list + when: os_family == 'Debian' vars: _debian_release_map: "12": bookworm @@ -13,11 +16,69 @@ dest: /mnt/etc/apt/sources.list mode: "0644" -- name: Ensure apt performance configuration persists +- name: Ensure apt performance and content-proxy configuration + when: os_family == 'Debian' ansible.builtin.copy: dest: /mnt/etc/apt/apt.conf.d/99performance content: | Acquire::Retries "3"; Acquire::http::Pipeline-Depth "10"; APT::Install-Recommends "false"; + {% if system_cfg.content.proxy | length > 0 %} + Acquire::http::Proxy "{{ system_cfg.content.proxy }}"; + Acquire::https::Proxy "{{ system_cfg.content.proxy }}"; + {% endif %} mode: "0644" + +- name: Drop the install-time DVD repo from the target on non-dvd sources + when: + - os_family == 'RedHat' + - system_cfg.content.source != 'dvd' + ansible.builtin.file: + path: /mnt/etc/yum.repos.d/redhat.repo + state: absent + +- name: Write the EL mirror repo on the target + when: + - os_family == 'RedHat' + - system_cfg.content.source == 'mirror' + - system_cfg.content.url | length > 0 + ansible.builtin.template: + src: el_mirror.repo.j2 + dest: "/mnt/etc/yum.repos.d/{{ os }}.repo" + mode: "0644" + +- name: Find the stock vendor repos shipped by the release package + when: + - os_family == 'RedHat' + - system_cfg.content.source == 'mirror' + - system_cfg.content.url | length > 0 + ansible.builtin.find: + paths: /mnt/etc/yum.repos.d + patterns: "*.repo" + excludes: "{{ os }}.repo" + register: el_stock_repos + +- name: Remove the stock vendor repos so only the custom mirror is reachable + when: + - os_family == 'RedHat' + - system_cfg.content.source == 'mirror' + - system_cfg.content.url | length > 0 + ansible.builtin.file: + path: "{{ item.path }}" + state: absent + loop: "{{ el_stock_repos.files | default([]) }}" + loop_control: + label: "{{ item.path }}" + +- name: Configure the dnf content proxy on the target + when: + - os_family == 'RedHat' + - system_cfg.content.proxy | length > 0 + ansible.builtin.lineinfile: + path: /mnt/etc/dnf/dnf.conf + line: "proxy={{ system_cfg.content.proxy }}" + regexp: "^proxy=" + create: true + mode: "0644" + state: present diff --git a/roles/configuration/tasks/satellite_register.yml b/roles/configuration/tasks/satellite_register.yml new file mode 100644 index 0000000..c8c380f --- /dev/null +++ b/roles/configuration/tasks/satellite_register.yml @@ -0,0 +1,46 @@ +--- +# Invoked post-reboot on the booted host, not in the chroot: subscription-manager +# needs a running systemd and the live network. +- name: Add the Satellite host to /etc/hosts + when: system_cfg.content.satellite.ip | length > 0 + ansible.builtin.lineinfile: + path: /etc/hosts + line: "{{ system_cfg.content.satellite.ip }} {{ system_cfg.content.satellite.host }}" + regexp: "[[:space:]]{{ system_cfg.content.satellite.host | regex_escape }}([[:space:]]|$)" + state: present + +- name: Fetch the Katello CA consumer RPM + ansible.builtin.get_url: + url: >- + {{ system_cfg.content.satellite.ca_url + if (system_cfg.content.satellite.ca_url | length > 0) + else 'https://' ~ system_cfg.content.satellite.host ~ '/pub/katello-ca-consumer-latest.noarch.rpm' }} + dest: /tmp/katello-ca-consumer-latest.noarch.rpm + validate_certs: false + mode: "0644" + +- name: Install the Katello CA consumer RPM + ansible.builtin.dnf: + name: /tmp/katello-ca-consumer-latest.noarch.rpm + state: present + disable_gpg_check: true + +- name: Clean any stale subscription identity + ansible.builtin.command: subscription-manager clean + changed_when: true + +- name: Register with Satellite via activation key + no_log: true + community.general.redhat_subscription: + state: present + server_hostname: "{{ system_cfg.content.satellite.host }}" + org_id: "{{ system_cfg.content.satellite.org }}" + activationkey: "{{ system_cfg.content.satellite.activation_key }}" + environment: "{{ system_cfg.content.satellite.environment | default(omit, true) }}" + auto_attach: true + force_register: true + server_proxy_hostname: "{{ (system_cfg.content.proxy | urlsplit('hostname')) | default(omit, true) }}" + server_proxy_port: "{{ (system_cfg.content.proxy | urlsplit('port')) | default(omit, true) }}" + syspurpose: + service_level_agreement: "{{ system_cfg.content.satellite.service_level | default(omit, true) }}" + sync: true diff --git a/roles/configuration/templates/debian.sources.list.j2 b/roles/configuration/templates/debian.sources.list.j2 index 0de043a..9946810 100644 --- a/roles/configuration/templates/debian.sources.list.j2 +++ b/roles/configuration/templates/debian.sources.list.j2 @@ -1,6 +1,6 @@ # Managed by Ansible. {% set release = _debian_release_map[os_version | string] | default('trixie') %} -{% set mirror = system_cfg.mirror | default('http://deb.debian.org/debian', true) %} +{% set mirror = system_cfg.content.url | default('http://deb.debian.org/debian', true) %} {% set components = 'main contrib non-free non-free-firmware' %} deb {{ mirror }} {{ release }} {{ components }} diff --git a/roles/configuration/templates/el_mirror.repo.j2 b/roles/configuration/templates/el_mirror.repo.j2 new file mode 100644 index 0000000..719e1cf --- /dev/null +++ b/roles/configuration/templates/el_mirror.repo.j2 @@ -0,0 +1,17 @@ +[{{ os }}{{ os_version_major }}-baseos] +name={{ os }} {{ os_version_major }} BaseOS +baseurl={{ system_cfg.content.url }}/BaseOS +enabled=1 +gpgcheck={{ 1 if system_cfg.content.gpgcheck | bool else 0 }} +{% if system_cfg.content.proxy | length > 0 %} +proxy={{ system_cfg.content.proxy }} +{% endif %} + +[{{ os }}{{ os_version_major }}-appstream] +name={{ os }} {{ os_version_major }} AppStream +baseurl={{ system_cfg.content.url }}/AppStream +enabled=1 +gpgcheck={{ 1 if system_cfg.content.gpgcheck | bool else 0 }} +{% if system_cfg.content.proxy | length > 0 %} +proxy={{ system_cfg.content.proxy }} +{% endif %} diff --git a/roles/configuration/templates/ubuntu.sources.list.j2 b/roles/configuration/templates/ubuntu.sources.list.j2 index 795d7c8..d1b2dc2 100644 --- a/roles/configuration/templates/ubuntu.sources.list.j2 +++ b/roles/configuration/templates/ubuntu.sources.list.j2 @@ -1,6 +1,6 @@ # Managed by Ansible. {% set release = _ubuntu_release_map[os] | default('resolute') %} -{% set mirror = system_cfg.mirror %} +{% set mirror = system_cfg.content.url %} {% set components = 'main restricted universe multiverse' %} deb {{ mirror }} {{ release }} {{ components }} diff --git a/roles/environment/templates/rhel.repo.j2 b/roles/environment/templates/rhel.repo.j2 index dee3c63..5bfbbf2 100644 --- a/roles/environment/templates/rhel.repo.j2 +++ b/roles/environment/templates/rhel.repo.j2 @@ -1,13 +1,24 @@ +{% set _baseurl = system_cfg.content.url if system_cfg.content.source == 'mirror' else 'file:///usr/local/install/redhat/dvd' %} [rhel{{ os_version_major }}-baseos] name=RHEL {{ os_version_major }} BaseOS -baseurl=file:///usr/local/install/redhat/dvd/BaseOS +baseurl={{ _baseurl }}/BaseOS enabled=1 gpgcheck=0 +{% if system_cfg.content.source != 'mirror' %} gpgkey=file:///usr/local/install/redhat/dvd/RPM-GPG-KEY-redhat-release +{% endif %} +{% if system_cfg.content.proxy | length > 0 %} +proxy={{ system_cfg.content.proxy }} +{% endif %} [rhel{{ os_version_major }}-appstream] name=RHEL {{ os_version_major }} AppStream -baseurl=file:///usr/local/install/redhat/dvd/AppStream +baseurl={{ _baseurl }}/AppStream enabled=1 gpgcheck=0 +{% if system_cfg.content.source != 'mirror' %} gpgkey=file:///usr/local/install/redhat/dvd/RPM-GPG-KEY-redhat-release +{% endif %} +{% if system_cfg.content.proxy | length > 0 %} +proxy={{ system_cfg.content.proxy }} +{% endif %} diff --git a/roles/global_defaults/defaults/main.yml b/roles/global_defaults/defaults/main.yml index ab98706..96ca246 100644 --- a/roles/global_defaults/defaults/main.yml +++ b/roles/global_defaults/defaults/main.yml @@ -78,7 +78,23 @@ system_defaults: timezone: "Europe/Vienna" locale: "en_US.UTF-8" keymap: "us" - mirror: "" + # Uniform content source, family-resolved. source: dvd|mirror|satellite|none + # ('' -> family default: EL=dvd, debian/ubuntu/arch=mirror). satellite values + # come from inventory/vault only, never committed code. + content: + source: "" + url: "" + proxy: "" + gpgcheck: true + satellite: + host: "" + ip: "" # optional /etc/hosts entry when DNS does not resolve host + org: "" + activation_key: "" + ca_url: "" + service_level: "" + environment: "" + install: false packages: [] disks: [] users: {} @@ -127,9 +143,6 @@ system_defaults: banner: motd: false sudo: true - rhel_repo: - source: "iso" # iso|satellite|none - how RHEL systems get packages post-install - url: "" # Satellite/custom repo URL when source=satellite chroot: tool: "arch-chroot" # arch-chroot|chroot|systemd-nspawn initramfs: diff --git a/roles/global_defaults/tasks/_normalize_system.yml b/roles/global_defaults/tasks/_normalize_system.yml index b1d8fb1..4747223 100644 --- a/roles/global_defaults/tasks/_normalize_system.yml +++ b/roles/global_defaults/tasks/_normalize_system.yml @@ -91,12 +91,27 @@ timezone: "{{ system_raw.timezone | string }}" locale: "{{ system_raw.locale | string }}" keymap: "{{ system_raw.keymap | string }}" - mirror: >- - {{ - system_raw.mirror | string | trim - if (system_raw.mirror | default('') | string | trim | length) > 0 - else _mirror_defaults[system_raw.os | default('') | string | lower] | default('') - }} + content: + source: >- + {%- set s = system_raw.content.source | default('') | string | lower | trim -%} + {%- if s | length > 0 -%}{{ s }} + {%- 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 }}" + gpgcheck: "{{ system_raw.content.gpgcheck | default(true) | bool }}" + satellite: + host: "{{ system_raw.content.satellite.host | default('') | string | trim }}" + ip: "{{ system_raw.content.satellite.ip | default('') | string | trim }}" + org: "{{ system_raw.content.satellite.org | default('') | string }}" + activation_key: "{{ system_raw.content.satellite.activation_key | default('') | string }}" + ca_url: "{{ system_raw.content.satellite.ca_url | default('') | string | trim }}" + service_level: "{{ system_raw.content.satellite.service_level | default('') | string }}" + environment: "{{ system_raw.content.satellite.environment | default('') | string }}" + install: "{{ system_raw.content.satellite.install | default(false) | bool }}" path: >- {{ (system_raw.path | default('') | string) @@ -161,9 +176,6 @@ banner: motd: "{{ system_raw.features.banner.motd | bool }}" sudo: "{{ system_raw.features.banner.sudo | bool }}" - rhel_repo: - source: "{{ system_raw.features.rhel_repo.source | default('iso') | string | lower }}" - url: "{{ system_raw.features.rhel_repo.url | default('') | string }}" chroot: tool: "{{ system_raw.features.chroot.tool | string }}" initramfs: diff --git a/roles/global_defaults/tasks/system.yml b/roles/global_defaults/tasks/system.yml index 4d54f84..dd59e16 100644 --- a/roles/global_defaults/tasks/system.yml +++ b/roles/global_defaults/tasks/system.yml @@ -50,23 +50,28 @@ ansible.builtin.set_fact: system_cfg: "{{ system_defaults | combine(system | default({}), recursive=True) | combine(system_cfg, recursive=True) }}" -- name: Apply mirror default for pre-computed system_cfg +- name: Apply content-source family defaults for pre-computed system_cfg when: - system_cfg is defined - _bootstrap_needs_enrichment | default(false) | bool - - system_cfg.mirror | default('') | string | trim | length == 0 vars: - # Same as _normalize_system.yml - kept in sync manually. + # 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({ - 'mirror': _mirror_defaults[system_cfg.os | default('') | string | lower] | default('') - }, recursive=True) + 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('')), + }}, recursive=True) }} - name: Populate primary network fields from first interface (pre-computed) diff --git a/roles/global_defaults/tasks/validation.yml b/roles/global_defaults/tasks/validation.yml index 6557520..d814d93 100644 --- a/roles/global_defaults/tasks/validation.yml +++ b/roles/global_defaults/tasks/validation.yml @@ -148,8 +148,8 @@ - name: Validate RHEL ISO requirement ansible.builtin.assert: that: - - os != "rhel" or (rhel_iso is defined and (rhel_iso | string | length) > 0) - fail_msg: "rhel_iso is required when os=rhel." + - os != "rhel" or system_cfg.content.source == "mirror" or (rhel_iso is defined and (rhel_iso | string | length) > 0) + fail_msg: "rhel_iso is required when os=rhel unless content.source is mirror." quiet: true - name: Validate hypervisor-specific required fields diff --git a/tests/content/_assert_content.yml b/tests/content/_assert_content.yml new file mode 100644 index 0000000..2812552 --- /dev/null +++ b/tests/content/_assert_content.yml @@ -0,0 +1,40 @@ +--- +- name: "Seed raw system input (content {{ cf.name }})" + ansible.builtin.set_fact: + system: "{{ cf.system }}" + +- name: "Normalize system (content {{ cf.name }})" + ansible.builtin.include_tasks: ../../roles/global_defaults/tasks/_normalize_system.yml + +- name: "Apply family defaults (content {{ cf.name }})" + ansible.builtin.include_tasks: ../../roles/global_defaults/tasks/_apply_family_defaults.yml + +- name: "Assert computed content source and url (content {{ cf.name }})" + ansible.builtin.assert: + that: + - system_cfg.content.source == cf.expect.source + - system_cfg.content.url == cf.expect.url + fail_msg: "[content {{ cf.name }}] FAIL got {{ system_cfg.content }}" + success_msg: "[content {{ cf.name }}] OK {{ system_cfg.content.source }} {{ system_cfg.content.url }}" + +- name: "Assert satellite register inputs (content {{ cf.name }})" + when: cf.expect.satellite is defined + ansible.builtin.assert: + that: + - system_cfg.content.satellite.host == cf.expect.satellite.host + - system_cfg.content.satellite.org == cf.expect.satellite.org + - system_cfg.content.satellite.activation_key == cf.expect.satellite.activation_key + fail_msg: "[content {{ cf.name }}] satellite FAIL got {{ system_cfg.content.satellite }}" + +- name: "Assert Arch reflector lock-out (content {{ cf.name }})" + when: cf.expect_reflector is defined + block: + - name: "Load Arch package set (content {{ cf.name }})" + ansible.builtin.include_vars: + file: ../../roles/bootstrap/vars/main.yml + + - name: "Assert reflector membership (content {{ cf.name }})" + ansible.builtin.assert: + that: + - ('reflector' in (bootstrap_archlinux.base + bootstrap_archlinux.conditional)) == (cf.expect_reflector == 'present') + fail_msg: "[content {{ cf.name }}] reflector expected {{ cf.expect_reflector }}, url='{{ system_cfg.content.url }}'" diff --git a/tests/content/content_fixtures.yml b/tests/content/content_fixtures.yml new file mode 100644 index 0000000..0c85be7 --- /dev/null +++ b/tests/content/content_fixtures.yml @@ -0,0 +1,78 @@ +--- +# Raw system.content input -> expected normalized system_cfg.content. Covers family +# source defaults (EL=dvd, else mirror), url pass-through, satellite inputs, and the +# Arch reflector lock-out (dropped only when a custom mirror url is set). +content_fixtures: + - name: arch-mirror-url-drops-reflector + system: + os: archlinux + content: { url: "https://mirror.internal/archlinux" } + expect: + source: mirror + url: "https://mirror.internal/archlinux" + expect_reflector: absent + + - name: arch-no-url-keeps-public-and-reflector + system: + os: archlinux + content: {} + expect: + source: mirror + url: "" + expect_reflector: present + + - name: el-mirror-url + system: + os: almalinux + version: "10" + content: { source: mirror, url: "https://mirror.internal/almalinux" } + expect: + source: mirror + url: "https://mirror.internal/almalinux" + + - name: el-default-dvd + system: + os: rhel + version: "10" + content: {} + expect: + source: dvd + url: "" + + - name: el-satellite-register + system: + os: almalinux + version: "10" + content: + source: satellite + satellite: { host: "sat.internal", org: "SG", activation_key: "ak-el10" } + expect: + source: satellite + url: "" + satellite: { host: "sat.internal", org: "SG", activation_key: "ak-el10" } + + - name: debian-default-mirror + system: + os: debian + version: "13" + content: {} + expect: + source: mirror + url: "https://deb.debian.org/debian/" + + - name: debian-custom-mirror-lands + system: + os: debian + version: "13" + content: { source: mirror, url: "https://mirror.internal/debian" } + expect: + source: mirror + url: "https://mirror.internal/debian" + + - name: ubuntu-default-mirror + system: + os: ubuntu + content: {} + expect: + source: mirror + url: "http://archive.ubuntu.com/ubuntu/" diff --git a/tests/content/test_content.yml b/tests/content/test_content.yml new file mode 100644 index 0000000..9afb64d --- /dev/null +++ b/tests/content/test_content.yml @@ -0,0 +1,22 @@ +--- +# Run: ansible-playbook tests/content/test_content.yml +- name: Content-source fixture tests + hosts: localhost + gather_facts: false + connection: local + vars: + hypervisor_type: libvirt + hypervisor_cfg: { folder: "" } + vars_files: + - content_fixtures.yml + tasks: + - name: Load global defaults (system_defaults, content_mirror_defaults, os maps) + ansible.builtin.include_vars: + file: ../../roles/global_defaults/defaults/main.yml + + - name: Run each content fixture + ansible.builtin.include_tasks: _assert_content.yml + loop: "{{ content_fixtures }}" + loop_control: + loop_var: cf + label: "{{ cf.name }}"