Repository: ansible/ansible-modules-extras

Issue:
Try to set selinux with sefcontext returns UnicodeEncodeError

##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
sefcontext

##### ANSIBLE VERSION
```
ansible 2.2.0.0
  config file = /etc/ansible/ansible.cfg
  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
Ansible execution:
 - Fedora 25
 - Python 2.7.12

Managed Host:
 - Fedora 24
 - Python 2.7.12

##### SUMMARY
Trying to set selinux fcontext fails with error : UnicodeEncodeError: 'ascii' codec can't encode character u'\\xe9' in position 8: ordinal not in range(128)
##### STEPS TO REPRODUCE
Execute following plabook on Host.
```
- name: Test selinux
  sefcontext: target='/srv/git_repos(/.*)?' setype=httpd_git_rw_content_t state=present
```
##### EXPECTED RESULTS
Selinux context checked/changes

##### ACTUAL RESULTS

```
Using /etc/ansible/ansible.cfg as config file
statically included: /home/sok/Documents/Informatique/Plateformes/serveurPerso/genericHandlers/reboot.yml
Loading callback plugin default of type stdout, v2.0 from /usr/lib/python2.7/site-packages/ansible/plugins/callback/__init__.pyc

PLAYBOOK: peroServers.yml ******************************************************
1 plays in peroServers.yml

PLAY [fullservers] *************************************************************

