First glance, looks like quoting issue. Module output seems to include a
mix of single and double quotes. module output should be json with double
quotes.

Looks like:

   output = json.dumps(queuesList)

is going to make output a string with json in it. If you want the
structured data in the result, you will want to include queuesList
as a value to exit_json()

    # assumes queuesList object can be json serialized. If not, you will
need to wrap it or transform it
    # to something that is (eg, a dict with string keys and values)
    module.exit_json(changed=True, failed=False, {'queueslist': queuesList})

(your pasted code also doesn't include 'output' in it's returns in
exit_json or fail_json. Those need to include the data to be returned)


Can you post the stdout output from the module called directly?

a couple of ways to do that:


1. use hacking/test-module

    # from ansible src checkout, assuming custom module is at ~/new_module

    $ hacking/test-module -m ~/new_module -a
'src=/home/ec2-user/ansible/routerconfig.xml'

2. use ad hoc mode

   # assumes new_module is somewhere on ansible module library path

   $ ansible -v localhost -m new_module -a
'src=/home/ec2-user/ansible/routerconfig.xml'


On Tue, Aug 8, 2017 at 1:28 PM, Adam Shantz <[email protected]> wrote:

> Hi all -
>
> I'm working on a custom module in python that takes a subset of an XML
> config, and turns it into JSON so I can use it with an Ansible playbook &
> Jinja2 template.
>
> My python code works fine to parse the output, but my module continually
> fails for reasons I can't explain.  Can someone give me a hint?
>
> *Module code:*
> #!/usr/bin/python
>
> ANSIBLE_METADATA = {
>     'metadata_version': '1.0',
>     'status': ['preview'],
>     'supported_by': 'curated'
> }
>
> import os
> import shutil
> import tempfile
> import traceback
> import xml.etree.cElementTree as etree
> import json
>
>
> from ansible.module_utils.basic import AnsibleModule
> from ansible.module_utils._text import to_bytes, to_native
>
> from collections import defaultdict
>
> class Xml2Dict(dict):
>     def __init__(self, parent_element):
>         if parent_element.items():
>             self.updateDict( dict(parent_element.items()) )
>         for element in parent_element:
>             if len(element):
>                 aDict = Xml2Dict(element)
>                 self.updateDict({element.tag: aDict})
>             elif element.items():    # items() is special for attributes
>                 elementattrib= element.items()
>                 if element.text:
>                     elementattrib.append((element.tag,element.text ))
> # add tag:text if exist
>                 self.updateDict({element.tag: dict(elementattrib)})
>             else:
>                 self.updateDict({element.tag: element.text})
>
>     def updateDict (self, aDict ):
>         for key in aDict.keys():   # keys() includes tag and attributes
>             if key in self:
>                 value = self.pop(key)
>                 if type(value) is not list:
>                     listOfDicts = []
>                     listOfDicts.append(value)
>                     listOfDicts.append(aDict[key])
>                     self.update({key: listOfDicts})
>                 else:
>                     value.append(aDict[key])
>                     self.update({key: value})
>             else:
>                 self.update({key:aDict[key]})  # it was self.update(aDict)
>
> def run_module():
>     # define the available arguments/parameters that a user can pass to
>     # the module
>     module_args = dict(
>         src=dict(type='path')
>     )
>
>     # seed the result dict in the object
>     # we primarily care about changed and state
>     # change is if this module effectively modified the target
>     # state will include any data that you want your module to pass back
>     # for consumption, for example, in a subsequent task
>     result = dict(
>         changed=False,
>         original_message='',
>         message='',
>         failed=''
>     )
>
>     # the AnsibleModule object will be our abstraction working with Ansible
>     # this includes instantiation, a couple of common attr would be the
>     # args/params passed to the execution, as well as if the module
>     # supports check mode
>     module = AnsibleModule(
>         argument_spec=module_args,
>         supports_check_mode=True
>     )
>
>     src = module.params['src']
> #    b_src = to_bytes(src, errors='surrogate_or_strict')
>
> #    if not os.path.exists(b_src):
> #        module.fail_json(msg="Source %s not found" % (src))
> #    if not os.access(b_src, os.R_OK):
> #        module.fail_json(msg="Source %s not readable" % (src))
> #    if os.path.isdir(b_src):
> #        module.fail_json(msg="Directory specified as the source instead
> of a file: %s" % (src))
>
> #    if os.path.exists(b_src):
> #        b_src = os.path.realpath(b_src)
> #        src = to_native(b_src, errors='surrogate_or_strict')
> #        if os.access(b_src, os.R_OK):
>     tree = etree.ElementTree(file=src)
>     #print tree.getroot()
>     root = tree.getroot()
>     #print "tag=%s, attrib=%s" % (root.tag, root.attrib)
>
>     from pprint import pprint
>     #router = pprint(etree_to_dict(root))
>     router = Xml2Dict(root)
>
>     for key in router['swiftlet'][4].iteritems():
>         if key[0] == 'aliases':
>             for item in enumerate(key[1]['alias']):
>                 dest = str(item[1]['map-to'])
>                 name = str(item[1]['name'])
>     #            print dest + ":" + name
>
>     queuesList = []
>     for key in router['swiftlet'][9].iteritems():
>         if key[0] == 'queues':
>             for item in enumerate(key[1]['queue']):
>                 name = str(item[1]['name'])
>                 attrList = item[1]
>                 print attrList
>                 queueDict = {}
>
>                 if len(attrList) == 1:
>                     queueDict[name] = {"propertyCount" : len(attrList)}
>                 else:
>                     for attrib, value in attrList.iteritems():
>                         if attrib != "name":
>                             if name in queueDict:
>                                 queueDict[name][attrib] = value
>                             else:
>                                 queueDict[name] = {attrib : value}
>                     if name in queueDict:
>                         queueDict[name]["propertyCount"] = len(attrList)
>                     else:
>                         queueDict[name] = {"propertyCount" : len(attrList)}
>
>                 queuesList.append(queueDict)
>
>     output = json.dumps(queuesList)
>
>     # if the user is working with this module in only check mode we do not
>     # want to make any changes to the environment, just return the current
>     # state with no modifications
> #    if module.check_mode:
> #        return result
>
>     # manipulate or modify the state as needed (this is going to be the
>     # part where your module will do what it needs to do)
>     result['original_message'] = module.params['src']
>     result['message'] = 'goodbye'
>
>     # use whatever logic you need to determine whether or not this module
>     # made any modifications to your target
> #    if module.params['src']:
> #        result['changed'] = True
>
>     # during the execution of the module, if there is an exception or a
>     # conditional state that effectively causes a failure, run
>     # AnsibleModule.fail_json() to pass in the message and the result
> #    if module.params['src'] == 'fail me':
> #        module.fail_json(msg='You requested this to fail', **result)
>
>     # in the event of a successful module execution, you will want to
>     # simple AnsibleModule.exit_json(), passing the key/value results
>     module.exit_json(
>         changed=True,
>         failed=False)
>
>
> def main():
>     run_module()
>
>
> if __name__ == '__main__':
>     main()
>
>
> *Results:*
> TASK [test module] ******************************
> ************************************************************
> *************************************************
> task path: /home/ec2-user/ansible/testmod.yml:7
> Using module_utils file /home/ec2-user/ansible/lib/ans
> ible/module_utils/_text.py
> Using module_utils file /home/ec2-user/ansible/lib/ans
> ible/module_utils/basic.py
> Using module_utils file /home/ec2-user/ansible/lib/ans
> ible/module_utils/six/__init__.py
> Using module_utils file /home/ec2-user/ansible/lib/ans
> ible/module_utils/parsing/convert_bool.py
> Using module_utils file /home/ec2-user/ansible/lib/ans
> ible/module_utils/parsing/__init__.py
> Using module_utils file /home/ec2-user/ansible/lib/ans
> ible/module_utils/pycompat24.py
> Using module file /home/ec2-user/ansible/lib/ans
> ible/modules/messaging/new_module.py
> <127.0.0.1> ESTABLISH LOCAL CONNECTION FOR USER: ec2-user
> <127.0.0.1> EXEC /bin/sh -c 'echo ~ && sleep 0'
> <127.0.0.1> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo
> /home/ec2-user/.ansible/tmp/ansible-tmp-1502132102.2-141765875120224 `"
> && echo ansible-tmp-1502132102.2-141765875120224="` echo
> /home/ec2-user/.ansible/tmp/ansible-tmp-1502132102.2-141765875120224 `" )
> && sleep 0'
> <127.0.0.1> PUT /tmp/tmpHHjMUg TO /home/ec2-user/.ansible/tmp/an
> sible-tmp-1502132102.2-141765875120224/new_module.py
> <127.0.0.1> EXEC /bin/sh -c 'chmod u+x /home/ec2-user/.ansible/tmp/an
> sible-tmp-1502132102.2-141765875120224/ /home/ec2-user/.ansible/tmp/an
> sible-tmp-1502132102.2-141765875120224/new_module.py && sleep 0'
> <127.0.0.1> EXEC /bin/sh -c '/home/ec2-user/ansible/venv/bin/python
> /home/ec2-user/.ansible/tmp/ansible-tmp-1502132102.2-141765875120224/new_module.py;
> rm -rf "/home/ec2-user/.ansible/tmp/ansible-tmp-1502132102.2-141765875120224/"
> > /dev/null 2>&1 && sleep 0'
> fatal: [localhost]: FAILED! => {
>     "changed": false,
>     "failed": true,
>     "module_stderr": "",
>     "module_stdout": "{'cache-size': '5000',
> 'flowcontrol-start-queuesize': '-1', 'name': 'q1', 'cleanup-interval':
> '-1', 'persistence-mode': 'non_persistent'}\n{'cache-size': '5000',
> 'flowcontrol-start-queuesize': '-1', 'name': 'q2', 'cleanup-interval':
> '-1', 'persistence-mode': 'non_persistent'}\n{'cache-size': '5000',
> 'flowcontrol-start-queuesize': '-1', 'name': 'q3', 'cleanup-interval':
> '-1', 'persistence-mode': 'non_persistent'}\n{'name':
> 'test'}\n\n{\"invocation\": {\"module_args\": {\"src\":
> \"/home/ec2-user/ansible/routerconfig.xml\"}}, \"failed\": false,
> \"changed\": true}\n",
>     "msg": "MODULE FAILURE",
>     "rc": 0
> }
>         to retry, use: --limit @/home/ec2-user/ansible/testmod.retry
>
> PLAY RECAP ************************************************************
> ************************************************************
> ***************************
> localhost                  : ok=1    changed=0    unreachable=0
>  failed=1
>
>
>
> *My playbook is below:*
>
> - name: test
>   gather_facts: yes
>   hosts: localhost
>   connection: local
>
>   tasks:
>     - name: test module
>       new_module:
>         src: /home/ec2-user/ansible/routerconfig.xml
>       register: testout
>
>
>     - name: debug
>       debug:
>         msg: '{{testout}}'
>
> --
> You received this message because you are subscribed to the Google Groups
> "Ansible Development" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to [email protected].
> For more options, visit https://groups.google.com/d/optout.
>

-- 
You received this message because you are subscribed to the Google Groups 
"Ansible Development" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/d/optout.

Reply via email to