diff --git a/lib/ansible/modules/network/aci/aci_rest.py b/lib/ansible/modules/network/aci/aci_rest.py index 7e64df31df..3df238a6b1 100644 --- a/lib/ansible/modules/network/aci/aci_rest.py +++ b/lib/ansible/modules/network/aci/aci_rest.py @@ -70,7 +70,22 @@ EXAMPLES = r''' src: /home/cisco/ansible/aci/configs/aci_config.xml delegate_to: localhost -- name: Add a tenant +- name: Add a tenant using inline YAML + aci_rest: + hostname: '{{ inventory_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: no + path: /api/mo/uni/tn-[Sales].json + method: post + content: + fvTenant: + attributes: + name: Sales + descr: Sales departement + delegate_to: localhost + +- name: Add a tenant using a JSON string aci_rest: hostname: '{{ inventory_hostname }}' username: '{{ aci_username }}' @@ -89,6 +104,18 @@ EXAMPLES = r''' } delegate_to: localhost +- name: Add a tenant using an XML string + aci_rest: + hostname: '{{ inventory_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: no + path: /api/mo/uni/tn-[Sales].xml + method: post + content: | + + delegate_to: localhost + - name: Get tenants aci_rest: hostname: '{{ inventory_hostname }}' @@ -187,6 +214,7 @@ url: sample: https://1.2.3.4/api/mo/uni/tn-[Dag].json?rsp-subtree=modified ''' +import json import os try: @@ -211,9 +239,17 @@ try: except ImportError: HAS_XMLJSON_COBRA = False +# Optional, only used for YAML validation +try: + import yaml + HAS_YAML = True +except: + HAS_YAML = False + from ansible.module_utils.aci import ACIModule, aci_argument_spec, aci_response_json, aci_response_xml from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.urls import fetch_url +from ansible.module_utils._text import to_text def update_qsl(url, params): @@ -267,7 +303,7 @@ def main(): path=dict(type='str', required=True, aliases=['uri']), method=dict(type='str', default='get', choices=['delete', 'get', 'post'], aliases=['action']), src=dict(type='path', aliases=['config_file']), - content=dict(type='str'), + content=dict(type='raw'), ) module = AnsibleModule( @@ -311,6 +347,30 @@ def main(): # TODO: Would be nice to template this, requires action-plugin payload = config_object.read() + # Validate content + if rest_type == 'json': + if content and isinstance(content, dict): + # Validate inline YAML/JSON + payload = json.dumps(payload) + elif payload and isinstance(payload, str) and HAS_YAML: + try: + # Validate YAML/JSON string + payload = json.dumps(yaml.load(payload)) + except Exception as e: + module.fail_json(msg='Failed to parse provided JSON/YAML content: %s' % to_text(e), exception=to_text(e), payload=payload) + elif rest_type == 'xml' and HAS_LXML_ETREE: + if content and isinstance(content, dict) and HAS_XMLJSON_COBRA: + # Validate inline YAML/JSON + # FIXME: Converting from a dictionary to XML is unsupported at this time + # payload = etree.tostring(payload) + pass + elif payload and isinstance(payload, str): + try: + # Validate XML string + payload = lxml.etree.tostring(lxml.etree.fromstring(payload)) + except Exception as e: + module.fail_json(msg='Failed to parse provided XML content: %s' % to_text(e), payload=payload) + # Perform actual request using auth cookie (Same as aci_request,but also supports XML) url = '%(protocol)s://%(hostname)s/' % aci.params + path.lstrip('/') if method != 'get': @@ -325,9 +385,9 @@ def main(): if info['status'] != 200: try: aci_response(aci.result, info['body'], rest_type) - module.fail_json(msg='Request failed: %(error_code)s %(error_text)s' % aci.result, **aci.result) + module.fail_json(msg='Request failed: %(error_code)s %(error_text)s' % aci.result, payload=payload, **aci.result) except KeyError: - module.fail_json(msg='Request failed for %(url)s. %(msg)s' % info, **aci.result) + module.fail_json(msg='Request failed for %(url)s. %(msg)s' % info, payload=payload, **aci.result) aci_response(aci.result, resp.read(), rest_type)