TASK [setup] *******************************************************************
Using module file /usr/lib/python2.7/site-packages/ansible/modules/core/system/setup.py
<web1.infra> ESTABLISH SSH CONNECTION FOR USER: ligodith
<web1.infra> SSH: ansible.cfg set ssh_args: (-C)(-o)(ControlMaster=auto)(-o)(ControlPersist=60s)
<web1.infra> SSH: ANSIBLE_REMOTE_PORT/remote_port/ansible_port set: (-o)(Port=4242)
<web1.infra> SSH: ansible_password/ansible_ssh_pass not set: (-o)(KbdInteractiveAuthentication=no)(-o)(PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey)(-o)(PasswordAuthentication=no)
<web1.infra> SSH: ANSIBLE_REMOTE_USER/remote_user/ansible_user/user/-u set: (-o)(User=ligodith)
<web1.infra> SSH: ANSIBLE_TIMEOUT/timeout set: (-o)(ConnectTimeout=10)
<web1.infra> SSH: PlayContext set ssh_common_args: ()
<web1.infra> SSH: PlayContext set ssh_extra_args: ()
<web1.infra> SSH: found only ControlPersist; added ControlPath: (-o)(ControlPath=/home/sok/.ansible/cp/ansible-ssh-%C)
<web1.infra> SSH: EXEC ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o Port=4242 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o User=ligodith -o ConnectTimeout=10 -o ControlPath=/home/sok/.ansible/cp/ansible-ssh-%C web1.infra '/bin/sh -c '"'"'( umask 77 && mkdir -p "` echo $HOME/.ansible/tmp/ansible-tmp-1480287343.32-243896499060526 `" && echo ansible-tmp-1480287343.32-243896499060526="` echo $HOME/.ansible/tmp/ansible-tmp-1480287343.32-243896499060526 `" ) && sleep 0'"'"''
<web1.infra> PUT /tmp/tmpICmQcI TO /home/ligodith/.ansible/tmp/ansible-tmp-1480287343.32-243896499060526/setup.py
<web1.infra> SSH: ansible.cfg set ssh_args: (-C)(-o)(ControlMaster=auto)(-o)(ControlPersist=60s)
<web1.infra> SSH: ANSIBLE_REMOTE_PORT/remote_port/ansible_port set: (-o)(Port=4242)
<web1.infra> SSH: ansible_password/ansible_ssh_pass not set: (-o)(KbdInteractiveAuthentication=no)(-o)(PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey)(-o)(PasswordAuthentication=no)
<web1.infra> SSH: ANSIBLE_REMOTE_USER/remote_user/ansible_user/user/-u set: (-o)(User=ligodith)
<web1.infra> SSH: ANSIBLE_TIMEOUT/timeout set: (-o)(ConnectTimeout=10)
<web1.infra> SSH: PlayContext set ssh_common_args: ()
<web1.infra> SSH: PlayContext set sftp_extra_args: ()
<web1.infra> SSH: found only ControlPersist; added ControlPath: (-o)(ControlPath=/home/sok/.ansible/cp/ansible-ssh-%C)
<web1.infra> SSH: EXEC sftp -b - -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o Port=4242 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o User=ligodith -o ConnectTimeout=10 -o ControlPath=/home/sok/.ansible/cp/ansible-ssh-%C '[web1.infra]'
<web1.infra> ESTABLISH SSH CONNECTION FOR USER: ligodith
<web1.infra> SSH: ansible.cfg set ssh_args: (-C)(-o)(ControlMaster=auto)(-o)(ControlPersist=60s)
<web1.infra> SSH: ANSIBLE_REMOTE_PORT/remote_port/ansible_port set: (-o)(Port=4242)
<web1.infra> SSH: ansible_password/ansible_ssh_pass not set: (-o)(KbdInteractiveAuthentication=no)(-o)(PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey)(-o)(PasswordAuthentication=no)
<web1.infra> SSH: ANSIBLE_REMOTE_USER/remote_user/ansible_user/user/-u set: (-o)(User=ligodith)
<web1.infra> SSH: ANSIBLE_TIMEOUT/timeout set: (-o)(ConnectTimeout=10)
<web1.infra> SSH: PlayContext set ssh_common_args: ()
<web1.infra> SSH: PlayContext set ssh_extra_args: ()
<web1.infra> SSH: found only ControlPersist; added ControlPath: (-o)(ControlPath=/home/sok/.ansible/cp/ansible-ssh-%C)
<web1.infra> SSH: EXEC ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o Port=4242 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o User=ligodith -o ConnectTimeout=10 -o ControlPath=/home/sok/.ansible/cp/ansible-ssh-%C web1.infra '/bin/sh -c '"'"'chmod u+x /home/ligodith/.ansible/tmp/ansible-tmp-1480287343.32-243896499060526/ /home/ligodith/.ansible/tmp/ansible-tmp-1480287343.32-243896499060526/setup.py && sleep 0'"'"''
<web1.infra> ESTABLISH SSH CONNECTION FOR USER: ligodith
<web1.infra> SSH: ansible.cfg set ssh_args: (-C)(-o)(ControlMaster=auto)(-o)(ControlPersist=60s)
<web1.infra> SSH: ANSIBLE_REMOTE_PORT/remote_port/ansible_port set: (-o)(Port=4242)
<web1.infra> SSH: ansible_password/ansible_ssh_pass not set: (-o)(KbdInteractiveAuthentication=no)(-o)(PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey)(-o)(PasswordAuthentication=no)
<web1.infra> SSH: ANSIBLE_REMOTE_USER/remote_user/ansible_user/user/-u set: (-o)(User=ligodith)
<web1.infra> SSH: ANSIBLE_TIMEOUT/timeout set: (-o)(ConnectTimeout=10)
<web1.infra> SSH: PlayContext set ssh_common_args: ()
<web1.infra> SSH: PlayContext set ssh_extra_args: ()
<web1.infra> SSH: found only ControlPersist; added ControlPath: (-o)(ControlPath=/home/sok/.ansible/cp/ansible-ssh-%C)
<web1.infra> SSH: EXEC ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o Port=4242 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o User=ligodith -o ConnectTimeout=10 -o ControlPath=/home/sok/.ansible/cp/ansible-ssh-%C -tt web1.infra '/bin/sh -c '"'"'/usr/bin/python /home/ligodith/.ansible/tmp/ansible-tmp-1480287343.32-243896499060526/setup.py; rm -rf "/home/ligodith/.ansible/tmp/ansible-tmp-1480287343.32-243896499060526/" > /dev/null 2>&1 && sleep 0'"'"''
ok: [web1.infra]

