Repository: ansible/ansible

Issue:
Selinux module fails if selinux is not installed
##### ISSUE TYPE
<!--- Pick one below and delete the rest: -->
 - Bug Report


##### COMPONENT NAME
selinux module

##### ANSIBLE VERSION
<!--- Paste verbatim output from “ansible --version” between quotes below -->
```
ansible 2.2.1.0
  config file = 
  configured module search path = Default w/o overrides
```

##### CONFIGURATION
<!---
Mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).
-->

##### 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.
-->

##### SUMMARY
If a task wants to set selinux to `permissive`, but selinux is not installed (RPMS have been erased, /etc/selinux/config doesn't exist) the module fails because it cannot find the config file. This particular case should be OK, as setting it to permissive is effectively disabling it, and it's already disabled.

##### STEPS TO REPRODUCE
deploy a CentOS/RHEL vm, delete the selinux rpms, rm -rf /etc/selinux
Then attempt to disable to set selinux to permissive
<!--- Paste example playbooks or commands between quotes below -->
```
- selinux:
    state: permissive

# Disable SELinux
- selinux:
    state: disabled

```

<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->
Since selinux is not installed, it should be a noop

##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->

<!--- Paste verbatim command output between quotes below -->
    ```
    IOError: [Errno 2] No such file or directory: '/etc/selinux/config'\n",
    "module_stdout": "", "msg": "MODULE FAILURE"}
    ```



Discussion:
cc @goozbach
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
+1 

faced similar issue when selinux is not installed on centOS6

```yaml
- name: Ensure Selinux Disabled
  selinux: state=disabled 
  when: ansible_os_family == 'RedHat'
  become: True
```

```log
TASK [role_under_test : Ensure Selinux Disabled] *******************************
task path: /etc/ansible/roles/role_under_test/tasks/main.yml:36
Using module file /root/.virtualenvs/ansible_2.2.0.0/lib/python2.6/site-packages/ansible/modules/core/system/selinux.py
<localhost> ESTABLISH LOCAL CONNECTION FOR USER: root
<localhost> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo $HOME/.ansible/tmp/ansible-tmp-1488086499.63-182067330321355 `" && echo ansible-tmp-1488086499.63-182067330321355="` echo $HOME/.ansible/tmp/ansible-tmp-1488086499.63-182067330321355 `" ) && sleep 0'
<localhost> PUT /tmp/tmppzlwwe TO /root/.ansible/tmp/ansible-tmp-1488086499.63-182067330321355/selinux.py
<localhost> EXEC /bin/sh -c 'chmod u+x /root/.ansible/tmp/ansible-tmp-1488086499.63-182067330321355/ /root/.ansible/tmp/ansible-tmp-1488086499.63-182067330321355/selinux.py && sleep 0'
<localhost> EXEC /bin/sh -c '/usr/bin/python /root/.ansible/tmp/ansible-tmp-1488086499.63-182067330321355/selinux.py; rm -rf "/root/.ansible/tmp/ansible-tmp-1488086499.63-182067330321355/" > /dev/null 2>&1 && sleep 0'
An exception occurred during task execution. The full traceback is:
Traceback (most recent call last):
  File "/tmp/ansible_Ik_yPG/ansible_module_selinux.py", line 211, in <module>
    main()
  File "/tmp/ansible_Ik_yPG/ansible_module_selinux.py", line 151, in main
    config_policy         = get_config_policy(configfile)
  File "/tmp/ansible_Ik_yPG/ansible_module_selinux.py", line 78, in get_config_policy
    myfile = open(configfile, "r")
IOError: [Errno 2] No such file or directory: '/etc/selinux/config'
fatal: [localhost]: FAILED! => {
    "changed": false, 
    "failed": true, 
    "invocation": {
        "module_name": "selinux"
    }, 
    "module_stderr": "Traceback (most recent call last):\n  File \"/tmp/ansible_Ik_yPG/ansible_module_selinux.py\", line 211, in <module>\n    main()\n  File \"/tmp/ansible_Ik_yPG/ansible_module_selinux.py\", line 151, in main\n    config_policy         = get_config_policy(configfile)\n  File \"/tmp/ansible_Ik_yPG/ansible_module_selinux.py\", line 78, in get_config_policy\n    myfile = open(configfile, \"r\")\nIOError: [Errno 2] No such file or directory: '/etc/selinux/config'\n", 
    "module_stdout": "", 
    "msg": "MODULE FAILURE"
}
```

Resolving patch:
diff --git a/lib/ansible/modules/system/selinux.py b/lib/ansible/modules/system/selinux.py
--- a/lib/ansible/modules/system/selinux.py
+++ b/lib/ansible/modules/system/selinux.py
@@ -71,76 +71,77 @@
 
 import os
 import re
-import sys
 
 try:
     import selinux
     HAS_SELINUX = True
 except ImportError:
     HAS_SELINUX = False
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.facts import get_file_lines
+
 
 # getter subroutines
 def get_config_state(configfile):
-    myfile = open(configfile, "r")
-    lines = myfile.readlines()
-    myfile.close()
+    lines = get_file_lines(configfile)
+
     for line in lines:
-        stateline = re.match('^SELINUX=.*$', line)
-        if (stateline):
-            return(line.split('=')[1].strip())
+        stateline = re.match(r'^SELINUX=.*$', line)
+        if stateline:
+            return line.split('=')[1].strip()
+
 
 def get_config_policy(configfile):
-    myfile = open(configfile, "r")
-    lines = myfile.readlines()
-    myfile.close()
+    lines = get_file_lines(configfile)
+
     for line in lines:
-        stateline = re.match('^SELINUXTYPE=.*$', line)
-        if (stateline):
-            return(line.split('=')[1].strip())
+        stateline = re.match(r'^SELINUXTYPE=.*$', line)
+        if stateline:
+            return line.split('=')[1].strip()
+
 
 # setter subroutines
 def set_config_state(state, configfile):
-    #SELINUX=permissive
+    # SELINUX=permissive
     # edit config file with state value
-    stateline='SELINUX=%s' % state
-    myfile = open(configfile, "r")
-    lines = myfile.readlines()
-    myfile.close()
-    myfile = open(configfile, "w")
-    for line in lines:
-        myfile.write(re.sub(r'^SELINUX=.*', stateline, line))
-    myfile.close()
+    stateline = 'SELINUX=%s' % state
+
+    lines = get_file_lines(configfile)
+
+    with open(configfile, "w") as write_file:
+        for line in lines:
+            write_file.write(re.sub(r'^SELINUX=.*', stateline, line))
+
 
-def set_state(state):
-    if (state == 'enforcing'):
+def set_state(module, state):
+    if state == 'enforcing':
         selinux.security_setenforce(1)
-    elif (state == 'permissive'):
+    elif state == 'permissive':
         selinux.security_setenforce(0)
-    elif (state == 'disabled'):
+    elif state == 'disabled':
         pass
     else:
         msg = 'trying to set invalid runtime state %s' % state
         module.fail_json(msg=msg)
 
+
 def set_config_policy(policy, configfile):
     # edit config file with state value
-    #SELINUXTYPE=targeted
-    policyline='SELINUXTYPE=%s' % policy
-    myfile = open(configfile, "r")
-    lines = myfile.readlines()
-    myfile.close()
-    myfile = open(configfile, "w")
-    for line in lines:
-        myfile.write(re.sub(r'^SELINUXTYPE=.*', policyline, line))
-    myfile.close()
+    # SELINUXTYPE=targeted
+    policyline = 'SELINUXTYPE=%s' % policy
+    lines = get_file_lines(configfile)
+
+    with open(configfile, "w") as write_file:
+        for line in lines:
+            write_file.write(re.sub(r'^SELINUXTYPE=.*', policyline, line))
 
-def main():
 
+def main():
     module = AnsibleModule(
-        argument_spec = dict(
+        argument_spec=dict(
             policy=dict(required=False),
             state=dict(choices=['enforcing', 'permissive', 'disabled'], required=True),
-            configfile=dict(aliases=['conf','file'], default='/etc/selinux/config')
+            configfile=dict(aliases=['conf', 'file'], default='/etc/selinux/config')
         ),
         supports_check_mode=True
     )
@@ -149,25 +150,32 @@ def main():
         module.fail_json(msg='libselinux-python required for this module')
 
     # global vars
-    changed=False
-    msgs                  = []
-    configfile            = module.params['configfile']
-    policy                = module.params['policy']
-    state                 = module.params['state']
-    runtime_enabled       = selinux.is_selinux_enabled()
-    runtime_policy        = selinux.selinux_getpolicytype()[1]
-    runtime_state         = 'disabled'
-    if (runtime_enabled):
+    changed = False
+    msgs = []
+    configfile = module.params['configfile']
+    policy = module.params['policy']
+    state = module.params['state']
+    runtime_enabled = selinux.is_selinux_enabled()
+    runtime_policy = selinux.selinux_getpolicytype()[1]
+    runtime_state = 'disabled'
+
+    if runtime_enabled:
         # enabled means 'enforcing' or 'permissive'
-        if (selinux.security_getenforce()):
+        if selinux.security_getenforce():
             runtime_state = 'enforcing'
         else:
             runtime_state = 'permissive'
-    config_policy         = get_config_policy(configfile)
-    config_state          = get_config_state(configfile)
+
+    if not os.path.isfile(configfile):
+        module.fail_json(msg="Unable to find file {0}".format(configfile),
+                         details="Please install SELinux-policy package, "
+                                 "if this package is not installed previously.")
+
+    config_policy = get_config_policy(configfile)
+    config_state = get_config_state(configfile)
 
     # check to see if policy is set if state is not 'disabled'
-    if (state != 'disabled'):
+    if state != 'disabled':
         if not policy:
             module.fail_json(msg='policy is required if state is not \'disabled\'')
     else:
@@ -175,53 +183,48 @@ def main():
             policy = config_policy
 
     # check changed values and run changes
-    if (policy != runtime_policy):
+    if policy != runtime_policy:
         if module.check_mode:
             module.exit_json(changed=True)
         # cannot change runtime policy
         msgs.append('reboot to change the loaded policy')
-        changed=True
+        changed = True
 
-    if (policy != config_policy):
+    if policy != config_policy:
         if module.check_mode:
             module.exit_json(changed=True)
         msgs.append('config policy changed from \'%s\' to \'%s\'' % (config_policy, policy))
         set_config_policy(policy, configfile)
-        changed=True
+        changed = True
 
-    if (state != runtime_state):
+    if state != runtime_state:
         if module.check_mode:
             module.exit_json(changed=True)
-        if (runtime_enabled):
-            if (state == 'disabled'):
-                if (runtime_state != 'permissive'):
+        if runtime_enabled:
+            if state == 'disabled':
+                if runtime_state != 'permissive':
                     # Temporarily set state to permissive
-                    set_state('permissive')
+                    set_state(module, 'permissive')
                     msgs.append('runtime state temporarily changed from \'%s\' to \'permissive\', state change will take effect next reboot' % (runtime_state))
                 else:
                     msgs.append('state change will take effect next reboot')
             else:
-                set_state(state)
+                set_state(module, state)
                 msgs.append('runtime state changed from \'%s\' to \'%s\'' % (runtime_state, state))
         else:
             msgs.append('state change will take effect next reboot')
-        changed=True
+        changed = True
 
-    if (state != config_state):
+    if state != config_state:
         if module.check_mode:
             module.exit_json(changed=True)
         msgs.append('config state changed from \'%s\' to \'%s\'' % (config_state, state))
         set_config_state(state, configfile)
-        changed=True
+        changed = True
 
-    module.exit_json(changed=changed, msg=', '.join(msgs),
-        configfile=configfile,
-        policy=policy, state=state)
+    module.exit_json(changed=changed, msg=', '.join(msgs), configfile=configfile, policy=policy, state=state)
 
 #################################################
-# import module snippets
-from ansible.module_utils.basic import *
 
 if __name__ == '__main__':
     main()
-


Test patch:
diff --git a/test/sanity/pep8/legacy-files.txt b/test/sanity/pep8/legacy-files.txt
--- a/test/sanity/pep8/legacy-files.txt
+++ b/test/sanity/pep8/legacy-files.txt
@@ -687,7 +687,6 @@ lib/ansible/modules/system/puppet.py
 lib/ansible/modules/system/runit.py
 lib/ansible/modules/system/seboolean.py
 lib/ansible/modules/system/sefcontext.py
-lib/ansible/modules/system/selinux.py
 lib/ansible/modules/system/seport.py
 lib/ansible/modules/system/service.py
 lib/ansible/modules/system/setup.py