mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
aci_rest: Implement idempotency
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
This commit is contained in:
parent
6108b46022
commit
a796391c9b
1 changed files with 72 additions and 16 deletions
|
@ -70,6 +70,25 @@ EXAMPLES = r'''
|
||||||
src: /home/cisco/ansible/aci/configs/aci_config.xml
|
src: /home/cisco/ansible/aci/configs/aci_config.xml
|
||||||
delegate_to: localhost
|
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
|
- name: Get tenants
|
||||||
aci_rest:
|
aci_rest:
|
||||||
hostname: '{{ inventory_hostname }}'
|
hostname: '{{ inventory_hostname }}'
|
||||||
|
@ -161,10 +180,21 @@ totalCount:
|
||||||
returned: always
|
returned: always
|
||||||
type: string
|
type: string
|
||||||
sample: '0'
|
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
|
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
|
# Optional, only used for XML payload
|
||||||
try:
|
try:
|
||||||
import lxml.etree
|
import lxml.etree
|
||||||
|
@ -186,15 +216,50 @@ from ansible.module_utils.basic import AnsibleModule
|
||||||
from ansible.module_utils.urls import fetch_url
|
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'):
|
def aci_response(result, rawoutput, rest_type='xml'):
|
||||||
''' Handle APIC response output '''
|
''' Handle APIC response output '''
|
||||||
|
|
||||||
if rest_type == 'json':
|
if rest_type == 'json':
|
||||||
aci_response_json(result, rawoutput)
|
aci_response_json(result, rawoutput)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
aci_response_xml(result, rawoutput)
|
aci_response_xml(result, rawoutput)
|
||||||
|
|
||||||
|
# Use APICs built-in idempotency
|
||||||
|
if HAS_URLPARSE:
|
||||||
|
result['changed'] = aci_changed(result)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
argument_spec = aci_argument_spec
|
argument_spec = aci_argument_spec
|
||||||
|
@ -208,7 +273,6 @@ def main():
|
||||||
module = AnsibleModule(
|
module = AnsibleModule(
|
||||||
argument_spec=argument_spec,
|
argument_spec=argument_spec,
|
||||||
mutually_exclusive=[['content', 'src']],
|
mutually_exclusive=[['content', 'src']],
|
||||||
supports_check_mode=True,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
path = module.params['path']
|
path = module.params['path']
|
||||||
|
@ -240,26 +304,18 @@ def main():
|
||||||
|
|
||||||
aci = ACIModule(module)
|
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
|
# We include the payload as it may be templated
|
||||||
payload = content
|
payload = content
|
||||||
elif file_exists:
|
if file_exists:
|
||||||
with open(src, 'r') as config_object:
|
with open(src, 'r') as config_object:
|
||||||
# TODO: Would be nice to template this, requires action-plugin
|
# TODO: Would be nice to template this, requires action-plugin
|
||||||
payload = config_object.read()
|
payload = config_object.read()
|
||||||
|
|
||||||
# Perform actual request using auth cookie (Same as aci_request,but also supports XML)
|
# Perform actual request using auth cookie (Same as aci_request,but also supports XML)
|
||||||
url = '%(protocol)s://%(hostname)s/' % aci.params + path.lstrip('/')
|
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)
|
resp, info = fetch_url(module, url, data=payload, method=method.upper(), timeout=timeout, headers=aci.headers)
|
||||||
aci.result['response'] = info['msg']
|
aci.result['response'] = info['msg']
|
||||||
|
|
Loading…
Reference in a new issue