feat(vmware): add VMware hypervisor support (node field, connection vars, validation)

This commit is contained in:
2026-03-12 07:43:34 +01:00
parent 13faf33296
commit 79227b4391
9 changed files with 54 additions and 13 deletions

View File

@@ -91,7 +91,7 @@ all:
username: root@pam username: root@pam
password: !vault | password: !vault |
$ANSIBLE_VAULT... $ANSIBLE_VAULT...
host: pve01 node: pve01
storage: local-lvm storage: local-lvm
children: children:
@@ -268,7 +268,7 @@ The first user's credentials are prompted interactively via `vars_prompt` unless
| `url` | string | -- | API host (Proxmox/VMware) | | `url` | string | -- | API host (Proxmox/VMware) |
| `username` | string | -- | API username | | `username` | string | -- | API username |
| `password` | string | -- | API password | | `password` | string | -- | API password |
| `host` | string | -- | Proxmox node name | | `node` | string | -- | Target compute node (Proxmox node / VMware ESXi host; mutually exclusive with `cluster` on VMware) |
| `storage` | string | -- | Storage identifier (Proxmox/VMware) | | `storage` | string | -- | Storage identifier (Proxmox/VMware) |
| `datacenter` | string | -- | VMware datacenter | | `datacenter` | string | -- | VMware datacenter |
| `cluster` | string | -- | VMware cluster | | `cluster` | string | -- | VMware cluster |

View File

@@ -6,7 +6,7 @@ all:
url: "pve01.example.com" url: "pve01.example.com"
username: "root@pam" username: "root@pam"
password: "CHANGE_ME" password: "CHANGE_ME"
host: "pve01" node: "pve01"
storage: "local-lvm" storage: "local-lvm"
boot_iso: "local:iso/archlinux-x86_64.iso" boot_iso: "local:iso/archlinux-x86_64.iso"
children: children:

View File

@@ -46,7 +46,7 @@ hypervisor_defaults:
url: "" url: ""
username: "" username: ""
password: "" password: ""
host: "" node: ""
storage: "" storage: ""
datacenter: "" datacenter: ""
cluster: "" cluster: ""
@@ -136,10 +136,10 @@ system_defaults:
# All virtual types additionally require network bridge or interfaces. # All virtual types additionally require network bridge or interfaces.
hypervisor_required_fields: hypervisor_required_fields:
proxmox: proxmox:
hypervisor: [url, username, password, host, storage] hypervisor: [url, username, password, node, storage]
system: [id] system: [id]
vmware: vmware:
hypervisor: [url, username, password, datacenter, cluster, storage] hypervisor: [url, username, password, datacenter, storage]
system: [] system: []
xen: xen:
hypervisor: [] hypervisor: []

View File

