How to alert via email in Ansible

Viewed 455

I have setup a mail task in ansible to send emails if yum update is marked as 'changed'.

Here is my current working code:

- name: Send mail alert if updated
      community.general.mail:
        to:
        - 'recipient1'
        cc:
        - 'recipient2'
        subject: Update Alert
        body: 'Ansible Tower Updates have been applied on the following system: {{ ansible_hostname }}'
        sender: "ansible.updates@domain.com"
      delegate_to: localhost
      when: yum_update.changed

This works great, however, every system that gets updated per host group sends a separate email. Last night for instance I had a group of 20 servers update and received 20 separate emails. I'm aware of why this happens, but my question is how would I script this to add all the systems to one email? Is that even possible or should I just alert that the group was updated and inform teams of what servers are in each group? (I'd prefer not to take the second option)

Edit 1:

I have added the code suggested and am now unable to receive any emails. Here's the error message:

"msg": "The conditional check '_changed|length > 0' failed. The error was: error while evaluating conditional (_changed|length > 0): {{ hostvars|dict2items| selectattr('value.yum_update.changed')| map(attribute='key')|list }}: 'ansible.vars.hostvars.HostVarsVars object' has no attribute 'yum_update'\n\nThe error appears to be in '/tmp/bwrap_1073_o8ibkgrl/awx_1073_0eojw5px/project/yum-update-ent_template_servers.yml': line 22, column 7, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n    - name: Send mail alert if updated\n      ^ here\n",

I am also attaching my entire playbook for reference:

---
- name: Update enterprise template servers
  hosts: ent_template_servers

  tasks:

    - name: Update all packages
      yum:
        name: '*'
        state: latest
      register: yum_update

    - name: Reboot if needed
      import_tasks: /usr/share/ansible/tasks/reboot-if-needed-centos.yml

    - name: Kernel Cleanup
      import_tasks: /usr/share/ansible/tasks/kernel-cleanup.yml

    - debug:
        var: yum_update.changed

    - name: Send mail alert if updated
      community.general.mail:
        to:
        - 'email@domain.com'
        subject: Update Alert
        body: |-
          Updates have been applied on the following system(s):
          {{ _changed }}
        sender: "ansible.updates@domain.com"
      delegate_to: localhost
      run_once: true
      when: _changed|length > 0
      vars:
        _changed: "{{ hostvars|dict2items|
                      selectattr('yum_update.changed')|
                      map(attribute='key')|list }}"

...

Ansible version is: 2.9.27 Ansible Tower version is: 3.8.3

Thanks in advance!

1 Answers

For example, the mail task below

    - debug:
        var: yum_update.changed
    - community.general.mail:
        sender: ansible
        to: root
        subject: Update Alert
        body: |-
          Updates have been applied to the following system:
          {{ _changed }}
      delegate_to: localhost
      run_once: true
      when: _changed|length > 0
      vars:
        _changed: "{{ hostvars|dict2items|
                      selectattr('value.yum_update.changed')|
                      map(attribute='key')|list }}"
TASK [debug] ***************************************************************
ok: [host01] => 
  yum_update.changed: true
ok: [host02] => 
  yum_update.changed: false
ok: [host03] => 
  yum_update.changed: true

TASK [community.general.mail] **********************************************
ok: [host01 -> localhost]

will send

From: ansible@domain.com
To: root@domain.com
Cc: 
Subject: Update Alert
Date: Wed, 09 Feb 2022 16:55:47 +0100
X-Mailer: Ansible mail module

Updates have been applied to the following system:
['host01', 'host03']

Remove the condition below if you want to receive also empty lists

      when: _changed|length > 0

Debug

'ansible.vars.hostvars.HostVarsVars object' has no attribute 'yum_update'

Q: "What I could try?"

A: Some of the hosts are missing the variables yum_update. You can test it

    - debug:
        msg: "{{ hostvars|dict2items|
                 selectattr('value.yum_update.changed')|
                 map(attribute='key')|list }}"
      run_once: true

Either make sure that the variable is defined on all hosts or use json_query. This filter tolerates missing attributes, e.g.

    - debug:
        msg: "{{ hostvars|dict2items|
                 json_query('[?value.yum_update.changed].key') }}"
      run_once: true

Q: "The 'debug' task prior to the 'mail' task gives me the same output. But it fails when the 'mail' task is executed."

A: Minimize the code and isolate the problem. For example, in the code below you can see

  • Variable yum_update.changed is missing on host03
  • The filter json_query ignores this
  • The filter selectattr fails
    - debug:
        var: yum_update.changed

    - debug:
        msg: "{{ hostvars|dict2items|
                 json_query('[?value.yum_update.changed].key') }}"
      run_once: true

    - debug:
        msg: "{{ hostvars|dict2items|
                 selectattr('value.yum_update.changed')|
                 map(attribute='key')|list }}"
      run_once: true

gives

TASK [debug] **************************************************
ok: [host01] => 
  yum_update.changed: true
ok: [host02] => 
  yum_update.changed: false
ok: [host03] => 
  yum_update.changed: VARIABLE IS NOT DEFINED!

TASK [debug] **************************************************
ok: [host01] => 
  msg:
  - host01

TASK [debug] **************************************************
fatal: [host01]: FAILED! => 
  msg: |-
    The task includes an option with an undefined variable.
    The error was: 'ansible.vars.hostvars.HostVarsVars object'
    has no attribute 'yum_update'

Both filters give the same results if all variables are present

TASK [debug] **************************************************
ok: [host01] => 
  yum_update.changed: true
ok: [host02] => 
  yum_update.changed: false
ok: [host03] => 
  yum_update.changed: true

TASK [debug] **************************************************
ok: [host01] => 
  msg:
  - host01
  - host03

TASK [debug] **************************************************
ok: [host01] => 
  msg:
  - host01
  - host03
Related