Repository: ansible/ansible

Issue:
`runit` module does not provide a good errorr message if `sv` is not installed
<!---
Verify first that your issue/request is not already reported on GitHub.
Also test if the latest release, and master branch are affected too.
-->

##### ISSUE TYPE
<!--- Pick one below and delete the rest: -->
 - Bug Report

##### COMPONENT NAME
<!--- Name of the module/plugin/task/feature -->
module/runit

##### ANSIBLE VERSION
<!--- Paste verbatim output from "ansible --version" between quotes below -->
```
ansible 2.4.1.0
  config file = None
  configured module search path = ['/Users/cn13/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /Users/cn13/work/projects/openstack/hgi-systems/venv/lib/python3.6/site-packages/ansible
  executable location = ./venv/bin/ansible
  python version = 3.6.2 (default, Aug  8 2017, 13:08:05) [GCC 4.2.1 Compatible Apple LLVM 7.3.0 (clang-703.0.31)]
```

##### CONFIGURATION
<!---
If using Ansible 2.4 or above, paste the results of "ansible-config dump --only-changed"

Otherwise, mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).

-->
N/A

##### OS / ENVIRONMENT
<!---
Mention the OS you are running Ansible from, and the OS you are
managing, or say "N/A" for anything that is not platform-specific.
Also mention the specific version of what you are trying to control,
e.g. if this is a network bug the version of firmware on the network device.
-->
N/A

##### SUMMARY
<!--- Explain the problem briefly -->
There is no check for whether the `sv` binary is not `None`:
https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/system/runit.py#L144-L145

The problem only manifests itself later on in an obscure way.

##### STEPS TO REPRODUCE
<!---
For bugs, show exactly how to reproduce the problem, using a minimal test-case.
For new features, show how the feature would be used.
-->
Attempt to use module on a machine without runit installed.

##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->
The module detects that runit is not installed and returns a friendly error message.

##### ACTUAL RESULTS
An error message that does not well describe the problem, e.g.:
```
fatal: [example-machine]: FAILED! => {
    "changed": false,
    "failed": true,
    "invocation": {
        "module_args": {
            "dist": "runit",
            "enabled": true,
            "name": "example",
            "service_dir": "/etc/service",
            "service_src": "/etc/sv",
            "state": "started"
        }
    },
    "msg": "failed to execute: sequence item 0: expected string, NoneType found"
}
```



Discussion:
cc @jsumners
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
When would `sv` not be installed when attempting to use the `runit` module?
After we accidentally forget to install runit on some of the nodes in our cluster that need it ;). A nice error message would have saved us the time that we spent investigating the problem.

Of course nobody should expect the module to work if `sv` is not installed. However it would be good if it was made explicit what the problem was. It is a common pattern for Ansible modules to validate that the necessary Python modules and binaries are present before running and to offer a useful error message if this is not the case. 
Can you point me to one such module so I can see how they do it? Python is not my primary language.
Another system module that does this is Puppet:
https://github.com/ansible/ansible/blob/722579356d46c09890ef26d32e2b195effaa2ba4/lib/ansible/modules/system/puppet.py#L175-L179
(I've not used the module or looked at it in any detail so I don't know if it's a good example - I just picked randomly at something I suspected would use an external binary.)

Resolving patch:
diff --git a/lib/ansible/modules/system/runit.py b/lib/ansible/modules/system/runit.py
--- a/lib/ansible/modules/system/runit.py
+++ b/lib/ansible/modules/system/runit.py
@@ -141,7 +141,7 @@ def __init__(self, module):
         self.pid            = None
         self.duration       = None
 
-        self.svc_cmd        = module.get_bin_path('sv', opt_dirs=self.extra_paths)
+        self.svc_cmd        = module.get_bin_path('sv', opt_dirs=self.extra_paths, required=True)
         self.svstat_cmd     = module.get_bin_path('sv', opt_dirs=self.extra_paths)
         self.svc_full = '/'.join([ self.service_dir, self.name ])
         self.src_full = '/'.join([ self.service_src, self.name ])


Test patch: