Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
180 changes: 180 additions & 0 deletions playbooks/verify.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
---
- name: Gather System Facts
hosts: all
gather_facts: true

vars:
# These are production specs for Itential P6
hardware_specs:
mongodb:
cpu_count: 16
ram_size: 128
disk_size: 1000
platform:
cpu_count: 16
ram_size: 64
disk_size: 250
redis:
cpu_count: 16
ram_size: 32
disk_size: 100

tasks:
# OS and Architecture validation
- name: Check OS compatibility
ansible.builtin.set_fact:
os_valid: >-
{{
(ansible_distribution == 'RedHat' and ansible_distribution_major_version in ['8', '9']) or

Check warning on line 28 in playbooks/verify.yml

View workflow job for this annotation

GitHub Actions / Ansible Lint

yaml[line-length]

Line too long (102 > 100 characters)
(ansible_distribution == 'Rocky' and ansible_distribution_major_version in ['8', '9']) or

Check warning on line 29 in playbooks/verify.yml

View workflow job for this annotation

GitHub Actions / Ansible Lint

yaml[line-length]

Line too long (101 > 100 characters)
(ansible_distribution == 'OracleLinux' and ansible_distribution_major_version in ['8', '9']) or

Check warning on line 30 in playbooks/verify.yml

View workflow job for this annotation

GitHub Actions / Ansible Lint

yaml[line-length]

Line too long (107 > 100 characters)
(ansible_distribution == 'Amazon' and ansible_distribution_version == '2023')
}}

- name: Assert that this is a supported OS
ansible.builtin.assert:
that: "{{ os_valid }} == true"
fail_msg: "{{ ansible_distribution }} {{ ansible_distribution_major_version }} is not a supported OS!"

Check warning on line 37 in playbooks/verify.yml

View workflow job for this annotation

GitHub Actions / Ansible Lint

yaml[line-length]

Line too long (110 > 100 characters)
success_msg: "OS validation passed!"
quiet: true

- name: Check architecture compatibility
ansible.builtin.set_fact:
arch_valid: "{{ ansible_architecture in ['x86_64', 'aarch64'] }}"

- name: Assert that this is a supported Architecture
ansible.builtin.assert:
that: "{{ arch_valid }} == true"
fail_msg: "{{ ansible_architecture }} is not a supported architecture!"
success_msg: "Architecture validation passed!"
quiet: true

# Hardware spec validation
- name: Determine which hardware spec applies to this host
ansible.builtin.set_fact:
applicable_spec: >-
{%- if 'mongodb' in group_names -%}
mongodb
{%- elif 'platform' in group_names -%}
platform
{%- elif 'redis' in group_names -%}
redis
{%- else -%}
none
{%- endif -%}

- name: Get root partition size
ansible.builtin.set_fact:
root_disk_size_gb: "{{ (ansible_mounts | selectattr('mount', 'equalto', '/') | map(attribute='size_total') | first / 1024 / 1024 / 1024) | round(2) }}"

Check warning on line 68 in playbooks/verify.yml

View workflow job for this annotation

GitHub Actions / Ansible Lint

yaml[line-length]

Line too long (159 > 100 characters)
when: ansible_mounts | selectattr('mount', 'equalto', '/') | list | length > 0

- name: Validate hardware specs against requirements
ansible.builtin.set_fact:
hardware_validation:
applicable_spec: "{{ applicable_spec }}"
required:
cpu_count: "{{ hardware_specs[applicable_spec].cpu_count if applicable_spec != 'none' else 'N/A' }}"

Check warning on line 76 in playbooks/verify.yml

View workflow job for this annotation

GitHub Actions / Ansible Lint

yaml[line-length]

Line too long (112 > 100 characters)
ram_size_gb: "{{ hardware_specs[applicable_spec].ram_size if applicable_spec != 'none' else 'N/A' }}"

Check warning on line 77 in playbooks/verify.yml

View workflow job for this annotation

GitHub Actions / Ansible Lint

yaml[line-length]

Line too long (113 > 100 characters)
disk_size_gb: "{{ hardware_specs[applicable_spec].disk_size if applicable_spec != 'none' else 'N/A' }}"

Check warning on line 78 in playbooks/verify.yml

View workflow job for this annotation

GitHub Actions / Ansible Lint

yaml[line-length]

Line too long (115 > 100 characters)
actual:
cpu_count: "{{ ansible_processor_vcpus }}"
ram_size_gb: "{{ (ansible_memtotal_mb / 1024) | round(2) }}"
disk_size_gb: "{{ root_disk_size_gb | default('N/A') }}"
validation:
cpu_valid: "{{ (applicable_spec == 'none') or (ansible_processor_vcpus >= hardware_specs[applicable_spec].cpu_count) }}"
ram_valid: "{{ (applicable_spec == 'none') or ((ansible_memtotal_mb / 1024) >= hardware_specs[applicable_spec].ram_size) }}"
disk_valid: "{{ (applicable_spec == 'none') or ((root_disk_size_gb | default(0) | float) >= hardware_specs[applicable_spec].disk_size) }}"
all_valid: "{{ (applicable_spec == 'none') or ((ansible_processor_vcpus >= hardware_specs[applicable_spec].cpu_count) and ((ansible_memtotal_mb / 1024) >= hardware_specs[applicable_spec].ram_size) and ((root_disk_size_gb | default(0) | float) >= hardware_specs[applicable_spec].disk_size)) }}"


