Compare commits
4 Commits
771b88a957
...
1f778a7aaa
| Author | SHA1 | Date | |
|---|---|---|---|
| 1f778a7aaa | |||
| 54ffe5ff91 | |||
| 335534176f | |||
| 79227b4391 |
@@ -91,7 +91,7 @@ all:
|
||||
username: root@pam
|
||||
password: !vault |
|
||||
$ANSIBLE_VAULT...
|
||||
host: pve01
|
||||
node: pve01
|
||||
storage: local-lvm
|
||||
|
||||
children:
|
||||
@@ -268,7 +268,7 @@ The first user's credentials are prompted interactively via `vars_prompt` unless
|
||||
| `url` | string | -- | API host (Proxmox/VMware) |
|
||||
| `username` | string | -- | API username |
|
||||
| `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) |
|
||||
| `datacenter` | string | -- | VMware datacenter |
|
||||
| `cluster` | string | -- | VMware cluster |
|
||||
|
||||
@@ -6,7 +6,7 @@ all:
|
||||
url: "pve01.example.com"
|
||||
username: "root@pam"
|
||||
password: "CHANGE_ME"
|
||||
host: "pve01"
|
||||
node: "pve01"
|
||||
storage: "local-lvm"
|
||||
boot_iso: "local:iso/archlinux-x86_64.iso"
|
||||
children:
|
||||
|
||||
73
main.yml
73
main.yml
@@ -110,32 +110,81 @@
|
||||
ansible.builtin.import_role:
|
||||
name: system_check
|
||||
|
||||
roles:
|
||||
- role: virtualization
|
||||
tasks:
|
||||
- name: Bootstrap pipeline
|
||||
block:
|
||||
- name: Record that no pre-existing VM was found
|
||||
ansible.builtin.set_fact:
|
||||
_vm_absent_before_bootstrap: true
|
||||
|
||||
- name: Create virtual machine
|
||||
when: system_cfg.type == "virtual"
|
||||
become: false
|
||||
ansible.builtin.include_role:
|
||||
name: virtualization
|
||||
public: true
|
||||
vars:
|
||||
ansible_connection: local
|
||||
ansible_become: false
|
||||
|
||||
- role: environment
|
||||
vars:
|
||||
ansible_connection: "{{ 'vmware_tools' if hypervisor_type == 'vmware' else 'ssh' }}"
|
||||
- name: Configure environment
|
||||
ansible.builtin.include_role:
|
||||
name: environment
|
||||
public: true
|
||||
|
||||
- role: partitioning
|
||||
- name: Partition disks
|
||||
ansible.builtin.include_role:
|
||||
name: partitioning
|
||||
public: true
|
||||
vars:
|
||||
partitioning_boot_partition_suffix: 1
|
||||
partitioning_main_partition_suffix: 2
|
||||
|
||||
- role: bootstrap
|
||||
- name: Install base system
|
||||
ansible.builtin.include_role:
|
||||
name: bootstrap
|
||||
public: true
|
||||
|
||||
- role: configuration
|
||||
- name: Apply system configuration
|
||||
ansible.builtin.include_role:
|
||||
name: configuration
|
||||
public: true
|
||||
|
||||
- role: cis
|
||||
- name: Apply CIS hardening
|
||||
when: system_cfg.features.cis.enabled | bool
|
||||
ansible.builtin.include_role:
|
||||
name: cis
|
||||
public: true
|
||||
|
||||
- role: cleanup
|
||||
- name: Clean up and finalize
|
||||
when: system_cfg.type in ["virtual", "physical"]
|
||||
become: false
|
||||
ansible.builtin.include_role:
|
||||
name: cleanup
|
||||
public: true
|
||||
vars:
|
||||
ansible_become: false
|
||||
|
||||
rescue:
|
||||
- name: Delete VM on bootstrap failure
|
||||
when:
|
||||
- _vm_absent_before_bootstrap | default(false) | bool
|
||||
- virtualization_vm_created_in_run | default(false) | bool
|
||||
- system_cfg.type == "virtual"
|
||||
ansible.builtin.include_role:
|
||||
name: virtualization
|
||||
tasks_from: delete
|
||||
vars:
|
||||
ansible_connection: local
|
||||
ansible_become: false
|
||||
tags:
|
||||
- rescue_cleanup
|
||||
|
||||
- name: Fail host after bootstrap rescue
|
||||
ansible.builtin.fail:
|
||||
msg: >-
|
||||
Bootstrap failed for {{ hostname }}.
|
||||
{{ 'VM was deleted to allow clean retry.'
|
||||
if (virtualization_vm_created_in_run | default(false))
|
||||
else 'VM was not created in this run (kept).' }}
|
||||
|
||||
post_tasks:
|
||||
- name: Set post-reboot connection flags
|
||||
|
||||
@@ -13,6 +13,14 @@
|
||||
| default('')
|
||||
}}
|
||||
|
||||
- name: Bring up network interface
|
||||
when:
|
||||
- hypervisor_type == "vmware"
|
||||
- environment_interface_name | default('') | length > 0
|
||||
ansible.builtin.command: "ip link set {{ environment_interface_name }} up"
|
||||
register: environment_link_result
|
||||
changed_when: environment_link_result.rc == 0
|
||||
|
||||
- name: Set IP-Address
|
||||
when:
|
||||
- hypervisor_type == "vmware"
|
||||
@@ -32,13 +40,31 @@
|
||||
register: environment_gateway_result
|
||||
changed_when: environment_gateway_result.rc == 0
|
||||
|
||||
- name: Configure DNS resolvers
|
||||
when:
|
||||
- hypervisor_type == "vmware"
|
||||
- system_cfg.network.dns.servers | default([]) | length > 0
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/resolv.conf
|
||||
content: |
|
||||
{% for server in system_cfg.network.dns.servers %}
|
||||
nameserver {{ server }}
|
||||
{% endfor %}
|
||||
{% if system_cfg.network.dns.search | default([]) | length > 0 %}
|
||||
search {{ system_cfg.network.dns.search | join(' ') }}
|
||||
{% endif %}
|
||||
mode: "0644"
|
||||
|
||||
- name: Synchronize clock via NTP
|
||||
ansible.builtin.command: timedatectl set-ntp true
|
||||
register: environment_ntp_result
|
||||
changed_when: environment_ntp_result.rc == 0
|
||||
|
||||
- name: Configure SSH for root login
|
||||
when: hypervisor_type == "vmware" and hypervisor_cfg.ssh | bool
|
||||
when:
|
||||
- hypervisor_type == "vmware"
|
||||
- hypervisor_cfg.ssh | default(false) | bool
|
||||
- system_cfg.network.ip is defined and system_cfg.network.ip | string | length > 0
|
||||
block:
|
||||
- name: Allow login
|
||||
ansible.builtin.replace:
|
||||
@@ -58,7 +84,18 @@
|
||||
name: sshd
|
||||
state: reloaded
|
||||
|
||||
- name: Set SSH connection for VMware
|
||||
- name: Switch to SSH connection
|
||||
ansible.builtin.set_fact:
|
||||
ansible_connection: ssh
|
||||
ansible_user: root
|
||||
ansible_password: ""
|
||||
ansible_host: "{{ system_cfg.network.ip }}"
|
||||
ansible_ssh_extra_args: "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
|
||||
|
||||
- name: Reset connection for SSH switchover
|
||||
ansible.builtin.meta: reset_connection
|
||||
|
||||
- name: Verify SSH connectivity
|
||||
ansible.builtin.wait_for_connection:
|
||||
timeout: 30
|
||||
delay: 2
|
||||
|
||||
@@ -79,6 +79,13 @@
|
||||
# bootstrapping RHEL-family distros from the Arch ISO, where the
|
||||
# host rpm/dnf does not trust target distro GPG keys. Package
|
||||
# integrity is verified by the target system's own rpm after reboot.
|
||||
- name: Create RPM macros directory
|
||||
when: is_rhel | bool
|
||||
ansible.builtin.file:
|
||||
path: /etc/rpm
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: Relax RPM Sequoia signature policy for RHEL bootstrap
|
||||
when: is_rhel | bool
|
||||
ansible.builtin.copy:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
- name: Configure work environment
|
||||
become: "{{ hypervisor_type != 'vmware' }}"
|
||||
become: "{{ (hypervisor_type | default('none')) != 'vmware' }}"
|
||||
block:
|
||||
- name: Detect and validate live environment
|
||||
ansible.builtin.include_tasks: _detect_live.yml
|
||||
|
||||
@@ -46,7 +46,7 @@ hypervisor_defaults:
|
||||
url: ""
|
||||
username: ""
|
||||
password: ""
|
||||
host: ""
|
||||
node: ""
|
||||
storage: ""
|
||||
datacenter: ""
|
||||
cluster: ""
|
||||
@@ -136,10 +136,10 @@ system_defaults:
|
||||
# All virtual types additionally require network bridge or interfaces.
|
||||
hypervisor_required_fields:
|
||||
proxmox:
|
||||
hypervisor: [url, username, password, host, storage]
|
||||
hypervisor: [url, username, password, node, storage]
|
||||
system: [id]
|
||||
vmware:
|
||||
hypervisor: [url, username, password, datacenter, cluster, storage]
|
||||
hypervisor: [url, username, password, datacenter, storage]
|
||||
system: []
|
||||
xen:
|
||||
hypervisor: []
|
||||
|
||||
@@ -77,7 +77,12 @@
|
||||
if (system_raw.mirror | default('') | string | trim | length) > 0
|
||||
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: >-
|
||||
{{
|
||||
(
|
||||
|
||||
@@ -32,12 +32,19 @@
|
||||
api_host: "{{ hypervisor_cfg.url }}"
|
||||
api_user: "{{ hypervisor_cfg.username }}"
|
||||
api_password: "{{ hypervisor_cfg.password }}"
|
||||
node: "{{ hypervisor_cfg.host }}"
|
||||
node: "{{ hypervisor_cfg.node }}"
|
||||
no_log: true
|
||||
|
||||
- name: Normalize system inputs
|
||||
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
|
||||
ansible.builtin.include_tasks: validation.yml
|
||||
|
||||
@@ -85,3 +92,12 @@
|
||||
when: hypervisor_type == "vmware"
|
||||
ansible.builtin.set_fact:
|
||||
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
|
||||
|
||||
@@ -48,6 +48,28 @@
|
||||
}, recursive=True)
|
||||
}}
|
||||
|
||||
- name: Populate primary network fields from first interface (pre-computed)
|
||||
when:
|
||||
- system_cfg is defined
|
||||
- _bootstrap_needs_enrichment | default(false) | bool
|
||||
- system_cfg.network.interfaces | default([]) | 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: Derive convenience facts from pre-computed system_cfg
|
||||
when:
|
||||
- system_cfg is defined
|
||||
|
||||
@@ -166,6 +166,23 @@
|
||||
label: "hypervisor.{{ item }}"
|
||||
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
|
||||
when:
|
||||
- system_cfg.type == "virtual"
|
||||
@@ -293,8 +310,8 @@
|
||||
system_disk_mounts: >-
|
||||
{{
|
||||
(system_cfg.disks | default([]))
|
||||
| map(attribute='mount')
|
||||
| map(attribute='path')
|
||||
| map(attribute='mount', default={})
|
||||
| map(attribute='path', default='')
|
||||
| map('string')
|
||||
| map('trim')
|
||||
| reject('equalto', '')
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
block:
|
||||
- name: Query Proxmox for existing VM
|
||||
community.proxmox.proxmox_vm_info:
|
||||
node: "{{ hypervisor_cfg.host }}"
|
||||
node: "{{ hypervisor_cfg.node }}"
|
||||
vmid: "{{ system_cfg.id }}"
|
||||
name: "{{ hostname }}"
|
||||
type: qemu
|
||||
@@ -66,6 +66,7 @@
|
||||
- name: Check VM existence in vCenter
|
||||
when: hypervisor_type == "vmware"
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
module_defaults:
|
||||
community.vmware.vmware_guest_info:
|
||||
hostname: "{{ hypervisor_cfg.url }}"
|
||||
@@ -106,6 +107,7 @@
|
||||
- name: Check if VM already exists on Xen
|
||||
when: hypervisor_type == "xen"
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- xl
|
||||
|
||||
79
roles/virtualization/tasks/delete.yml
Normal file
79
roles/virtualization/tasks/delete.yml
Normal file
@@ -0,0 +1,79 @@
|
||||
---
|
||||
- name: Delete VMware VM
|
||||
when: hypervisor_type == "vmware"
|
||||
delegate_to: localhost
|
||||
community.vmware.vmware_guest:
|
||||
hostname: "{{ hypervisor_cfg.url }}"
|
||||
username: "{{ hypervisor_cfg.username }}"
|
||||
password: "{{ hypervisor_cfg.password }}"
|
||||
validate_certs: "{{ hypervisor_cfg.certs | bool }}"
|
||||
datacenter: "{{ hypervisor_cfg.datacenter }}"
|
||||
name: "{{ hostname }}"
|
||||
folder: "{{ system_cfg.path if system_cfg.path | string | length > 0 else omit }}"
|
||||
state: absent
|
||||
force: true
|
||||
no_log: true
|
||||
|
||||
- name: Delete Proxmox VM
|
||||
when: hypervisor_type == "proxmox"
|
||||
delegate_to: localhost
|
||||
community.proxmox.proxmox_kvm:
|
||||
api_host: "{{ hypervisor_cfg.url }}"
|
||||
api_user: "{{ hypervisor_cfg.username }}"
|
||||
api_password: "{{ hypervisor_cfg.password }}"
|
||||
node: "{{ hypervisor_cfg.node }}"
|
||||
vmid: "{{ system_cfg.id | default(omit, true) }}"
|
||||
name: "{{ hostname }}"
|
||||
state: absent
|
||||
force: true
|
||||
no_log: true
|
||||
|
||||
- name: Destroy libvirt VM
|
||||
when: hypervisor_type == "libvirt"
|
||||
delegate_to: localhost
|
||||
block:
|
||||
- name: Stop libvirt VM
|
||||
community.libvirt.virt:
|
||||
name: "{{ hostname }}"
|
||||
state: destroyed
|
||||
uri: "{{ libvirt_uri | default('qemu:///system') }}"
|
||||
failed_when: false
|
||||
|
||||
- name: Undefine libvirt VM
|
||||
community.libvirt.virt:
|
||||
name: "{{ hostname }}"
|
||||
command: undefine
|
||||
uri: "{{ libvirt_uri | default('qemu:///system') }}"
|
||||
failed_when: false
|
||||
|
||||
- name: Remove libvirt disk images
|
||||
ansible.builtin.file:
|
||||
path: "{{ item.path }}"
|
||||
state: absent
|
||||
loop: "{{ virtualization_libvirt_disks | default([]) }}"
|
||||
loop_control:
|
||||
label: "{{ item.path | default('unknown') }}"
|
||||
|
||||
- name: Remove libvirt cloud-init disk
|
||||
ansible.builtin.file:
|
||||
path: "{{ virtualization_libvirt_cloudinit_path | default('/dev/null') }}"
|
||||
state: absent
|
||||
when: virtualization_libvirt_cloudinit_path is defined
|
||||
|
||||
- name: Destroy Xen VM
|
||||
when: hypervisor_type == "xen"
|
||||
delegate_to: localhost
|
||||
block:
|
||||
- name: Stop Xen VM
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- xl
|
||||
- destroy
|
||||
- "{{ hostname }}"
|
||||
failed_when: false
|
||||
|
||||
- name: Remove Xen VM config
|
||||
ansible.builtin.file:
|
||||
path: "/etc/xen/{{ hostname }}.cfg"
|
||||
state: absent
|
||||
failed_when: false
|
||||
@@ -32,7 +32,8 @@
|
||||
{%- endfor -%}
|
||||
{{ ns.out }}
|
||||
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 }}"
|
||||
name: "{{ hostname }}"
|
||||
# Generic guest ID — VMware auto-detects OS post-install
|
||||
|
||||
@@ -7,7 +7,7 @@ hypervisor:
|
||||
url: "pve01.example.com"
|
||||
username: "root@pam"
|
||||
password: "CHANGE_ME"
|
||||
host: "pve01"
|
||||
node: "pve01"
|
||||
storage: "local-lvm"
|
||||
datacenter: "dc01"
|
||||
cluster: "cluster01"
|
||||
|
||||
Reference in New Issue
Block a user