Search code examples
ansibleansible-template

Register output ansible playbook


I have a playbook to run command on a list of servers and put the results in a file.
But, sometimes, I can't connect to those servers due to an incorrect login/password or because it is unreachable.

When it is unreachable, I have this on the console:

fatal: [fqdn]: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via ssh: ssh: Could not resolve hostname fqdn: Name or service not known", "unreachable": true}

When I have an incorrect login or password, I have this:

fatal: [fqdn]: UNREACHABLE! => {"changed": false, "msg": "Invalid/incorrect password: Permission denied, please try again.", "unreachable": true}

I would like to capture this message in the output console to write in a template Jinja.
In order to have kind of a report.

I have done this in the playbook

- name: Listing des packages
  hosts: all
  gather_facts: false

  tasks:
    - name: Collect only selected facts
      setup:
        gather_subset: min

    - name: Total number of updates
      shell: yum check-update | wc -l
      register: nbupdates

    - name: Output to html file
      template:
        src: ./jinja/src/tpl_dashboard_update.j2
        dest: ./jinja/dst/dashboard_update.html
        force: yes
      delegate_to: localhost
      run_once: true

and the Jinja template

{% for host in ansible_play_hosts_all %}
        <tr>
            <td>{{ loop.index }}</td>
            <td>{{ host | upper }}</td>
            {% if hostvars[host]['ansible_distribution'] is not defined %}
                <td colspan="4">Injoignable</td>
            {% else %}
                <td>{{ hostvars[host]['ansible_distribution'] }} {{ hostvars[host]['ansible_distribution_version'] }}</td>
                <td> {% if hostvars[host].nbupdates.stdout_lines.0 is defined %} {{ hostvars[host].nbupdates.stdout_lines.0 }} {% else %} --- {% endif %} </td>
            {% endif %}
        </tr>
{% endfor %}

It is not working properly, I only have the result when it is a success in the file and not the result, when it fails.

How I can "capture" the error message when it's unreachable or when I have a login/password error?


Solution

  • You will have to use ignore_unreachable: yes in order to gather a result form an unreachable host.
    You will also have to skip those unreachable hosts in the tasks that follows your setup.
    And, in order to overcome the possibility that all hosts are unreachable, you probably want to target localhost, all and skip localhost when it is not needed.

    Two other side notes:

    • Prefer the use of a dedicated module when it exists. You shell task to get all the available update would be better fitted in a yum task.
    • This is a quite heavy constructions:
      {% if hostvars[host].nbupdates.stdout_lines.0 is defined %} 
        {{ hostvars[host].nbupdates.stdout_lines.0 }} 
      {% else %} 
        --- 
      {% endif %}
      
      And can be easily shortened with a default filter:
      {{ hostvars[host].nbupdates.stdout_lines.0 | default('---') }}
      
      But, this is not something we should use here since we should rely on the length of the list returned by the yum module

    Here would be an example of a playbook with all this together:

    - hosts: localhost, all
      gather_facts: no
    
      tasks:
        - setup:
            gather_subset: min
          register: setup
          ignore_unreachable: yes
          when: inventory_hostname != 'localhost'
    
        - yum:
            list: updates
          register: yum
          when:
            - setup is reachable
            - inventory_hostname != 'localhost'
    
        - template:
            src: ./jinja/src/tpl_dashboard_update.j2
            dest: ./jinja/dst/dashboard_update.html
            force: yes
          delegate_to: localhost
          run_once: true
    

    And here is the adapted template tpl_dashboard_update.j2:

    <table>
      {% for host in ansible_play_hosts_all if host != 'localhost' %}
        <tr>
          <td>{{ loop.index }}</td>
          <td>{{ host | upper }}</td>
          {% if hostvars[host].setup is unreachable %}
            <td>Injoignable</td>
            <td>{{ hostvars[host].setup.msg }}</td>
          {% else %}
            <td>
              {{ hostvars[host].ansible_distribution }}
              {{ hostvars[host].ansible_distribution_version }}
            </td>
            <td>
              {% if hostvars[host].yum.results is defined %}
                {{ hostvars[host].yum.results | length }}
              {% else %}
                ---
              {% endif %}
            </td>
          {% endif %}
        </tr>
      {% endfor %}
    </table>
    

    This all would yield a table like (style added for readability):

    table, th, td {
      border: 1px solid black;
      border-collapse: collapse;
      padding: 4px;
    }
    <table>
      <tr>
        <td>1</td>
        <td>TEST</td>
        <td>Injoignable</td>
        <td>Failed to connect to the host via ssh: ssh: Could not resolve hostname test: Name does not resolve</td>
      </tr>
      <tr>
        <td>2</td>
        <td>NODE1</td>
        <td>
          Fedora 35
        </td>
        <td>
          29
        </td>
      </tr>
      <tr>
        <td>3</td>
        <td>NODE2</td>
        <td>Injoignable</td>
        <td>Invalid/incorrect password: Permission denied, please try again.</td>
      </tr>
    </table>