# Network interface IP version check
- name: Check network interface IP support
ansible.builtin.set_fact:
interface_info: >-
{{
interface_info | default([]) + [{
'interface': item,
'ipv4_enabled': hostvars[inventory_hostname]['ansible_' + item].ipv4 is defined,
'ipv6_enabled': hostvars[inventory_hostname]['ansible_' + item].ipv6 is defined and
(hostvars[inventory_hostname]['ansible_' + item].ipv6 | length > 0),
'ipv4_address': hostvars[inventory_hostname]['ansible_' + item].ipv4.address | default('N/A'),
'ipv6_addresses': hostvars[inventory_hostname]['ansible_' + item].ipv6 | map(attribute='address') | list | default([])
}]
}}
loop: "{{ ansible_interfaces }}"
when:
- item != 'lo'
- not item.startswith('docker')
- not item.startswith('veth')

- name: Determine dual stack support
ansible.builtin.set_fact:
has_dual_stack: "{{ interface_info | selectattr('ipv4_enabled') | selectattr('ipv6_enabled') | list | length > 0 }}"
has_ipv4: "{{ interface_info | selectattr('ipv4_enabled') | list | length > 0 }}"
has_ipv6: "{{ interface_info | selectattr('ipv6_enabled') | list | length > 0 }}"

- name: Build simplified disk list
ansible.builtin.set_fact:
disk_list: "{{ disk_list | default([]) + [{'mount': item.mount, 'size_gb': (item.size_total / 1024 / 1024 / 1024) | round(2)}] }}"
loop: "{{ ansible_mounts | selectattr('size_total', 'defined') | list }}"

- name: Build host information dictionary
ansible.builtin.set_fact:
host_info:
hostname: "{{ inventory_hostname }}"
groups: "{{ group_names }}"
validation:
os_valid: "{{ os_valid }}"
os_details: "{{ ansible_distribution }} {{ ansible_distribution_version }}"
arch_valid: "{{ arch_valid }}"
arch_details: "{{ ansible_architecture }}"
hardware: "{{ hardware_validation }}"
networking:
has_ipv4: "{{ has_ipv4 }}"
has_ipv6: "{{ has_ipv6 }}"
has_dual_stack: "{{ has_dual_stack }}"
interfaces: "{{ interface_info }}"
cpu:
physical_cpus: "{{ ansible_processor_count }}"
cores_per_cpu: "{{ ansible_processor_cores }}"
total_vcpus: "{{ ansible_processor_vcpus }}"
threads_per_core: "{{ ansible_processor_threads_per_core }}"
processor_model: "{{ ansible_processor[2] | default('N/A') }}"
memory:
total_mb: "{{ ansible_memtotal_mb }}"
total_gb: "{{ (ansible_memtotal_mb / 1024) | round(2) }}"
free_mb: "{{ ansible_memfree_mb }}"
swap_total_mb: "{{ ansible_swaptotal_mb }}"
disks: "{{ disk_list }}"
selinux: "{{ ansible_selinux | default({'status': 'not available'}) }}"
firewalld: "{{ ansible_facts.services['firewalld.service'] | default(ansible_facts.services['firewalld'] | default({'state': 'not installed', 'status': 'not installed'})) }}"
os:
distribution: "{{ ansible_distribution }}"
version: "{{ ansible_distribution_version }}"
family: "{{ ansible_os_family }}"
kernel: "{{ ansible_kernel }}"
architecture: "{{ ansible_architecture }}"
hostname: "{{ ansible_hostname }}"
fqdn: "{{ ansible_fqdn }}"

- name: Gather host information
itential.deployer.gather_host_information:
register: host_info

- name: Debug host info
ansible.builtin.debug:
msg: "{{ host_info }}"

- name: Aggregate Results
hosts: localhost
gather_facts: false

tasks:
- name: Collect all host information
ansible.builtin.set_fact:
all_systems_info: "{{ all_systems_info | default([]) + [hostvars[item].host_info] }}"
loop: "{{ groups['all'] }}"

- name: Display aggregated information
ansible.builtin.debug:
var: all_systems_info
154 changes: 154 additions & 0 deletions plugins/modules/gather_host_information.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
#!/usr/bin/python

# Copyright (c) 2026, Itential, Inc
# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

