Repository: ansible/ansible

Issue:
gconftool2 fails on value: "google-chrome %s"
##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
gconftool2

##### ANSIBLE VERSION
```
ansible 2.3.0.0
  configured module search path = Default w/o overrides
  python version = 2.7.10 (default, Feb  6 2017, 23:53:20) [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.34)]
```

##### OS / ENVIRONMENT
macOS 10.12.4 managing OpenSUSE Leap 42.2

##### SUMMARY
Cannot change a GNOME configuration value "firefox %s" to "google-chrome %s"

##### STEPS TO REPRODUCE

```yaml
- gconftool2:
    key: "/desktop/gnome/url-handlers/https/command"
    value_type: "string"
    value: "google-chrome %s"
    state: present
```

##### ACTUAL RESULTS
```
<linux.local> SSH: EXEC ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o User=root -o ConnectTimeout=10 -o ControlPath=~/.ansible/cp/f9f5234dc1 -tt linux.local '/bin/sh -c '"'"'/usr/bin/python /root/.ansible/tmp/ansible-tmp-1492459444.36-157557468549298/gconftool2.py; rm -rf "/root/.ansible/tmp/ansible-tmp-1492459444.36-157557468549298/" > /dev/null 2>&1 && sleep 0'"'"''
<linux.local> (0, '\r\n{"msg": "gconftool-2 failed with error: No value to set for key: `%s\'\\n", "failed": true, "invocation": {"module_args": {"direct": false, "value": "google-chrome %s", "config_source": null, "state": "present", "value_type": "string", "key": "/desktop/gnome/url-handlers/https/command"}}}\r\n', 'OpenSSH_7.4p1, LibreSSL 2.5.0\r\ndebug1: Reading configuration data ~/.ssh/config\r\ndebug3: kex names ok: [curve25519-sha256@libssh.org]\r\ndebug3: kex names ok: [curve25519-sha256@libssh.org,ecdh-sha2-nistp256]\r\ndebug1: ~/.ssh/config line 32: Applying options for linux.local\r\ndebug3: kex names ok: [curve25519-sha256@libssh.org,ecdh-sha2-nistp256]\r\ndebug3: kex names ok: [ecdh-sha2-nistp384]\r\ndebug3: kex names ok: [curve25519-sha256@libssh.org]\r\ndebug1: ~/.ssh/config line 96: Applying options for *\r\ndebug1: Reading configuration data /etc/ssh/ssh_config\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 = 83781\r\ndebug3: mux_client_request_session: session request sent\r\ndebug1: mux_client_request_session: master session id: 2\r\ndebug3: mux_client_read_packet: read header failed: Broken pipe\r\ndebug2: Received exit status from master 0\r\nShared connection to linux.local closed.\r\n')
fatal: [linux.local]: FAILED! => {
    "changed": false,
    "failed": true,
    "invocation": {
        "module_args": {
            "config_source": null,
            "direct": false,
            "key": "/desktop/gnome/url-handlers/https/command",
            "state": "present",
            "value": "google-chrome %s",
            "value_type": "string"
        }
    },
    "msg": "gconftool-2 failed with error: No value to set for key: `%s'\n"
}
```



Discussion:
I can reproduce this issue. 
This happens when key (e.g. `/desktop/gnome/url-handlers/https/command`) doesn't exists. 
gconftool2 module gets value for key before setting value to key. When key doesn't exists, fails in getting the value. Need to fix [this](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/system/gconftool2.py#L139-L163) logic.
@Akasurde Am I incorrectly using gconftool2? That key does already exist.
```
$ gconftool-2 -a /desktop/gnome/url-handlers/https
 enabled = true
 needs_terminal = false
 command = firefox %s
