Repository: ansible/ansible-modules-core

Issue:
The module service will fail in check mode if the target service is not yet installed on server.
##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
ansible module service

##### ANSIBLE VERSION

```
ansible 2.2.0.0
  config file = 
  configured module search path = Default w/o overrides
```

##### CONFIGURATION
no settings in ansible.cfg

##### OS / ENVIRONMENT
Debian/wheezy

##### SUMMARY
The module service will fail in check mode if the target service is not yet
installed on server.
For instance my playbook installs apache2 and, later restart apache2.
Running ansible-playbook --check on a server without apache2 will fail.

This may be a duplicate but I did not find any issue on this topic.

##### STEPS TO REPRODUCE

This Dockerfile will reproduce this issue:
https://github.com/pgrange/ansible_service_check_mode_issue

Running this playbook in check mode on a brand new server will
reproduce this issue:

```
- hosts: all

  tasks:
  - apt: name=apache2
  - name: no need but I would like to restart apache
service: name=apache2 state=restarted
```

##### EXPECTED RESULTS

ansible-playbook --check should not fail if a service to restart is not
already installed on server.

Why not raise a warning that we are trying to restart an unknown service
but that's it.

##### ACTUAL RESULTS

What actually happens is that running ansible-playbook in check mode fails:

```
PLAYBOOK: apache.yml ***********************************************************
1 plays in /tmp/ansible/apache.yml

PLAY [all] *********************************************************************

TASK [setup] *******************************************************************
Using module file /usr/local/lib/python2.7/dist-packages/ansible/modules/core/system/setup.py
<localhost> ESTABLISH LOCAL CONNECTION FOR USER: root
<localhost> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo $HOME/.ansible/tmp/ansible-tmp-1479828044.78-237878029370849 `" && echo ansible-tmp-1479828044.78-237878029370849="` echo $HOME/.ansible/tmp/ansible-tmp-1479828044.78-237878029370849 `" ) && sleep 0'
<localhost> PUT /tmp/tmpbW6GVG TO /root/.ansible/tmp/ansible-tmp-1479828044.78-237878029370849/setup.py
<localhost> EXEC /bin/sh -c 'chmod u+x /root/.ansible/tmp/ansible-tmp-1479828044.78-237878029370849/ /root/.ansible/tmp/ansible-tmp-1479828044.78-237878029370849/setup.py && sleep 0'
<localhost> EXEC /bin/sh -c '/usr/bin/python /root/.ansible/tmp/ansible-tmp-1479828044.78-237878029370849/setup.py; rm -rf "/root/.ansible/tmp/ansible-tmp-1479828044.78-237878029370849/" > /dev/null 2>&1 && sleep 0'
ok: [localhost]

TASK [apt] *********************************************************************
task path: /tmp/ansible/apache.yml:4
Using module file /usr/local/lib/python2.7/dist-packages/ansible/modules/core/packaging/os/apt.py
<localhost> ESTABLISH LOCAL CONNECTION FOR USER: root
<localhost> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo $HOME/.ansible/tmp/ansible-tmp-1479828045.04-80599335973148 `" && echo ansible-tmp-1479828045.04-80599335973148="` echo $HOME/.ansible/tmp/ansible-tmp-1479828045.04-80599335973148 `" ) && sleep 0'
<localhost> PUT /tmp/tmpBgPUnm TO /root/.ansible/tmp/ansible-tmp-1479828045.04-80599335973148/apt.py
<localhost> EXEC /bin/sh -c 'chmod u+x /root/.ansible/tmp/ansible-tmp-1479828045.04-80599335973148/ /root/.ansible/tmp/ansible-tmp-1479828045.04-80599335973148/apt.py && sleep 0'
<localhost> EXEC /bin/sh -c '/usr/bin/python /root/.ansible/tmp/ansible-tmp-1479828045.04-80599335973148/apt.py; rm -rf "/root/.ansible/tmp/ansible-tmp-1479828045.04-80599335973148/" > /dev/null 2>&1 && sleep 0'
changed: [localhost] => {
    "cache_update_time": 1479825681, 
    "cache_updated": false, 
    "changed": true, 
    "diff": {}, 
    "invocation": {
        "module_args": {
            "allow_unauthenticated": false, 
            "autoremove": false, 
            "cache_valid_time": 0, 
            "deb": null, 
            "default_release": null, 
            "dpkg_options": "force-confdef,force-confold", 
            "force": false, 
            "install_recommends": null, 
            "name": "apache2", 
            "only_upgrade": false, 
            "package": [
                "apache2"
            ], 
            "purge": false, 
            "state": "present", 
            "update_cache": false, 
            "upgrade": null
        }, 
        "module_name": "apt"
    }, 
    "stderr": "", 
    "stdout": "Reading package lists...\nBuilding dependency tree...\nReading state information...\nThe following extra packages will be installed:\n  apache2-mpm-worker apache2-utils apache2.2-bin apache2.2-common libapr1\n  libaprutil1 libaprutil1-dbd-sqlite3 libaprutil1-ldap libcap2 libpcre3\n  libprocps0 procps psmisc ssl-cert\nSuggested packages:\n  www-browser apache2-doc apache2-suexec apache2-suexec-custom\n  openssl-blacklist\nThe following NEW packages will be installed:\n  apache2 apache2-mpm-worker apache2-utils apache2.2-bin apache2.2-common\n  libapr1 libaprutil1 libaprutil1-dbd-sqlite3 libaprutil1-ldap libcap2\n  libpcre3 libprocps0 procps psmisc ssl-cert\n0 upgraded, 15 newly installed, 0 to remove and 0 not upgraded.\nInst libprocps0 (1:3.3.3-3 Debian:7.11/oldstable [amd64])\nInst libcap2 (1:2.22-1.2 Debian:7.11/oldstable [amd64])\nInst libpcre3 (1:8.30-5 Debian:7.11/oldstable [amd64])\nInst procps (1:3.3.3-3 Debian:7.11/oldstable [amd64])\nInst libapr1 (1.4.6-3+deb7u1 Debian:7.11/oldstable [amd64])\nInst libaprutil1 (1.4.1-3 Debian:7.11/oldstable [amd64])\nInst libaprutil1-dbd-sqlite3 (1.4.1-3 Debian:7.11/oldstable [amd64])\nInst libaprutil1-ldap (1.4.1-3 Debian:7.11/oldstable [amd64])\nInst apache2.2-bin (2.2.22-13+deb7u7 Debian-Security:7.0/oldstable [amd64])\nInst apache2-utils (2.2.22-13+deb7u7 Debian-Security:7.0/oldstable [amd64])\nInst apache2.2-common (2.2.22-13+deb7u7 Debian-Security:7.0/oldstable [amd64])\nInst apache2-mpm-worker (2.2.22-13+deb7u7 Debian-Security:7.0/oldstable [amd64])\nInst apache2 (2.2.22-13+deb7u7 Debian-Security:7.0/oldstable [amd64])\nInst psmisc (22.19-1+deb7u1 Debian:7.11/oldstable [amd64])\nInst ssl-cert (1.0.32+deb7u1 Debian:7.11/oldstable [all])\nConf libprocps0 (1:3.3.3-3 Debian:7.11/oldstable [amd64])\nConf libcap2 (1:2.22-1.2 Debian:7.11/oldstable [amd64])\nConf libpcre3 (1:8.30-5 Debian:7.11/oldstable [amd64])\nConf procps (1:3.3.3-3 Debian:7.11/oldstable [amd64])\nConf libapr1 (1.4.6-3+deb7u1 Debian:7.11/oldstable [amd64])\nConf libaprutil1 (1.4.1-3 Debian:7.11/oldstable [amd64])\nConf libaprutil1-dbd-sqlite3 (1.4.1-3 Debian:7.11/oldstable [amd64])\nConf libaprutil1-ldap (1.4.1-3 Debian:7.11/oldstable [amd64])\nConf apache2.2-bin (2.2.22-13+deb7u7 Debian-Security:7.0/oldstable [amd64])\nConf apache2-utils (2.2.22-13+deb7u7 Debian-Security:7.0/oldstable [amd64])\nConf apache2.2-common (2.2.22-13+deb7u7 Debian-Security:7.0/oldstable [amd64])\nConf apache2-mpm-worker (2.2.22-13+deb7u7 Debian-Security:7.0/oldstable [amd64])\nConf apache2 (2.2.22-13+deb7u7 Debian-Security:7.0/oldstable [amd64])\nConf psmisc (22.19-1+deb7u1 Debian:7.11/oldstable [amd64])\nConf ssl-cert (1.0.32+deb7u1 Debian:7.11/oldstable [all])\n", 
    "stdout_lines": [
        "Reading package lists...", 
        "Building dependency tree...", 
        "Reading state information...", 
        "The following extra packages will be installed:", 
        "  apache2-mpm-worker apache2-utils apache2.2-bin apache2.2-common libapr1", 
        "  libaprutil1 libaprutil1-dbd-sqlite3 libaprutil1-ldap libcap2 libpcre3", 
        "  libprocps0 procps psmisc ssl-cert", 
        "Suggested packages:", 
        "  www-browser apache2-doc apache2-suexec apache2-suexec-custom", 
        "  openssl-blacklist", 
        "The following NEW packages will be installed:", 
        "  apache2 apache2-mpm-worker apache2-utils apache2.2-bin apache2.2-common", 
        "  libapr1 libaprutil1 libaprutil1-dbd-sqlite3 libaprutil1-ldap libcap2", 
        "  libpcre3 libprocps0 procps psmisc ssl-cert", 
        "0 upgraded, 15 newly installed, 0 to remove and 0 not upgraded.", 
        "Inst libprocps0 (1:3.3.3-3 Debian:7.11/oldstable [amd64])", 
        "Inst libcap2 (1:2.22-1.2 Debian:7.11/oldstable [amd64])", 
        "Inst libpcre3 (1:8.30-5 Debian:7.11/oldstable [amd64])", 
        "Inst procps (1:3.3.3-3 Debian:7.11/oldstable [amd64])", 
        "Inst libapr1 (1.4.6-3+deb7u1 Debian:7.11/oldstable [amd64])", 
        "Inst libaprutil1 (1.4.1-3 Debian:7.11/oldstable [amd64])", 
        "Inst libaprutil1-dbd-sqlite3 (1.4.1-3 Debian:7.11/oldstable [amd64])", 
        "Inst libaprutil1-ldap (1.4.1-3 Debian:7.11/oldstable [amd64])", 
        "Inst apache2.2-bin (2.2.22-13+deb7u7 Debian-Security:7.0/oldstable [amd64])", 
        "Inst apache2-utils (2.2.22-13+deb7u7 Debian-Security:7.0/oldstable [amd64])", 
        "Inst apache2.2-common (2.2.22-13+deb7u7 Debian-Security:7.0/oldstable [amd64])", 
        "Inst apache2-mpm-worker (2.2.22-13+deb7u7 Debian-Security:7.0/oldstable [amd64])", 
        "Inst apache2 (2.2.22-13+deb7u7 Debian-Security:7.0/oldstable [amd64])", 
        "Inst psmisc (22.19-1+deb7u1 Debian:7.11/oldstable [amd64])", 
        "Inst ssl-cert (1.0.32+deb7u1 Debian:7.11/oldstable [all])", 
        "Conf libprocps0 (1:3.3.3-3 Debian:7.11/oldstable [amd64])", 
        "Conf libcap2 (1:2.22-1.2 Debian:7.11/oldstable [amd64])", 
        "Conf libpcre3 (1:8.30-5 Debian:7.11/oldstable [amd64])", 
        "Conf procps (1:3.3.3-3 Debian:7.11/oldstable [amd64])", 
        "Conf libapr1 (1.4.6-3+deb7u1 Debian:7.11/oldstable [amd64])", 
        "Conf libaprutil1 (1.4.1-3 Debian:7.11/oldstable [amd64])", 
        "Conf libaprutil1-dbd-sqlite3 (1.4.1-3 Debian:7.11/oldstable [amd64])", 
        "Conf libaprutil1-ldap (1.4.1-3 Debian:7.11/oldstable [amd64])", 
        "Conf apache2.2-bin (2.2.22-13+deb7u7 Debian-Security:7.0/oldstable [amd64])", 
        "Conf apache2-utils (2.2.22-13+deb7u7 Debian-Security:7.0/oldstable [amd64])", 
        "Conf apache2.2-common (2.2.22-13+deb7u7 Debian-Security:7.0/oldstable [amd64])", 
        "Conf apache2-mpm-worker (2.2.22-13+deb7u7 Debian-Security:7.0/oldstable [amd64])", 
        "Conf apache2 (2.2.22-13+deb7u7 Debian-Security:7.0/oldstable [amd64])", 
        "Conf psmisc (22.19-1+deb7u1 Debian:7.11/oldstable [amd64])", 
        "Conf ssl-cert (1.0.32+deb7u1 Debian:7.11/oldstable [all])"
    ]
}

TASK [no need but I would like to restart apache] ******************************
task path: /tmp/ansible/apache.yml:5
Using module file /usr/local/lib/python2.7/dist-packages/ansible/modules/core/system/service.py
<localhost> ESTABLISH LOCAL CONNECTION FOR USER: root
<localhost> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo $HOME/.ansible/tmp/ansible-tmp-1479828046.91-253442262595803 `" && echo ansible-tmp-1479828046.91-253442262595803="` echo $HOME/.ansible/tmp/ansible-tmp-1479828046.91-253442262595803 `" ) && sleep 0'
<localhost> PUT /tmp/tmpu0znF1 TO /root/.ansible/tmp/ansible-tmp-1479828046.91-253442262595803/service.py
<localhost> EXEC /bin/sh -c 'chmod u+x /root/.ansible/tmp/ansible-tmp-1479828046.91-253442262595803/ /root/.ansible/tmp/ansible-tmp-1479828046.91-253442262595803/service.py && sleep 0'
<localhost> EXEC /bin/sh -c '/usr/bin/python /root/.ansible/tmp/ansible-tmp-1479828046.91-253442262595803/service.py; rm -rf "/root/.ansible/tmp/ansible-tmp-1479828046.91-253442262595803/" > /dev/null 2>&1 && sleep 0'
fatal: [localhost]: FAILED! => {
    "changed": false, 
    "failed": true, 
    "invocation": {
        "module_args": {
            "arguments": "", 
            "enabled": null, 
            "name": "apache2", 
            "pattern": null, 
            "runlevel": "default", 
            "sleep": null, 
            "state": "restarted"
        }
    }, 
    "msg": "no service or tool found for: apache2"
}
        to retry, use: --limit @/tmp/ansible/apache.retry