DOCUMENTATION = r'''
---
module: gather_host_information

short_description: Inspect facts and gather interesting data

version_added: "3.0.0"

description: This module will inspect the host facts and gather interesting data to be used in the
verification and certification of environments.

author:
- Steven Schattenberg (@steven-schattenberg-itential)
'''

EXAMPLES = r'''
- name: Gather standard facts
itential.deployer.gather_host_information:
'''

RETURN = r'''
details:
description: Details from the host
type: object
returned: always
sample: false
'''

from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.facts.compat import ansible_facts

def build_disk_list(ansible_mounts):
"""Build simplified disk list from ansible_mounts data"""
disk_list = []

for item in ansible_mounts:
if 'size_total' in item:
disk_list.append({
'mount': item['mount'],
'size_gb': round(item['size_total'] / 1024 / 1024 / 1024, 2)
})

return disk_list

def build_interface_list(facts):
"""Build simplified interface information"""
interfaces = []

# Get list of all interfaces
interface_names = facts.get('interfaces', [])

for iface_name in interface_names:
# Skip loopback
if iface_name == 'lo':
continue

# Get the interface details
iface_data = facts.get(iface_name, {})

if not iface_data or not isinstance(iface_data, dict):
continue

interface_info = {
'name': iface_name,
'active': iface_data.get('active', False),
'type': iface_data.get('type', 'unknown'),
'ipv4': iface_data.get('ipv4', {}),
'ipv6': iface_data.get('ipv6', [])
}

interfaces.append(interface_info)

return interfaces

def run_module():
# define available arguments/parameters a user can pass to the module
module_args = dict()

# seed the result dict in the object
result = dict(
changed=False,
details=False,
)

# the AnsibleModule object will be our abstraction working with Ansible
module = AnsibleModule(
argument_spec=module_args,
supports_check_mode=True
)

# if the user is working with this module in only check mode we do not
# want to make any changes to the environment, just return the current
# state with no modifications
if module.check_mode:
module.exit_json(**result)

# Get the facts from the host
facts = ansible_facts(module)

# Gather OS information...
result["os"] = {}
result["os"]["distribution"] = facts.get("distribution", "unknown")
result["os"]["distribution_version"] = facts.get("distribution_version", "unknown")
result["os"]["os_family"] = facts.get("os_family", "unknown")
result["os"]["kernel"] = facts.get("kernel", "unknown")
result["os"]["architecture"] = facts.get("architecture", "unknown")
result["os"]["hostname"] = facts.get("hostname", "unknown")
result["os"]["fqdn"] = facts.get("fqdn", "unknown")

# Gather hardware information...
result["hardware"] = {}
result["hardware"]["cpu"] = {}
result["hardware"]["cpu"]["processor_count"] = facts.get("processor_count", 0)
result["hardware"]["cpu"]["processor_cores"] = facts.get("processor_cores", 0)
result["hardware"]["cpu"]["processor_vcpus"] = facts.get("processor_vcpus", 0)
result["hardware"]["cpu"]["processor_threads_per_core"] = facts.get("processor_threads_per_core", 0)
result["hardware"]["cpu"]["processor"] = facts.get("processor", [])
result["hardware"]["memory"] = {}
result["hardware"]["memory"]["memtotal_mb"] = facts.get("memtotal_mb", 0)
result["hardware"]["memory"]["memfree_mb"] = facts.get("memfree_mb", 0)
result["hardware"]["memory"]["swaptotal_mb"] = facts.get("swaptotal_mb", 0)
result["hardware"]["disk"] = build_disk_list(facts.get("mounts", []))

# Gather security information...
result["security"] = {}
result["security"]["selinux"] = facts.get("selinux", {"status": "not available"})

# Is firewalld running?
firewalld = facts.get('services', {}).get('firewalld.service')
if firewalld:
result["security"]["firewalld"] = firewalld

# Gather networking information...
result["networking"] = {}
result["networking"]["interfaces"] = build_interface_list(facts)
result["networking"]["default_ipv4"] = facts.get("default_ipv4", {})
result["networking"]["default_ipv6"] = facts.get("default_ipv6", {})

# in the event of a successful module execution, you will want to
# simple AnsibleModule.exit_json(), passing the key/value results
module.exit_json(**result)

def main():
run_module()

if __name__ == '__main__':
main()
4 changes: 4 additions & 0 deletions roles/redis/defaults/main/install.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,7 @@
{{ ansible_distribution_version }}.rpm"
redis_epel_repo_url: "https://dl.fedoraproject.org/pub/epel/epel-release-latest-\
{{ ansible_distribution_major_version }}.noarch.rpm"

# The name and location of the certification report
redis_report_dir: "/tmp/itential-reports"
redis_report_file: "{{ redis_report_dir }}/redis_report_{{ inventory_hostname }}.md"

Check failure on line 33 in roles/redis/defaults/main/install.yml

View workflow job for this annotation

GitHub Actions / Ansible Lint

yaml[new-line-at-end-of-file]

No new line character at the end of file
Loading
Loading