(#19162) Execute shutdown command after puppet exits

Previously, there was a race condition between the time that puppet exited and
when the OS rebooted the system. This is because we were requesting a shutdown
(reboot) in `timeout` seconds, but puppet sends a report, which is processed
synchronously by the master. So it was possible for puppet to still be running
when the OS reboots the system.

This commit creates a Watcher class whose job is to wait for the puppet process
to complete, and then execute a command at the end.
(#19162) Create a separate catalog_apply_timeout

Previously, the reboot `timeout` parameter was used both as the maximum amount
of time to wait for puppet to apply the catalog and when the reboot is
requested, as well as the amount of time between when the reboot is requested
and when it is performed.

This commit introduces a catalog_apply_timeout, which defaults to 7200 seconds
(2 hours).
(#19162) Only execute a reboot if puppet finishes

Previously, if the watcher process timed out while waiting for puppet to
finish, or if it failed to wait on the puppet process, then the reboot would be
performed. This could cause problems if puppet was making (slow) progress when
applying the catalog.

This commit changes the watcher process to only execute the shutdown command if
the puppet process exits. If a timeout is reached or it fails to wait on the
process, then a message will be generated that the reboot has been aborted.
(#19162) Ensure parent's stdout isn't closed

Previously, the provider was calling `Open3.popen2` to pipe data to the child
process. However, some combination of popen2 and puppet, likely due to
Puppet::Util::Colors::WideIO, resulted in no additional messages being written
to the console after the call to `popen2`.

I think this is because parent's stdout handle is somehow being closed (perhaps
because it is inherited by the child process), and never reattached to the
:console log destination.

However, we don't actually need to pipe the child's stdout, so this commit
switches to using `Open3.pipeline_w`. This only pipes stdin (parent writes,
child reads). As a result, you will now see messages following the reboot
resource:

    Debug: /Stage[main]//Notify[after]: Transaction canceled, skipping
(#19162) Report on the localized last error code

If we fail to wait on the puppet process, we will now include the localized
GetLastError message, which gives more insight into why it failed.
Merge pull request #4 from joshcooper/ticket/master/19162-reboot

(#19162) Execute shutdown command after puppet exits