Repository: ansible/ansible

Issue:
runit: can't start a stopped service
##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
runit

##### ANSIBLE VERSION
```
ansible 2.4.2.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/root/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.7/dist-packages/ansible
  executable location = /usr/bin/ansible
  python version = 2.7.13 (default, Jan 19 2017, 14:48:08) [GCC 6.3.0 20170124]
```


##### OS / ENVIRONMENT

```
root@pi-gert:/etc/sv# cat /etc/*-release
PRETTY_NAME="Raspbian GNU/Linux 9 (stretch)"
NAME="Raspbian GNU/Linux"
VERSION_ID="9"
VERSION="9 (stretch)"
ID=raspbian
ID_LIKE=debian
HOME_URL="http://www.raspbian.org/"
SUPPORT_URL="http://www.raspbian.org/RaspbianForums"
BUG_REPORT_URL="http://www.raspbian.org/RaspbianBugs"


root@pi-gert:/etc/sv# apt-cache policy runit
runit:
  Installed: 2.1.2-9.2
  Candidate: 2.1.2-9.2
  Version table:
 *** 2.1.2-9.2 500
        500 http://mirrordirector.raspbian.org/raspbian stretch/main armhf Packages
        100 /var/lib/dpkg/status
```

##### SUMMARY
A service that is down can't be started.

##### STEPS TO REPRODUCE

* given a service `updater` in `/etc/sv/updater` that can be operated via runit (ie `sv start|stop|restart /etc/sv/updater` all work)
* and this service is currently down:
   ```bash
   $ sv status updater
   down: updater: 89s, normally up; run: log: (pid 364) 261174s
   ```
* when I provision this service to have state `started`
   ```bash
   $ ansible -m runit -a 'name=updater state=started enabled=yes service_src=/etc/sv service_dir=/etc/service' localhost
   ```

##### EXPECTED RESULTS

I expect...:
* the task to be marked as `changed`
* the updater-service to be up (via `sv status /etc/sv/updater`)

##### ACTUAL RESULTS

The task reports no changes:
```
$ ansible -m runit -a 'name=updater state=started enabled=yes service_src=/etc/sv service_dir=/etc/service' localhost
 [WARNING]: Could not match supplied host pattern, ignoring: all

 [WARNING]: provided hosts list is empty, only localhost is available

localhost | SUCCESS => {
    "changed": false,
    "sv": {
        "duration": "129",
        "enabled": true,
        "full_state": "down: /etc/service/updater: 129s, normally up; run: log: (pid 364) 261214s\n",
        "pid": "364",
        "src_full": "/etc/sv/updater",
        "state": "started",
        "svc_full": "/etc/service/updater"
    }
}
```

The service is still down:
```
$ sv status updater
down: updater: 146s, normally up; run: log: (pid 364) 261231s
```

More:
* when the service is running and I run the above ansible-command with `state=stopped`, the service is stopped as one would expect

Suspect:
* `full_state` equals `"down: /etc/service/updater: 129s, normally up; run: log: (pid 364) 261214s\n"` (as per the output above)
* `full_state` is matched against the substring `'run:'` (https://github.com/ansible/ansible/blob/ae49dd65d9b52dea3c5c07c924e7008a8448b67f/lib/ansible/modules/system/runit.py#L180)
* this matches because `'run:'` is contained *somewhere* in `full_state`, not on the position where `sv` puts the actual state of the service (the first word)
* what it should do is check whether `full_state` *starts* with `'run:'`

PR upcoming






Discussion:
Files identified in the description:
* [lib/ansible/modules/system/runit.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/system/runit.py)

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @jsumners
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->

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
@@ -168,18 +168,22 @@ def get_status(self):
             self.full_state = self.state = err
         else:
             self.full_state = out
+            # full_state *may* contain information about the logger:
+            # "down: /etc/service/service-without-logger: 1s, normally up\n"
+            # "down: /etc/service/updater: 127s, normally up; run: log: (pid 364) 263439s\n"
+            full_state_no_logger = self.full_state.split("; ")[0]
 
-            m = re.search(r'\(pid (\d+)\)', out)
+            m = re.search(r'\(pid (\d+)\)', full_state_no_logger)
             if m:
                 self.pid = m.group(1)
 
-            m = re.search(r' (\d+)s', out)
+            m = re.search(r' (\d+)s', full_state_no_logger)
             if m:
                 self.duration = m.group(1)
 
-            if re.search('run:', out):
+            if re.search(r'^run:', full_state_no_logger):
                 self.state = 'started'
-            elif re.search('down:', out):
+            elif re.search(r'^down:', full_state_no_logger):
                 self.state = 'stopped'
             else:
                 self.state = 'unknown'


Test patch: