Repository: ansible/ansible

Issue:
consul_kv cas opertation broken
##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
consul_kv

##### ANSIBLE VERSION
```
ansible 2.5.0rc1
  config file = None
  configured module search path = [u'/root/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /opt/tower/lib/python2.7/site-packages/ansible
  executable location = /opt/tower/bin/ansible
  python version = 2.7.14 (default, Jan 10 2018, 05:38:41) [GCC 6.3.0]
```

##### CONFIGURATION
```
ANSIBLE_PIPELINING(env: ANSIBLE_SSH_PIPELINING) = True
DEFAULT_ROLES_PATH(env: ANSIBLE_ROLES_PATH) = [u'/opt/tower/var/tower/playbooks/NOC/additional_roles', u'/opt/tower/var/tower/playbooks/NOC/system_roles', u'/opt/tower/var/tower/playbooks/NOC/noc_roles']
DEFAULT_STDOUT_CALLBACK(env: ANSIBLE_STDOUT_CALLBACK) = debug
HOST_KEY_CHECKING(env: ANSIBLE_HOST_KEY_CHECKING) = False
```

##### OS / ENVIRONMENT
N/A

##### SUMMARY
Cas operation require read than update if changed. last ansible version fails with


##### STEPS TO REPRODUCE
```
# Read default value.
- name: Read key/value pair for key {{consul_service_key}}
  consul_kv:
    key: "{{consul_service_key}}"
    cas: 0
    #token: "{{consul_service_token}}"
register: consul_key_response
```
full file: https://github.com/nocproject/ansible_deploy/blob/microservices/system_roles/consul/tasks/service_config_update.yml


##### EXPECTED RESULTS
Read operation completed ok. as on 2.4.3

##### ACTUAL RESULTS
```
TASK [consul : Read key/value pair for key noc/slots/discovery-default/manifest] ***
fatal: [node1]: FAILED! => {
    "changed": false
}

MSG:

state is present but all of the following are missing: value
```

Also no changes mention to that module in changelog


Discussion:
As far as i can see changes made in https://github.com/ansible/ansible/pull/32340 by @dagwieers 
Dag can you please made value not required as it was before. 
cc @sgargan
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
@dagwieers 
@dagwieers @sgargan is 2.5 will be broken for consul_kv ? 
bump
cc @colin-nolan
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
@nitzmahone What for are you releasing rc versions if release comes with introduced broken modules ?

Resolving patch:
diff --git a/lib/ansible/modules/clustering/consul_kv.py b/lib/ansible/modules/clustering/consul_kv.py
--- a/lib/ansible/modules/clustering/consul_kv.py
+++ b/lib/ansible/modules/clustering/consul_kv.py
@@ -1,7 +1,7 @@
 #!/usr/bin/python
 #
 # (c) 2015, Steve Gargan <steve.gargan@gmail.com>
-# (c) 2017, 2018 Genome Research Ltd.
+# (c) 2018 Genome Research Ltd.
 # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
 
 from __future__ import absolute_import, division, print_function
@@ -17,7 +17,7 @@
 module: consul_kv
 short_description: Manipulate entries in the key/value store of a consul cluster
 description:
-  - Allows the addition, modification and deletion of key/value entries in a
+  - Allows the retrieval, addition, modification and deletion of key/value entries in a
     consul cluster via the agent. The entire contents of the record, including
     the indices, flags and session are returned as 'value'.
   - If the key represents a prefix then Note that when a value is removed, the existing
@@ -34,10 +34,10 @@
 options:
     state:
         description:
-          - The action to take with the supplied key and value. If the state is
-            'present', the key contents will be set to the value supplied,
-            'changed' will be set to true only if the value was different to the
-            current contents. The state 'absent' will remove the key/value pair,
+          - The action to take with the supplied key and value. If the state is 'present' and `value` is set, the key
+            contents will be set to the value supplied and `changed` will be set to `true` only if the value was
+            different to the current contents. If the state is 'present' and `value` is not set, the existing value
+            associated to the key will be returned. The state 'absent' will remove the key/value pair,
             again 'changed' will be set to true only if the key actually existed
             prior to the removal. An attempt can be made to obtain or free the
             lock associated with a key/value pair with the states 'acquire' or
@@ -101,6 +101,13 @@
 
 
 EXAMPLES = '''
+# If the key does not exist, the value associated to the "data" property in `retrieved_key` will be `None`
+# If the key value is empty string, `retrieved_key["data"]["Value"]` will be `None`
+- name: retrieve a value from the key/value store
+  consul_kv:
+    key: somekey
+  register: retrieved_key
+
 - name: Add or update the value associated with a key in the key/value store
   consul_kv:
     key: somekey
@@ -135,6 +142,11 @@
 
 from ansible.module_utils.basic import AnsibleModule
 
+# Note: although the python-consul documentation implies that using a key with a value of `None` with `put` has a
+# special meaning (https://python-consul.readthedocs.io/en/latest/#consul-kv), if not set in the subsequently API call,
+# the value just defaults to an empty string (https://www.consul.io/api/kv.html#create-update-key)
+NOT_SET = None
+
 
 def _has_value_changed(consul_client, key, target_value):
     """
@@ -158,15 +170,19 @@ def _has_value_changed(consul_client, key, target_value):
 
 
 def execute(module):
-
     state = module.params.get('state')
 
     if state == 'acquire' or state == 'release':
         lock(module, state)
-    if state == 'present':
-        add_value(module)
-    else:
+    elif state == 'present':
+        if module.params.get('value') is NOT_SET:
+            get_value(module)
+        else:
+            set_value(module)
+    elif state == 'absent':
         remove_value(module)
+    else:
+        module.exit_json(msg="Unsupported state: %s" % (state, ))
 
 
 def lock(module, state):
@@ -201,13 +217,24 @@ def lock(module, state):
                      key=key)
 
 
-def add_value(module):
+def get_value(module):
+    consul_api = get_consul_api(module)
+    key = module.params.get('key')
+
+    index, existing_value = consul_api.kv.get(key, recurse=module.params.get('recurse'))
 
+    module.exit_json(changed=False, index=index, data=existing_value)
+
+
+def set_value(module):
     consul_api = get_consul_api(module)
 
     key = module.params.get('key')
     value = module.params.get('value')
 
+    if value is NOT_SET:
+        raise AssertionError('Cannot set value of "%s" to `NOT_SET`', (key, ))
+
     index, changed = _has_value_changed(consul_api, key, value)
 
     if changed and not module.check_mode:
@@ -274,13 +301,10 @@ def main():
             retrieve=dict(type='bool', default=True),
             state=dict(type='str', default='present', choices=['absent', 'acquire', 'present', 'release']),
             token=dict(type='str', no_log=True),
-            value=dict(type='str'),
+            value=dict(type='str', default=NOT_SET),
             session=dict(type='str'),
         ),
-        supports_check_mode=False,
-        required_if=[
-            ['state', 'present', ['value']],
-        ],
+        supports_check_mode=False
     )
 
     test_dependencies(module)


Test patch: