Most run_xxx() return stdout and stderr. There's no way, really, for the caller to get hold of the exit code of the spawned executable. It can pass throw=true, catch, and assume a non-zero exit status. But that's not semantically clean, since the spawned function can well be a test function which is expected to return a non-zero status code, and the caller might be interested in what code that was, exactly.
The clearest way to solve this is to return the exit code as well. This commit does that.
This is a code maintenance commit: some run_xxx() helper functions take a string, some a list, and some just digest all arguments and pass them on as a list to exec() to be executed. That's highly inconsistent. This commit changes that to list-only.
Except for the run_cmd() method of SSHClient, which is still run as a shell method, because, erm, it's a shell. Might be changed in the future for consistency reasons.
Add a function get_profile_env(), a function returning environment variables from /etc/profile. Pass add=True to add its contents to the existing environment dictionary, overwriting old entries, or pass False to get the pristine content.
Add a parameter "output_encoding" to run_cmd(). The parameter allows the caller to specify if the output encoding should be detected as is by passing None (the default), if the output should be returned as undecoded bytes by passing the special string "bytes", or if the output should be treated as the encoding with the specified name and decoded to strings.
Don't log an Exception as {e} but as str(e) producing nicer output. Or as repr(e) if a backtrace is requested, because to people who can read backtraces, type info might be of interest. Also, remove pointless time stamps, those belong into the logging framework.
Move the body of BackendCmd.sudo() into a function. The rationale behind that is that its functionality is independent of the calling object for the most part, so having it in a function instead of a method is the more modular pattern.