Search code examples
ansiblejinja2ansible-templateansible-awx

Loop in Jinja2 template does not find attribute in var after AWX upgrade


I recently made an upgrade of our AWX instance from 19.4.0 to 21.11.0. We're using AWX among other things to install Windows Updates on Windows servers. At the end of the whole play we're using Jinja2 template to generate an HTML report file listing the installed (or not) patches (KBs), which is sent in a mail after the play has run successfully.

After the upgrade of AWX and using AWX-EE 21.11.0 we get the error "AnsibleUndefinedVariable: 'ansible.utils.unsafe_proxy.AnsibleUnsafeText object' has no attribute 'kb'"

Versions before ugprade: AWX: 19.4.0 AWX-EE: 0.6.0

Versions after upgrade: AWX version: 21.11.0 AWX-EE version: 21.11.0

Role: winupdates_deploy:

---
# Installation of Windows Updates
- name: Install Windows Updates
  ignore_errors: yes
  ansible.windows.win_updates:
    category_names: "{{ categorylist }}"
    accept_list:  "{{ accepted_kb }}"
    reject_list: "{{ rejected_kb }}"
    reboot: no
    state: installed
    log_path: "{{ loglocation }}\\awxlog_winupdates_{{ report_type }}.txt"
  register: list_of_updates

Sample of var "list_of_updates":

{
  "list_of_updates": {
    "changed": false,
    "reboot_required": false,
    "found_update_count": 1,
    "failed_update_count": 0,
    "installed_update_count": 0,
    "updates": {
      "88569462-c620-4437-9d8f-baeea24ca37e": {
        "title": "Security Intelligence Update for Microsoft Defender Antivirus - KB2267602 (Version 1.383.1098.0)",
        "kb": [
          "2267602"
        ],
        "categories": [
          "Definition Updates",
          "Microsoft Defender Antivirus"
        ],
        "id": "88569462-c620-4437-9d8f-baeea24ca37e",
        "downloaded": false,
        "installed": false
      }
    },
    "filtered_updates": {
      "8e212273-94f0-4b31-8bb5-00676a5423f4": {
        "title": "Windows Malicious Software Removal Tool x64 - v5.110 (KB890830)",
        "kb": [
          "890830"
        ],
        "categories": [
          "Update Rollups",
          "Windows Server 2016",
          "Windows Server 2019"
        ],
        "id": "8e212273-94f0-4b31-8bb5-00676a5423f4",
        "downloaded": false,
        "installed": false,
        "filtered_reason": "category_names",
        "filtered_reasons": [
          "category_names"
        ]
      }
    },
    "failed": false
  },
  "_ansible_verbose_always": true,
  "_ansible_no_log": false,
  "changed": false
}

Role: send_report_mail

---
# Generieren des HTML-Reports mit Jinja2 Templates
- name: Clean Up Reports Directory if it exists
  file:
    path: reports
    state: absent
  delegate_to: localhost
  run_once: yes

- name: Create Reports Directory
  file:
    path: reports
    state: directory
    mode: 0755
  delegate_to: localhost
  run_once: yes

- name: Template Rendering
  template:
    src: individual_vm_report.html.j2
    dest: "reports/z{{ inventory_hostname }}_interim_report.html"
  delegate_to: localhost

- name: Create Interim Report
  template:
    src: consolidated_vms_report.html.j2
    dest: "reports/Consolidated_VMs_Report.html"
  delegate_to: localhost
  run_once: yes

- name: Consolidate Interim Report
  assemble:
    src: reports/
    dest: reports/Consolidated_VMs_Report.html
  delegate_to: localhost
  run_once: yes

- name: Generate Remaining Portion of Report
  template:
    src: consolidated_vms_report_last_block.html.j2
    dest: "reports/consolidated_vms_report_last_block.html"
  delegate_to: localhost
  run_once: yes

- name: Get the List of Interim Reports
  find:
    paths: reports/
    patterns: '*_interim_report.html'
  register: interim_reports_to_delete
  delegate_to: localhost
  run_once: yes

- name: Clean Up Interim Reports
  file:
    path: "{{ item.path }}"
    state: absent
  loop: "{{ interim_reports_to_delete.files }}"
  delegate_to: localhost
  run_once: yes

- name: Consolidate Final Report
  assemble:
    src: reports/
    dest: reports/Consolidated_VMs_Report.html
  delegate_to: localhost
  run_once: yes

- name: Rename Report File
  command: mv reports/Consolidated_VMs_Report.html reports/AWXReport_WinUpdates_{{ report_type }}_{{ customername }}.html
  delegate_to: localhost
  run_once: yes

- name: Final Clean Up
  file:
    path: reports/consolidated_vms_report_last_block.html
    state: absent
  delegate_to: localhost
  run_once: yes

# Send Mail mit HTML-Report 
- name: Send Email with Report
  community.general.mail:
    host: *****
    port: 25
    from: "***@***.***"
    to: "{{ sendto_email }}"
    subject: "AWX Report - WinUpdates {{ report_type }} ({{ customername }})"
    body: |
      AWX Report - WinUpdates {{ report_type }} ({{ customername }})

      Lorem Ipsum...
          
    attach:
      - reports/AWXReport_WinUpdates_{{ report_type }}_{{ customername }}.html
  delegate_to: localhost
  run_once: yes

Jinja2 template block (individual_vm_report.html.j2):

...
        <tr>
            <td>{{ inventory_hostname }}</td>
            <td>{{ list_of_updates.found_update_count }} </td>
            <td>
               <ul>
{% for win_update in list_of_updates.updates %}
                  <li>{{ win_update.kb }}</li>
{% endfor %}
               </ul>
            </td>
            <td>
               <ul>
{% for win_update in list_of_updates.updates %}
                  <li>{{ win_update.title }}</li>
{% endfor %}
               </ul>
            </td>
            <td>
               <ul>
{% for win_update in list_of_updates.updates %}
                  <li>{{ win_update.installed }}</li>
{% endfor %}
               </ul>
            </td>
            <td><font color="red">{{ report_comment }} </font></td>
         </tr>
...

Error (TASK [send_report_mail : Template Rendering]):

{
  "msg": "AnsibleUndefinedVariable: 'ansible.utils.unsafe_proxy.AnsibleUnsafeText object' has no attribute 'kb'",
  "exception": "Traceback (most recent call last):\n  File \"/usr/local/lib/python3.8/site-packages/ansible/template/__init__.py\", line 1156, in do_template\n    res = j2_concat(rf)\n  File \"<template>\", line 22, in root\n  File \"/usr/lib/python3.8/site-packages/jinja2/runtime.py\", line 747, in _fail_with_undefined_error\n    raise self._undefined_exception(self._undefined_message)\njinja2.exceptions.UndefinedError: 'ansible.utils.unsafe_proxy.AnsibleUnsafeText object' has no attribute 'kb'\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n  File \"/usr/local/lib/python3.8/site-packages/ansible/plugins/action/template.py\", line 150, in run\n    resultant = templar.do_template(template_data, preserve_trailing_newlines=True, escape_backslashes=False)\n  File \"/usr/local/lib/python3.8/site-packages/ansible/template/__init__.py\", line 1193, in do_template\n    raise AnsibleUndefinedVariable(e)\nansible.errors.AnsibleUndefinedVariable: 'ansible.utils.unsafe_proxy.AnsibleUnsafeText object' has no attribute 'kb'\n",
  "_ansible_no_log": false,
  "changed": false,
  "_ansible_delegated_vars": {
    "ansible_host": "localhost",
    "ansible_port": 5986,
    "ansible_user": "root",
    "ansible_connection": "local"
  }
}

Before with AWX-EE 0.6.0 it worked. Any idea/hint how I can fix this?

(Btw, I also tried AWX-EE lates and also AWX-EE 21.12.0, but they fail with "credssp: requests auth method is credssp, but requests-credssp is not installed")


Solution

  • I don't have an awx nor windows to test this, but I'm almost positive the problem is for win_update in list_of_updates.updates is iterating over a dict not a list. Thus, you'll want

    {% for win_update in list_of_updates.updates.values() %}
    {% endfor %}
    

    to get the values from the dict, versus what's happening now is that win_update is the key from that dict