```

Resolving patch:
diff --git a/lib/ansible/modules/system/gconftool2.py b/lib/ansible/modules/system/gconftool2.py
--- a/lib/ansible/modules/system/gconftool2.py
+++ b/lib/ansible/modules/system/gconftool2.py
@@ -1,6 +1,7 @@
 #!/usr/bin/python
 # -*- coding: utf-8 -*-
 # (c) 2016, Kenneth D. Evensen <kevensen@redhat.com>
+# (c) 2017, Abhijeet Kasurde <akasurde@redhat.com>
 #
 # This file is part of Ansible (sort of)
 #
@@ -103,9 +104,10 @@
 ...
 '''
 
+from subprocess import Popen, PIPE
 from ansible.module_utils.basic import AnsibleModule, BOOLEANS_TRUE
 from ansible.module_utils.pycompat24 import get_exception
-import subprocess
+
 
 
 class GConf2Preference(object):
@@ -121,7 +123,8 @@ def __init__(self, ansible, key, value_type, value,
     def value_already_set(self):
         return False
 
-    def call(self, call_type):
+    def call(self, call_type, fail_onerr=True):
+        """ Helper function to perform gconftool-2 operations """
         config_source = ''
         direct = ''
         changed = False
@@ -137,34 +140,41 @@ def call(self, call_type):
             direct = "--direct"
 
         # Execute the call
+        cmd = "gconftool-2 "
         try:
             # If the call is "get", then we don't need as many parameters and
             # we can ignore some
             if call_type == 'get':
-                process = subprocess.Popen(["gconftool-2 --get " + self.key],
-                                           stdout=subprocess.PIPE,
-                                           stderr=subprocess.PIPE, shell=True)
+                cmd += "--get {0}".format(self.key)
             # Otherwise, we will use all relevant parameters
-            else:
-                process = subprocess.Popen(["gconftool-2 " + direct + " " +
-                                           config_source + " --type " +
-                                           self.value_type + " --" +
-                                           call_type + " " + self.key + " " +
-                                           self.value], stdout=subprocess.PIPE,
-                                           stderr=subprocess.PIPE, shell=True)
+            elif call_type == 'set':
+                cmd += "{0} {1} --type {2} --{3} {4} \"{5}\"".format(direct,
+                                                                     config_source,
+                                                                     self.value_type,
+                                                                     call_type,
+                                                                     self.key,
+                                                                     self.value)
+            elif call_type == 'unset':
+                cmd += "--unset {0}".format(self.key)
+
+            # Start external command
+            process = Popen([cmd], stdout=PIPE, stderr=PIPE, shell=True)
+
             # In either case, we will capture the output
             out = process.stdout.read()
             err = process.stderr.read()
 
             if len(err) > 0:
-                self.ansible.fail_json(msg='gconftool-2 failed with error: %s'
-                                       % (str(err)))
+                if fail_onerr:
+                    self.ansible.fail_json(msg='gconftool-2 failed with '
+                                               'error: %s' % (str(err)))
             else:
                 changed = True
 
         except OSError:
-            self.ansible.fail_json(msg='gconftool-2 failed with and exception')
-
+            exception = get_exception()
+            self.ansible.fail_json(msg='gconftool-2 failed with exception: '
+                                       '%s' % exception)
         return changed, out.rstrip()
 
 
@@ -174,18 +184,15 @@ def main():
         argument_spec=dict(
             key=dict(required=True, default=None, type='str'),
             value_type=dict(required=False,
-                                           choices=['int', 'bool',
-                                                    'float', 'string'],
-                                           type='str'),
-            value=dict(required=False, default=None,
-                                      type='str'),
-            state=dict(required=True, default=None,
-                                      choices=['present', 'get', 'absent'],
-                                      type='str'),
-            direct=dict(required=False,
-                                       default=False, type='bool'),
-            config_source=dict(required=False,
-                                              default=None, type='str')
+                            choices=['int', 'bool', 'float', 'string'],
+                            type='str'),
+            value=dict(required=False, default=None, type='str'),
+            state=dict(required=True,
+                       default=None,
+                       choices=['present', 'get', 'absent'],
+                       type='str'),
+            direct=dict(required=False, default=False, type='bool'),
+            config_source=dict(required=False, default=None, type='str')
             ),
         supports_check_mode=True
     )
@@ -230,8 +237,8 @@ def main():
     # Create a gconf2 preference
     gconf_pref = GConf2Preference(module, key, value_type,
                                   value, direct, config_source)
-    # Now we get the current value
-    _, current_value = gconf_pref.call("get")
+    # Now we get the current value, if not found don't fail
+    _, current_value = gconf_pref.call("get", fail_onerr=False)
 
     # Check if the current value equals the value we want to set.  If not, make
     # a change
@@ -250,11 +257,12 @@ def main():
     else:
         new_value = current_value
 
-    facts = {}
-    facts['gconftool2'] = {'changed': change, 'key': key,
-                           'value_type': value_type, 'new_value': new_value,
-                           'previous_value': current_value,
-                           'playbook_value': module.params['value']}
+    facts = dict(gconftool2={'changed': change,
+                             'key': key,
+                             'value_type': value_type,
+                             'new_value': new_value,
+                             'previous_value': current_value,
+                             'playbook_value': module.params['value']})
 
     module.exit_json(changed=change, ansible_facts=facts)
 


Test patch: