Linux Patching with Ansible
Today I ran into an issue building a patching playbook for my Linux VMs. I have a mix of Ubuntu, and a couple of CentOS versions. I began with testing against Ubuntu and CentOS 8, and everything worked really well. As soon as I brought a CentOS 7 box into the mix, I started receiving errors on reboot detection.
Ubuntu, or debian-based linux distributions will create a file when reboots are required after patching. This makes reboot required detection simple, as you can just check to see if this file exists: '/var/run/reboot-required'. If that file the server needs to be rebooted, and the playbook will do so.
When you get to CentOS/RedHat, they don't generate that same file. Fortunately there's a utility baked into the "yum-utils" package, which should be installed by default, called needs-restarting. This little utility will run a number of scripts on the box to determine if a reboot is required. Most of the time this is for kernel updates or other major system changes. Running 'needs-restarting -r' will provide input like this:
No core libraries or services have been updated since boot-up. Reboot should not be necessary.
Core libraries or services have been updated since boot-up. Reboot is required
Sweet, nice and simple, just check for these in the response right? Not quite...
When Ansible looks at the responses from the commands when run with the command or shell module, it looks at the return code to determine if the task was successful or not. This is where the issues begin. CentOS 8 returns 0 as the "rc" (return code) when the command sends it's output to stdout. Running 'needs-restarting -r &>/dev/null; echo $?' will show the return code. Running this on CentOS 7, the rc is 1. When Ansible sees an rc of 1 it thinks the task fails even though stdout shows the expected output. I'm not sure why this is, but I used a workaround that forces a return code of 0 whether the command is successful or not since we aren't concerned with the rc, but with stdout. So the command used is 'needs-restarting -r || True' for that. Bingo! We're in business.... but wait...
Now Ansible is complaining because we are using the command module like is preferred, but the command module doesn't handle pipeline input and things are run separately so we don't get the proper returns. Switching to the shell module solves this problem! Now we're in business!