TASK [webServer : Test selinux] ************************************************
task path: /home/sok/Documents/Informatique/Plateformes/serveurPerso/roles/webServer/tasks/main.yml:44
Using module file /usr/lib/python2.7/site-packages/ansible/modules/extras/system/sefcontext.py
<web1.infra> ESTABLISH SSH CONNECTION FOR USER: ligodith
<web1.infra> SSH: ansible.cfg set ssh_args: (-C)(-o)(ControlMaster=auto)(-o)(ControlPersist=60s)
<web1.infra> SSH: ANSIBLE_REMOTE_PORT/remote_port/ansible_port set: (-o)(Port=4242)
<web1.infra> SSH: ansible_password/ansible_ssh_pass not set: (-o)(KbdInteractiveAuthentication=no)(-o)(PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey)(-o)(PasswordAuthentication=no)
<web1.infra> SSH: ANSIBLE_REMOTE_USER/remote_user/ansible_user/user/-u set: (-o)(User=ligodith)
<web1.infra> SSH: ANSIBLE_TIMEOUT/timeout set: (-o)(ConnectTimeout=10)
<web1.infra> SSH: PlayContext set ssh_common_args: ()
<web1.infra> SSH: PlayContext set ssh_extra_args: ()
<web1.infra> SSH: found only ControlPersist; added ControlPath: (-o)(ControlPath=/home/sok/.ansible/cp/ansible-ssh-%C)
<web1.infra> SSH: EXEC ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o Port=4242 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o User=ligodith -o ConnectTimeout=10 -o ControlPath=/home/sok/.ansible/cp/ansible-ssh-%C web1.infra '/bin/sh -c '"'"'( umask 77 && mkdir -p "` echo $HOME/.ansible/tmp/ansible-tmp-1480287344.14-116222128683348 `" && echo ansible-tmp-1480287344.14-116222128683348="` echo $HOME/.ansible/tmp/ansible-tmp-1480287344.14-116222128683348 `" ) && sleep 0'"'"''
<web1.infra> PUT /tmp/tmpebPXbF TO /home/ligodith/.ansible/tmp/ansible-tmp-1480287344.14-116222128683348/sefcontext.py
<web1.infra> SSH: ansible.cfg set ssh_args: (-C)(-o)(ControlMaster=auto)(-o)(ControlPersist=60s)
<web1.infra> SSH: ANSIBLE_REMOTE_PORT/remote_port/ansible_port set: (-o)(Port=4242)
<web1.infra> SSH: ansible_password/ansible_ssh_pass not set: (-o)(KbdInteractiveAuthentication=no)(-o)(PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey)(-o)(PasswordAuthentication=no)
<web1.infra> SSH: ANSIBLE_REMOTE_USER/remote_user/ansible_user/user/-u set: (-o)(User=ligodith)
<web1.infra> SSH: ANSIBLE_TIMEOUT/timeout set: (-o)(ConnectTimeout=10)
<web1.infra> SSH: PlayContext set ssh_common_args: ()
<web1.infra> SSH: PlayContext set sftp_extra_args: ()
<web1.infra> SSH: found only ControlPersist; added ControlPath: (-o)(ControlPath=/home/sok/.ansible/cp/ansible-ssh-%C)
<web1.infra> SSH: EXEC sftp -b - -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o Port=4242 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o User=ligodith -o ConnectTimeout=10 -o ControlPath=/home/sok/.ansible/cp/ansible-ssh-%C '[web1.infra]'
<web1.infra> ESTABLISH SSH CONNECTION FOR USER: ligodith
<web1.infra> SSH: ansible.cfg set ssh_args: (-C)(-o)(ControlMaster=auto)(-o)(ControlPersist=60s)
<web1.infra> SSH: ANSIBLE_REMOTE_PORT/remote_port/ansible_port set: (-o)(Port=4242)
<web1.infra> SSH: ansible_password/ansible_ssh_pass not set: (-o)(KbdInteractiveAuthentication=no)(-o)(PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey)(-o)(PasswordAuthentication=no)
<web1.infra> SSH: ANSIBLE_REMOTE_USER/remote_user/ansible_user/user/-u set: (-o)(User=ligodith)
<web1.infra> SSH: ANSIBLE_TIMEOUT/timeout set: (-o)(ConnectTimeout=10)
<web1.infra> SSH: PlayContext set ssh_common_args: ()
<web1.infra> SSH: PlayContext set ssh_extra_args: ()
<web1.infra> SSH: found only ControlPersist; added ControlPath: (-o)(ControlPath=/home/sok/.ansible/cp/ansible-ssh-%C)
<web1.infra> SSH: EXEC ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o Port=4242 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o User=ligodith -o ConnectTimeout=10 -o ControlPath=/home/sok/.ansible/cp/ansible-ssh-%C web1.infra '/bin/sh -c '"'"'chmod u+x /home/ligodith/.ansible/tmp/ansible-tmp-1480287344.14-116222128683348/ /home/ligodith/.ansible/tmp/ansible-tmp-1480287344.14-116222128683348/sefcontext.py && sleep 0'"'"''
<web1.infra> ESTABLISH SSH CONNECTION FOR USER: ligodith
<web1.infra> SSH: ansible.cfg set ssh_args: (-C)(-o)(ControlMaster=auto)(-o)(ControlPersist=60s)
<web1.infra> SSH: ANSIBLE_REMOTE_PORT/remote_port/ansible_port set: (-o)(Port=4242)
<web1.infra> SSH: ansible_password/ansible_ssh_pass not set: (-o)(KbdInteractiveAuthentication=no)(-o)(PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey)(-o)(PasswordAuthentication=no)
<web1.infra> SSH: ANSIBLE_REMOTE_USER/remote_user/ansible_user/user/-u set: (-o)(User=ligodith)
<web1.infra> SSH: ANSIBLE_TIMEOUT/timeout set: (-o)(ConnectTimeout=10)
<web1.infra> SSH: PlayContext set ssh_common_args: ()
<web1.infra> SSH: PlayContext set ssh_extra_args: ()
<web1.infra> SSH: found only ControlPersist; added ControlPath: (-o)(ControlPath=/home/sok/.ansible/cp/ansible-ssh-%C)
<web1.infra> SSH: EXEC ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o Port=4242 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o User=ligodith -o ConnectTimeout=10 -o ControlPath=/home/sok/.ansible/cp/ansible-ssh-%C -tt web1.infra '/bin/sh -c '"'"'/usr/bin/python /home/ligodith/.ansible/tmp/ansible-tmp-1480287344.14-116222128683348/sefcontext.py; rm -rf "/home/ligodith/.ansible/tmp/ansible-tmp-1480287344.14-116222128683348/" > /dev/null 2>&1 && sleep 0'"'"''
fatal: [web1.infra]: FAILED! => {
    "changed": false, 
    "failed": true, 
    "invocation": {
        "module_name": "sefcontext"
    }, 
    "module_stderr": "OpenSSH_7.3p1, OpenSSL 1.0.2j-fips  26 Sep 2016\r\ndebug1: Reading configuration data /home/sok/.ssh/config\r\ndebug1: /home/sok/.ssh/config line 48: Applying options for *\r\ndebug1: /home/sok/.ssh/config line 64: Applying options for gre*\r\ndebug1: Reading configuration data /etc/ssh/ssh_config\r\ndebug3: /etc/ssh/ssh_config line 56: Including file /etc/ssh/ssh_config.d/05-redhat.conf depth 0\r\ndebug1: Reading configuration data /etc/ssh/ssh_config.d/05-redhat.conf\r\ndebug1: /etc/ssh/ssh_config.d/05-redhat.conf line 2: include /etc/crypto-policies/back-ends/openssh.txt matched no files\r\ndebug1: /etc/ssh/ssh_config.d/05-redhat.conf line 8: Applying options for *\r\ndebug1: auto-mux: Trying existing master\r\ndebug2: fd 3 setting O_NONBLOCK\r\ndebug2: mux_client_hello_exchange: master version 4\r\ndebug3: mux_client_forwards: request forwardings: 0 local, 0 remote\r\ndebug3: mux_client_request_session: entering\r\ndebug3: mux_client_request_alive: entering\r\ndebug3: mux_client_request_alive: done pid = 15831\r\ndebug3: mux_client_request_session: session request sent\r\ndebug1: mux_client_request_session: master session id: 2\r\nX11 forwarding request failed\r\ndebug3: mux_client_read_packet: read header failed: Broken pipe\r\ndebug2: Received exit status from master 0\r\nShared connection to web1.infra closed.\r\n", 
    "module_stdout": "Traceback (most recent call last):\r\n  File \"/tmp/ansible_5hHAPf/ansible_module_sefcontext.py\", line 246, in <module>\r\n    main()\r\n  File \"/tmp/ansible_5hHAPf/ansible_module_sefcontext.py\", line 238, in main\r\n    semanage_fcontext_modify(module, result, target, ftype, setype, do_reload, serange, seuser)\r\n  File \"/tmp/ansible_5hHAPf/ansible_module_sefcontext.py\", line 163, in semanage_fcontext_modify\r\n    module.fail_json(msg=\"%s: %s\\n\" % (e.__class__.__name__, str(e)))\r\nUnicodeEncodeError: 'ascii' codec can't encode character u'\\xe9' in position 8: ordinal not in range(128)\r\n", 
    "msg": "MODULE FAILURE"
}
	to retry, use: --limit @/home/sok/Documents/Informatique/Plateformes/serveurPerso/peroServers.retry

