Files
infra-automation/plays/deploy-linux-vm.yml
Infrastructure Team a5337029ff Add multi-distribution VM deployment playbooks
- Add deploy-debian12-vm.yml for basic Debian 12 deployment
- Add deploy-linux-vm.yml for multi-distribution support
  - Support for Debian, Ubuntu, RHEL, CentOS, Rocky, Alma, SUSE
  - Cloud-init based provisioning
  - Distribution-specific security hardening
  - Automatic security updates configuration
  - UFW/firewalld setup per OS family
  - SELinux enforcing for RHEL family
2025-11-10 22:51:30 +01:00

977 lines
31 KiB
YAML

---
# =============================================================================
# Multi-Distribution Linux VM Deployment Playbook
# =============================================================================
# Deploys Linux VMs on KVM hypervisor with support for:
# - Debian (11, 12)
# - Ubuntu (20.04 LTS, 22.04 LTS, 24.04 LTS)
# - RHEL (8, 9)
# - CentOS Stream (8, 9)
# - Rocky Linux (8, 9)
# - AlmaLinux (8, 9)
# - SLES (15)
# - openSUSE Leap (15)
#
# Uses libvirt/KVM with cloud-init for unattended configuration
# =============================================================================
- name: Deploy Linux VM on KVM hypervisor
hosts: grokbox
gather_facts: yes
become: yes
vars:
# VM Configuration
vm_name: "linux-guest"
vm_hostname: "linux-vm"
vm_domain: "localdomain"
vm_vcpus: 2
vm_memory_mb: 2048
vm_disk_size_gb: 20
# Distribution Selection (REQUIRED - set via -e flag)
# Format: "distro-version" or "distro-major.minor"
# Examples: debian-12, ubuntu-22.04, rhel-9, centos-stream-9, sles-15
os_distribution: "debian-12"
# Network Configuration
vm_network: "default"
vm_bridge: "virbr0"
# Storage Configuration
vm_disk_path: "/var/lib/libvirt/images/{{ vm_name }}.qcow2"
cloud_init_iso_path: "/var/lib/libvirt/images/{{ vm_name }}-cloud-init.iso"
# Ansible User Configuration
ansible_user_ssh_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILBrnivsqjhAxWYeuuvnYc3neeRRuHsr2SjeKv+Drtpu user@debian"
# ==========================================================================
# Cloud Image Repository Configuration
# ==========================================================================
cloud_images:
# Debian
debian-11:
url: "https://cloud.debian.org/images/cloud/bullseye/latest/debian-11-generic-amd64.qcow2"
checksum_url: "https://cloud.debian.org/images/cloud/bullseye/latest/SHA512SUMS"
checksum_type: "sha512"
os_variant: "debian11"
cache_name: "debian-11-generic-amd64.qcow2"
package_manager: "apt"
family: "debian"
debian-12:
url: "https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-generic-amd64.qcow2"
checksum_url: "https://cloud.debian.org/images/cloud/bookworm/latest/SHA512SUMS"
checksum_type: "sha512"
os_variant: "debian12"
cache_name: "debian-12-generic-amd64.qcow2"
package_manager: "apt"
family: "debian"
# Ubuntu
ubuntu-20.04:
url: "https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img"
checksum_url: "https://cloud-images.ubuntu.com/focal/current/SHA256SUMS"
checksum_type: "sha256"
os_variant: "ubuntu20.04"
cache_name: "ubuntu-20.04-server-cloudimg-amd64.img"
package_manager: "apt"
family: "debian"
ubuntu-22.04:
url: "https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img"
checksum_url: "https://cloud-images.ubuntu.com/jammy/current/SHA256SUMS"
checksum_type: "sha256"
os_variant: "ubuntu22.04"
cache_name: "ubuntu-22.04-server-cloudimg-amd64.img"
package_manager: "apt"
family: "debian"
ubuntu-24.04:
url: "https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img"
checksum_url: "https://cloud-images.ubuntu.com/noble/current/SHA256SUMS"
checksum_type: "sha256"
os_variant: "ubuntu24.04"
cache_name: "ubuntu-24.04-server-cloudimg-amd64.img"
package_manager: "apt"
family: "debian"
# RHEL (requires subscription)
rhel-8:
url: "https://access.redhat.com/downloads/content/rhel/8/x86_64/latest/rhel-8-x86_64-kvm.qcow2"
os_variant: "rhel8.0"
cache_name: "rhel-8-x86_64-kvm.qcow2"
package_manager: "dnf"
family: "rhel"
note: "Requires Red Hat subscription and manual download"
rhel-9:
url: "https://access.redhat.com/downloads/content/rhel/9/x86_64/latest/rhel-9-x86_64-kvm.qcow2"
os_variant: "rhel9.0"
cache_name: "rhel-9-x86_64-kvm.qcow2"
package_manager: "dnf"
family: "rhel"
note: "Requires Red Hat subscription and manual download"
# CentOS Stream
centos-stream-8:
url: "https://cloud.centos.org/centos/8-stream/x86_64/images/CentOS-Stream-GenericCloud-8-latest.x86_64.qcow2"
checksum_url: "https://cloud.centos.org/centos/8-stream/x86_64/images/CHECKSUM"
checksum_type: "sha256"
os_variant: "centos-stream8"
cache_name: "centos-stream-8-genericcloud-amd64.qcow2"
package_manager: "dnf"
family: "rhel"
centos-stream-9:
url: "https://cloud.centos.org/centos/9-stream/x86_64/images/CentOS-Stream-GenericCloud-9-latest.x86_64.qcow2"
checksum_url: "https://cloud.centos.org/centos/9-stream/x86_64/images/CHECKSUM"
checksum_type: "sha256"
os_variant: "centos-stream9"
cache_name: "centos-stream-9-genericcloud-amd64.qcow2"
package_manager: "dnf"
family: "rhel"
# Rocky Linux
rocky-8:
url: "https://download.rockylinux.org/pub/rocky/8/images/x86_64/Rocky-8-GenericCloud-Base.latest.x86_64.qcow2"
checksum_url: "https://download.rockylinux.org/pub/rocky/8/images/x86_64/CHECKSUM"
checksum_type: "sha256"
os_variant: "rocky8"
cache_name: "rocky-8-genericcloud-amd64.qcow2"
package_manager: "dnf"
family: "rhel"
rocky-9:
url: "https://download.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud-Base.latest.x86_64.qcow2"
checksum_url: "https://download.rockylinux.org/pub/rocky/9/images/x86_64/CHECKSUM"
checksum_type: "sha256"
os_variant: "rocky9"
cache_name: "rocky-9-genericcloud-amd64.qcow2"
package_manager: "dnf"
family: "rhel"
# AlmaLinux
almalinux-8:
url: "https://repo.almalinux.org/almalinux/8/cloud/x86_64/images/AlmaLinux-8-GenericCloud-latest.x86_64.qcow2"
checksum_url: "https://repo.almalinux.org/almalinux/8/cloud/x86_64/images/CHECKSUM"
checksum_type: "sha256"
os_variant: "almalinux8"
cache_name: "almalinux-8-genericcloud-amd64.qcow2"
package_manager: "dnf"
family: "rhel"
almalinux-9:
url: "https://repo.almalinux.org/almalinux/9/cloud/x86_64/images/AlmaLinux-9-GenericCloud-latest.x86_64.qcow2"
checksum_url: "https://repo.almalinux.org/almalinux/9/cloud/x86_64/images/CHECKSUM"
checksum_type: "sha256"
os_variant: "almalinux9"
cache_name: "almalinux-9-genericcloud-amd64.qcow2"
package_manager: "dnf"
family: "rhel"
# SLES (requires registration)
sles-15:
url: "https://download.suse.com/Download?buildid=XXXXX"
os_variant: "sles15"
cache_name: "sles-15-genericcloud-amd64.qcow2"
package_manager: "zypper"
family: "suse"
note: "Requires SUSE subscription and manual download"
# openSUSE Leap
opensuse-leap-15.5:
url: "https://download.opensuse.org/distribution/leap/15.5/appliances/openSUSE-Leap-15.5-Minimal-VM.x86_64-Cloud.qcow2"
checksum_url: "https://download.opensuse.org/distribution/leap/15.5/appliances/openSUSE-Leap-15.5-Minimal-VM.x86_64-Cloud.qcow2.sha256"
checksum_type: "sha256"
os_variant: "opensuse15.5"
cache_name: "opensuse-leap-15.5-minimal-vm-amd64.qcow2"
package_manager: "zypper"
family: "suse"
opensuse-leap-15.6:
url: "https://download.opensuse.org/distribution/leap/15.6/appliances/openSUSE-Leap-15.6-Minimal-VM.x86_64-Cloud.qcow2"
checksum_url: "https://download.opensuse.org/distribution/leap/15.6/appliances/openSUSE-Leap-15.6-Minimal-VM.x86_64-Cloud.qcow2.sha256"
checksum_type: "sha256"
os_variant: "opensuse15.6"
cache_name: "opensuse-leap-15.6-minimal-vm-amd64.qcow2"
package_manager: "zypper"
family: "suse"
tasks:
# =========================================================================
# Validation and Setup
# =========================================================================
- name: Validate distribution selection
assert:
that:
- os_distribution is defined
- os_distribution in cloud_images.keys()
fail_msg: |
Invalid distribution '{{ os_distribution }}'.
Supported distributions: {{ cloud_images.keys() | list | join(', ') }}
tags: [validate, preflight]
- name: Set distribution facts
set_fact:
distro_config: "{{ cloud_images[os_distribution] }}"
image_cache_path: "/var/lib/libvirt/images/{{ cloud_images[os_distribution].cache_name }}"
tags: [always]
- name: Display deployment information
debug:
msg:
- "=== VM Deployment Configuration ==="
- "VM Name: {{ vm_name }}"
- "Distribution: {{ os_distribution }}"
- "OS Family: {{ distro_config.family }}"
- "Package Manager: {{ distro_config.package_manager }}"
- "vCPUs: {{ vm_vcpus }}"
- "Memory: {{ vm_memory_mb }} MB"
- "Disk: {{ vm_disk_size_gb }} GB"
tags: [validate, preflight]
- name: Check if VM already exists
command: virsh dominfo {{ vm_name }}
register: vm_exists
failed_when: false
changed_when: false
tags: [validate, preflight]
- name: Fail if VM already exists
fail:
msg: "VM '{{ vm_name }}' already exists on hypervisor. Please choose a different name or destroy the existing VM."
when: vm_exists.rc == 0
tags: [validate, preflight]
- name: Verify virtualization support
command: virt-host-validate qemu
register: virt_validation
failed_when: false
changed_when: false
tags: [validate, preflight]
- name: Display virtualization validation results
debug:
var: virt_validation.stdout_lines
tags: [validate, preflight]
# =========================================================================
# Package Installation
# =========================================================================
- name: Install required packages for VM deployment (Debian/Ubuntu)
apt:
name:
- libvirt-daemon-system
- libvirt-clients
- virtinst
- qemu-kvm
- qemu-utils
- cloud-image-utils
- genisoimage
- wget
- curl
- python3-libvirt
state: present
update_cache: yes
when: ansible_os_family == "Debian"
tags: [install]
- name: Install required packages for VM deployment (RHEL/CentOS)
dnf:
name:
- libvirt
- libvirt-client
- virt-install
- qemu-kvm
- qemu-img
- cloud-utils
- genisoimage
- wget
- curl
- python3-libvirt
state: present
when: ansible_os_family == "RedHat"
tags: [install]
- name: Ensure libvirtd service is running
systemd:
name: libvirtd
state: started
enabled: yes
tags: [install]
# =========================================================================
# Download Cloud Image
# =========================================================================
- name: Check if cloud image already exists
stat:
path: "{{ image_cache_path }}"
register: cloud_image_stat
tags: [download]
- name: Display image cache status
debug:
msg: "Cloud image {{ 'exists' if cloud_image_stat.stat.exists else 'not found' }}: {{ image_cache_path }}"
tags: [download]
- name: Check for manual download requirement
debug:
msg:
- "WARNING: {{ os_distribution }} requires manual download"
- "{{ distro_config.note | default('') }}"
- "Please download the image and place it at: {{ image_cache_path }}"
when:
- not cloud_image_stat.stat.exists
- distro_config.note is defined
tags: [download]
- name: Download cloud image
get_url:
url: "{{ distro_config.url }}"
dest: "{{ image_cache_path }}"
mode: '0644'
timeout: 1200
when:
- not cloud_image_stat.stat.exists
- distro_config.note is not defined
register: download_result
tags: [download]
- name: Download checksum file
get_url:
url: "{{ distro_config.checksum_url }}"
dest: "/tmp/{{ os_distribution }}-CHECKSUM"
mode: '0644'
when:
- distro_config.checksum_url is defined
- download_result is changed or cloud_image_stat.stat.exists
tags: [download, verify]
- name: Verify cloud image checksum (SHA512)
shell: |
cd /var/lib/libvirt/images
grep "{{ distro_config.cache_name }}" /tmp/{{ os_distribution }}-CHECKSUM | sha512sum -c -
register: checksum_result
changed_when: false
when:
- distro_config.checksum_type is defined
- distro_config.checksum_type == "sha512"
- distro_config.checksum_url is defined
tags: [verify]
- name: Verify cloud image checksum (SHA256)
shell: |
cd /var/lib/libvirt/images
grep "{{ distro_config.cache_name }}" /tmp/{{ os_distribution }}-CHECKSUM | sha256sum -c -
register: checksum_result
changed_when: false
when:
- distro_config.checksum_type is defined
- distro_config.checksum_type == "sha256"
- distro_config.checksum_url is defined
tags: [verify]
- name: Ensure image file exists before proceeding
stat:
path: "{{ image_cache_path }}"
register: final_image_check
failed_when: not final_image_check.stat.exists
tags: [verify]
# =========================================================================
# Create VM Disk
# =========================================================================
- name: Create VM disk from cloud image
command: >
qemu-img create -f qcow2 -F qcow2
-b {{ image_cache_path }}
{{ vm_disk_path }}
{{ vm_disk_size_gb }}G
args:
creates: "{{ vm_disk_path }}"
tags: [storage]
- name: Set proper permissions on VM disk
file:
path: "{{ vm_disk_path }}"
owner: libvirt-qemu
group: kvm
mode: '0600'
when: ansible_os_family == "Debian"
tags: [storage]
- name: Set proper permissions on VM disk (RHEL)
file:
path: "{{ vm_disk_path }}"
owner: qemu
group: qemu
mode: '0600'
when: ansible_os_family == "RedHat"
tags: [storage]
# =========================================================================
# Create Cloud-Init Configuration
# =========================================================================
- name: Create cloud-init directory
file:
path: /tmp/cloud-init-{{ vm_name }}
state: directory
mode: '0755'
tags: [cloud-init]
- name: Create cloud-init meta-data
copy:
content: |
instance-id: {{ vm_name }}
local-hostname: {{ vm_hostname }}
dest: /tmp/cloud-init-{{ vm_name }}/meta-data
mode: '0644'
tags: [cloud-init]
- name: Create cloud-init user-data for Debian/Ubuntu
copy:
content: |
#cloud-config
hostname: {{ vm_hostname }}
fqdn: {{ vm_hostname }}.{{ vm_domain }}
manage_etc_hosts: true
# Create ansible user with sudo privileges
users:
- name: ansible
groups: sudo
shell: /bin/bash
sudo: ['ALL=(ALL) NOPASSWD:ALL']
ssh_authorized_keys:
- {{ ansible_user_ssh_key }}
- name: root
lock_passwd: false
# Set root password (for emergency console access)
chpasswd:
list: |
root:ChangeMe123!
expire: false
# SSH configuration
ssh_pwauth: false
disable_root: false
# Install essential packages per CLAUDE.md guidelines
packages:
- sudo
- vim
- htop
- tmux
- curl
- wget
- rsync
- git
- python3
- python3-pip
- jq
- bc
- aide
- auditd
- chrony
- ufw
- lvm2
- cloud-guest-utils
- parted
- unattended-upgrades
- apt-listchanges
# Security configuration files
write_files:
- path: /etc/ssh/sshd_config.d/99-security.conf
content: |
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
MaxAuthTries 3
MaxSessions 10
ClientAliveInterval 300
ClientAliveCountMax 2
permissions: '0644'
- path: /etc/sudoers.d/ansible
content: |
ansible ALL=(ALL) NOPASSWD:ALL
permissions: '0440'
- path: /etc/apt/apt.conf.d/50unattended-upgrades
content: |
Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}-security";
};
Unattended-Upgrade::AutoFixInterruptedDpkg "true";
Unattended-Upgrade::MinimalSteps "true";
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
Unattended-Upgrade::Remove-Unused-Dependencies "true";
Unattended-Upgrade::Automatic-Reboot "false";
permissions: '0644'
- path: /etc/apt/apt.conf.d/20auto-upgrades
content: |
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
APT::Periodic::AutocleanInterval "7";
permissions: '0644'
# System configuration commands
runcmd:
- systemctl enable ssh
- systemctl restart ssh
- systemctl enable chrony
- systemctl start chrony
- ufw --force enable
- ufw allow ssh
- systemctl enable auditd
- systemctl start auditd
- growpart /dev/vda 1 || true
- resize2fs /dev/vda1 || true
package_update: true
package_upgrade: true
package_reboot_if_required: false
timezone: UTC
locale: en_US.UTF-8
output:
all: '| tee -a /var/log/cloud-init-output.log'
final_message: "{{ os_distribution }} VM deployment completed. System is ready after $UPTIME seconds."
dest: /tmp/cloud-init-{{ vm_name }}/user-data
mode: '0644'
when: distro_config.family == "debian"
tags: [cloud-init]
- name: Create cloud-init user-data for RHEL/CentOS/Rocky/Alma
copy:
content: |
#cloud-config
hostname: {{ vm_hostname }}
fqdn: {{ vm_hostname }}.{{ vm_domain }}
manage_etc_hosts: true
# Create ansible user with sudo privileges
users:
- name: ansible
groups: wheel
shell: /bin/bash
sudo: ['ALL=(ALL) NOPASSWD:ALL']
ssh_authorized_keys:
- {{ ansible_user_ssh_key }}
- name: root
lock_passwd: false
# Set root password (for emergency console access)
chpasswd:
list: |
root:ChangeMe123!
expire: false
# SSH configuration
ssh_pwauth: false
disable_root: false
# Install essential packages per CLAUDE.md guidelines
packages:
- sudo
- vim
- htop
- tmux
- curl
- wget
- rsync
- git
- python3
- python3-pip
- jq
- bc
- aide
- audit
- chrony
- firewalld
- lvm2
- cloud-utils-growpart
- gdisk
- dnf-automatic
- policycoreutils-python-utils
# Security configuration files
write_files:
- path: /etc/ssh/sshd_config.d/99-security.conf
content: |
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
MaxAuthTries 3
MaxSessions 10
ClientAliveInterval 300
ClientAliveCountMax 2
permissions: '0644'
- path: /etc/sudoers.d/ansible
content: |
ansible ALL=(ALL) NOPASSWD:ALL
permissions: '0440'
- path: /etc/dnf/automatic.conf
content: |
[commands]
upgrade_type = security
download_updates = yes
apply_updates = yes
[emitters]
emit_via = stdio
[email]
email_from = root@{{ vm_hostname }}.{{ vm_domain }}
[base]
debuglevel = 1
permissions: '0644'
# System configuration commands
runcmd:
- systemctl enable sshd
- systemctl restart sshd
- systemctl enable chronyd
- systemctl start chronyd
- systemctl enable firewalld
- systemctl start firewalld
- firewall-cmd --permanent --add-service=ssh
- firewall-cmd --reload
- systemctl enable auditd
- systemctl start auditd
- systemctl enable dnf-automatic.timer
- systemctl start dnf-automatic.timer
- setenforce 1
- sed -i 's/^SELINUX=.*/SELINUX=enforcing/' /etc/selinux/config
- growpart /dev/vda 1 || true
- xfs_growfs / || resize2fs /dev/vda1 || true
package_update: true
package_upgrade: true
package_reboot_if_required: false
timezone: UTC
locale: en_US.UTF-8
output:
all: '| tee -a /var/log/cloud-init-output.log'
final_message: "{{ os_distribution }} VM deployment completed. System is ready after $UPTIME seconds."
dest: /tmp/cloud-init-{{ vm_name }}/user-data
mode: '0644'
when: distro_config.family == "rhel"
tags: [cloud-init]
- name: Create cloud-init user-data for SUSE/openSUSE
copy:
content: |
#cloud-config
hostname: {{ vm_hostname }}
fqdn: {{ vm_hostname }}.{{ vm_domain }}
manage_etc_hosts: true
# Create ansible user with sudo privileges
users:
- name: ansible
groups: wheel
shell: /bin/bash
sudo: ['ALL=(ALL) NOPASSWD:ALL']
ssh_authorized_keys:
- {{ ansible_user_ssh_key }}
- name: root
lock_passwd: false
# Set root password (for emergency console access)
chpasswd:
list: |
root:ChangeMe123!
expire: false
# SSH configuration
ssh_pwauth: false
disable_root: false
# Install essential packages
packages:
- sudo
- vim
- htop
- tmux
- curl
- wget
- rsync
- git
- python3
- python3-pip
- jq
- bc
- aide
- audit
- chrony
- firewalld
- lvm2
- cloud-utils-growpart
- gdisk
# Security configuration files
write_files:
- path: /etc/ssh/sshd_config.d/99-security.conf
content: |
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
MaxAuthTries 3
MaxSessions 10
ClientAliveInterval 300
ClientAliveCountMax 2
permissions: '0644'
- path: /etc/sudoers.d/ansible
content: |
ansible ALL=(ALL) NOPASSWD:ALL
permissions: '0440'
# System configuration commands
runcmd:
- systemctl enable sshd
- systemctl restart sshd
- systemctl enable chronyd
- systemctl start chronyd
- systemctl enable firewalld
- systemctl start firewalld
- firewall-cmd --permanent --add-service=ssh
- firewall-cmd --reload
- systemctl enable auditd
- systemctl start auditd
- growpart /dev/vda 1 || true
- xfs_growfs / || resize2fs /dev/vda1 || btrfs filesystem resize max / || true
package_update: true
package_upgrade: true
package_reboot_if_required: false
timezone: UTC
locale: en_US.UTF-8
output:
all: '| tee -a /var/log/cloud-init-output.log'
final_message: "{{ os_distribution }} VM deployment completed. System is ready after $UPTIME seconds."
dest: /tmp/cloud-init-{{ vm_name }}/user-data
mode: '0644'
when: distro_config.family == "suse"
tags: [cloud-init]
- name: Create cloud-init ISO
command: >
genisoimage -output {{ cloud_init_iso_path }}
-volid cidata -joliet -rock
/tmp/cloud-init-{{ vm_name }}/user-data
/tmp/cloud-init-{{ vm_name }}/meta-data
args:
creates: "{{ cloud_init_iso_path }}"
tags: [cloud-init]
- name: Set proper permissions on cloud-init ISO (Debian/Ubuntu)
file:
path: "{{ cloud_init_iso_path }}"
owner: libvirt-qemu
group: kvm
mode: '0644'
when: ansible_os_family == "Debian"
tags: [cloud-init]
- name: Set proper permissions on cloud-init ISO (RHEL)
file:
path: "{{ cloud_init_iso_path }}"
owner: qemu
group: qemu
mode: '0644'
when: ansible_os_family == "RedHat"
tags: [cloud-init]
# =========================================================================
# Create and Start VM
# =========================================================================
- name: Create VM using virt-install
command: >
virt-install
--name {{ vm_name }}
--memory {{ vm_memory_mb }}
--vcpus {{ vm_vcpus }}
--disk path={{ vm_disk_path }},format=qcow2,bus=virtio
--disk path={{ cloud_init_iso_path }},device=cdrom
--network network={{ vm_network }},model=virtio
--os-variant {{ distro_config.os_variant }}
--graphics none
--console pty,target_type=serial
--import
--noautoconsole
register: vm_create
tags: [deploy]
- name: Wait for VM to boot and cloud-init to complete
pause:
seconds: 90
prompt: "Waiting for VM to boot and cloud-init to complete configuration..."
tags: [deploy]
- name: Get VM IP address
shell: |
virsh domifaddr {{ vm_name }} | grep -oP '(\d{1,3}\.){3}\d{1,3}' | head -1
register: vm_ip
retries: 15
delay: 10
until: vm_ip.stdout != ""
changed_when: false
tags: [deploy, validate]
- name: Display VM information
debug:
msg:
- "=== VM Deployment Successful ==="
- "VM Name: {{ vm_name }}"
- "Distribution: {{ os_distribution }}"
- "IP Address: {{ vm_ip.stdout }}"
- "vCPUs: {{ vm_vcpus }}"
- "Memory: {{ vm_memory_mb }} MB"
- "Disk: {{ vm_disk_size_gb }} GB"
- "OS Variant: {{ distro_config.os_variant }}"
- "Package Manager: {{ distro_config.package_manager }}"
- "Access: ssh ansible@{{ vm_ip.stdout }}"
- ""
- "Add to inventory:"
- " {{ vm_name }}:"
- " ansible_host: {{ vm_ip.stdout }}"
- " ansible_user: ansible"
- " ansible_ssh_common_args: '-o ProxyJump=grokbox -o StrictHostKeyChecking=accept-new'"
- " os_distribution: {{ os_distribution }}"
- " os_family: {{ distro_config.family }}"
tags: [deploy, validate]
# =========================================================================
# Validation
# =========================================================================
- name: Test SSH connectivity to new VM
wait_for:
host: "{{ vm_ip.stdout }}"
port: 22
timeout: 300
state: started
tags: [validate]
- name: Get VM details
command: virsh dominfo {{ vm_name }}
register: vm_details
changed_when: false
tags: [validate]
- name: Display VM details
debug:
var: vm_details.stdout_lines
tags: [validate]
# =========================================================================
# Cleanup
# =========================================================================
- name: Remove temporary cloud-init directory
file:
path: /tmp/cloud-init-{{ vm_name }}
state: absent
tags: [cleanup]
- name: Remove downloaded checksums
file:
path: /tmp/{{ os_distribution }}-CHECKSUM
state: absent
tags: [cleanup]
# =============================================================================
# Post-Deployment Validation (Optional)
# =============================================================================
- name: Validate deployed VM
hosts: "{{ hostvars['grokbox']['vm_ip'].stdout }}"
gather_facts: yes
become: yes
vars:
ansible_user: ansible
ansible_ssh_common_args: '-o ProxyJump=grokbox -o StrictHostKeyChecking=accept-new'
tasks:
- name: Wait for cloud-init to complete
command: cloud-init status --wait
changed_when: false
failed_when: false
tags: [validate]
- name: Gather system facts
setup:
tags: [validate]
- name: Display system information
debug:
msg:
- "=== System Information ==="
- "OS: {{ ansible_distribution }} {{ ansible_distribution_version }}"
- "Kernel: {{ ansible_kernel }}"
- "Architecture: {{ ansible_architecture }}"
- "Hostname: {{ ansible_hostname }}"
- "FQDN: {{ ansible_fqdn }}"
- "Python: {{ ansible_python_version }}"
- "Package Manager: {{ ansible_pkg_mgr }}"
tags: [validate]
- name: Gather disk usage
command: df -h
register: disk_usage
changed_when: false
tags: [validate]
- name: Display disk usage
debug:
var: disk_usage.stdout_lines
tags: [validate]
- name: Gather memory usage
command: free -h
register: memory_usage
changed_when: false
tags: [validate]
- name: Display memory usage
debug:
var: memory_usage.stdout_lines
tags: [validate]
- name: Check SELinux status (RHEL family)
command: getenforce
register: selinux_status
changed_when: false
failed_when: false
when: ansible_os_family == "RedHat"
tags: [validate]
- name: Display SELinux status
debug:
msg: "SELinux Status: {{ selinux_status.stdout }}"
when: ansible_os_family == "RedHat"
tags: [validate]