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.

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

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.

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!