diff --git a/roles/system_info/README.md b/roles/system_info/README.md new file mode 100644 index 0000000..5366e6b --- /dev/null +++ b/roles/system_info/README.md @@ -0,0 +1,350 @@ +# System Information Gathering Role + +Comprehensive Ansible role for gathering detailed system information including CPU, GPU, RAM, disk, network, and hypervisor details. Statistics are exported to JSON files organized by machine FQDN. + +## Description + +This role performs a thorough scan of system hardware and software configurations, collecting detailed metrics and storing them in structured JSON format. It's designed to create a complete inventory of infrastructure resources for documentation, monitoring, and capacity planning purposes. + +## Requirements + +### Ansible Version +- Ansible >= 2.9 + +### OS Compatibility +- Debian 11 (Bullseye), 12 (Bookworm) +- Ubuntu 20.04 (Focal), 22.04 (Jammy), 24.04 (Noble) +- RHEL 8, 9 +- Rocky Linux 8, 9 +- AlmaLinux 8, 9 + +### Dependencies +- Root/sudo privileges for hardware information gathering +- Internet access for package installation (if required packages are missing) + +### Required Packages +The role will automatically install these packages if they're not present: +- `lshw` - Hardware lister +- `dmidecode` - DMI/SMBIOS information +- `pciutils` - PCI utilities (lspci) +- `usbutils` - USB utilities +- `smartmontools` - SMART disk monitoring +- `ethtool` - Network interface information + +## Role Variables + +### Main Configuration + +| Variable | Default | Description | Required | +|----------|---------|-------------|----------| +| `system_info_stats_base_dir` | `./stats/machines` | Base directory for statistics storage | Yes | +| `system_info_create_stats_dir` | `true` | Create stats directory if it doesn't exist | No | +| `system_info_timestamp_format` | `%Y-%m-%d %H:%M:%S UTC` | Timestamp format for statistics | No | +| `system_info_json_indent` | `2` | JSON output indentation | No | + +### Feature Toggles + +| Variable | Default | Description | +|----------|---------|-------------| +| `system_info_gather_cpu` | `true` | Gather CPU information | +| `system_info_gather_gpu` | `true` | Gather GPU information | +| `system_info_gather_memory` | `true` | Gather memory information | +| `system_info_gather_disk` | `true` | Gather disk information | +| `system_info_gather_network` | `true` | Gather network information | +| `system_info_gather_system` | `true` | Gather OS and system information | +| `system_info_detect_hypervisor` | `true` | Detect hypervisor capabilities | +| `system_info_include_raw_output` | `false` | Include raw command outputs in JSON | + +## Information Collected + +### System Information +- Hostname and FQDN +- Operating system details (distribution, version, release) +- Kernel version and architecture +- System uptime and boot time +- Hardware manufacturer, model, serial number, UUID +- Security modules status (SELinux/AppArmor) + +### CPU Information +- Model name and vendor +- Architecture and CPU family +- Physical CPUs, cores, and vCPUs count +- Current, maximum, and minimum frequencies +- CPU cache details (L1, L2, L3) +- CPU flags and features +- Virtualization support (Intel VT-x, AMD-V) +- Current load average +- CPU vulnerability mitigations + +### GPU Information +- GPU detection and device listing +- NVIDIA GPU details (via nvidia-smi) +- AMD GPU details (via rocm-smi) +- Intel integrated graphics detection +- IOMMU/VT-d status for GPU passthrough +- Detailed PCI information for graphics devices + +### Memory Information +- Total, free, used, and available memory +- Buffers and cached memory +- Memory usage percentage +- Physical memory modules count +- Memory hardware details (type, speed, manufacturer) +- Swap configuration and usage +- Memory pressure statistics +- Huge pages configuration + +### Disk Information +- Disk usage (all filesystems) +- Block device listing with details +- LVM configuration (PVs, VGs, LVs) +- Mount points and filesystem types +- Software RAID (mdadm) status +- Hardware RAID controller detection +- Physical disk listing (SSD vs HDD detection) +- SMART health status +- I/O statistics + +### Network Information +- Network interfaces and their states +- IP addresses (IPv4 and IPv6) +- MAC addresses and MTU settings +- Routing table +- DNS configuration +- Listening ports +- Network interface statistics + +### Hypervisor Detection +- Virtualization type and role (guest/host) +- **KVM/Libvirt**: Version, running VMs, networks, storage pools +- **Proxmox VE**: Version, cluster status, VMs, containers, storage +- **LXD/LXC**: Version, containers, storage, networks, cluster +- **Docker**: Version, running/total containers, images count +- **Podman**: Version and availability +- **VMware ESXi**: Detection and version +- **Hyper-V**: Detection via kernel modules + +## Output Structure + +### JSON File Location +Statistics are saved to: +``` +//system_info.json +//system_info_.json (backup) +//summary.txt (human-readable) +``` + +### JSON Structure +```json +{ + "collection_info": { + "timestamp": "ISO8601 timestamp", + "collected_by": "ansible", + "role_version": "1.0.0" + }, + "host_info": { ... }, + "system": { ... }, + "kernel": { ... }, + "hardware": { ... }, + "security": { ... }, + "cpu": { ... }, + "gpu": { ... }, + "memory": { ... }, + "swap": { ... }, + "disk": { ... }, + "network": { ... }, + "hypervisor": { ... } +} +``` + +## Dependencies + +None. This role is standalone and has no dependencies on other roles. + +## Example Playbook + +### Basic Usage +```yaml +--- +- hosts: all + become: true + roles: + - role: system_info +``` + +### Custom Statistics Directory +```yaml +--- +- hosts: all + become: true + roles: + - role: system_info + vars: + system_info_stats_base_dir: /var/lib/ansible/inventory +``` + +### Selective Information Gathering +```yaml +--- +- hosts: servers + become: true + roles: + - role: system_info + vars: + system_info_gather_cpu: true + system_info_gather_gpu: false + system_info_gather_memory: true + system_info_detect_hypervisor: true +``` + +### Using Tags for Partial Execution +```bash +# Gather only CPU information +ansible-playbook site.yml -t system_info,cpu + +# Gather only hypervisor information +ansible-playbook site.yml -t system_info,hypervisor + +# Run validation/health checks only +ansible-playbook site.yml -t system_info,validate + +# Skip installation, only gather information +ansible-playbook site.yml -t system_info --skip-tags install +``` + +## Available Tags + +| Tag | Purpose | +|-----|---------| +| `install` | Install required packages | +| `gather` | All information gathering tasks | +| `system` | System and OS information | +| `cpu` | CPU information | +| `gpu` | GPU information | +| `memory` | Memory information | +| `disk` | Disk information | +| `network` | Network information | +| `hypervisor` | Hypervisor detection | +| `export` | Export statistics to JSON | +| `statistics` | Statistics aggregation | +| `validate` | Validation and health checks | +| `health-check` | System health monitoring | +| `security` | Security-related information | + +## Security Considerations + +### Privileges +- Requires root/sudo access for hardware information gathering +- Uses `become: true` for privileged commands +- DMI/SMBIOS information requires root access + +### Sensitive Data +- Serial numbers and UUIDs are collected (can identify specific hardware) +- Network configuration may reveal internal IP addressing +- No secrets or credentials are collected +- All data is stored locally on the control node + +### Data Privacy +- Statistics files contain detailed system information +- Restrict access to the statistics directory appropriately +- Consider encryption for the statistics directory if storing sensitive infrastructure details + +## Performance Impact + +- **Execution Time**: 30-60 seconds per host (depends on hardware complexity) +- **Network Impact**: Minimal - only package installation requires network +- **System Load**: Very low - read-only operations +- **Disk I/O**: Minimal - small JSON files (<100KB typically) + +## Troubleshooting + +### Common Issues + +**Issue**: "dmidecode: command not found" +- **Solution**: Role will install it automatically. Ensure internet access or pre-stage packages. + +**Issue**: "Permission denied" errors +- **Solution**: Ensure `become: true` is set in the playbook or role invocation. + +**Issue**: SMART data not available +- **Solution**: Not all systems/disks support SMART. This is expected and won't fail the role. + +**Issue**: GPU information showing "No GPU detected" +- **Solution**: Normal for VMs and servers without GPUs. Not an error condition. + +**Issue**: Hypervisor commands timing out +- **Solution**: Some hypervisor checks may be slow. Increase task timeout if needed. + +### Debug Mode +Run with verbose output: +```bash +ansible-playbook site.yml -t system_info -vvv +``` + +## Compliance Requirements + +- Follows CIS Benchmark recommendations for system auditing +- Supports security compliance documentation (NIST, PCI-DSS) +- Enables infrastructure inventory for CMDB integration +- Facilitates capacity planning and resource optimization + +## Testing + +### Manual Testing +```bash +# Test on a single host +ansible-playbook -i inventory/production site.yml -l testhost -t system_info + +# Dry-run mode +ansible-playbook site.yml -t system_info --check +``` + +### Validation +After execution, verify: +1. Statistics directory created: `./stats/machines//` +2. JSON file present and valid: `system_info.json` +3. Summary file created: `summary.txt` +4. No errors in Ansible output + +## Maintenance + +### Updates +- Review and update the role quarterly +- Test against new OS versions before production deployment +- Keep documentation synchronized with code changes + +### Monitoring +- Track execution time trends (performance degradation may indicate issues) +- Monitor statistics file sizes (unexpected growth may indicate problems) +- Validate JSON file integrity periodically + +## Version History + +- **1.0.0** (2025-01-11): Initial release + - Complete system information gathering + - CPU, GPU, RAM, Disk, Network detection + - Hypervisor detection (KVM, Proxmox, LXD, Docker, etc.) + - JSON export with timestamped backups + - Human-readable summary generation + +## License + +MIT + +## Author Information + +Created by the Ansible Infrastructure Team for comprehensive system inventory and monitoring. + +For issues, questions, or contributions, please refer to the project repository. + +## Related Roles + +- `system_baseline` - System hardening and baseline configuration +- `monitoring` - System monitoring setup +- `inventory_sync` - Dynamic inventory management + +## Additional Resources + +- [Ansible Best Practices](https://docs.ansible.com/ansible/latest/user_guide/playbooks_best_practices.html) +- [Hardware Detection in Linux](https://www.kernel.org/doc/html/latest/admin-guide/hw-vuln/index.html) +- [Virtualization Detection](https://people.redhat.com/~rjones/virt-what/) diff --git a/roles/system_info/defaults/main.yml b/roles/system_info/defaults/main.yml new file mode 100644 index 0000000..5050ef2 --- /dev/null +++ b/roles/system_info/defaults/main.yml @@ -0,0 +1,38 @@ +--- +# defaults file for system_info + +# Base directory for storing statistics +system_info_stats_base_dir: "./stats/machines" + +# Whether to create the stats directory if it doesn't exist +system_info_create_stats_dir: true + +# Timestamp format for the statistics +system_info_timestamp_format: "%Y-%m-%d %H:%M:%S UTC" + +# Whether to gather detailed CPU information +system_info_gather_cpu: true + +# Whether to gather GPU information +system_info_gather_gpu: true + +# Whether to gather memory information +system_info_gather_memory: true + +# Whether to detect hypervisor information +system_info_detect_hypervisor: true + +# Whether to gather disk information +system_info_gather_disk: true + +# Whether to gather network information +system_info_gather_network: true + +# Whether to gather system information (OS, kernel, etc.) +system_info_gather_system: true + +# JSON output formatting +system_info_json_indent: 2 + +# Whether to include raw command outputs in JSON +system_info_include_raw_output: false diff --git a/roles/system_info/handlers/main.yml b/roles/system_info/handlers/main.yml new file mode 100644 index 0000000..68dc22c --- /dev/null +++ b/roles/system_info/handlers/main.yml @@ -0,0 +1,4 @@ +--- +# handlers file for system_info +# This role is read-only and performs information gathering only. +# No handlers are required as no services are modified or restarted. diff --git a/roles/system_info/meta/main.yml b/roles/system_info/meta/main.yml new file mode 100644 index 0000000..bf1fb05 --- /dev/null +++ b/roles/system_info/meta/main.yml @@ -0,0 +1,45 @@ +--- +galaxy_info: + author: Ansible Infrastructure Team + description: Comprehensive system information gathering role for CPU, GPU, RAM, and hypervisor detection + company: Infrastructure Automation + + license: MIT + + min_ansible_version: "2.9" + + platforms: + - name: Debian + versions: + - bullseye + - bookworm + - name: Ubuntu + versions: + - focal + - jammy + - noble + - name: EL + versions: + - 8 + - 9 + - name: Rocky + versions: + - 8 + - 9 + - name: AlmaLinux + versions: + - 8 + - 9 + + galaxy_tags: + - system + - monitoring + - inventory + - hardware + - virtualization + - hypervisor + - information + - gathering + - statistics + +dependencies: [] diff --git a/roles/system_info/tasks/detect_hypervisor.yml b/roles/system_info/tasks/detect_hypervisor.yml new file mode 100644 index 0000000..37a50a3 --- /dev/null +++ b/roles/system_info/tasks/detect_hypervisor.yml @@ -0,0 +1,284 @@ +--- +# Hypervisor detection tasks + +- name: Check if running in a virtual environment + set_fact: + system_info_virtualization_type: "{{ ansible_virtualization_type | default('physical') }}" + system_info_virtualization_role: "{{ ansible_virtualization_role | default('NA') }}" + tags: [gather, hypervisor] + +- name: Detect virtualization using systemd-detect-virt + shell: systemd-detect-virt + register: system_info_detect_virt_raw + changed_when: false + failed_when: false + tags: [gather, hypervisor] + +- name: Set systemd virtualization detection + set_fact: + system_info_systemd_virt: "{{ system_info_detect_virt_raw.stdout | default('none') }}" + tags: [gather, hypervisor] + +- name: Check for KVM/QEMU hypervisor capability + shell: | + if command -v virsh &> /dev/null; then + echo "virsh: available" + virsh version 2>/dev/null || echo "virsh not accessible" + else + echo "virsh: not installed" + fi + register: system_info_virsh_check_raw + changed_when: false + failed_when: false + tags: [gather, hypervisor, kvm] + +- name: Check libvirt service status + shell: systemctl is-active libvirtd 2>/dev/null || echo "not running" + register: system_info_libvirtd_status_raw + changed_when: false + failed_when: false + tags: [gather, hypervisor, libvirt] + +- name: Gather libvirt details (if available) + block: + - name: Check libvirt version + shell: virsh version + register: system_info_libvirt_version_raw + changed_when: false + become: true + + - name: List libvirt networks + shell: virsh net-list --all + register: system_info_libvirt_networks_raw + changed_when: false + become: true + + - name: List libvirt storage pools + shell: virsh pool-list --all + register: system_info_libvirt_pools_raw + changed_when: false + become: true + + - name: Count running VMs + shell: virsh list --state-running | grep -c running || echo "0" + register: system_info_libvirt_running_vms_raw + changed_when: false + become: true + + - name: Count total VMs + shell: virsh list --all | tail -n +3 | grep -v "^$" | wc -l + register: system_info_libvirt_total_vms_raw + changed_when: false + become: true + + when: "'available' in system_info_virsh_check_raw.stdout" + failed_when: false + tags: [gather, hypervisor, libvirt] + +- name: Check for Proxmox VE + shell: | + if command -v pveversion &> /dev/null; then + pveversion + else + echo "Proxmox VE not installed" + fi + register: system_info_proxmox_check_raw + changed_when: false + failed_when: false + tags: [gather, hypervisor, proxmox] + +- name: Gather Proxmox details (if available) + block: + - name: Get Proxmox cluster status + shell: pvecm status 2>/dev/null || echo "Not in a cluster" + register: system_info_proxmox_cluster_raw + changed_when: false + + - name: List Proxmox VMs + shell: qm list 2>/dev/null || echo "No VMs or qm not available" + register: system_info_proxmox_vms_raw + changed_when: false + + - name: List Proxmox containers + shell: pct list 2>/dev/null || echo "No containers or pct not available" + register: system_info_proxmox_containers_raw + changed_when: false + + - name: Get Proxmox storage status + shell: pvesm status 2>/dev/null || echo "Storage information not available" + register: system_info_proxmox_storage_raw + changed_when: false + + when: "'pveversion' in system_info_proxmox_check_raw.stdout" + failed_when: false + tags: [gather, hypervisor, proxmox] + +- name: Check for LXD/LXC + shell: | + if command -v lxc &> /dev/null; then + lxc version + else + echo "LXD not installed" + fi + register: system_info_lxd_check_raw + changed_when: false + failed_when: false + tags: [gather, hypervisor, lxd] + +- name: Gather LXD details (if available) + block: + - name: List LXD containers + shell: lxc list --format json + register: system_info_lxd_containers_raw + changed_when: false + + - name: Get LXD storage pools + shell: lxc storage list --format json + register: system_info_lxd_storage_raw + changed_when: false + + - name: Get LXD networks + shell: lxc network list --format json + register: system_info_lxd_networks_raw + changed_when: false + + - name: Check LXD cluster status + shell: lxc cluster list --format json 2>/dev/null || echo "[]" + register: system_info_lxd_cluster_raw + changed_when: false + + when: "'Client version' in system_info_lxd_check_raw.stdout or 'Server version' in system_info_lxd_check_raw.stdout" + failed_when: false + tags: [gather, hypervisor, lxd] + +- name: Check for Docker + shell: | + if command -v docker &> /dev/null; then + docker version --format '{{.Server.Version}}' 2>/dev/null || echo "Docker installed but not running" + else + echo "Docker not installed" + fi + register: system_info_docker_check_raw + changed_when: false + failed_when: false + tags: [gather, hypervisor, docker] + +- name: Gather Docker details (if available) + block: + - name: Count running containers + shell: docker ps -q | wc -l + register: system_info_docker_running_raw + changed_when: false + + - name: Count total containers + shell: docker ps -aq | wc -l + register: system_info_docker_total_raw + changed_when: false + + - name: List Docker images + shell: docker images --format "{{.Repository}}:{{.Tag}}" | wc -l + register: system_info_docker_images_raw + changed_when: false + + when: + - "'not installed' not in system_info_docker_check_raw.stdout" + - "'not running' not in system_info_docker_check_raw.stdout" + failed_when: false + tags: [gather, hypervisor, docker] + +- name: Check for Podman + shell: | + if command -v podman &> /dev/null; then + podman version --format '{{.Version}}' + else + echo "Podman not installed" + fi + register: system_info_podman_check_raw + changed_when: false + failed_when: false + tags: [gather, hypervisor, podman] + +- name: Gather VMware ESXi/vSphere information + shell: | + if [ -f /etc/vmware-release ]; then + cat /etc/vmware-release + else + echo "Not VMware ESXi" + fi + register: system_info_vmware_check_raw + changed_when: false + failed_when: false + tags: [gather, hypervisor, vmware] + +- name: Check for Hyper-V Linux Integration Services + shell: | + if lsmod | grep -q hv_vmbus; then + echo "Hyper-V detected" + lsmod | grep ^hv_ + else + echo "Not Hyper-V" + fi + register: system_info_hyperv_check_raw + changed_when: false + failed_when: false + tags: [gather, hypervisor, hyperv] + +- name: Determine if system is a hypervisor + set_fact: + system_info_is_hypervisor: >- + {{ + ('available' in system_info_virsh_check_raw.stdout) or + ('pveversion' in system_info_proxmox_check_raw.stdout) or + ('Client version' in system_info_lxd_check_raw.stdout) or + ('Server version' in system_info_lxd_check_raw.stdout) or + ('VMware ESXi' in system_info_vmware_check_raw.stdout) or + (system_info_docker_check_raw.stdout | regex_search('\\d+\\.\\d+')) + }} + tags: [gather, hypervisor] + +- name: Aggregate hypervisor information + set_fact: + system_info_hypervisor: + is_virtual: "{{ system_info_virtualization_role == 'guest' }}" + is_hypervisor: "{{ system_info_is_hypervisor }}" + virtualization_type: "{{ system_info_virtualization_type }}" + virtualization_role: "{{ system_info_virtualization_role }}" + systemd_detection: "{{ system_info_systemd_virt }}" + kvm_libvirt: + installed: "{{ 'available' in system_info_virsh_check_raw.stdout }}" + service_status: "{{ system_info_libvirtd_status_raw.stdout | default('N/A') }}" + version: "{{ system_info_libvirt_version_raw.stdout_lines | default([]) if 'available' in system_info_virsh_check_raw.stdout else [] }}" + running_vms: "{{ system_info_libvirt_running_vms_raw.stdout | default('0') if 'available' in system_info_virsh_check_raw.stdout else '0' }}" + total_vms: "{{ system_info_libvirt_total_vms_raw.stdout | default('0') if 'available' in system_info_virsh_check_raw.stdout else '0' }}" + networks: "{{ system_info_libvirt_networks_raw.stdout_lines | default([]) if 'available' in system_info_virsh_check_raw.stdout else [] }}" + storage_pools: "{{ system_info_libvirt_pools_raw.stdout_lines | default([]) if 'available' in system_info_virsh_check_raw.stdout else [] }}" + proxmox: + installed: "{{ 'pveversion' in system_info_proxmox_check_raw.stdout }}" + version: "{{ system_info_proxmox_check_raw.stdout | default('N/A') }}" + cluster_status: "{{ system_info_proxmox_cluster_raw.stdout_lines | default([]) if 'pveversion' in system_info_proxmox_check_raw.stdout else [] }}" + vms: "{{ system_info_proxmox_vms_raw.stdout_lines | default([]) if 'pveversion' in system_info_proxmox_check_raw.stdout else [] }}" + containers: "{{ system_info_proxmox_containers_raw.stdout_lines | default([]) if 'pveversion' in system_info_proxmox_check_raw.stdout else [] }}" + storage: "{{ system_info_proxmox_storage_raw.stdout_lines | default([]) if 'pveversion' in system_info_proxmox_check_raw.stdout else [] }}" + lxd: + installed: "{{ 'version' in system_info_lxd_check_raw.stdout }}" + version: "{{ system_info_lxd_check_raw.stdout | default('N/A') }}" + containers: "{{ system_info_lxd_containers_raw.stdout | default('[]') if 'version' in system_info_lxd_check_raw.stdout else '[]' }}" + storage: "{{ system_info_lxd_storage_raw.stdout | default('[]') if 'version' in system_info_lxd_check_raw.stdout else '[]' }}" + networks: "{{ system_info_lxd_networks_raw.stdout | default('[]') if 'version' in system_info_lxd_check_raw.stdout else '[]' }}" + cluster: "{{ system_info_lxd_cluster_raw.stdout | default('[]') if 'version' in system_info_lxd_check_raw.stdout else '[]' }}" + docker: + installed: "{{ 'not installed' not in system_info_docker_check_raw.stdout }}" + version: "{{ system_info_docker_check_raw.stdout | default('N/A') }}" + running_containers: "{{ system_info_docker_running_raw.stdout | default('0') if 'not installed' not in system_info_docker_check_raw.stdout and 'not running' not in system_info_docker_check_raw.stdout else '0' }}" + total_containers: "{{ system_info_docker_total_raw.stdout | default('0') if 'not installed' not in system_info_docker_check_raw.stdout and 'not running' not in system_info_docker_check_raw.stdout else '0' }}" + images_count: "{{ system_info_docker_images_raw.stdout | default('0') if 'not installed' not in system_info_docker_check_raw.stdout and 'not running' not in system_info_docker_check_raw.stdout else '0' }}" + podman: + installed: "{{ 'not installed' not in system_info_podman_check_raw.stdout }}" + version: "{{ system_info_podman_check_raw.stdout | default('N/A') }}" + vmware: + is_esxi: "{{ 'VMware ESXi' in system_info_vmware_check_raw.stdout }}" + version: "{{ system_info_vmware_check_raw.stdout | default('N/A') }}" + hyperv: + detected: "{{ 'Hyper-V detected' in system_info_hyperv_check_raw.stdout }}" + modules: "{{ system_info_hyperv_check_raw.stdout_lines | default([]) }}" + tags: [gather, hypervisor] diff --git a/roles/system_info/tasks/export_stats.yml b/roles/system_info/tasks/export_stats.yml new file mode 100644 index 0000000..5559c35 --- /dev/null +++ b/roles/system_info/tasks/export_stats.yml @@ -0,0 +1,73 @@ +--- +# Statistics aggregation and export tasks + +- name: Set collection timestamp + set_fact: + system_info_timestamp: "{{ ansible_date_time.iso8601 }}" + system_info_timestamp_epoch: "{{ ansible_date_time.epoch }}" + tags: [export, statistics] + +- name: Aggregate all system information + set_fact: + system_info_complete: + collection_info: + timestamp: "{{ system_info_timestamp }}" + timestamp_epoch: "{{ system_info_timestamp_epoch }}" + collected_by: "ansible" + role_version: "{{ system_info_role_version }}" + ansible_version: "{{ ansible_version.full }}" + host_info: + hostname: "{{ system_info_hostname }}" + fqdn: "{{ system_info_fqdn }}" + uptime: "{{ system_info_uptime }}" + boot_time: "{{ system_info_boot_time }}" + system: "{{ system_info_os | default({}) }}" + kernel: "{{ system_info_kernel | default({}) }}" + hardware: "{{ system_info_hardware | default({}) }}" + security: + selinux: "{{ system_info_selinux_status | default('N/A') }}" + apparmor: "{{ system_info_apparmor_status | default('N/A') }}" + cpu: "{{ system_info_cpu | default({}) }}" + gpu: "{{ system_info_gpu | default({}) }}" + memory: "{{ system_info_memory | default({}) }}" + swap: "{{ system_info_swap | default({}) }}" + disk: "{{ system_info_disk | default({}) }}" + network: "{{ system_info_network | default({}) }}" + hypervisor: "{{ system_info_hypervisor | default({}) }}" + tags: [export, statistics] + +- name: Create JSON statistics file on control node + copy: + content: "{{ system_info_complete | to_nice_json(indent=system_info_json_indent) }}" + dest: "{{ system_info_stats_dir }}/system_info.json" + mode: '0644' + delegate_to: localhost + become: false + tags: [export, statistics] + +- name: Create timestamped JSON backup + copy: + content: "{{ system_info_complete | to_nice_json(indent=system_info_json_indent) }}" + dest: "{{ system_info_stats_dir }}/system_info_{{ system_info_timestamp_epoch }}.json" + mode: '0644' + delegate_to: localhost + become: false + tags: [export, statistics, backup] + +- name: Create human-readable summary file + template: + src: summary.txt.j2 + dest: "{{ system_info_stats_dir }}/summary.txt" + mode: '0644' + delegate_to: localhost + become: false + tags: [export, statistics, summary] + +- name: Display statistics file location + debug: + msg: + - "System information collected successfully" + - "Statistics saved to: {{ system_info_stats_dir }}/system_info.json" + - "Summary saved to: {{ system_info_stats_dir }}/summary.txt" + - "Timestamped backup: {{ system_info_stats_dir }}/system_info_{{ system_info_timestamp_epoch }}.json" + tags: [export, statistics] diff --git a/roles/system_info/tasks/gather_cpu.yml b/roles/system_info/tasks/gather_cpu.yml new file mode 100644 index 0000000..30a7f17 --- /dev/null +++ b/roles/system_info/tasks/gather_cpu.yml @@ -0,0 +1,105 @@ +--- +# CPU information gathering tasks + +- name: Gather CPU information from /proc/cpuinfo + shell: | + cat /proc/cpuinfo | grep -E "model name|processor|cpu MHz|cache size|physical id|cpu cores|flags" | head -20 + register: system_info_cpu_proc_raw + changed_when: false + tags: [gather, cpu] + +- name: Gather CPU count information + set_fact: + system_info_cpu_count: + physical: "{{ ansible_processor_count }}" + cores_per_socket: "{{ ansible_processor_cores }}" + threads_per_core: "{{ ansible_processor_threads_per_core }}" + vcpus: "{{ ansible_processor_vcpus }}" + total_cores: "{{ ansible_processor_count * ansible_processor_cores }}" + tags: [gather, cpu] + +- name: Gather CPU model information + set_fact: + system_info_cpu_model: "{{ ansible_processor[2] | default(ansible_processor[0]) }}" + tags: [gather, cpu] + +- name: Gather detailed CPU information using lscpu + shell: lscpu + register: system_info_lscpu_raw + changed_when: false + tags: [gather, cpu] + +- name: Parse lscpu output + set_fact: + system_info_cpu_architecture: "{{ system_info_lscpu_raw.stdout | regex_search('Architecture:\\s+(.+)', '\\1') | default(['Unknown'], true) | first | trim }}" + system_info_cpu_op_modes: "{{ system_info_lscpu_raw.stdout | regex_search('CPU op-mode\\(s\\):\\s+(.+)', '\\1') | default(['Unknown'], true) | first | trim }}" + system_info_cpu_vendor: "{{ system_info_lscpu_raw.stdout | regex_search('Vendor ID:\\s+(.+)', '\\1') | default(['Unknown'], true) | first | trim }}" + system_info_cpu_family: "{{ system_info_lscpu_raw.stdout | regex_search('CPU family:\\s+(.+)', '\\1') | default(['Unknown'], true) | first | trim }}" + system_info_cpu_model_name: "{{ system_info_lscpu_raw.stdout | regex_search('Model name:\\s+(.+)', '\\1') | default(['Unknown'], true) | first | trim }}" + system_info_cpu_mhz: "{{ system_info_lscpu_raw.stdout | regex_search('CPU MHz:\\s+(.+)', '\\1') | default(['Unknown'], true) | first | trim }}" + system_info_cpu_max_mhz: "{{ system_info_lscpu_raw.stdout | regex_search('CPU max MHz:\\s+(.+)', '\\1') | default(['Unknown'], true) | first | trim }}" + system_info_cpu_min_mhz: "{{ system_info_lscpu_raw.stdout | regex_search('CPU min MHz:\\s+(.+)', '\\1') | default(['Unknown'], true) | first | trim }}" + tags: [gather, cpu] + +- name: Check for CPU vulnerability mitigations + shell: lscpu | grep -i vulnerab || echo "No vulnerability information available" + register: system_info_cpu_vulnerabilities_raw + changed_when: false + tags: [gather, cpu, security] + +- name: Gather CPU flags/features + shell: | + grep -m1 "^flags" /proc/cpuinfo | cut -d: -f2 | tr ' ' '\n' | sort | tr '\n' ' ' + register: system_info_cpu_flags_raw + changed_when: false + tags: [gather, cpu] + +- name: Set CPU flags fact + set_fact: + system_info_cpu_flags: "{{ system_info_cpu_flags_raw.stdout.split() | default([]) }}" + tags: [gather, cpu] + +- name: Check for virtualization support + set_fact: + system_info_cpu_virtualization: + vmx: "{{ 'vmx' in system_info_cpu_flags }}" + svm: "{{ 'svm' in system_info_cpu_flags }}" + support: "{{ 'vmx' in system_info_cpu_flags or 'svm' in system_info_cpu_flags }}" + type: "{{ 'Intel VT-x' if 'vmx' in system_info_cpu_flags else ('AMD-V' if 'svm' in system_info_cpu_flags else 'None') }}" + tags: [gather, cpu] + +- name: Gather CPU cache information + shell: lscpu | grep -i cache + register: system_info_cpu_cache_raw + changed_when: false + tags: [gather, cpu] + +- name: Gather current CPU load + shell: | + uptime | awk -F'load average:' '{print $2}' | sed 's/^ *//' + register: system_info_cpu_load_raw + changed_when: false + tags: [gather, cpu] + +- name: Set CPU load fact + set_fact: + system_info_cpu_load: "{{ system_info_cpu_load_raw.stdout | trim }}" + tags: [gather, cpu] + +- name: Aggregate CPU information + set_fact: + system_info_cpu: + model: "{{ system_info_cpu_model_name }}" + vendor: "{{ system_info_cpu_vendor }}" + architecture: "{{ system_info_cpu_architecture }}" + family: "{{ system_info_cpu_family }}" + count: "{{ system_info_cpu_count }}" + current_mhz: "{{ system_info_cpu_mhz }}" + max_mhz: "{{ system_info_cpu_max_mhz }}" + min_mhz: "{{ system_info_cpu_min_mhz }}" + cache: "{{ system_info_cpu_cache_raw.stdout_lines | default([]) }}" + flags: "{{ system_info_cpu_flags[:50] }}" + virtualization: "{{ system_info_cpu_virtualization }}" + current_load: "{{ system_info_cpu_load }}" + vulnerabilities: "{{ system_info_cpu_vulnerabilities_raw.stdout_lines | default([]) }}" + tags: [gather, cpu] diff --git a/roles/system_info/tasks/gather_disk.yml b/roles/system_info/tasks/gather_disk.yml new file mode 100644 index 0000000..bb7a598 --- /dev/null +++ b/roles/system_info/tasks/gather_disk.yml @@ -0,0 +1,139 @@ +--- +# Disk information gathering tasks + +- name: Gather disk usage information + shell: df -h | grep -vE '^Filesystem|tmpfs|cdrom' + register: system_info_disk_usage_raw + changed_when: false + failed_when: false + tags: [gather, disk] + +- name: Gather disk usage in machine-readable format + shell: df -B1 | grep -vE '^Filesystem|tmpfs|cdrom' + register: system_info_disk_usage_bytes_raw + changed_when: false + failed_when: false + tags: [gather, disk] + +- name: List block devices + shell: lsblk -o NAME,SIZE,TYPE,MOUNTPOINT,FSTYPE,MODEL,SERIAL + register: system_info_lsblk_raw + changed_when: false + tags: [gather, disk] + +- name: Gather detailed block device information + shell: lsblk -J -o NAME,SIZE,TYPE,MOUNTPOINT,FSTYPE,UUID,PARTUUID,MODEL,SERIAL,STATE,ROTA + register: system_info_lsblk_json_raw + changed_when: false + tags: [gather, disk] + +- name: Check for LVM usage + shell: | + if command -v pvs &> /dev/null; then + echo "=== Physical Volumes ===" + pvs + echo "=== Volume Groups ===" + vgs + echo "=== Logical Volumes ===" + lvs + else + echo "LVM not configured or not available" + fi + register: system_info_lvm_raw + changed_when: false + become: true + failed_when: false + tags: [gather, disk] + +- name: Detect if LVM is in use + set_fact: + system_info_lvm_detected: "{{ 'Physical Volumes' in system_info_lvm_raw.stdout }}" + tags: [gather, disk] + +- name: Gather mount points information + shell: mount | grep -vE "tmpfs|devtmpfs|sysfs|proc|cgroup" + register: system_info_mounts_raw + changed_when: false + failed_when: false + tags: [gather, disk] + +- name: Check for RAID arrays + shell: | + if [ -f /proc/mdstat ]; then + cat /proc/mdstat + else + echo "No software RAID detected" + fi + register: system_info_mdstat_raw + changed_when: false + tags: [gather, disk] + +- name: Detect hardware RAID controllers + shell: lspci | grep -iE "raid|storage controller" || echo "No hardware RAID controllers detected" + register: system_info_hw_raid_raw + changed_when: false + tags: [gather, disk] + +- name: Gather disk I/O statistics + shell: iostat -x 1 2 | tail -n +4 + register: system_info_iostat_raw + changed_when: false + failed_when: false + tags: [gather, disk] + +- name: List physical disks + shell: lsblk -d -o NAME,SIZE,TYPE,ROTA,MODEL | grep disk + register: system_info_physical_disks_raw + changed_when: false + tags: [gather, disk] + +- name: Check for SSD vs HDD + shell: | + for disk in $(lsblk -d -n -o NAME,TYPE | grep disk | awk '{print $1}'); do + rota=$(cat /sys/block/$disk/queue/rotational 2>/dev/null || echo "unknown") + if [ "$rota" = "0" ]; then + echo "$disk: SSD" + elif [ "$rota" = "1" ]; then + echo "$disk: HDD" + else + echo "$disk: Unknown" + fi + done + register: system_info_disk_types_raw + changed_when: false + failed_when: false + tags: [gather, disk] + +- name: Gather SMART status (if available) + shell: | + if command -v smartctl &> /dev/null; then + for disk in $(lsblk -d -n -o NAME,TYPE | grep disk | awk '{print $1}'); do + echo "=== /dev/$disk ===" + smartctl -H /dev/$disk 2>/dev/null || echo "SMART not available for /dev/$disk" + done + else + echo "smartctl not available" + fi + register: system_info_smart_raw + changed_when: false + become: true + failed_when: false + tags: [gather, disk] + +- name: Aggregate disk information + set_fact: + system_info_disk: + usage_human: "{{ system_info_disk_usage_raw.stdout_lines | default([]) }}" + block_devices: "{{ system_info_lsblk_raw.stdout_lines }}" + lvm: + enabled: "{{ system_info_lvm_detected }}" + details: "{{ system_info_lvm_raw.stdout_lines | default([]) }}" + mounts: "{{ system_info_mounts_raw.stdout_lines | default([]) }}" + raid: + software: "{{ system_info_mdstat_raw.stdout_lines }}" + hardware: "{{ system_info_hw_raid_raw.stdout_lines }}" + physical_disks: "{{ system_info_physical_disks_raw.stdout_lines | default([]) }}" + disk_types: "{{ system_info_disk_types_raw.stdout_lines | default([]) }}" + smart_status: "{{ system_info_smart_raw.stdout_lines | default([]) }}" + io_stats: "{{ system_info_iostat_raw.stdout_lines | default([]) }}" + tags: [gather, disk] diff --git a/roles/system_info/tasks/gather_gpu.yml b/roles/system_info/tasks/gather_gpu.yml new file mode 100644 index 0000000..03c8f55 --- /dev/null +++ b/roles/system_info/tasks/gather_gpu.yml @@ -0,0 +1,96 @@ +--- +# GPU information gathering tasks + +- name: Detect GPU devices using lspci + shell: lspci | grep -iE "VGA|3D|Display" || echo "No GPU detected" + register: system_info_gpu_lspci_raw + changed_when: false + tags: [gather, gpu] + +- name: Gather detailed GPU information + shell: lspci -v -s $(lspci | grep -iE "VGA|3D" | cut -d' ' -f1) 2>/dev/null || echo "No detailed GPU info available" + register: system_info_gpu_detailed_raw + changed_when: false + tags: [gather, gpu] + +- name: Check for NVIDIA GPU + shell: lspci | grep -i nvidia + register: system_info_nvidia_check + changed_when: false + failed_when: false + tags: [gather, gpu] + +- name: Gather NVIDIA GPU details (if available) + shell: nvidia-smi --query-gpu=name,driver_version,memory.total,compute_cap --format=csv,noheader 2>/dev/null || echo "nvidia-smi not available" + register: system_info_nvidia_smi_raw + changed_when: false + failed_when: false + when: system_info_nvidia_check.rc == 0 + tags: [gather, gpu] + +- name: Check for AMD GPU + shell: lspci | grep -iE "AMD|ATI" + register: system_info_amd_check + changed_when: false + failed_when: false + tags: [gather, gpu] + +- name: Gather AMD GPU details (if available) + shell: | + if command -v rocm-smi &> /dev/null; then + rocm-smi --showproductname --showdriverversion + else + echo "rocm-smi not available" + fi + register: system_info_amd_rocm_raw + changed_when: false + failed_when: false + when: system_info_amd_check.rc == 0 + tags: [gather, gpu] + +- name: Check for Intel GPU + shell: lspci | grep -i "intel.*graphics" + register: system_info_intel_gpu_check + changed_when: false + failed_when: false + tags: [gather, gpu] + +- name: Parse GPU information + set_fact: + system_info_gpu_detected: "{{ system_info_gpu_lspci_raw.stdout != 'No GPU detected' }}" + system_info_gpu_list: "{{ system_info_gpu_lspci_raw.stdout_lines | default([]) }}" + tags: [gather, gpu] + +- name: Build GPU details structure + set_fact: + system_info_gpu: + detected: "{{ system_info_gpu_detected }}" + devices: "{{ system_info_gpu_list }}" + nvidia: + present: "{{ system_info_nvidia_check.rc == 0 }}" + details: "{{ system_info_nvidia_smi_raw.stdout_lines | default([]) if system_info_nvidia_check.rc == 0 else [] }}" + amd: + present: "{{ system_info_amd_check.rc == 0 }}" + details: "{{ system_info_amd_rocm_raw.stdout_lines | default([]) if system_info_amd_check.rc == 0 else [] }}" + intel: + present: "{{ system_info_intel_gpu_check.rc == 0 }}" + detailed_info: "{{ system_info_gpu_detailed_raw.stdout_lines | default([]) }}" + tags: [gather, gpu] + +- name: Check for GPU passthrough support (IOMMU) + shell: | + if dmesg | grep -iE "IOMMU|AMD-Vi|Intel VT-d" | grep -i enabled; then + echo "IOMMU enabled" + else + echo "IOMMU disabled or not available" + fi + register: system_info_iommu_status_raw + changed_when: false + become: true + failed_when: false + tags: [gather, gpu] + +- name: Add IOMMU status to GPU info + set_fact: + system_info_gpu: "{{ system_info_gpu | combine({'iommu_status': system_info_iommu_status_raw.stdout | default('Unknown')}) }}" + tags: [gather, gpu] diff --git a/roles/system_info/tasks/gather_memory.yml b/roles/system_info/tasks/gather_memory.yml new file mode 100644 index 0000000..90b49c6 --- /dev/null +++ b/roles/system_info/tasks/gather_memory.yml @@ -0,0 +1,109 @@ +--- +# Memory information gathering tasks + +- name: Gather memory information from Ansible facts + set_fact: + system_info_memory_total_mb: "{{ ansible_memtotal_mb }}" + system_info_memory_free_mb: "{{ ansible_memfree_mb }}" + system_info_swap_total_mb: "{{ ansible_swaptotal_mb }}" + system_info_swap_free_mb: "{{ ansible_swapfree_mb }}" + tags: [gather, memory] + +- name: Gather detailed memory information + shell: free -h + register: system_info_free_raw + changed_when: false + tags: [gather, memory] + +- name: Gather memory information from /proc/meminfo + shell: cat /proc/meminfo | grep -iE "MemTotal|MemFree|MemAvailable|Buffers|Cached|SwapTotal|SwapFree|SwapCached" + register: system_info_meminfo_raw + changed_when: false + tags: [gather, memory] + +- name: Parse detailed memory values + set_fact: + system_info_mem_available_kb: "{{ system_info_meminfo_raw.stdout | regex_search('MemAvailable:\\s+(\\d+)', '\\1') | default(['0'], true) | first }}" + system_info_mem_buffers_kb: "{{ system_info_meminfo_raw.stdout | regex_search('Buffers:\\s+(\\d+)', '\\1') | default(['0'], true) | first }}" + system_info_mem_cached_kb: "{{ system_info_meminfo_raw.stdout | regex_search('Cached:\\s+(\\d+)', '\\1') | default(['0'], true) | first }}" + tags: [gather, memory] + +- name: Gather physical memory hardware information + shell: dmidecode -t memory | grep -iE "Size|Type|Speed|Manufacturer|Serial Number|Locator" || echo "DMI memory info not available" + register: system_info_dmi_memory_raw + changed_when: false + become: true + failed_when: false + tags: [gather, memory] + +- name: Count physical memory modules + shell: dmidecode -t memory | grep -c "^[[:space:]]*Size.*MB" || echo "0" + register: system_info_memory_modules_count_raw + changed_when: false + become: true + failed_when: false + tags: [gather, memory] + +- name: Gather swap devices information + shell: swapon --show --noheadings || echo "No swap configured" + register: system_info_swap_devices_raw + changed_when: false + tags: [gather, memory] + +- name: Calculate memory usage percentage + set_fact: + system_info_memory_used_mb: "{{ ansible_memtotal_mb - ansible_memfree_mb }}" + system_info_memory_usage_percent: "{{ ((ansible_memtotal_mb - ansible_memfree_mb) / ansible_memtotal_mb * 100) | round(2) }}" + tags: [gather, memory] + +- name: Calculate swap usage percentage + set_fact: + system_info_swap_used_mb: "{{ ansible_swaptotal_mb - ansible_swapfree_mb }}" + system_info_swap_usage_percent: "{{ ((ansible_swaptotal_mb - ansible_swapfree_mb) / ansible_swaptotal_mb * 100) | round(2) if ansible_swaptotal_mb > 0 else 0 }}" + tags: [gather, memory] + +- name: Check for memory pressure + shell: | + if [ -f /proc/pressure/memory ]; then + cat /proc/pressure/memory + else + echo "Memory pressure statistics not available" + fi + register: system_info_memory_pressure_raw + changed_when: false + failed_when: false + tags: [gather, memory] + +- name: Gather huge pages information + shell: | + grep -E "HugePages_|Hugepagesize" /proc/meminfo || echo "Huge pages not configured" + register: system_info_hugepages_raw + changed_when: false + tags: [gather, memory] + +- name: Aggregate memory information + set_fact: + system_info_memory: + total_mb: "{{ system_info_memory_total_mb }}" + free_mb: "{{ system_info_memory_free_mb }}" + used_mb: "{{ system_info_memory_used_mb }}" + available_kb: "{{ system_info_mem_available_kb }}" + buffers_kb: "{{ system_info_mem_buffers_kb }}" + cached_kb: "{{ system_info_mem_cached_kb }}" + usage_percent: "{{ system_info_memory_usage_percent }}" + physical_modules: "{{ system_info_memory_modules_count_raw.stdout | default('0') }}" + hardware_details: "{{ system_info_dmi_memory_raw.stdout_lines | default([]) }}" + pressure: "{{ system_info_memory_pressure_raw.stdout_lines | default([]) }}" + hugepages: "{{ system_info_hugepages_raw.stdout_lines | default([]) }}" + free_output: "{{ system_info_free_raw.stdout_lines }}" + tags: [gather, memory] + +- name: Aggregate swap information + set_fact: + system_info_swap: + total_mb: "{{ system_info_swap_total_mb }}" + free_mb: "{{ system_info_swap_free_mb }}" + used_mb: "{{ system_info_swap_used_mb }}" + usage_percent: "{{ system_info_swap_usage_percent }}" + devices: "{{ system_info_swap_devices_raw.stdout_lines | default([]) }}" + tags: [gather, memory] diff --git a/roles/system_info/tasks/gather_network.yml b/roles/system_info/tasks/gather_network.yml new file mode 100644 index 0000000..0aa8d38 --- /dev/null +++ b/roles/system_info/tasks/gather_network.yml @@ -0,0 +1,90 @@ +--- +# Network information gathering tasks + +- name: Gather network interfaces information + set_fact: + system_info_interfaces: "{{ ansible_interfaces }}" + tags: [gather, network] + +- name: Gather IP addresses + shell: ip -br addr show | grep -v "^lo" + register: system_info_ip_addr_raw + changed_when: false + failed_when: false + tags: [gather, network] + +- name: Gather detailed network configuration + shell: ip addr show + register: system_info_ip_full_raw + changed_when: false + tags: [gather, network] + +- name: Gather routing table + shell: ip route show + register: system_info_routes_raw + changed_when: false + tags: [gather, network] + +- name: Gather DNS configuration + shell: | + if [ -f /etc/resolv.conf ]; then + grep "^nameserver" /etc/resolv.conf + else + echo "No resolv.conf found" + fi + register: system_info_dns_raw + changed_when: false + tags: [gather, network] + +- name: Gather network interface statistics + shell: ip -s link show + register: system_info_net_stats_raw + changed_when: false + tags: [gather, network] + +- name: Check for active connections + shell: ss -tunapl | head -20 + register: system_info_connections_raw + changed_when: false + become: true + failed_when: false + tags: [gather, network] + +- name: Gather listening ports + shell: ss -tlnp + register: system_info_listening_raw + changed_when: false + become: true + failed_when: false + tags: [gather, network] + +- name: Build network interface details + set_fact: + system_info_network_interfaces: {} + tags: [gather, network] + +- name: Gather details for each interface + set_fact: + system_info_network_interfaces: "{{ system_info_network_interfaces | combine({item: { + 'ipv4': ansible_facts[item]['ipv4'] | default({}), + 'ipv6': ansible_facts[item]['ipv6'] | default([]), + 'mac': ansible_facts[item]['macaddress'] | default('N/A'), + 'mtu': ansible_facts[item]['mtu'] | default('N/A'), + 'state': ansible_facts[item]['active'] | default(false) | string, + 'type': ansible_facts[item]['type'] | default('unknown') + }}) }}" + loop: "{{ ansible_interfaces }}" + when: item != 'lo' + tags: [gather, network] + +- name: Aggregate network information + set_fact: + system_info_network: + interfaces: "{{ system_info_network_interfaces }}" + ip_addresses: "{{ system_info_ip_addr_raw.stdout_lines | default([]) }}" + routes: "{{ system_info_routes_raw.stdout_lines }}" + dns_servers: "{{ system_info_dns_raw.stdout_lines | default([]) }}" + listening_ports: "{{ system_info_listening_raw.stdout_lines | default([]) }}" + default_ipv4: "{{ ansible_default_ipv4 | default({}) }}" + default_ipv6: "{{ ansible_default_ipv6 | default({}) }}" + tags: [gather, network] diff --git a/roles/system_info/tasks/gather_system.yml b/roles/system_info/tasks/gather_system.yml new file mode 100644 index 0000000..25c7ad3 --- /dev/null +++ b/roles/system_info/tasks/gather_system.yml @@ -0,0 +1,91 @@ +--- +# System information gathering tasks + +- name: Gather system information - Hostname + set_fact: + system_info_hostname: "{{ ansible_hostname }}" + system_info_fqdn: "{{ ansible_fqdn }}" + tags: [gather, system] + +- name: Gather system information - OS details + set_fact: + system_info_os: + distribution: "{{ ansible_distribution }}" + distribution_version: "{{ ansible_distribution_version }}" + distribution_release: "{{ ansible_distribution_release }}" + distribution_major_version: "{{ ansible_distribution_major_version }}" + os_family: "{{ ansible_os_family }}" + tags: [gather, system] + +- name: Gather system information - Kernel + set_fact: + system_info_kernel: + version: "{{ ansible_kernel }}" + architecture: "{{ ansible_architecture }}" + tags: [gather, system] + +- name: Gather system uptime + shell: uptime -p + register: system_info_uptime_raw + changed_when: false + failed_when: false + tags: [gather, system] + +- name: Gather system boot time + shell: uptime -s + register: system_info_boot_time_raw + changed_when: false + failed_when: false + tags: [gather, system] + +- name: Set uptime facts + set_fact: + system_info_uptime: "{{ system_info_uptime_raw.stdout | default('Unknown') }}" + system_info_boot_time: "{{ system_info_boot_time_raw.stdout | default('Unknown') }}" + tags: [gather, system] + +- name: Gather DMI/SMBIOS information + shell: dmidecode -t system | grep -E "Manufacturer|Product Name|Serial Number|UUID" || echo "Not available" + register: system_info_dmi_raw + changed_when: false + become: true + failed_when: false + tags: [gather, system] + +- name: Parse DMI information + set_fact: + system_info_hardware: + manufacturer: "{{ system_info_dmi_raw.stdout | regex_search('Manufacturer: (.+)', '\\1') | default(['Unknown'], true) | first }}" + product: "{{ system_info_dmi_raw.stdout | regex_search('Product Name: (.+)', '\\1') | default(['Unknown'], true) | first }}" + serial: "{{ system_info_dmi_raw.stdout | regex_search('Serial Number: (.+)', '\\1') | default(['Unknown'], true) | first }}" + uuid: "{{ system_info_dmi_raw.stdout | regex_search('UUID: (.+)', '\\1') | default(['Unknown'], true) | first }}" + tags: [gather, system] + +- name: Gather SELinux status (RHEL-based) + shell: getenforce + register: system_info_selinux_raw + changed_when: false + failed_when: false + when: ansible_os_family == "RedHat" + tags: [gather, system, security] + +- name: Set SELinux status + set_fact: + system_info_selinux_status: "{{ system_info_selinux_raw.stdout | default('Not applicable') }}" + when: ansible_os_family == "RedHat" + tags: [gather, system, security] + +- name: Gather AppArmor status (Debian-based) + shell: aa-status --enabled && echo "Enabled" || echo "Disabled" + register: system_info_apparmor_raw + changed_when: false + failed_when: false + become: true + when: ansible_os_family == "Debian" + tags: [gather, system, security] + +- name: Set AppArmor status + set_fact: + system_info_apparmor_status: "{{ system_info_apparmor_raw.stdout | default('Not applicable') }}" + when: ansible_os_family == "Debian" + tags: [gather, system, security] diff --git a/roles/system_info/tasks/install.yml b/roles/system_info/tasks/install.yml new file mode 100644 index 0000000..7129203 --- /dev/null +++ b/roles/system_info/tasks/install.yml @@ -0,0 +1,28 @@ +--- +# Installation tasks for system_info role + +- name: Install required packages (Debian/Ubuntu) + apt: + name: "{{ system_info_packages_debian }}" + state: present + update_cache: true + cache_valid_time: 3600 + when: ansible_os_family == "Debian" + become: true + tags: [install, packages] + +- name: Install required packages (RHEL/Rocky/AlmaLinux) + dnf: + name: "{{ system_info_packages_redhat }}" + state: present + when: ansible_os_family == "RedHat" + become: true + tags: [install, packages] + +- name: Ensure dmidecode is executable + file: + path: /usr/sbin/dmidecode + mode: '0755' + become: true + ignore_errors: true + tags: [install, packages] diff --git a/roles/system_info/tasks/main.yml b/roles/system_info/tasks/main.yml new file mode 100644 index 0000000..200537e --- /dev/null +++ b/roles/system_info/tasks/main.yml @@ -0,0 +1,77 @@ +--- +# tasks file for system_info + +- name: Include OS-specific variables + include_vars: "{{ ansible_os_family }}.yml" + ignore_errors: true + tags: [always] + +- name: Validate required variables + assert: + that: + - system_info_stats_base_dir is defined + - system_info_stats_base_dir | length > 0 + fail_msg: "Required variable 'system_info_stats_base_dir' is not defined or empty" + tags: [validate] + +- name: Set statistics directory path + set_fact: + system_info_stats_dir: "{{ system_info_stats_base_dir }}/{{ ansible_fqdn }}" + tags: [always] + +- name: Create statistics directory on control node + file: + path: "{{ system_info_stats_dir }}" + state: directory + mode: '0755' + delegate_to: localhost + become: false + when: system_info_create_stats_dir | bool + tags: [install, setup] + +- name: Include installation tasks + include_tasks: install.yml + tags: [install] + +- name: Include system information gathering tasks + include_tasks: gather_system.yml + when: system_info_gather_system | bool + tags: [gather, system] + +- name: Include CPU information gathering tasks + include_tasks: gather_cpu.yml + when: system_info_gather_cpu | bool + tags: [gather, cpu] + +- name: Include GPU information gathering tasks + include_tasks: gather_gpu.yml + when: system_info_gather_gpu | bool + tags: [gather, gpu] + +- name: Include memory information gathering tasks + include_tasks: gather_memory.yml + when: system_info_gather_memory | bool + tags: [gather, memory] + +- name: Include disk information gathering tasks + include_tasks: gather_disk.yml + when: system_info_gather_disk | bool + tags: [gather, disk] + +- name: Include network information gathering tasks + include_tasks: gather_network.yml + when: system_info_gather_network | bool + tags: [gather, network] + +- name: Include hypervisor detection tasks + include_tasks: detect_hypervisor.yml + when: system_info_detect_hypervisor | bool + tags: [gather, hypervisor] + +- name: Include statistics aggregation and export tasks + include_tasks: export_stats.yml + tags: [export, statistics] + +- name: Include validation tasks + include_tasks: validate.yml + tags: [validate, health-check] diff --git a/roles/system_info/tasks/validate.yml b/roles/system_info/tasks/validate.yml new file mode 100644 index 0000000..bf5c1e0 --- /dev/null +++ b/roles/system_info/tasks/validate.yml @@ -0,0 +1,101 @@ +--- +# Validation and health check tasks + +- name: Gather disk usage statistics + shell: df -h | grep -vE '^Filesystem|tmpfs|cdrom' + register: validate_disk_usage + changed_when: false + failed_when: false + tags: [validate, health-check] + +- name: Gather memory usage statistics + shell: free -h + register: validate_memory_usage + changed_when: false + tags: [validate, health-check] + +- name: Gather swap usage statistics + shell: swapon --show + register: validate_swap_usage + changed_when: false + failed_when: false + tags: [validate, health-check] + +- name: Gather system uptime + shell: uptime + register: validate_system_uptime + changed_when: false + tags: [validate, health-check] + +- name: Gather logged-in users + shell: who + register: validate_logged_users + changed_when: false + failed_when: false + tags: [validate, health-check] + +- name: Check high CPU processes + shell: ps aux --sort=-%cpu | head -10 + register: validate_top_cpu_processes + changed_when: false + tags: [validate, health-check] + +- name: Check high memory processes + shell: ps aux --sort=-%mem | head -10 + register: validate_top_mem_processes + changed_when: false + tags: [validate, health-check] + +- name: Check for disk usage warnings (>80%) + shell: df -h | awk 'NR>1 {gsub(/%/,"",$5); if($5>80) print $0}' + register: validate_disk_warnings + changed_when: false + failed_when: false + tags: [validate, health-check] + +- name: Verify statistics directory exists + stat: + path: "{{ system_info_stats_dir }}" + register: validate_stats_dir + delegate_to: localhost + become: false + tags: [validate] + +- name: Verify JSON file was created + stat: + path: "{{ system_info_stats_dir }}/system_info.json" + register: validate_json_file + delegate_to: localhost + become: false + tags: [validate] + +- name: Display system health summary + debug: + msg: + - "=== System Health Check for {{ ansible_fqdn }} ===" + - "Uptime: {{ validate_system_uptime.stdout }}" + - "" + - "=== Disk Usage ===" + - "{{ validate_disk_usage.stdout_lines }}" + - "" + - "=== Memory Usage ===" + - "{{ validate_memory_usage.stdout_lines }}" + - "" + - "{% if validate_swap_usage.stdout_lines | length > 0 %}=== Swap Usage ==={{ validate_swap_usage.stdout_lines }}{% else %}No swap configured{% endif %}" + - "" + - "{% if validate_disk_warnings.stdout_lines | length > 0 %}=== DISK WARNINGS (>80% usage) ==={{ validate_disk_warnings.stdout_lines }}{% endif %}" + - "" + - "=== Logged Users ===" + - "{{ validate_logged_users.stdout_lines if validate_logged_users.stdout_lines | length > 0 else ['No users logged in'] }}" + - "" + - "=== Top CPU Processes ===" + - "{{ validate_top_cpu_processes.stdout_lines[:5] }}" + - "" + - "=== Top Memory Processes ===" + - "{{ validate_top_mem_processes.stdout_lines[:5] }}" + - "" + - "=== Statistics Files ===" + - "Directory exists: {{ validate_stats_dir.stat.exists }}" + - "JSON file created: {{ validate_json_file.stat.exists }}" + - "Location: {{ system_info_stats_dir }}" + tags: [validate, health-check] diff --git a/roles/system_info/templates/summary.txt.j2 b/roles/system_info/templates/summary.txt.j2 new file mode 100644 index 0000000..6f72b46 --- /dev/null +++ b/roles/system_info/templates/summary.txt.j2 @@ -0,0 +1,171 @@ +================================================================================ + SYSTEM INFORMATION SUMMARY +================================================================================ + +Generated: {{ system_info_timestamp }} +Host: {{ system_info_fqdn }} + +-------------------------------------------------------------------------------- +HOST INFORMATION +-------------------------------------------------------------------------------- +Hostname: {{ system_info_hostname }} +FQDN: {{ system_info_fqdn }} +Uptime: {{ system_info_uptime }} +Boot Time: {{ system_info_boot_time }} + +-------------------------------------------------------------------------------- +HARDWARE +-------------------------------------------------------------------------------- +Manufacturer: {{ system_info_hardware.manufacturer }} +Product: {{ system_info_hardware.product }} +Serial Number: {{ system_info_hardware.serial }} +UUID: {{ system_info_hardware.uuid }} + +-------------------------------------------------------------------------------- +OPERATING SYSTEM +-------------------------------------------------------------------------------- +Distribution: {{ system_info_os.distribution }} {{ system_info_os.distribution_version }} +OS Family: {{ system_info_os.os_family }} +Kernel: {{ system_info_kernel.version }} +Architecture: {{ system_info_kernel.architecture }} + +-------------------------------------------------------------------------------- +SECURITY +-------------------------------------------------------------------------------- +{% if system_info_selinux_status is defined and system_info_selinux_status != 'N/A' %} +SELinux: {{ system_info_selinux_status }} +{% endif %} +{% if system_info_apparmor_status is defined and system_info_apparmor_status != 'N/A' %} +AppArmor: {{ system_info_apparmor_status }} +{% endif %} + +-------------------------------------------------------------------------------- +CPU INFORMATION +-------------------------------------------------------------------------------- +Model: {{ system_info_cpu.model }} +Vendor: {{ system_info_cpu.vendor }} +Architecture: {{ system_info_cpu.architecture }} +Physical CPUs: {{ system_info_cpu.count.physical }} +Cores per CPU: {{ system_info_cpu.count.cores_per_socket }} +Total vCPUs: {{ system_info_cpu.count.vcpus }} +Current MHz: {{ system_info_cpu.current_mhz }} +Max MHz: {{ system_info_cpu.max_mhz }} +Virtualization: {{ system_info_cpu.virtualization.type }} +Current Load: {{ system_info_cpu.current_load }} + +-------------------------------------------------------------------------------- +GPU INFORMATION +-------------------------------------------------------------------------------- +GPU Detected: {{ system_info_gpu.detected }} +{% if system_info_gpu.detected %} +{% for device in system_info_gpu.devices %} + - {{ device }} +{% endfor %} +NVIDIA Present: {{ system_info_gpu.nvidia.present }} +AMD Present: {{ system_info_gpu.amd.present }} +Intel Present: {{ system_info_gpu.intel.present }} +IOMMU Status: {{ system_info_gpu.iommu_status }} +{% endif %} + +-------------------------------------------------------------------------------- +MEMORY INFORMATION +-------------------------------------------------------------------------------- +Total Memory: {{ system_info_memory.total_mb }} MB +Used Memory: {{ system_info_memory.used_mb }} MB +Free Memory: {{ system_info_memory.free_mb }} MB +Usage: {{ system_info_memory.usage_percent }}% +Physical Modules: {{ system_info_memory.physical_modules }} + +Swap Total: {{ system_info_swap.total_mb }} MB +Swap Used: {{ system_info_swap.used_mb }} MB +Swap Usage: {{ system_info_swap.usage_percent }}% + +-------------------------------------------------------------------------------- +DISK INFORMATION +-------------------------------------------------------------------------------- +LVM Enabled: {{ system_info_disk.lvm.enabled }} + +Disk Usage: +{% for line in system_info_disk.usage_human %} +{{ line }} +{% endfor %} + +Physical Disks: +{% for disk in system_info_disk.physical_disks %} +{{ disk }} +{% endfor %} + +Disk Types: +{% for dtype in system_info_disk.disk_types %} +{{ dtype }} +{% endfor %} + +-------------------------------------------------------------------------------- +NETWORK INFORMATION +-------------------------------------------------------------------------------- +{% if system_info_network.default_ipv4 %} +Default IPv4: {{ system_info_network.default_ipv4.address | default('N/A') }} +Default Gateway: {{ system_info_network.default_ipv4.gateway | default('N/A') }} +Default Interface: {{ system_info_network.default_ipv4.interface | default('N/A') }} +{% endif %} + +Network Interfaces: +{% for iface, details in system_info_network.interfaces.items() %} + {{ iface }}: + MAC: {{ details.mac }} + IPv4: {{ details.ipv4.address | default('N/A') }} + State: {{ details.state }} + MTU: {{ details.mtu }} +{% endfor %} + +DNS Servers: +{% for dns in system_info_network.dns_servers %} +{{ dns }} +{% endfor %} + +-------------------------------------------------------------------------------- +VIRTUALIZATION / HYPERVISOR +-------------------------------------------------------------------------------- +Is Virtual: {{ system_info_hypervisor.is_virtual }} +Is Hypervisor: {{ system_info_hypervisor.is_hypervisor }} +Virtualization Type: {{ system_info_hypervisor.virtualization_type }} +Role: {{ system_info_hypervisor.virtualization_role }} + +{% if system_info_hypervisor.kvm_libvirt.installed %} +KVM/Libvirt: + Installed: {{ system_info_hypervisor.kvm_libvirt.installed }} + Service Status: {{ system_info_hypervisor.kvm_libvirt.service_status }} + Running VMs: {{ system_info_hypervisor.kvm_libvirt.running_vms }} + Total VMs: {{ system_info_hypervisor.kvm_libvirt.total_vms }} +{% endif %} + +{% if system_info_hypervisor.proxmox.installed %} +Proxmox VE: + Installed: {{ system_info_hypervisor.proxmox.installed }} + Version: {{ system_info_hypervisor.proxmox.version }} +{% endif %} + +{% if system_info_hypervisor.lxd.installed %} +LXD: + Installed: {{ system_info_hypervisor.lxd.installed }} + Version: {{ system_info_hypervisor.lxd.version }} +{% endif %} + +{% if system_info_hypervisor.docker.installed %} +Docker: + Installed: {{ system_info_hypervisor.docker.installed }} + Version: {{ system_info_hypervisor.docker.version }} + Running: {{ system_info_hypervisor.docker.running_containers }} + Total: {{ system_info_hypervisor.docker.total_containers }} + Images: {{ system_info_hypervisor.docker.images_count }} +{% endif %} + +{% if system_info_hypervisor.podman.installed %} +Podman: + Installed: {{ system_info_hypervisor.podman.installed }} + Version: {{ system_info_hypervisor.podman.version }} +{% endif %} + +================================================================================ +End of Report +================================================================================ diff --git a/roles/system_info/tests/inventory b/roles/system_info/tests/inventory new file mode 100644 index 0000000..13cfabe --- /dev/null +++ b/roles/system_info/tests/inventory @@ -0,0 +1,2 @@ +[local] +localhost ansible_connection=local diff --git a/roles/system_info/tests/test.yml b/roles/system_info/tests/test.yml new file mode 100644 index 0000000..776a903 --- /dev/null +++ b/roles/system_info/tests/test.yml @@ -0,0 +1,52 @@ +--- +# Test playbook for system_info role +- name: Test system_info role + hosts: localhost + become: true + gather_facts: true + + vars: + system_info_stats_base_dir: "/tmp/ansible-test/stats/machines" + system_info_create_stats_dir: true + + pre_tasks: + - name: Display test information + debug: + msg: + - "Testing system_info role" + - "Target: {{ ansible_fqdn }}" + - "Stats directory: {{ system_info_stats_base_dir }}" + + roles: + - role: ../../system_info + + post_tasks: + - name: Verify JSON file was created + stat: + path: "{{ system_info_stats_base_dir }}/{{ ansible_fqdn }}/system_info.json" + register: json_file_check + delegate_to: localhost + become: false + + - name: Verify summary file was created + stat: + path: "{{ system_info_stats_base_dir }}/{{ ansible_fqdn }}/summary.txt" + register: summary_file_check + delegate_to: localhost + become: false + + - name: Display test results + debug: + msg: + - "=== Test Results ===" + - "JSON file created: {{ json_file_check.stat.exists }}" + - "Summary file created: {{ summary_file_check.stat.exists }}" + - "Test {{ 'PASSED' if (json_file_check.stat.exists and summary_file_check.stat.exists) else 'FAILED' }}" + + - name: Assert test passed + assert: + that: + - json_file_check.stat.exists + - summary_file_check.stat.exists + success_msg: "All tests passed successfully" + fail_msg: "Test failed - files were not created" diff --git a/roles/system_info/vars/main.yml b/roles/system_info/vars/main.yml new file mode 100644 index 0000000..468d02a --- /dev/null +++ b/roles/system_info/vars/main.yml @@ -0,0 +1,22 @@ +--- +# vars file for system_info + +# Internal variables - do not override +system_info_role_version: "1.0.0" + +# Packages required for information gathering (OS-specific) +system_info_packages_debian: + - lshw + - dmidecode + - pciutils + - usbutils + - smartmontools + - ethtool + +system_info_packages_redhat: + - lshw + - dmidecode + - pciutils + - usbutils + - smartmontools + - ethtool