Skip to content

Commit

Permalink
aci_rest: Implement idempotency
Browse files Browse the repository at this point in the history
This PR includes:
- A new function to modify query strings in URLs
- Add rsp-subtree=modified to post/delete requests
- Test the ACI response for changes and report back
- Return the used URL back to the user
- Remove check-mode support (as it was non-functional anyway)
- Fix a bug related to method=delete and not having content set

This fixes datacenter/aci-ansible#111
  • Loading branch information
dagwieers committed Sep 5, 2017
1 parent 6108b46 commit a796391
Showing 1 changed file with 72 additions and 16 deletions.
88 changes: 72 additions & 16 deletions lib/ansible/modules/network/aci/aci_rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,25 @@
src: /home/cisco/ansible/aci/configs/aci_config.xml
delegate_to: localhost
- name: Add a tenant
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: Get tenants
aci_rest:
hostname: '{{ inventory_hostname }}'
Expand Down Expand Up @@ -161,10 +180,21 @@
returned: always
type: string
sample: '0'
url:
description: URL used for APIC REST call
returned: success
type: string
sample: https://1.2.3.4/api/mo/uni/tn-[Dag].json?rsp-subtree=modified
'''

import os

try:
from ansible.module_utils.six.moves.urllib.parse import parse_qsl, urlencode, urlparse, urlunparse
HAS_URLPARSE = True
except:
HAS_URLPARSE = False

# Optional, only used for XML payload
try:
import lxml.etree
Expand All @@ -186,15 +216,50 @@
from ansible.module_utils.urls import fetch_url


def update_qsl(url, params):
''' Add or update a URL query string '''

if HAS_URLPARSE:
url_parts = list(urlparse(url))
query = dict(parse_qsl(url_parts[4]))
query.update(params)
url_parts[4] = urlencode(query)
return urlunparse(url_parts)
elif '?' in url:
return url + '&' + '&'.join(['%s=%s' % (k, v) for k, v in params.items()])
else:
return url + '?' + '&'.join(['%s=%s' % (k, v) for k, v in params.items()])


def aci_changed(d):
''' Check ACI response for changes '''

if isinstance(d, dict):
for k, v in d.items():
if k == 'status' and v in ('created', 'modified', 'deleted'):
return True
elif aci_changed(v) is True:
return True
elif isinstance(d, list):
for i in d:
if aci_changed(i) is True:
return True

return False


def aci_response(result, rawoutput, rest_type='xml'):
''' Handle APIC response output '''

if rest_type == 'json':
aci_response_json(result, rawoutput)

else:
aci_response_xml(result, rawoutput)

# Use APICs built-in idempotency
if HAS_URLPARSE:
result['changed'] = aci_changed(result)


def main():
argument_spec = aci_argument_spec
Expand All @@ -208,7 +273,6 @@ def main():
module = AnsibleModule(
argument_spec=argument_spec,
mutually_exclusive=[['content', 'src']],
supports_check_mode=True,
)

path = module.params['path']
Expand Down Expand Up @@ -240,26 +304,18 @@ def main():

aci = ACIModule(module)

if method == 'get':
aci.request(path)
module.exit_json(**aci.result)
elif module.check_mode:
# In check_mode we assume it works, but we don't actually perform the requested change
# TODO: Could we turn this request in a GET instead ?
aci.result['changed'] = True
module.exit_json(response='OK (Check mode)', status=200, **aci.result)

# Prepare request data
if content:
# We include the payload as it may be templated
payload = content
elif file_exists:
# We include the payload as it may be templated
payload = content
if file_exists:
with open(src, 'r') as config_object:
# TODO: Would be nice to template this, requires action-plugin
payload = config_object.read()

# 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':
url = update_qsl(url, {'rsp-subtree': 'modified'})
aci.result['url'] = url

resp, info = fetch_url(module, url, data=payload, method=method.upper(), timeout=timeout, headers=aci.headers)
aci.result['response'] = info['msg']
Expand Down

0 comments on commit a796391

Please sign in to comment.