Compare commits
11 Commits
4fff9f8d80
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 9c4a86c0ec | |||
| 723f0516da | |||
| 975384661e | |||
| ad25bec9fd | |||
| 6240217318 | |||
| 1c00ec9370 | |||
| 371d5a0acd | |||
| 41ccf2a5b9 | |||
| 8f9cfe3b2f | |||
| 6a75237197 | |||
| b04aad12fb |
@@ -183,7 +183,7 @@ Top-level variables outside `system`/`hypervisor`.
|
|||||||
|
|
||||||
#### `system.content`
|
#### `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.
|
Uniform package content source, family-resolved. `source: ''` defaults to `dvd` on `rhel` and to `mirror` everywhere else, AlmaLinux, Rocky and Fedora included: they have public mirrors, RHEL does not. Satellite values come from inventory/vault only, never committed code.
|
||||||
|
|
||||||
| Key | Type | Default | Description |
|
| Key | Type | Default | Description |
|
||||||
| -------------------------- | ------ | -------------- | ----------------------------------------------------------------- |
|
| -------------------------- | ------ | -------------- | ----------------------------------------------------------------- |
|
||||||
|
|||||||
@@ -3,3 +3,5 @@
|
|||||||
cleanup_verify_boot: true
|
cleanup_verify_boot: true
|
||||||
cleanup_boot_timeout: 300
|
cleanup_boot_timeout: 300
|
||||||
cleanup_remove_on_failure: true
|
cleanup_remove_on_failure: true
|
||||||
|
cleanup_poweroff_retries: 24
|
||||||
|
cleanup_poweroff_delay: 5
|
||||||
|
|||||||
@@ -6,7 +6,30 @@
|
|||||||
module_defaults:
|
module_defaults:
|
||||||
community.proxmox.proxmox_disk: "{{ _proxmox_auth }}"
|
community.proxmox.proxmox_disk: "{{ _proxmox_auth }}"
|
||||||
community.proxmox.proxmox_kvm: "{{ _proxmox_auth_node }}"
|
community.proxmox.proxmox_kvm: "{{ _proxmox_auth_node }}"
|
||||||
|
community.proxmox.proxmox_vm_info: "{{ _proxmox_auth_node }}"
|
||||||
block:
|
block:
|
||||||
|
- name: Wait for the installer environment to power off
|
||||||
|
community.proxmox.proxmox_vm_info:
|
||||||
|
vmid: "{{ system_cfg.id }}"
|
||||||
|
register: cleanup_proxmox_power
|
||||||
|
until: >-
|
||||||
|
((cleanup_proxmox_power.proxmox_vms | default([]) | first | default({})).status
|
||||||
|
| default('')) == 'stopped'
|
||||||
|
retries: "{{ cleanup_poweroff_retries }}"
|
||||||
|
delay: "{{ cleanup_poweroff_delay }}"
|
||||||
|
failed_when: false
|
||||||
|
no_log: true
|
||||||
|
|
||||||
|
- name: Force the installer environment off when it did not shut down
|
||||||
|
when: >-
|
||||||
|
((cleanup_proxmox_power.proxmox_vms | default([]) | first | default({})).status
|
||||||
|
| default('')) != 'stopped'
|
||||||
|
community.proxmox.proxmox_kvm:
|
||||||
|
vmid: "{{ system_cfg.id }}"
|
||||||
|
state: stopped
|
||||||
|
force: true
|
||||||
|
no_log: true
|
||||||
|
|
||||||
- name: Cleanup Setup Disks
|
- name: Cleanup Setup Disks
|
||||||
community.proxmox.proxmox_disk:
|
community.proxmox.proxmox_disk:
|
||||||
name: "{{ hostname }}"
|
name: "{{ hostname }}"
|
||||||
@@ -21,8 +44,8 @@
|
|||||||
failed_when: false
|
failed_when: false
|
||||||
no_log: true
|
no_log: true
|
||||||
|
|
||||||
- name: Start the VM
|
- name: Boot the installed OS
|
||||||
community.proxmox.proxmox_kvm:
|
community.proxmox.proxmox_kvm:
|
||||||
vmid: "{{ system_cfg.id }}"
|
vmid: "{{ system_cfg.id }}"
|
||||||
state: restarted
|
state: started
|
||||||
no_log: true
|
no_log: true
|
||||||
|
|||||||
@@ -5,9 +5,26 @@
|
|||||||
become: false
|
become: false
|
||||||
module_defaults:
|
module_defaults:
|
||||||
community.vmware.vmware_guest: "{{ _vmware_auth }}"
|
community.vmware.vmware_guest: "{{ _vmware_auth }}"
|
||||||
|
community.vmware.vmware_guest_info: "{{ _vmware_auth }}"
|
||||||
vmware.vmware.vm_powerstate: "{{ _vmware_auth }}"
|
vmware.vmware.vm_powerstate: "{{ _vmware_auth }}"
|
||||||
no_log: true
|
no_log: true
|
||||||
block:
|
block:
|
||||||
|
- name: Wait for the installer environment to power off
|
||||||
|
community.vmware.vmware_guest_info:
|
||||||
|
name: "{{ hostname }}"
|
||||||
|
register: cleanup_vmware_power
|
||||||
|
until: (cleanup_vmware_power.instance.hw_power_status | default('') | lower) == 'poweredoff'
|
||||||
|
retries: "{{ cleanup_poweroff_retries }}"
|
||||||
|
delay: "{{ cleanup_poweroff_delay }}"
|
||||||
|
failed_when: false
|
||||||
|
|
||||||
|
- name: Force the installer environment off when it did not shut down
|
||||||
|
when: (cleanup_vmware_power.instance.hw_power_status | default('') | lower) != 'poweredoff'
|
||||||
|
vmware.vmware.vm_powerstate:
|
||||||
|
name: "{{ hostname }}"
|
||||||
|
state: powered-off
|
||||||
|
force: true
|
||||||
|
|
||||||
- name: Remove CD-ROM from VM in vCenter
|
- name: Remove CD-ROM from VM in vCenter
|
||||||
community.vmware.vmware_guest:
|
community.vmware.vmware_guest:
|
||||||
name: "{{ hostname }}"
|
name: "{{ hostname }}"
|
||||||
@@ -41,7 +58,7 @@
|
|||||||
}}
|
}}
|
||||||
failed_when: false
|
failed_when: false
|
||||||
|
|
||||||
- name: Start VM in vCenter
|
- name: Boot the installed OS
|
||||||
vmware.vmware.vm_powerstate:
|
vmware.vmware.vm_powerstate:
|
||||||
name: "{{ hostname }}"
|
name: "{{ hostname }}"
|
||||||
state: powered-on
|
state: powered-on
|
||||||
|
|||||||
@@ -37,7 +37,6 @@
|
|||||||
org_id: "{{ system_cfg.content.satellite.org }}"
|
org_id: "{{ system_cfg.content.satellite.org }}"
|
||||||
activationkey: "{{ system_cfg.content.satellite.activation_key }}"
|
activationkey: "{{ system_cfg.content.satellite.activation_key }}"
|
||||||
environment: "{{ system_cfg.content.satellite.environment | default(omit, true) }}"
|
environment: "{{ system_cfg.content.satellite.environment | default(omit, true) }}"
|
||||||
auto_attach: true
|
|
||||||
force_register: true
|
force_register: true
|
||||||
server_proxy_hostname: "{{ (system_cfg.content.proxy | urlsplit('hostname')) | default(omit, 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) }}"
|
server_proxy_port: "{{ (system_cfg.content.proxy | urlsplit('port')) | default(omit, true) }}"
|
||||||
|
|||||||
@@ -54,6 +54,18 @@
|
|||||||
and 'No such file or directory' not in (configuration_enable_service_result.stderr | default(''))
|
and 'No such file or directory' not in (configuration_enable_service_result.stderr | default(''))
|
||||||
and 'does not exist' not in (configuration_enable_service_result.stderr | default(''))
|
and 'does not exist' not in (configuration_enable_service_result.stderr | default(''))
|
||||||
|
|
||||||
|
- name: Check for the EL qemu-guest-agent RPC allow-list
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: /mnt/etc/sysconfig/qemu-ga
|
||||||
|
register: configuration_qga_sysconfig
|
||||||
|
|
||||||
|
- name: Allow clone-stamping RPCs in the EL qemu-guest-agent allow-list
|
||||||
|
when: configuration_qga_sysconfig.stat.exists
|
||||||
|
ansible.builtin.replace:
|
||||||
|
path: /mnt/etc/sysconfig/qemu-ga
|
||||||
|
regexp: '^(FILTER_RPC_ARGS="--allow-rpcs=(?:(?!guest-exec)[^"])*)"'
|
||||||
|
replace: '\1,guest-exec,guest-exec-status,guest-file-open,guest-file-close,guest-file-read,guest-file-write"'
|
||||||
|
|
||||||
- name: Enable display manager for selected desktop
|
- name: Enable display manager for selected desktop
|
||||||
when:
|
when:
|
||||||
- _configuration_platform.init_system == 'systemd'
|
- _configuration_platform.init_system == 'systemd'
|
||||||
|
|||||||
@@ -15,8 +15,7 @@
|
|||||||
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
|
||||||
# Jinja truthiness: bool true / a rule string => deploy; false / '' / unset => skip.
|
when: (item.value.sudo | default(false)) not in [false, '', none]
|
||||||
when: item.value.sudo | default(false)
|
|
||||||
vars:
|
vars:
|
||||||
configuration_sudoers_rule: >-
|
configuration_sudoers_rule: >-
|
||||||
{{ item.value.sudo if item.value.sudo is string else 'ALL=(ALL) NOPASSWD: ALL' }}
|
{{ item.value.sudo if item.value.sudo is string else 'ALL=(ALL) NOPASSWD: ALL' }}
|
||||||
|
|||||||
@@ -26,17 +26,22 @@
|
|||||||
|
|
||||||
- name: Create user accounts
|
- name: Create user accounts
|
||||||
vars:
|
vars:
|
||||||
configuration_user_group: "{{ _configuration_platform.user_group }}"
|
|
||||||
# plaintext is hashed; a pre-computed crypt hash ($6$/$y$/...) passes through.
|
# plaintext is hashed; a pre-computed crypt hash ($6$/$y$/...) passes through.
|
||||||
configuration_user_pw: >-
|
configuration_user_pw: >-
|
||||||
{{ item.value.password if (item.value.password | string)[:1] == '$'
|
{{ item.value.password if (item.value.password | string)[:1] == '$'
|
||||||
else item.value.password | password_hash('sha512') }}
|
else item.value.password | password_hash('sha512') }}
|
||||||
|
configuration_user_groups: >-
|
||||||
|
{{ item.value.groups | default(
|
||||||
|
[_configuration_platform.user_group]
|
||||||
|
if (item.value.sudo | default(false)) not in [false, '', none]
|
||||||
|
else []) }}
|
||||||
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 + _idx }}
|
--uid {{ 1000 + _idx }}
|
||||||
--groups {{ configuration_user_group }} {{ item.key }}
|
{{ ('--groups ' ~ (configuration_user_groups | join(','))) if (configuration_user_groups | length) > 0 else '' }}
|
||||||
{{ ('--password ' ~ configuration_user_pw) if (item.value.password | default('') | string | length > 0) else '' }}
|
{{ ('--password ' ~ configuration_user_pw) if (item.value.password | default('') | string | length > 0) else '' }}
|
||||||
--shell {{ item.value.shell | default('/bin/bash') }}
|
--shell {{ item.value.shell | default('/bin/bash') }}
|
||||||
|
{{ item.key }}
|
||||||
ansible.builtin.command: "{{ configuration_useradd_cmd }}"
|
ansible.builtin.command: "{{ configuration_useradd_cmd }}"
|
||||||
loop: "{{ system_cfg.users | dict2items }}"
|
loop: "{{ system_cfg.users | dict2items }}"
|
||||||
loop_control:
|
loop_control:
|
||||||
|
|||||||
@@ -73,28 +73,25 @@
|
|||||||
mode: "0755"
|
mode: "0755"
|
||||||
|
|
||||||
- name: Detect RHEL ISO device
|
- name: Detect RHEL ISO device
|
||||||
ansible.builtin.command: lsblk -rno NAME,TYPE
|
ansible.builtin.command: lsblk -rbno NAME,TYPE,SIZE
|
||||||
register: environment_lsblk_result
|
register: environment_lsblk_result
|
||||||
changed_when: false
|
changed_when: false
|
||||||
|
|
||||||
- name: Select RHEL ISO device
|
- name: Select RHEL ISO device
|
||||||
vars:
|
vars:
|
||||||
_rom_devices: >-
|
_roms: >-
|
||||||
{{
|
{%- set out = [] -%}
|
||||||
environment_lsblk_result.stdout_lines
|
{%- for line in environment_lsblk_result.stdout_lines -%}
|
||||||
| map('split', ' ')
|
{%- set p = line.split() -%}
|
||||||
| selectattr('1', 'equalto', 'rom')
|
{%- if (p | length) >= 3 and p[1] == 'rom' -%}
|
||||||
| map('first')
|
{%- set _ = out.append({'name': p[0], 'size': p[2] | int}) -%}
|
||||||
| map('regex_replace', '^', '/dev/')
|
{%- endif -%}
|
||||||
| list
|
{%- endfor -%}
|
||||||
}}
|
{{ out }}
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
environment_rhel_iso_device: >-
|
environment_rhel_iso_device: >-
|
||||||
{{
|
{{ ('/dev/' ~ (_roms | sort(attribute='size') | last).name)
|
||||||
_rom_devices[-1]
|
if (_roms | length) > 0 else '/dev/sr1' }}
|
||||||
if _rom_devices | length > 1
|
|
||||||
else (_rom_devices[0] | default('/dev/sr1'))
|
|
||||||
}}
|
|
||||||
|
|
||||||
- name: Mount RHEL ISO
|
- name: Mount RHEL ISO
|
||||||
ansible.posix.mount:
|
ansible.posix.mount:
|
||||||
|
|||||||
@@ -78,7 +78,8 @@ system_defaults:
|
|||||||
timezone: "Europe/Vienna"
|
timezone: "Europe/Vienna"
|
||||||
locale: "en_US.UTF-8"
|
locale: "en_US.UTF-8"
|
||||||
keymap: "us"
|
keymap: "us"
|
||||||
# source: dvd|mirror|satellite|none ('' -> family default: EL=dvd, else mirror).
|
# source: dvd|mirror|satellite|none ('' -> family default: rhel=dvd, else mirror;
|
||||||
|
# almalinux/rocky/fedora have public mirrors, rhel does not).
|
||||||
# satellite values come from inventory/vault only, never committed code.
|
# satellite values come from inventory/vault only, never committed code.
|
||||||
content:
|
content:
|
||||||
source: ""
|
source: ""
|
||||||
|
|||||||
243
roles/system_check/tasks/_placement.yml
Normal file
243
roles/system_check/tasks/_placement.yml
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
---
|
||||||
|
- name: Collect the boot media this run attaches
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
system_check_media: >-
|
||||||
|
{{ [{'name': 'boot_iso', 'value': boot_iso}]
|
||||||
|
+ ([{'name': 'rhel_iso', 'value': rhel_iso}]
|
||||||
|
if (rhel_iso | default('') | string | length > 0) else []) }}
|
||||||
|
|
||||||
|
- name: Verify vCenter boot media and placement target
|
||||||
|
when: hypervisor_type == "vmware"
|
||||||
|
delegate_to: localhost
|
||||||
|
become: false
|
||||||
|
module_defaults:
|
||||||
|
group/community.vmware.vmware:
|
||||||
|
hostname: "{{ hypervisor_cfg.url }}"
|
||||||
|
username: "{{ hypervisor_cfg.username }}"
|
||||||
|
password: "{{ hypervisor_cfg.password }}"
|
||||||
|
validate_certs: "{{ hypervisor_cfg.certs | bool }}"
|
||||||
|
block:
|
||||||
|
- name: Assert the boot media is a datastore path
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- item.value is match('^\\[[^\\]]+\\]\\s*\\S')
|
||||||
|
fail_msg: |
|
||||||
|
{{ item.name }} is "{{ item.value }}", which is not a vSphere datastore path.
|
||||||
|
Set {{ item.name }} to the form "[datastore] folder/file.iso",
|
||||||
|
for example "[ssdmsa_lun1] ISO/SGBoot-2026.06.09-x86_64.iso".
|
||||||
|
quiet: true
|
||||||
|
loop: "{{ system_check_media }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.name }}"
|
||||||
|
|
||||||
|
- name: Resolve the datastores this run depends on
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
system_check_datastores_needed: >-
|
||||||
|
{%- set ns = namespace(refs=[{'source': 'hypervisor.storage',
|
||||||
|
'datastore': hypervisor_cfg.storage, 'path': ''}]) -%}
|
||||||
|
{%- for media in system_check_media -%}
|
||||||
|
{%- set ns.refs = ns.refs + [{
|
||||||
|
'source': media.name,
|
||||||
|
'datastore': media.value | regex_replace('^\\[([^\\]]+)\\].*$', '\\1'),
|
||||||
|
'path': media.value | regex_replace('^\\[[^\\]]+\\]\\s*', '')
|
||||||
|
}] -%}
|
||||||
|
{%- endfor -%}
|
||||||
|
{{ ns.refs }}
|
||||||
|
|
||||||
|
- name: Query the ESXi placement host
|
||||||
|
when: hypervisor_cfg.node | default('') | length > 0
|
||||||
|
community.vmware.vmware_host_facts:
|
||||||
|
esxi_hostname: "{{ hypervisor_cfg.node }}"
|
||||||
|
register: system_check_esxi
|
||||||
|
delegate_facts: true
|
||||||
|
failed_when: false
|
||||||
|
changed_when: false
|
||||||
|
no_log: true
|
||||||
|
|
||||||
|
- name: Assert the ESXi placement host is readable
|
||||||
|
when: hypervisor_cfg.node | default('') | length > 0
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- system_check_esxi.ansible_facts is defined
|
||||||
|
fail_msg: |
|
||||||
|
ESXi host {{ hypervisor_cfg.node }} (hypervisor.node) could not be read from vCenter {{ hypervisor_cfg.url }}.
|
||||||
|
{{ system_check_esxi.msg | default('Unknown error') }}
|
||||||
|
Set hypervisor.node to an ESXi host in datacenter {{ hypervisor_cfg.datacenter }},
|
||||||
|
or clear hypervisor.node and set hypervisor.cluster instead.
|
||||||
|
quiet: true
|
||||||
|
|
||||||
|
- name: Assert the ESXi placement host can take a new VM
|
||||||
|
when: hypervisor_cfg.node | default('') | length > 0
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- system_check_esxi.ansible_facts.ansible_host_connection_state == "connected"
|
||||||
|
- not (system_check_esxi.ansible_facts.ansible_in_maintenance_mode | bool)
|
||||||
|
fail_msg: |
|
||||||
|
ESXi host {{ hypervisor_cfg.node }} (hypervisor.node) cannot take a new VM:
|
||||||
|
connection state is {{ system_check_esxi.ansible_facts.ansible_host_connection_state }},
|
||||||
|
maintenance mode is {{ system_check_esxi.ansible_facts.ansible_in_maintenance_mode }}.
|
||||||
|
Wait for the host to come back, or set hypervisor.node to another host.
|
||||||
|
quiet: true
|
||||||
|
|
||||||
|
- name: Query the datastores of the placement cluster
|
||||||
|
when: hypervisor_cfg.node | default('') | length == 0
|
||||||
|
community.vmware.vmware_datastore_info:
|
||||||
|
cluster: "{{ hypervisor_cfg.cluster }}"
|
||||||
|
register: system_check_cluster
|
||||||
|
failed_when: false
|
||||||
|
changed_when: false
|
||||||
|
no_log: true
|
||||||
|
|
||||||
|
- name: Assert the placement cluster is readable
|
||||||
|
when: hypervisor_cfg.node | default('') | length == 0
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- system_check_cluster.datastores is defined
|
||||||
|
fail_msg: |
|
||||||
|
Cluster {{ hypervisor_cfg.cluster }} (hypervisor.cluster) could not be read from vCenter {{ hypervisor_cfg.url }}.
|
||||||
|
{{ system_check_cluster.msg | default('Unknown error') }}
|
||||||
|
Set hypervisor.cluster to a cluster in datacenter {{ hypervisor_cfg.datacenter }},
|
||||||
|
or clear hypervisor.cluster and pin hypervisor.node instead.
|
||||||
|
quiet: true
|
||||||
|
|
||||||
|
- name: Resolve the datastores available at the placement target
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
system_check_target: >-
|
||||||
|
{{ ('ESXi host ' ~ hypervisor_cfg.node ~ ' (hypervisor.node)')
|
||||||
|
if (hypervisor_cfg.node | default('') | length > 0)
|
||||||
|
else ('cluster ' ~ hypervisor_cfg.cluster ~ ' (hypervisor.cluster)') }}
|
||||||
|
system_check_datastores_available: >-
|
||||||
|
{{ (system_check_esxi.ansible_facts.ansible_datastore | map(attribute='name') | list)
|
||||||
|
if (hypervisor_cfg.node | default('') | length > 0)
|
||||||
|
else (system_check_cluster.datastores | map(attribute='name') | list) }}
|
||||||
|
|
||||||
|
- name: Assert every datastore this run needs is available at the placement target
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- item.datastore in system_check_datastores_available
|
||||||
|
fail_msg: |
|
||||||
|
Datastore {{ item.datastore }} (from {{ item.source }}) is not available on {{ system_check_target }}.
|
||||||
|
Datastores available there: {{ system_check_datastores_available | join(', ') }}.
|
||||||
|
Point {{ item.source }} at one of those datastores, or move placement to a host or cluster
|
||||||
|
that mounts {{ item.datastore }}.
|
||||||
|
quiet: true
|
||||||
|
loop: "{{ system_check_datastores_needed }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.source }}: {{ item.datastore }}"
|
||||||
|
|
||||||
|
- name: Query the boot media on its datastore
|
||||||
|
community.vmware.vsphere_file:
|
||||||
|
datacenter: "{{ hypervisor_cfg.datacenter }}"
|
||||||
|
datastore: "{{ item.datastore }}"
|
||||||
|
path: "{{ item.path }}"
|
||||||
|
state: file
|
||||||
|
register: system_check_media_files
|
||||||
|
failed_when: false
|
||||||
|
changed_when: false
|
||||||
|
loop: "{{ system_check_datastores_needed | rejectattr('source', 'equalto', 'hypervisor.storage') | list }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.source }}"
|
||||||
|
|
||||||
|
- name: Assert the boot media exists on its datastore
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- (item.status | default(0)) != 404
|
||||||
|
fail_msg: |
|
||||||
|
{{ item.item.source }} points at "[{{ item.item.datastore }}] {{ item.item.path }}",
|
||||||
|
which does not exist on datastore {{ item.item.datastore }}.
|
||||||
|
Set {{ item.item.source }} to a file that exists there, normally the current SGBoot ISO.
|
||||||
|
quiet: true
|
||||||
|
loop: "{{ system_check_media_files.results }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.item.source }}"
|
||||||
|
|
||||||
|
- name: Verify Proxmox boot media
|
||||||
|
when: hypervisor_type == "proxmox"
|
||||||
|
delegate_to: localhost
|
||||||
|
become: false
|
||||||
|
module_defaults:
|
||||||
|
community.proxmox.proxmox_storage_contents_info:
|
||||||
|
api_host: "{{ hypervisor_cfg.url }}"
|
||||||
|
api_user: "{{ hypervisor_cfg.username }}"
|
||||||
|
api_password: "{{ hypervisor_cfg.password | default(omit, true) }}"
|
||||||
|
api_token_id: "{{ hypervisor_cfg.token_id | default(omit, true) }}"
|
||||||
|
api_token_secret: "{{ hypervisor_cfg.token_secret | default(omit, true) }}"
|
||||||
|
block:
|
||||||
|
- name: Assert the boot media is a storage volume id
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- item.value is match('^[^:]+:iso/\\S')
|
||||||
|
fail_msg: |
|
||||||
|
{{ item.name }} is "{{ item.value }}", which is not a Proxmox volume id.
|
||||||
|
Set {{ item.name }} to the form "<storage>:iso/<file>.iso",
|
||||||
|
for example "local:iso/SGBoot-2026.06.09-x86_64.iso".
|
||||||
|
quiet: true
|
||||||
|
loop: "{{ system_check_media }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.name }}"
|
||||||
|
|
||||||
|
- name: Query the ISO volumes on the Proxmox node
|
||||||
|
community.proxmox.proxmox_storage_contents_info:
|
||||||
|
node: "{{ hypervisor_cfg.node }}"
|
||||||
|
storage: "{{ item.value | regex_replace('^([^:]+):.*$', '\\1') }}"
|
||||||
|
content: iso
|
||||||
|
register: system_check_proxmox_media
|
||||||
|
failed_when: false
|
||||||
|
changed_when: false
|
||||||
|
loop: "{{ system_check_media }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.name }}"
|
||||||
|
|
||||||
|
- name: Assert the boot media storage serves ISOs on the Proxmox node
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- item.proxmox_storage_content is defined
|
||||||
|
fail_msg: |
|
||||||
|
Storage {{ item.item.value | regex_replace('^([^:]+):.*$', '\\1') }} (from {{ item.item.name }})
|
||||||
|
does not serve ISO content on Proxmox node {{ hypervisor_cfg.node }}.
|
||||||
|
{{ item.msg | default('Unknown error') }}
|
||||||
|
Point {{ item.item.name }} at a storage that is enabled for ISO images on that node,
|
||||||
|
or change hypervisor.node.
|
||||||
|
quiet: true
|
||||||
|
loop: "{{ system_check_proxmox_media.results }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.item.name }}"
|
||||||
|
|
||||||
|
- name: Assert the boot media exists on the Proxmox node
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- item.item.value in (item.proxmox_storage_content | map(attribute='volid') | list)
|
||||||
|
fail_msg: |
|
||||||
|
{{ item.item.name }} points at "{{ item.item.value }}", which does not exist on Proxmox node
|
||||||
|
{{ hypervisor_cfg.node }}.
|
||||||
|
ISO volumes on that storage: {{ item.proxmox_storage_content | map(attribute='volid') | join(', ') }}.
|
||||||
|
Set {{ item.item.name }} to one of those volumes.
|
||||||
|
quiet: true
|
||||||
|
loop: "{{ system_check_proxmox_media.results }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.item.name }}"
|
||||||
|
|
||||||
|
- name: Verify libvirt boot media
|
||||||
|
when: hypervisor_type == "libvirt"
|
||||||
|
delegate_to: localhost
|
||||||
|
become: false
|
||||||
|
block:
|
||||||
|
- name: Stat the libvirt boot media
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "{{ item.value }}"
|
||||||
|
register: system_check_libvirt_media
|
||||||
|
loop: "{{ system_check_media }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.name }}"
|
||||||
|
|
||||||
|
- name: Assert the libvirt boot media exists
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- item.stat.exists
|
||||||
|
fail_msg: |
|
||||||
|
{{ item.item.name }} points at "{{ item.item.value }}", which does not exist on the libvirt host.
|
||||||
|
Set {{ item.item.name }} to the path of an ISO file that exists there.
|
||||||
|
quiet: true
|
||||||
|
loop: "{{ system_check_libvirt_media.results }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.item.name }}"
|
||||||
@@ -132,3 +132,7 @@
|
|||||||
To avoid data loss, the playbook will not overwrite or delete existing VMs.
|
To avoid data loss, the playbook will not overwrite or delete existing VMs.
|
||||||
Please choose a different hostname or remove the existing VM manually before proceeding.
|
Please choose a different hostname or remove the existing VM manually before proceeding.
|
||||||
quiet: true
|
quiet: true
|
||||||
|
|
||||||
|
- name: Verify boot media and placement target
|
||||||
|
when: system_cfg.type == "virtual"
|
||||||
|
ansible.builtin.include_tasks: _placement.yml
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
# Raw system.content input -> expected normalized system_cfg.content. Covers family
|
# Raw system.content input -> expected normalized system_cfg.content. Covers family
|
||||||
# source defaults (EL=dvd, else mirror), url pass-through, satellite inputs, and the
|
# source defaults (rhel=dvd, else mirror), url pass-through, satellite inputs, and the
|
||||||
# Arch reflector lock-out (dropped only when a custom mirror url is set).
|
# Arch reflector lock-out (dropped only when a custom mirror url is set).
|
||||||
content_fixtures:
|
content_fixtures:
|
||||||
- name: arch-mirror-url-drops-reflector
|
- name: arch-mirror-url-drops-reflector
|
||||||
|
|||||||
Reference in New Issue
Block a user