@@ -77,7 +77,12 @@
if (system_raw.mirror | default('') | string | trim | length) > 0 if (system_raw.mirror | default('') | string | trim | length) > 0
else _mirror_defaults[system_raw.os | default('') | string | lower] | default('') else _mirror_defaults[system_raw.os | default('') | string | lower] | default('')
}} }}
path: "{{ system_raw.path | default('') | string }}" path: >-
{{
(system_raw.path | default('') | string)
if (system_raw.path | default('') | string | length > 0)
else (hypervisor_cfg.folder | default('') | string)
}}
packages: >- packages: >-
{{ {{
( (

View File

@@ -32,12 +32,19 @@
api_host: "{{ hypervisor_cfg.url }}" api_host: "{{ hypervisor_cfg.url }}"
api_user: "{{ hypervisor_cfg.username }}" api_user: "{{ hypervisor_cfg.username }}"
api_password: "{{ hypervisor_cfg.password }}" api_password: "{{ hypervisor_cfg.password }}"
node: "{{ hypervisor_cfg.host }}" node: "{{ hypervisor_cfg.node }}"
no_log: true no_log: true
- name: Normalize system inputs - name: Normalize system inputs
ansible.builtin.include_tasks: system.yml ansible.builtin.include_tasks: system.yml
- name: Inherit folder from hypervisor when system path is empty
when:
- system_cfg.path | default('') | string | length == 0
- hypervisor_cfg.folder | default('') | string | length > 0
ansible.builtin.set_fact:
system_cfg: "{{ system_cfg | combine({'path': hypervisor_cfg.folder | string}, recursive=True) }}"
- name: Validate variables - name: Validate variables
ansible.builtin.include_tasks: validation.yml ansible.builtin.include_tasks: validation.yml
@@ -85,3 +92,12 @@
when: hypervisor_type == "vmware" when: hypervisor_type == "vmware"
ansible.builtin.set_fact: ansible.builtin.set_fact:
ansible_connection: vmware_tools ansible_connection: vmware_tools
ansible_vmware_host: "{{ hypervisor_cfg.url }}"
ansible_vmware_port: 443
ansible_vmware_user: "{{ hypervisor_cfg.username }}"
ansible_vmware_password: "{{ hypervisor_cfg.password }}"
ansible_vmware_guest_path: "/{{ hypervisor_cfg.datacenter }}/vm{{ system_cfg.path }}/{{ hostname }}"
ansible_vmware_validate_certs: "{{ hypervisor_cfg.certs | bool }}"
ansible_vmware_tools_user: root
ansible_vmware_tools_password: "{{ system_cfg.root.password }}"
no_log: true

View File

@@ -166,6 +166,23 @@
label: "hypervisor.{{ item }}" label: "hypervisor.{{ item }}"
no_log: true no_log: true
- name: Validate VMware placement (cluster or node required, mutually exclusive)
when:
- system_cfg.type == "virtual"
- hypervisor_type == "vmware"
ansible.builtin.assert:
that:
- >-
(hypervisor_cfg.cluster | default('') | string | length > 0)
or (hypervisor_cfg.node | default('') | string | length > 0)
- >-
(hypervisor_cfg.cluster | default('') | string | length == 0)
or (hypervisor_cfg.node | default('') | string | length == 0)
fail_msg: >-
VMware requires either hypervisor.cluster or hypervisor.node (mutually exclusive).
cluster targets a vSphere cluster; node targets a specific ESXi host.
quiet: true
- name: Validate hypervisor-specific required system fields - name: Validate hypervisor-specific required system fields
when: when:
- system_cfg.type == "virtual" - system_cfg.type == "virtual"
@@ -293,8 +310,8 @@
system_disk_mounts: >- system_disk_mounts: >-
{{ {{
(system_cfg.disks | default([])) (system_cfg.disks | default([]))
| map(attribute='mount') | map(attribute='mount', default={})
| map(attribute='path') | map(attribute='path', default='')
| map('string') | map('string')
| map('trim') | map('trim')
| reject('equalto', '') | reject('equalto', '')

View File

@@ -45,7 +45,7 @@
block: block:
- name: Query Proxmox for existing VM - name: Query Proxmox for existing VM
community.proxmox.proxmox_vm_info: community.proxmox.proxmox_vm_info:
node: "{{ hypervisor_cfg.host }}" node: "{{ hypervisor_cfg.node }}"
vmid: "{{ system_cfg.id }}" vmid: "{{ system_cfg.id }}"
name: "{{ hostname }}" name: "{{ hostname }}"
type: qemu type: qemu
@@ -66,6 +66,7 @@
- name: Check VM existence in vCenter - name: Check VM existence in vCenter
when: hypervisor_type == "vmware" when: hypervisor_type == "vmware"
delegate_to: localhost delegate_to: localhost
become: false
module_defaults: module_defaults:
community.vmware.vmware_guest_info: community.vmware.vmware_guest_info:
hostname: "{{ hypervisor_cfg.url }}" hostname: "{{ hypervisor_cfg.url }}"
@@ -106,6 +107,7 @@
- name: Check if VM already exists on Xen - name: Check if VM already exists on Xen
when: hypervisor_type == "xen" when: hypervisor_type == "xen"
delegate_to: localhost delegate_to: localhost
become: false
ansible.builtin.command: ansible.builtin.command:
argv: argv:
- xl - xl

View File

@@ -32,7 +32,8 @@
{%- endfor -%} {%- endfor -%}
{{ ns.out }} {{ ns.out }}
community.vmware.vmware_guest: community.vmware.vmware_guest:
cluster: "{{ hypervisor_cfg.cluster }}" cluster: "{{ hypervisor_cfg.cluster if (hypervisor_cfg.node | default('') | length == 0) else omit }}"
esxi_hostname: "{{ hypervisor_cfg.node if (hypervisor_cfg.node | default('') | length > 0) else omit }}"
folder: "{{ system_cfg.path if system_cfg.path | string | length > 0 else omit }}" folder: "{{ system_cfg.path if system_cfg.path | string | length > 0 else omit }}"
name: "{{ hostname }}" name: "{{ hostname }}"
# Generic guest ID — VMware auto-detects OS post-install # Generic guest ID — VMware auto-detects OS post-install

View File

@@ -7,7 +7,7 @@ hypervisor:
url: "pve01.example.com" url: "pve01.example.com"
username: "root@pam" username: "root@pam"
password: "CHANGE_ME" password: "CHANGE_ME"
host: "pve01" node: "pve01"
storage: "local-lvm" storage: "local-lvm"
datacenter: "dc01" datacenter: "dc01"
cluster: "cluster01" cluster: "cluster01"