Repository: ansible/ansible

Issue:
xml module: print_match parameter fails due to "TypeError: 'bool' object is not callable"
##### ISSUE TYPE
 Bug Report

##### COMPONENT NAME
xml

##### ANSIBLE VERSION
```
ansible 2.4.0 (devel beed59f303) last updated 2017/08/19 23:45:26 (GMT +000)
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/u/nwahl/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /u/nwahl/ansible/lib/ansible
  executable location = /u/nwahl/ansible/bin/ansible
  python version = 2.7.5 (default, Aug  2 2016, 04:20:16) [GCC 4.8.5 20150623 (Red Hat 4.8.5-4)]
```

##### CONFIGURATION
remote_tmp = /tmp/.ansible-${USER}/tmp

##### OS / ENVIRONMENT
Control server:
OS release: RHEL 7.3
Kernel version: 3.10.0-514.2.2.el7.x86_64

Running against localhost
Module: xml

##### SUMMARY
The xml module fails with error "TypeError: 'bool' object is not callable" when print_match parameter is set to true.

##### STEPS TO REPRODUCE
1. Run a playbook using the xml module with the print_match parameter set to "yes".
```
# Playbook test-playbook.yml
---
- hosts: localhost
  connection: local
  gather_facts: no
  tasks:
      - name: Get level2 element from XML file
        xml:
            path: ./test.xml
            xpath: /body/level1/level2
            print_match: yes
```
```
# XML file test.xml
<body>
    <level1>
        <level2>TEST MESSAGE</level2>
        <level2>TEST MESSAGE2</level2>
    </level1>
    <level1>
        <level2>TEST MESSAGE3</level2>
    </level1>
</body>
```
```
# Inventory file hosts.ini
localhost
```
```
# Example run command
$ ansible-playbook -vvvv -i ./hosts.ini test-playbook.yml
```

##### EXPECTED RESULTS
This example is from after I modified xml.py to what I perceive as a working version behaving as intended. As far as I can tell, this is all the print_match parameter is supposed to yield; the content parameter prints the content within the elements and is mutually exclusive with the print_match parameter.

```
ansible-playbook 2.4.0 (devel beed59f303) last updated 2017/08/19 23:45:26 (GMT +000)
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/u/nwahl/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /u/nwahl/ansible/lib/ansible
  executable location = /u/nwahl/ansible/bin/ansible-playbook
  python version = 2.7.5 (default, Aug  2 2016, 04:20:16) [GCC 4.8.5 20150623 (Red Hat 4.8.5-4)]
Using /etc/ansible/ansible.cfg as config file
setting up inventory plugins
Set default localhost to localhost
Parsed /tmp/hosts.ini inventory source with ini plugin
Loading callback plugin default of type stdout, v2.0 from /u/nwahl/ansible/lib/ansible/plugins/callback/__init__.pyc

PLAYBOOK: test-playbook.yml **************************************************************************************************
1 plays in test-playbook.yml

PLAY [localhost] *************************************************************************************************************
META: ran handlers

TASK [Get level2 element from XML file] **************************************************************************************
task path: /tmp/test-playbook.yml:6
Using module file /tmp/library/xml.py
<localhost> ESTABLISH LOCAL CONNECTION FOR USER: nwahl
<localhost> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo /tmp/.ansible-${USER}/tmp/ansible-tmp-1503190677.96-72174309842941 `" && echo ansible-tmp-1503190677.96-72174309842941="` echo /tmp/.ansible-${USER}/tmp/ansible-tmp-1503190677.96-72174309842941 `" ) && sleep 0'
<localhost> PUT /tmp/tmpgGexPZ TO /tmp/.ansible-nwahl/tmp/ansible-tmp-1503190677.96-72174309842941/xml.py
<localhost> EXEC /bin/sh -c 'chmod u+x /tmp/.ansible-nwahl/tmp/ansible-tmp-1503190677.96-72174309842941/ /tmp/.ansible-nwahl/tmp/ansible-tmp-1503190677.96-72174309842941/xml.py && sleep 0'
<localhost> EXEC /bin/sh -c '/usr/bin/python /tmp/.ansible-nwahl/tmp/ansible-tmp-1503190677.96-72174309842941/xml.py; rm -rf "/tmp/.ansible-nwahl/tmp/ansible-tmp-1503190677.96-72174309842941/" > /dev/null 2>&1 && sleep 0'
ok: [localhost] => {
    "actions": {
        "namespaces": {},
        "state": "present",
        "xpath": "/body/level1/level2"
    },
    "changed": false,
    "count": 0,
    "failed": false,
    "invocation": {
        "module_args": {
            "add_children": null,
            "attribute": null,
            "content": null,
            "count": false,
            "input_type": "yaml",
            "namespaces": {},
            "path": "./test.xml",
            "pretty_print": false,
            "print_match": true,
            "set_children": null,
            "state": "present",
            "value": null,
            "xmlstring": null,
            "xpath": "/body/level1/level2"
        }
    },
    "matches": [],
    "msg": "selector '/body/level1/level2' match: [\"/body/level1[1]/level2[1]\", \"/body/level1[1]/level2[2]\", \"/body/level1[2]/level2\"]"
}
META: ran handlers
META: ran handlers