PLAY RECAP *********************************************************************
web1.infra         : ok=1    changed=0    unreachable=0    failed=1   

```



Discussion:
<!-- @sok3t: Thanks for filing this bug report! -->
@dagwieers 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 --->
formatted traceback...
```
Traceback (most recent call last):
  File "/tmp/ansible_5hHAPf/ansible_module_sefcontext.py", line 246, in <module>
    main()
  File "/tmp/ansible_5hHAPf/ansible_module_sefcontext.py", line 238, in main
    semanage_fcontext_modify(module, result, target, ftype, setype, do_reload, serange, seuser)
  File "/tmp/ansible_5hHAPf/ansible_module_sefcontext.py", line 163, in semanage_fcontext_modify
    module.fail_json(msg="%s: %s\n" % (e.__class__.__name__, str(e)))
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe9' in position 8: ordinal not in range(128)
```

Resolving patch:
diff --git a/system/sefcontext.py b/system/sefcontext.py
--- a/system/sefcontext.py
+++ b/system/sefcontext.py
@@ -81,6 +81,7 @@
 
 from ansible.module_utils.basic import AnsibleModule
 from ansible.module_utils.pycompat24 import get_exception
+from ansible.module_utils._text import to_native
 
 try:
     import selinux
@@ -94,21 +95,35 @@
 except ImportError:
     HAVE_SEOBJECT=False
 
+### Add missing entries (backward compatible)
+seobject.file_types.update(dict(
+  a = seobject.SEMANAGE_FCONTEXT_ALL,
+  b = seobject.SEMANAGE_FCONTEXT_BLOCK,
+  c = seobject.SEMANAGE_FCONTEXT_CHAR,
+  d = seobject.SEMANAGE_FCONTEXT_DIR,
+  f = seobject.SEMANAGE_FCONTEXT_REG,
+  l = seobject.SEMANAGE_FCONTEXT_LINK,
+  p = seobject.SEMANAGE_FCONTEXT_PIPE,
+  s = seobject.SEMANAGE_FCONTEXT_SOCK,
+))
+
 ### Make backward compatible
-option_to_file_type_str = {
-    'a': 'all files',
-    'b': 'block device',
-    'c': 'character device',
-    'd': 'directory',
-    'f': 'regular file',
-    'l': 'symbolic link',
-    's': 'socket file',
-    'p': 'named pipe',
-}
+option_to_file_type_str = dict(
+  a = 'all files',
+  b = 'block device',
+  c = 'character device',
+  d = 'directory',
+  f = 'regular file',
+  l = 'symbolic link',
+  p = 'named pipe',
+  s = 'socket file',
+)
 
 def semanage_fcontext_exists(sefcontext, target, ftype):
     ''' Get the SELinux file context mapping definition from policy. Return None if it does not exist. '''
-    record = (target, ftype)
+
+    # Beware that records comprise of a string representation of the file_type
+    record = (target, option_to_file_type_str[ftype])
     records = sefcontext.get_all()
     try:
         return records[record]
@@ -160,7 +175,7 @@ def semanage_fcontext_modify(module, result, target, ftype, setype, do_reload, s
 
     except Exception:
         e = get_exception()
-        module.fail_json(msg="%s: %s\n" % (e.__class__.__name__, str(e)))
+        module.fail_json(msg="%s: %s\n" % (e.__class__.__name__, to_native(e)))
 
     if module._diff and prepared_diff:
         result['diff'] = dict(prepared=prepared_diff)
@@ -191,7 +206,7 @@ def semanage_fcontext_delete(module, result, target, ftype, do_reload, sestore='
 
     except Exception:
         e = get_exception()
-        module.fail_json(msg="%s: %s\n" % (e.__class__.__name__, str(e)))
+        module.fail_json(msg="%s: %s\n" % (e.__class__.__name__, to_native(e)))
 
     if module._diff and prepared_diff:
         result['diff'] = dict(prepared=prepared_diff)
@@ -231,9 +246,6 @@ def main():
 
     result = dict(target=target, ftype=ftype, setype=setype, state=state)
 
-    # Convert file types to (internally used) strings
-    ftype = option_to_file_type_str[ftype]
-
     if state == 'present':
         semanage_fcontext_modify(module, result, target, ftype, setype, do_reload, serange, seuser)
     elif state == 'absent':


Test patch: