--- # ============================================================================= # 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]