PLAY RECAP *******************************************************************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0

```

##### ACTUAL RESULTS
```
ansible-playbook 2.4.0 (devel beed59f303) last updated 2017/08/19 23:45:26 (GMT +000)
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/u/nwahl/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /u/nwahl/ansible/lib/ansible
  executable location = /u/nwahl/ansible/bin/ansible-playbook
  python version = 2.7.5 (default, Aug  2 2016, 04:20:16) [GCC 4.8.5 20150623 (Red Hat 4.8.5-4)]
Using /etc/ansible/ansible.cfg as config file
setting up inventory plugins
Set default localhost to localhost
Parsed /tmp/hosts.ini inventory source with ini plugin
Loading callback plugin default of type stdout, v2.0 from /u/nwahl/ansible/lib/ansible/plugins/callback/__init__.pyc

PLAYBOOK: test-playbook.yml **************************************************************************************************
1 plays in test-playbook.yml

PLAY [localhost] *************************************************************************************************************
META: ran handlers

TASK [Get level2 element from XML file] **************************************************************************************
task path: /tmp/test-playbook.yml:6
Using module file /tmp/library/xml.py
<localhost> ESTABLISH LOCAL CONNECTION FOR USER: nwahl
<localhost> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo /tmp/.ansible-${USER}/tmp/ansible-tmp-1503190838.81-197787373613845 `" && echo ansible-tmp-1503190838.81-197787373613845="` echo /tmp/.ansible-${USER}/tmp/ansible-tmp-1503190838.81-197787373613845 `" ) && sleep 0'
<localhost> PUT /tmp/tmpiZxI_q TO /tmp/.ansible-nwahl/tmp/ansible-tmp-1503190838.81-197787373613845/xml.py
<localhost> EXEC /bin/sh -c 'chmod u+x /tmp/.ansible-nwahl/tmp/ansible-tmp-1503190838.81-197787373613845/ /tmp/.ansible-nwahl/tmp/ansible-tmp-1503190838.81-197787373613845/xml.py && sleep 0'
<localhost> EXEC /bin/sh -c '/usr/bin/python /tmp/.ansible-nwahl/tmp/ansible-tmp-1503190838.81-197787373613845/xml.py; rm -rf "/tmp/.ansible-nwahl/tmp/ansible-tmp-1503190838.81-197787373613845/" > /dev/null 2>&1 && sleep 0'
The full traceback is:
Traceback (most recent call last):
  File "/tmp/ansible_FoqYAj/ansible_module_xml.py", line 771, in <module>
    main()
  File "/tmp/ansible_FoqYAj/ansible_module_xml.py", line 724, in main
    print_match(module, doc, xpath, namespaces)
TypeError: 'bool' object is not callable

fatal: [localhost]: FAILED! => {
    "changed": false,
    "failed": true,
    "module_stderr": "Traceback (most recent call last):\n  File \"/tmp/ansible_FoqYAj/ansible_module_xml.py\", line 771, in <module>\n    main()\n  File \"/tmp/ansible_FoqYAj/ansible_module_xml.py\", line 724, in main\n    print_match(module, doc, xpath, namespaces)\nTypeError: 'bool' object is not callable\n",
    "module_stdout": "",
    "msg": "MODULE FAILURE",
    "rc": 0
}
        to retry, use: --limit @/tmp/test-playbook.retry

PLAY RECAP *******************************************************************************************************************
localhost                  : ok=0    changed=0    unreachable=0    failed=1
```


Discussion:
cc @cmprescott @dagwieers @magnus919 @sm4rk0 @tbielawa
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->

Resolving patch:
diff --git a/lib/ansible/modules/files/xml.py b/lib/ansible/modules/files/xml.py
old mode 100755
new mode 100644
--- a/lib/ansible/modules/files/xml.py
+++ b/lib/ansible/modules/files/xml.py
@@ -260,7 +260,7 @@
 _RE_SPLITONLYEQVALUE = re.compile("^(.*)/text\\(\\)=" + _XPSTR + "$")
 
 
-def print_match(module, tree, xpath, namespaces):
+def do_print_match(module, tree, xpath, namespaces):
     match = tree.xpath(xpath, namespaces=namespaces)
     match_xpaths = []
     for m in match:
@@ -721,7 +721,7 @@ def main():
         module.fail_json(msg="Error while parsing path: %s" % e)
 
     if print_match:
-        print_match(module, doc, xpath, namespaces)
+        do_print_match(module, doc, xpath, namespaces)
 
     if count:
         count_nodes(module, doc, xpath, namespaces)


Test patch: