diff --git a/inventory_example.yml b/inventory_example.yml
index 02aadbf..533a966 100644
--- a/inventory_example.yml
+++ b/inventory_example.yml
@@ -45,7 +45,8 @@ all:
user:
name: "ops"
password: "CHANGE_ME"
- key: "ssh-ed25519 AAAA..."
+ keys:
+ - "ssh-ed25519 AAAA..."
root:
password: "CHANGE_ME"
packages:
@@ -89,7 +90,9 @@ all:
prefix: 24
gateway: 10.0.0.1
dns:
- servers: "1.1.1.1,1.0.0.1"
+ servers:
+ - "1.1.1.1"
+ - "1.0.0.1"
disks:
- size: 80
- size: 200
@@ -99,7 +102,8 @@ all:
user:
name: "dbadmin"
password: "CHANGE_ME"
- key: "ssh-ed25519 AAAA..."
+ keys:
+ - "ssh-ed25519 AAAA..."
root:
password: "CHANGE_ME"
luks:
diff --git a/inventory_libvirt_example.yml b/inventory_libvirt_example.yml
index ee4764e..08c03c7 100644
--- a/inventory_libvirt_example.yml
+++ b/inventory_libvirt_example.yml
@@ -42,7 +42,8 @@ all:
user:
name: "web"
password: "CHANGE_ME"
- key: "ssh-ed25519 AAAA..."
+ keys:
+ - "ssh-ed25519 AAAA..."
root:
password: "CHANGE_ME"
packages:
@@ -83,7 +84,8 @@ all:
user:
name: "db"
password: "CHANGE_ME"
- key: "ssh-ed25519 AAAA..."
+ keys:
+ - "ssh-ed25519 AAAA..."
root:
password: "CHANGE_ME"
luks:
@@ -111,7 +113,9 @@ all:
prefix: 24
gateway: 192.168.122.1
dns:
- servers: "1.1.1.1,1.0.0.1"
+ servers:
+ - "1.1.1.1"
+ - "1.0.0.1"
disks:
- size: 80
- size: 200
@@ -121,7 +125,8 @@ all:
user:
name: "compute"
password: "CHANGE_ME"
- key: "ssh-ed25519 AAAA..."
+ keys:
+ - "ssh-ed25519 AAAA..."
root:
password: "CHANGE_ME"
features:
diff --git a/main.yml b/main.yml
index d5f1e4c..f0fe352 100644
--- a/main.yml
+++ b/main.yml
@@ -44,9 +44,9 @@
'name': (
(system_user_input.name | default('') | string | length) > 0
) | ternary(system_user_input.name | string, prompt_user_name),
- 'key': (
- system_user_input.key
- if (system_user_input.key is iterable and system_user_input.key is not string and system_user_input.key | length > 0)
+ 'keys': (
+ system_user_input.keys
+ if (system_user_input.keys is iterable and system_user_input.keys is not string and system_user_input.keys | length > 0)
else (
[prompt_user_key]
if (prompt_user_key | length > 0)
diff --git a/roles/configuration/tasks/network.yml b/roles/configuration/tasks/network.yml
index 983e0c9..bb641e8 100644
--- a/roles/configuration/tasks/network.yml
+++ b/roles/configuration/tasks/network.yml
@@ -1,9 +1,4 @@
---
-- name: Generate UUID for Network Profile
- ansible.builtin.set_fact:
- configuration_net_uuid: "{{ ('LAN-' ~ hostname) | ansible.builtin.to_uuid }}"
- changed_when: false
-
- name: Read network interfaces
ansible.builtin.command:
argv:
@@ -15,81 +10,41 @@
changed_when: false
failed_when: false
-- name: Resolve network interface and MAC address
+- name: Detect available network interface names
vars:
- configuration_net_inf_from_facts: "{{ (ansible_default_ipv4 | default({})).get('interface', '') }}"
- configuration_net_inf_from_ip: >-
+ configuration_detected_interfaces: >-
{{
- (
- configuration_ip_link.stdout
- | default('')
- | regex_findall('^[0-9]+: ([^:]+):', multiline=True)
- | reject('equalto', 'lo')
- | list
- | first
- )
- | default('')
- }}
- configuration_net_inf_detected: >-
- {{ configuration_net_inf_from_facts | default(configuration_net_inf_from_ip, true) }}
- configuration_net_inf_regex: "{{ configuration_net_inf_detected | ansible.builtin.regex_escape }}"
- configuration_net_mac_from_virtualization: "{{ virtualization_mac_address | default('') }}"
- configuration_net_mac_from_facts: >-
- {{
- (
- (ansible_facts | default({})).get(configuration_net_inf_detected, {}).get('macaddress', '')
- )
- | default(
- (ansible_facts | default({})).get('ansible_' + configuration_net_inf_detected, {}).get('macaddress', ''),
- true
- )
- }}
- configuration_net_mac_from_ip: >-
- {{
- (
- configuration_ip_link.stdout
- | default('')
- | regex_findall(
- '^\\d+: ' ~ configuration_net_inf_regex ~ ':.*?link/ether\\s+([0-9A-Fa-f:]{17})',
- multiline=True
- )
- | first
- )
+ configuration_ip_link.stdout
| default('')
+ | regex_findall('^[0-9]+: ([^:]+):', multiline=True)
+ | reject('equalto', 'lo')
+ | list
}}
ansible.builtin.set_fact:
- configuration_net_inf: "{{ configuration_net_inf_detected }}"
- configuration_net_mac: >-
- {{
- (
- configuration_net_mac_from_virtualization
- | default(configuration_net_mac_from_facts, true)
- | default(configuration_net_mac_from_ip, true)
- )
- | upper
- }}
- changed_when: false
+ configuration_detected_interfaces: "{{ configuration_detected_interfaces }}"
-- name: Validate Network Interface Name
+- name: Validate at least one network interface detected
ansible.builtin.assert:
that:
- - configuration_net_inf | length > 0
- fail_msg: Failed to detect an active network interface.
+ - configuration_detected_interfaces | length > 0
+ fail_msg: Failed to detect any network interfaces.
-- name: Validate Network Interface MAC Address
- ansible.builtin.assert:
- that:
- - configuration_net_mac | length > 0
- fail_msg: Failed to detect the MAC address for network interface {{ configuration_net_inf }}.
-
-- name: Configure NetworkManager profile
+- name: Configure NetworkManager profiles
when: os | lower not in ["alpine", "void"]
block:
- - name: Copy NetworkManager keyfile
+ - name: Copy NetworkManager keyfile per interface
+ vars:
+ configuration_iface: "{{ item }}"
+ configuration_iface_name: "{{ configuration_detected_interfaces[idx] | default('eth' ~ idx) }}"
+ configuration_net_uuid: "{{ ('LAN-' ~ idx ~ '-' ~ hostname) | ansible.builtin.to_uuid }}"
ansible.builtin.template:
src: network.j2
- dest: /mnt/etc/NetworkManager/system-connections/LAN.nmconnection
+ dest: "/mnt/etc/NetworkManager/system-connections/LAN-{{ idx }}.nmconnection"
mode: "0600"
+ loop: "{{ system_cfg.network.interfaces }}"
+ loop_control:
+ index_var: idx
+ label: "LAN-{{ idx }}"
- name: Fix Ubuntu unmanaged devices
when: os | lower in ["ubuntu", "ubuntu-lts"]
@@ -102,13 +57,6 @@
when: os | lower == "alpine"
vars:
configuration_dns_list: "{{ system_cfg.network.dns.servers | default([]) }}"
- configuration_alpine_static: >-
- {{
- system_cfg.network.ip is defined
- and system_cfg.network.ip | string | length > 0
- and system_cfg.network.prefix is defined
- and (system_cfg.network.prefix | string | length) > 0
- }}
block:
- name: Write Alpine network interfaces
ansible.builtin.copy:
@@ -117,15 +65,19 @@
content: |
auto lo
iface lo inet loopback
+ {% for iface in system_cfg.network.interfaces %}
+ {% set iface_name = configuration_detected_interfaces[loop.index0] | default(iface.name | default('eth' ~ loop.index0)) %}
+ {% set has_static = (iface.ip | default('') | string | length) > 0 %}
- auto {{ configuration_net_inf }}
- iface {{ configuration_net_inf }} inet {{ 'static' if configuration_alpine_static | bool else 'dhcp' }}
- {% if configuration_alpine_static | bool %}
- address {{ system_cfg.network.ip }}/{{ system_cfg.network.prefix }}
- {% if system_cfg.network.gateway is defined and system_cfg.network.gateway | string | length %}
- gateway {{ system_cfg.network.gateway }}
+ auto {{ iface_name }}
+ iface {{ iface_name }} inet {{ 'static' if has_static else 'dhcp' }}
+ {% if has_static %}
+ address {{ iface.ip }}/{{ iface.prefix }}
+ {% if iface.gateway | default('') | string | length %}
+ gateway {{ iface.gateway }}
{% endif %}
{% endif %}
+ {% endfor %}
- name: Set Alpine DNS resolvers
when: configuration_dns_list | length > 0
@@ -141,25 +93,24 @@
when: os | lower == "void"
vars:
configuration_dns_list: "{{ system_cfg.network.dns.servers | default([]) }}"
- configuration_void_static: >-
- {{
- system_cfg.network.ip is defined
- and system_cfg.network.ip | string | length > 0
- and system_cfg.network.prefix is defined
- and (system_cfg.network.prefix | string | length) > 0
- }}
block:
- - name: Write dhcpcd configuration for static networking
- when: configuration_void_static | bool
+ - name: Write dhcpcd configuration
ansible.builtin.copy:
dest: /mnt/etc/dhcpcd.conf
mode: "0644"
content: |
- interface {{ configuration_net_inf }}
- static ip_address={{ system_cfg.network.ip }}/{{ system_cfg.network.prefix }}
- {% if system_cfg.network.gateway is defined and system_cfg.network.gateway | string | length %}
- static routers={{ system_cfg.network.gateway }}
+ {% for iface in system_cfg.network.interfaces %}
+ {% set iface_name = configuration_detected_interfaces[loop.index0] | default(iface.name | default('eth' ~ loop.index0)) %}
+ {% set has_static = (iface.ip | default('') | string | length) > 0 %}
+ {% if has_static %}
+ interface {{ iface_name }}
+ static ip_address={{ iface.ip }}/{{ iface.prefix }}
+ {% if iface.gateway | default('') | string | length %}
+ static routers={{ iface.gateway }}
{% endif %}
- {% if configuration_dns_list | length > 0 %}
+ {% if loop.index0 == 0 and configuration_dns_list | length > 0 %}
static domain_name_servers={{ configuration_dns_list | join(' ') }}
{% endif %}
+
+ {% endif %}
+ {% endfor %}
diff --git a/roles/configuration/tasks/users.yml b/roles/configuration/tasks/users.yml
index 0c0a47a..e683743 100644
--- a/roles/configuration/tasks/users.yml
+++ b/roles/configuration/tasks/users.yml
@@ -18,7 +18,7 @@
changed_when: configuration_user_result.rc == 0
- name: Ensure .ssh directory exists
- when: system_cfg.user.key | length > 0
+ when: system_cfg.user.keys | length > 0
ansible.builtin.file:
path: /mnt/home/{{ system_cfg.user.name }}/.ssh
state: directory
@@ -27,7 +27,7 @@
mode: "0700"
- name: Add SSH public keys to authorized_keys
- when: system_cfg.user.key | length > 0
+ when: system_cfg.user.keys | length > 0
ansible.builtin.lineinfile:
path: /mnt/home/{{ system_cfg.user.name }}/.ssh/authorized_keys
line: "{{ item }}"
@@ -35,4 +35,4 @@
group: 1000
mode: "0600"
create: true
- loop: "{{ system_cfg.user.key }}"
+ loop: "{{ system_cfg.user.keys }}"
diff --git a/roles/configuration/templates/network.j2 b/roles/configuration/templates/network.j2
index 92215ae..fd8ebd9 100644
--- a/roles/configuration/templates/network.j2
+++ b/roles/configuration/templates/network.j2
@@ -1,24 +1,26 @@
[connection]
-id=LAN
+id=LAN-{{ idx }}
uuid={{ configuration_net_uuid }}
type=ethernet
+interface-name={{ configuration_iface_name }}
[ipv4]
+{% set iface = configuration_iface %}
{% set dns_list = system_cfg.network.dns.servers | default([]) %}
{% set search_list = system_cfg.network.dns.search | default([]) %}
-{% if system_cfg.network.ip is defined and system_cfg.network.ip | string | length %}
-address1={{ system_cfg.network.ip }}/{{ system_cfg.network.prefix }}{{ (',' ~ system_cfg.network.gateway) if (system_cfg.network.gateway is defined and system_cfg.network.gateway | string | length) else '' }}
+{% if iface.ip | default('') | string | length %}
+address1={{ iface.ip }}/{{ iface.prefix }}{{ (',' ~ iface.gateway) if (iface.gateway | default('') | string | length) else '' }}
method=manual
{% else %}
method=auto
{% endif %}
-{% if dns_list %}
+{% if idx | int == 0 and dns_list %}
dns={{ dns_list | join(';') }}
{% endif %}
-{% if dns_list %}
+{% if idx | int == 0 and dns_list %}
ignore-auto-dns=true
{% endif %}
-{% if search_list %}
+{% if idx | int == 0 and search_list %}
dns-search={{ search_list | join(';') }}
{% endif %}
diff --git a/roles/global_defaults/defaults/main.yml b/roles/global_defaults/defaults/main.yml
index e9a962b..cca462e 100644
--- a/roles/global_defaults/defaults/main.yml
+++ b/roles/global_defaults/defaults/main.yml
@@ -36,13 +36,14 @@ system_defaults:
dns:
servers: []
search: []
+ interfaces: []
path: ""
packages: []
disks: []
user:
name: ""
password: ""
- key: []
+ keys: []
root:
password: ""
luks:
diff --git a/roles/global_defaults/tasks/system.yml b/roles/global_defaults/tasks/system.yml
index 8ca21a5..d20d8c0 100644
--- a/roles/global_defaults/tasks/system.yml
+++ b/roles/global_defaults/tasks/system.yml
@@ -16,7 +16,7 @@
fail_msg: "system and its nested keys (network, user, root, luks, features) must be dictionaries."
quiet: true
-- name: Validate DNS and user.key are lists (not strings)
+- name: Validate DNS and user.keys are lists (not strings)
when: system.network is defined and system.network.dns is defined
ansible.builtin.assert:
that:
@@ -25,12 +25,12 @@
fail_msg: "system.network.dns.servers and system.network.dns.search must be lists, not strings."
quiet: true
-- name: Validate user.key is a list
- when: system.user is defined and system.user.key is defined
+- name: Validate user.keys is a list
+ when: system.user is defined and system.user.keys is defined
ansible.builtin.assert:
that:
- - system.user.key is iterable and system.user.key is not string
- fail_msg: "system.user.key must be a list of SSH public key strings."
+ - system.user.keys is iterable and system.user.keys is not string
+ fail_msg: "system.user.keys must be a list of SSH public key strings."
quiet: true
- name: Validate system features input types
@@ -88,6 +88,27 @@
dns:
servers: "{{ system_raw.network.dns.servers | default([]) }}"
search: "{{ system_raw.network.dns.search | default([]) }}"
+ interfaces: >-
+ {{
+ system_raw.network.interfaces
+ if (system_raw.network.interfaces | default([]) | length > 0)
+ else (
+ [{
+ 'name': 'eth0',
+ 'bridge': system_raw.network.bridge | default('') | string,
+ 'vlan': system_raw.network.vlan | default('') | string,
+ 'ip': system_raw.network.ip | default('') | string,
+ 'prefix': (
+ (system_raw.network.prefix | int | string)
+ if (system_raw.network.prefix | default('') | string | length) > 0
+ else ''
+ ),
+ 'gateway': system_raw.network.gateway | default('') | string
+ }]
+ if (system_raw.network.bridge | default('') | string | length > 0)
+ else []
+ )
+ }}
path: "{{ system_raw.path | default('') | string }}"
packages: >-
{{
@@ -104,7 +125,7 @@
user:
name: "{{ system_raw.user.name | string }}"
password: "{{ system_raw.user.password | string }}"
- key: "{{ system_raw.user.key | default([]) }}"
+ keys: "{{ system_raw.user.keys | default([]) }}"
root:
password: "{{ system_raw.root.password | string }}"
luks:
@@ -150,6 +171,25 @@
os: "{{ system_os_input if system_os_input | length > 0 else ('archlinux' if system_type == 'physical' else '') }}"
os_version: "{{ system_raw.version | default('') | string }}"
+- name: Populate primary network fields from first interface
+ when:
+ - system_cfg.network.interfaces | length > 0
+ - system_cfg.network.bridge | default('') | string | length == 0
+ vars:
+ _primary: "{{ system_cfg.network.interfaces[0] }}"
+ ansible.builtin.set_fact:
+ system_cfg: >-
+ {{
+ system_cfg | combine({
+ 'network': system_cfg.network | combine({
+ 'bridge': _primary.bridge | default(''),
+ 'vlan': _primary.vlan | default(''),
+ 'ip': _primary.ip | default(''),
+ 'prefix': _primary.prefix | default(''),
+ 'gateway': _primary.gateway | default('')
+ })
+ }, recursive=True)
+ }}
- name: Normalize system disks input
vars:
diff --git a/roles/global_defaults/tasks/validation.yml b/roles/global_defaults/tasks/validation.yml
index 13c616b..7f9f554 100644
--- a/roles/global_defaults/tasks/validation.yml
+++ b/roles/global_defaults/tasks/validation.yml
@@ -171,8 +171,12 @@
- hypervisor_cfg.host | string | length > 0
- hypervisor_cfg.storage | string | length > 0
- system_cfg.id | string | length > 0
- - system_cfg.network.bridge | string | length > 0
- fail_msg: "Missing required Proxmox inputs. Define hypervisor.(url,username,password,host,storage), system.id, and system.network.bridge."
+ - >-
+ (system_cfg.network.bridge | default('') | string | length > 0)
+ or (system_cfg.network.interfaces | default([]) | length > 0)
+ fail_msg: >-
+ Missing required Proxmox inputs. Define hypervisor.(url,username,password,host,storage),
+ system.id, and system.network.bridge (or system.network.interfaces[]).
quiet: true
- name: Validate VMware hypervisor inputs
@@ -187,8 +191,12 @@
- hypervisor_cfg.datacenter | string | length > 0
- hypervisor_cfg.cluster | string | length > 0
- hypervisor_cfg.storage | string | length > 0
- - system_cfg.network.bridge | string | length > 0
- fail_msg: "Missing required VMware inputs. Define hypervisor.(url,username,password,datacenter,cluster,storage) and system.network.bridge."
+ - >-
+ (system_cfg.network.bridge | default('') | string | length > 0)
+ or (system_cfg.network.interfaces | default([]) | length > 0)
+ fail_msg: >-
+ Missing required VMware inputs. Define hypervisor.(url,username,password,datacenter,cluster,storage)
+ and system.network.bridge (or system.network.interfaces[]).
quiet: true
- name: Validate Xen hypervisor inputs
@@ -197,8 +205,10 @@
- hypervisor_type == "xen"
ansible.builtin.assert:
that:
- - system_cfg.network.bridge | string | length > 0
- fail_msg: "Missing required Xen inputs. Define system.network.bridge."
+ - >-
+ (system_cfg.network.bridge | default('') | string | length > 0)
+ or (system_cfg.network.interfaces | default([]) | length > 0)
+ fail_msg: "Missing required Xen inputs. Define system.network.bridge (or system.network.interfaces[])."
quiet: true
- name: Validate virtual installer ISO requirement
@@ -329,3 +339,15 @@
- (system_cfg.network.prefix | int) > 0
fail_msg: "system.network.prefix is required when system.network.ip is set."
quiet: true
+
+- name: Validate network interfaces entries
+ when: system_cfg.network.interfaces | default([]) | length > 0
+ ansible.builtin.assert:
+ that:
+ - item is mapping
+ - item.bridge is defined and (item.bridge | string | length) > 0
+ fail_msg: "Each system.network.interfaces[] entry must be a dict with at least a 'bridge' key."
+ quiet: true
+ loop: "{{ system_cfg.network.interfaces }}"
+ loop_control:
+ label: "{{ item | to_json }}"
diff --git a/roles/virtualization/defaults/main.yml b/roles/virtualization/defaults/main.yml
index 65b2b81..eb7a7be 100644
--- a/roles/virtualization/defaults/main.yml
+++ b/roles/virtualization/defaults/main.yml
@@ -9,8 +9,6 @@ virtualization_libvirt_disk_path: >-
{{ [virtualization_libvirt_image_dir, hostname ~ '.qcow2'] | ansible.builtin.path_join }}
virtualization_libvirt_cloudinit_path: >-
{{ [virtualization_libvirt_image_dir, hostname ~ '-cloudinit.iso'] | ansible.builtin.path_join }}
-virtualization_mac_address: >-
- {{ '52:54:00' | community.general.random_mac(seed=hostname) }}
virtualization_xen_disk_path: /var/lib/xen/images
virtualization_tpm2_enabled: >-
diff --git a/roles/virtualization/tasks/proxmox.yml b/roles/virtualization/tasks/proxmox.yml
index bba405f..75834b6 100644
--- a/roles/virtualization/tasks/proxmox.yml
+++ b/roles/virtualization/tasks/proxmox.yml
@@ -8,6 +8,28 @@
{%- set _ = out.update({ 'scsi' ~ loop.index0: hypervisor_cfg.storage ~ ':' ~ (disk.size | int) }) -%}
{%- endfor -%}
{{ out }}
+ virtualization_proxmox_net: >-
+ {%- set out = {} -%}
+ {%- for iface in system_cfg.network.interfaces -%}
+ {%- set val = 'virtio,bridge=' ~ iface.bridge -%}
+ {%- if iface.vlan | default('') | string | length > 0 -%}
+ {%- set val = val ~ ',tag=' ~ iface.vlan -%}
+ {%- endif -%}
+ {%- set _ = out.update({ 'net' ~ loop.index0: val }) -%}
+ {%- endfor -%}
+ {{ out }}
+ virtualization_proxmox_ipconfig: >-
+ {%- set out = {} -%}
+ {%- for iface in system_cfg.network.interfaces -%}
+ {%- if iface.ip | default('') | string | length > 0 -%}
+ {%- set val = 'ip=' ~ iface.ip ~ '/' ~ iface.prefix
+ ~ ((',gw=' ~ iface.gateway) if (iface.gateway | default('') | length > 0) else '') -%}
+ {%- else -%}
+ {%- set val = 'ip=dhcp' -%}
+ {%- endif -%}
+ {%- set _ = out.update({ 'ipconfig' ~ loop.index0: val }) -%}
+ {%- endfor -%}
+ {{ out }}
community.proxmox.proxmox_kvm:
api_host: "{{ hypervisor_cfg.url }}"
api_user: "{{ hypervisor_cfg.username }}"
@@ -46,20 +68,8 @@
ide0: "{{ boot_iso }},media=cdrom"
ide1: "{{ rhel_iso + ',media=cdrom' if rhel_iso is defined and rhel_iso | length > 0 else omit }}"
ide2: "{{ hypervisor_cfg.storage }}:cloudinit"
- net:
- net0: >-
- virtio,bridge={{ system_cfg.network.bridge
- }}{%- if system_cfg.network.vlan is defined
- and system_cfg.network.vlan | string | length > 0
- %},tag={{ system_cfg.network.vlan }}{% endif %}
- ipconfig:
- ipconfig0: >-
- {{
- 'ip=' ~ system_cfg.network.ip ~ '/' ~ system_cfg.network.prefix
- ~ (',gw=' ~ system_cfg.network.gateway if system_cfg.network.gateway is defined and system_cfg.network.gateway | length else '')
- if system_cfg.network.ip is defined and system_cfg.network.ip | string | length
- else 'ip=dhcp'
- }}
+ net: "{{ virtualization_proxmox_net }}"
+ ipconfig: "{{ virtualization_proxmox_ipconfig }}"
nameservers: "{{ system_cfg.network.dns.servers if system_cfg.network.dns.servers | length else omit }}"
searchdomains: "{{ system_cfg.network.dns.search if system_cfg.network.dns.search | length else omit }}"
onboot: true
diff --git a/roles/virtualization/tasks/vmware.yml b/roles/virtualization/tasks/vmware.yml
index dac607f..eb783c7 100644
--- a/roles/virtualization/tasks/vmware.yml
+++ b/roles/virtualization/tasks/vmware.yml
@@ -14,6 +14,17 @@
- name: Create VM in vCenter
delegate_to: localhost
+ vars:
+ virtualization_vmware_networks: >-
+ {%- set ns = namespace(out=[]) -%}
+ {%- for iface in system_cfg.network.interfaces -%}
+ {%- set entry = {'name': iface.bridge, 'type': 'dhcp'} -%}
+ {%- if (iface.vlan | default('') | string | length) > 0 -%}
+ {%- set entry = entry | combine({'vlan': iface.vlan | int}) -%}
+ {%- endif -%}
+ {%- set ns.out = ns.out + [entry] -%}
+ {%- endfor -%}
+ {{ ns.out }}
community.vmware.vmware_guest:
hostname: "{{ hypervisor_cfg.url }}"
username: "{{ hypervisor_cfg.username }}"
@@ -53,10 +64,7 @@
"iso_path": rhel_iso
} ] if rhel_iso is defined and rhel_iso | length > 0 else [] )
}}
- networks:
- - name: "{{ system_cfg.network.bridge }}"
- type: dhcp
- vlan: "{{ system_cfg.network.vlan if system_cfg.network.vlan is defined and system_cfg.network.vlan | string | length > 0 else omit }}"
+ networks: "{{ virtualization_vmware_networks }}"
register: virtualization_vmware_create_result
- name: Set VM created fact when VM was powered on during creation
diff --git a/roles/virtualization/templates/cloud-network-config.yml.j2 b/roles/virtualization/templates/cloud-network-config.yml.j2
index d439a1e..f98b601 100644
--- a/roles/virtualization/templates/cloud-network-config.yml.j2
+++ b/roles/virtualization/templates/cloud-network-config.yml.j2
@@ -1,21 +1,23 @@
network:
version: 2
ethernets:
- id0:
- match:
- macaddress: "{{ virtualization_mac_address }}"
-{% set has_static = system_cfg.network.ip is defined and system_cfg.network.ip | string | length %}
{% set dns_list = system_cfg.network.dns.servers | default([]) %}
{% set search_list = system_cfg.network.dns.search | default([]) %}
+{% for iface in system_cfg.network.interfaces %}
+{% set iface_mac = '52:54:00' | community.general.random_mac(seed=hostname if loop.index0 == 0 else hostname ~ '-nic' ~ loop.index0) %}
+{% set has_static = (iface.ip | default('') | string | length) > 0 %}
+ id{{ loop.index0 }}:
+ match:
+ macaddress: "{{ iface_mac }}"
{% if has_static %}
addresses:
- - "{{ system_cfg.network.ip }}/{{ system_cfg.network.prefix }}"
-{% if system_cfg.network.gateway is defined and system_cfg.network.gateway | string | length %}
- gateway4: "{{ system_cfg.network.gateway }}"
+ - "{{ iface.ip }}/{{ iface.prefix }}"
+{% if iface.gateway | default('') | string | length %}
+ gateway4: "{{ iface.gateway }}"
{% endif %}
{% else %}
dhcp4: true
-{% if dns_list | length or search_list | length %}
+{% if loop.index0 == 0 and (dns_list | length or search_list | length) %}
dhcp4-overrides:
{% if dns_list | length %}
use-dns: false
@@ -25,7 +27,7 @@ network:
{% endif %}
{% endif %}
{% endif %}
-{% if dns_list or search_list %}
+{% if loop.index0 == 0 and (dns_list or search_list) %}
nameservers:
{% if dns_list %}
addresses:
@@ -40,3 +42,4 @@ network:
{% endfor %}
{% endif %}
{% endif %}
+{% endfor %}
diff --git a/roles/virtualization/templates/vm.xml.j2 b/roles/virtualization/templates/vm.xml.j2
index 938d1ad..73d662f 100644
--- a/roles/virtualization/templates/vm.xml.j2
+++ b/roles/virtualization/templates/vm.xml.j2
@@ -46,11 +46,13 @@
{% endif %}
+ {% for iface in system_cfg.network.interfaces %}
-
- 0 else "default" }}'/>
+
+
+ {% endfor %}
{% if virtualization_tpm2_enabled %}
diff --git a/templates/xen.cfg.j2 b/templates/xen.cfg.j2
index d13400b..7801d14 100644
--- a/templates/xen.cfg.j2
+++ b/templates/xen.cfg.j2
@@ -10,7 +10,11 @@ disk = [
'{{ boot_iso }},,hdc,cdrom'{% if rhel_iso is defined and rhel_iso | length > 0 %}, '{{ rhel_iso }},,hdd,cdrom'{% endif %}
{%- endif -%}
]
-vif = [ 'bridge={{ system_cfg.network.bridge }},model=e1000' ]
+vif = [
+{%- for iface in system_cfg.network.interfaces -%}
+ 'bridge={{ iface.bridge }},model=e1000'{% if not loop.last %}, {% endif %}
+{%- endfor -%}
+]
boot = "{{ 'dc' if xen_installer_media_enabled | bool else 'c' }}"
on_crash = "preserve"
on_poweroff = "destroy"
diff --git a/vars_baremetal_example.yml b/vars_baremetal_example.yml
index 7e48e22..cf4c78d 100644
--- a/vars_baremetal_example.yml
+++ b/vars_baremetal_example.yml
@@ -28,7 +28,8 @@ system:
user:
name: "admin"
password: "CHANGE_ME"
- key: "ssh-ed25519 AAAA..."
+ keys:
+ - "ssh-ed25519 AAAA..."
root:
password: "CHANGE_ME"
luks:
diff --git a/vars_example.yml b/vars_example.yml
index 9cc169a..3574771 100644
--- a/vars_example.yml
+++ b/vars_example.yml
@@ -25,6 +25,7 @@ system:
memory: 8192
balloon: 0
network:
+ # Flat fields (AWX survey compatibility, builds single-entry interfaces[])
bridge: "vmbr0"
ip: "{{ inventory_hostname }}"
prefix: 24
@@ -35,6 +36,16 @@ system:
- "1.0.0.1"
search:
- "example.com"
+ # Multi-NIC: use interfaces[] instead of flat fields above
+ # interfaces:
+ # - name: "eth0"
+ # bridge: "vmbr0"
+ # ip: "10.0.0.10"
+ # prefix: 24
+ # gateway: "10.0.0.1"
+ # - name: "eth1"
+ # bridge: "vmbr1"
+ # vlan: "100"
path: "/Lab/Example"
disks:
- size: 80
@@ -47,7 +58,8 @@ system:
user:
name: "ops"
password: "CHANGE_ME"
- key: "ssh-ed25519 AAAA..."
+ keys:
+ - "ssh-ed25519 AAAA..."
root:
password: "CHANGE_ME"
luks: