Reboot Servers, Check Last Reboot and Email Report Anomaly

Hi,

I’ve got an ansible role with a task to reboot a bunch of servers, check last reboot and then email last reboot output:

# tasks file for reboot_status
- name: reboot
  ansible.builtin.reboot:
  become: true
  when: inventory_hostname != 'localhost'

- name: last reboot
  ansible.builtin.command: last reboot | head -1
  register: last_reboot_result

- name: email reboot_result
  community.general.mail:
    host: smtp.com.local
    port: 25
    from: Ansible Alert <ansible@com.local>
    to:
      - systemalerts@com.local
    subject: Reboot Status for Linux Servers
    body: "{{ lookup('template', 'templates/email_body.j2') }}"
    subtype: html
  run_once: true
  delegate_to: localhost

The email template looks like this:

<body>
    {% if 'wed4' in group_names %}
        <h3>Reboot Status for 4th Wednesday Collection</h3>
    {% elif 'wed3' in group_names %}
        <h3>Reboot Status for 3rd Wednesday Collection</h3>
    {% else %}
        <h3>Reboot Status for Ansible Server</h3>
    {% endif %}
    <p>Verify reboot status for the following servers:</p>
    <table>
        <thead>
            <tr>
                <th>Server</th>
                <th>Status</th>
            </tr>
        </thead>
        <tbody>
            {% if 'wed4' in group_names %}
            {% for host in groups['wed4'] %}
            <tr>
                <td>{{ host}}</td>
                <td>{{ last_reboot_result.stdout_lines[0] }}</td>
            </tr>
            {% endfor %}
            {% elif 'wed3' in group_names %}
            {% for host in groups['wed3'] %}
            <tr>
                <td>{{ host}}</td>
                <td>{{ last_reboot_result.stdout_lines[0] }}</td>
            </tr>
            {% endfor %}
            {% else %}
            <tr>
                <td>ansibleserver</td>
                <td>{{ last_reboot_result.stdout_lines[0] }}</td>
            </tr>
            {% endif %}
        </tbody>
    </table>
</body>

However, the Status column in the report is not accurate. I think its using the …

last_reboot_result.stdout_lines[0]

… from the first server its processing and repeating the result to all servers in the Status column.

The Status column looks like this:

reboot system boot 5.15.0-161-gener Wed Nov 19 18:00 still running

Which is wrong for some of the servers which are on a different kernel version. The logic in the role’s task isn’t quite right.

The cron job runs this command:

/usr/bin/ansible-playbook /etc/ansible/playbooks/playbook_reboot_status.yml --limit wed3

Any advice would be most appreciated.

Thanks.

Think I figured it out. At least in terms of the solution by replacing the email body’s code to:

<tbody>
	{% for host in ansible_play_hosts_all %}
	<tr>
		<td>{{ host }}</td>
		<td>{{ hostvars[host].last_reboot_result.stdout_lines[0] }}</td>
	</tr>
	{% endfor %}
</tbody>

Previous convoluted code was probably just rehashing the variable “last_reboot_result.stdout_lines[0]” from just one host.

Cheers.

1 Like

I am just wondering whether ansible.buibuin.command works with a shell command line:

last reboot | head -1

Shouldnt command take only a single host command and its parameters? Not even glob expressions for file pathes are supported neither any redirects nor piping.

For such cases module shell should be used

Br
Roland

1 Like

Yes, command takes a command and arguments to that command. In this case, it works by accident due to a quirk in how last is implemented. Something @felixfontein and @sivel helped me to figure out.

The last command takes a list of usernames as well as some arguments. It happens to accept -1 as an argument that works just head -1 would have. This is documented in man last under -number.

So the ‘|’ character is seen as a username, as is the head command while the -1 arg limits the output. As it happens, the pipe and head args get ignored because no users logged in with that name while the -1 arg does limit the output. It can be tested like this:

last reboot -1

So the command model can safely an clearly be used here to display only the most recent reboot with an alternate syntax.

1 Like

Thanks for the heads up. I’ll re-look at my playbook and switch to the shell module.

As alternative to use shell you can set also use “-n 1” with ‘last’. I tried this only now in my Linux box after until now only posting from an Android phone.

- name: last reboot
  #1#ansible.builtin.command: last reboot | head -1
  #2#
  ansible.builtin.command: last reboot -n 1
  register: last_reboot_result_raw
- debug: msg="{{ last_reboot_result_raw.stdout_lines }}"

Both variants of using ‘last’ have a three line output.

ok: [localhost] => {
“msg”: [
“reboot system boot 5.15.0-161-gener Wed Nov 26 07:45 still running”,
“”,
“wtmp begins Wed Aug 14 08:31:33 2024”
]
}

Then you can also set a fact and use variable last_boot_result in the template without need to fetch several times the first line.

- set_fact: last_reboot_result="{{ last_reboot_result_raw.stdout_lines[0] }}"

You are right. I missed this the first time I searched man last because it’s not under -1 in the docs, it’s under -number. I’ve updated my answer to reflect that. Thanks!