PLAY RECAP *********************************************************************
localhost                  : ok=2    changed=1    unreachable=0    failed=1   

The command '/bin/sh -c ansible-playbook -vvv -i localhost, -c local /tmp/ansible/apache.yml --check' returned a non-zero code: 2

```



Discussion:
<!-- @pgrange: Thanks for filing this bug report! -->
@ansible ping, this issue is waiting for your response.
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: issue_bug_new --->
This worked in 1.9.x and below and I also see references to it being fixed a few times:

https://github.com/ansible/ansible/issues/3615
https://github.com/ansible/ansible/issues/4102
https://github.com/ansible/ansible/pull/4106
https://github.com/ansible/ansible-modules-core/pull/1407

I see the described [incorrect] behavior on devel (2.3), so I'm going to dig in and see where we went wrong.

Resolving patch:
diff --git a/system/service.py b/system/service.py
--- a/system/service.py
+++ b/system/service.py
@@ -131,6 +131,7 @@
 import time
 import string
 import glob
+from ansible.module_utils.service import fail_if_missing
 
 # The distutils module is not shipped with SUNWPython on Solaris.
 # It's in the SUNWPython-devel package which also contains development files
@@ -493,7 +494,7 @@ def check_systemd():
                 self.enable_cmd = location['chkconfig']
 
         if self.enable_cmd is None:
-            self.module.fail_json(msg="no service or tool found for: %s" % self.name)
+            fail_if_missing(self.module, False, self.name, msg='host')
 
         # If no service control tool selected yet, try to see if 'service' is available
         if self.svc_cmd is None and location.get('service', False):
diff --git a/system/systemd.py b/system/systemd.py
--- a/system/systemd.py
+++ b/system/systemd.py
@@ -329,8 +329,7 @@ def main():
                 (rc, out, err) = module.run_command("%s %s '%s'" % (systemctl, action, unit))
                 if rc != 0:
                     # some versions of system CAN mask/unmask non existing services, we only fail on missing if they don't
-                    fail_if_missing(module, found, unit, "cannot %s" % (action))
-                    module.fail_json(msg="Unable to %s service %s: %s" % (action, unit, err))
+                    fail_if_missing(module, found, unit, msg='host')
 
 
     # Enable/disable service startup at boot if requested
@@ -341,7 +340,7 @@ def main():
         else:
             action = 'disable'
 
-        fail_if_missing(module, found, unit, "cannot %s" % (action))
+        fail_if_missing(module, found, unit, msg='host')
 
         # do we need to enable the service?
         enabled = False
@@ -370,7 +369,7 @@ def main():
 
     # set service state if requested
     if module.params['state'] is not None:
-        fail_if_missing(module, found, unit, "cannot check nor set state")
+        fail_if_missing(module, found, unit, msg="host")
 
         # default to desired state
         result['state'] = module.params['state']


Test patch: