I am learning to write playbooks and trying to write a playbook that runs in different release of RHEL servers and report 3 different software installation and send a compliance report in email.
Here is the condition.
Software1 -
RHEL5 - Not Applicable so does not require to check
RHEL6,7,8,9 - Run the command rpm -q software1 to detect the installation
Software2
RHEL6 -Not Applicable so does not require to check
RHEL 5,7,8,9 - Locate the below directory path for each version
RHEL 5 - /opt/software2-base/
RHEL7,8,9 - /opt/software2/base/
Software3
For all versions - run the command rpm -q software3
Here is my playbook.
---
- name: Check Software Installation Status on RHEL servers and Send Email
hosts: all
become: yes
vars_files:
- cred.yml
vars:
software_list:
- name: "software1"
package: "software1"
check_type: "rpm"
- name: "software2"
check_type: "directory"
- name: "software3"
package: "software3"
check_type: "rpm"
email_to: "emailid@domain"
email_from: "emailid@domain"
email_subject: "Software Installation Compliance Report for RHEL Servers"
tasks:
- name: Detect Python interpreter
shell: |
if command -v python &> /dev/null; then
echo /usr/bin/python
elif command -v python2 &> /dev/null; then
echo /usr/bin/python2
elif command -v python3 &> /dev/null; then
echo /usr/bin/python3
else
echo "Python not found"
exit 1
fi
register: python_interpreter
ignore_errors: yes
changed_when: false
- name: Set ansible_python_interpreter
set_fact:
ansible_python_interpreter: "{{ python_interpreter.stdout }}"
when: python_interpreter.stdout != "Python not found"
- name: Gather OS dist and version
setup:
filter: "ansible_distribution*"
register: os_facts
- name: Debug os_facts
debug:
var: os_facts
- name: Initialize compliance report
set_fact:
compliance_report: [ ]
- name: Initialize software2_installed_rhel5 with default value
set_fact:
software2_installed_rhel5:
stat:
exists: false
- name: Check if software2 is installed (Directory - RHEL5)
stat:
path: "/opt/software2-base/"
register: software2_installed_rhel5
when: >
os_facts.ansible_facts.ansible_distribution_major_version | int == 5
- name: Debug software2_installed_rhel5
debug:
var: software2_installed_rhel5
- name: Check if software2 is installed (Directory - RHEL 7/8/9 )
stat:
path: "/opt/software2/base"
register: software2_installed_rhel7
when: >
os_facts.ansible_facts.ansible_distribution_major_version | int >= 7
- name: Debug software2_installed_rhel7
debug:
var: software2_installed_rhel7
- name: Check if software is installed (RPM)
shell: rpm -q {{ item.package }}
register: rpm_installed
ignore_errors: yes
loop: "{{ software_list }}"
loop_control:
loop_var: item
when: >
item.check_type == "rpm" and
(item.name != 'software1' or (item.name == 'software1' and os_facts.ansible_facts.ansible_distribution_major_version | int >= 6))
- name: Debug rpm_installed
debug:
var: rpm_installed
- name: Add non-compliant software to compliance report (RPM)
set_fact:
compliance_report: >
{{
compliance_report + [{
'server': ansible_fqdn,
'software': item.item.name,
'status': 'Not Installed'
}]
}}
loop: "{{ rpm_installed.results }}"
loop_control:
loop_var: item
when: >
item.item.check_type == "rpm" and
item is failed and
(rpm_installed is failed) and
(item.item.name != 'software1' or (item.item.name == 'software1' and os_facts.ansible_facts.ansible_distribution_major_version | int >= 6))
- name: Add non-compliant software to compliance report (Directory - RHEL 5)
set_fact:
compliance_report: >
{{
compliance_report + [{
'server': ansible_fqdn,
'software': item.name,
'status': 'Not Installed'
}]
}}
when: >
software2_installed_rhel5 is defined and
software2_installed_rhel5.stat is defined and
software2_installed_rhel5.stat.exists == false and
os_facts.ansible_facts.ansible_distribution_major_version | int == 5
- name: Add non-compliant software to compliance report (Directory - RHEL 7/8/9)
set_fact:
compliance_report: >
{{
compliance_report + [{
'server': ansible_fqdn,
'software': item.name,
'status': 'Not Installed'
}]
}}
when: >
(software2_installed_rhel7.stat.exists == false) and
os_facts.ansible_facts.ansible_distribution_major_version | int >= 7
- name: Debug compliance_report
debug:
var: compliance_report
- name: Collect compliance reports from all servers
set_fact:
global_compliance_report: "{{ global_compliance_report | default([]) + compliance_report }}"
run_once: yes
delegate_to: localhost
- name: debug global_compliance_report after set_fact
debug:
var: global_compliance_report
run_once: yes
delegate_to: localhost
- name: Send consolidated email report
hosts: localhost
become: no
tasks:
- name: Debug global_compliance_report
debug:
var: global_compliance_report
- name: Send email with consolidated compliance report
community.general.mail:
host: smtp.domain
port: 25
from: "{{ email_from }}"
to: "{{ email_to }}"
subject: "{{ email_subject }}"
body: |
Software Installation Compliance Report:
{% for entry in global_compliance_report %}
- Server: {{ entry.server }}
Software: {{ entry.software }}
Status: {{ entry.status }}
{% endfor %}
when: global_compliance_report is defined and global_compliance_report | length > 0
when I run the playbook against one server for testing. it runs without failing but I get the below in debug output. The server runs RHEL7 where software3 is not installed.
…
…
TASK [Add non-compliant software to compliance report (RPM)] *******************
skipping: [server1.domain] => (item={'changed': True, 'end': '2025-03-12 12:32:11.963910', 'stdout': 'software1.el7.x86_64', 'cmd': 'rpm -q software1', 'start': '2025-03-12 12:32:11.918758', 'delta': '0:00:00.045152', 'stderr': '', 'rc': 0, 'invocation': {'module_args': {'executable': N
one, '_uses_shell': True, 'strip_empty_ends': True, '_raw_params': 'rpm -q software1', 'removes': None, 'argv': None, 'creates': None, 'chdir': None, 'stdin_add_newline': True, 'stdin': None}}, 'msg': '', 'stdout_lines': ['software1.el7.x86_64'], 'stderr_lines': [], 'failed': False, 'item': {'na
me': 'software1', 'package': 'software1', 'check_type': 'rpm'}, 'ansible_loop_var': 'item'})
skipping: [server1.domain] => (item={'changed': False, 'skipped': True, 'skip_reason': 'Conditional result was False', 'item': {'name': 'software2', 'check_type': 'directory'}, 'ansible_loop_var': 'item'})
ok: [server1.domain] => (item={'changed': True, 'end': '2025-03-12 12:32:13.080696', 'stdout': 'package software3 is not installed', 'cmd': 'rpm -q software3', 'failed': True, 'delta': '0:00:00.043627', 'stderr': '', 'rc': 1, 'invocation': {'module_args': {'executable': None, '_uses_shell': True, 's
trip_empty_ends': True, '_raw_params': 'rpm -q open-vm-tools', 'removes': None, 'argv': None, 'creates': None, 'chdir': None, 'stdin_add_newline': True, 'stdin': None}}, 'start': '2025-03-12 12:32:13.037069', 'msg': 'non-zero return code', 'stdout_lines': ['package software3 is not installed'], 'stderr_lines': [
], 'item': {'name': 'software3', 'package': 'software3', 'check_type': 'rpm'}, 'ansible_loop_var': 'item'})
TASK [Add non-compliant software to compliance report (Directory - RHEL 5)] ****
skipping: [server1.domain]
TASK [Add non-compliant software to compliance report (Directory - RHEL 7/8/9)] ***
skipping: [server1.domain]
TASK [Debug compliance_report] *************************************************
ok: [server1.domain] => {
"compliance_report": [
{
"server": "server1.domain",
"software": "software3",
"status": "Not Installed"
}
]
}
TASK [Collect compliance reports from all servers] *****************************
ok: [server1.domain -> localhost]
TASK [debug global_compliance_report after set_fact] ***************************
ok: [server1.domain -> localhost] => {
"global_compliance_report": [
{
"server": "server1.domain",
"software": "software3",
"status": "Not Installed"
}
]
}
PLAY [Send consolidated email report] ******************************************
TASK [Gathering Facts] *********************************************************
ok: [localhost]
TASK [Debug global_compliance_report] ******************************************
ok: [localhost] => {
"global_compliance_report": "VARIABLE IS NOT DEFINED!"
}
TASK [Send email with consolidated compliance report] **************************
skipping: [localhost]
PLAY RECAP *********************************************************************
server1.domain : ok=16 changed=1 unreachable=0 failed=0 skipped=3 rescued=0 ignored=1
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0