mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
update aci module util with methods to support check_mode and idempotency (#28089)
This commit is contained in:
parent
1e00f7e56c
commit
c04349088e
1 changed files with 178 additions and 0 deletions
|
@ -242,3 +242,181 @@ class ACIModule(object):
|
||||||
|
|
||||||
if self.result['diff']['before'] != self.result['diff']['after']:
|
if self.result['diff']['before'] != self.result['diff']['after']:
|
||||||
self.result['changed'] = True
|
self.result['changed'] = True
|
||||||
|
|
||||||
|
def delete_config(self):
|
||||||
|
"""
|
||||||
|
This method is used to handle the logic when the modules state is equal to absent. The method only pushes a change if
|
||||||
|
the object exists, and if check_mode is Fasle. A successful change will mark the module as changed.
|
||||||
|
"""
|
||||||
|
self.result['proposed'] = {}
|
||||||
|
|
||||||
|
if not self.result['existing']:
|
||||||
|
return
|
||||||
|
|
||||||
|
elif not self.module.check_mode:
|
||||||
|
resp, info = fetch_url(self.module, self.result['url'],
|
||||||
|
headers=self.headers,
|
||||||
|
method='DELETE',
|
||||||
|
timeout=self.params['timeout'],
|
||||||
|
use_proxy=self.params['use_proxy'])
|
||||||
|
|
||||||
|
self.result['response'] = info['msg']
|
||||||
|
self.result['status'] = info['status']
|
||||||
|
self.result['method'] = 'DELETE'
|
||||||
|
|
||||||
|
# Handle APIC response
|
||||||
|
if info['status'] == 200:
|
||||||
|
self.result['changed'] = True
|
||||||
|
aci_response_json(self.result, resp.read())
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
# APIC error
|
||||||
|
aci_response_json(self.result, info['body'])
|
||||||
|
self.module.fail_json(msg='Request failed: %(error_code)s %(error_text)s' % self.result, **self.result)
|
||||||
|
except KeyError:
|
||||||
|
# Connection error
|
||||||
|
self.module.fail_json(msg='Request failed for %(url)s. %(msg)s' % info)
|
||||||
|
else:
|
||||||
|
self.result['changed'] = True
|
||||||
|
self.result['method'] = 'DELETE'
|
||||||
|
|
||||||
|
def get_diff(self, aci_class):
|
||||||
|
"""
|
||||||
|
This method is used to get the difference between the proposed and existing configurations. Each module
|
||||||
|
should call the get_existing method before this method, and add the proposed config to the module results
|
||||||
|
using the module's config parameters. The new config will added to the self.result dictionary.
|
||||||
|
|
||||||
|
:param aci_class: Type str.
|
||||||
|
This is the root dictionary key for the MO's configuration body, or the ACI class of the MO.
|
||||||
|
"""
|
||||||
|
proposed_config = self.result['proposed'][aci_class]['attributes']
|
||||||
|
proposed_children = self.result['proposed'][aci_class].get('children')
|
||||||
|
if self.result['existing']:
|
||||||
|
existing_config = self.result['existing'][0][aci_class]['attributes']
|
||||||
|
config = {}
|
||||||
|
|
||||||
|
# values are strings, so any diff between proposed and existing can be a straight replace
|
||||||
|
for key, value in proposed_config.items():
|
||||||
|
existing_field = existing_config.get(key)
|
||||||
|
if value != existing_field:
|
||||||
|
config[key] = value
|
||||||
|
|
||||||
|
# add name back to config only if the configs do not match
|
||||||
|
if config:
|
||||||
|
config["name"] = proposed_config["name"]
|
||||||
|
config = {aci_class: {'attributes': config}}
|
||||||
|
|
||||||
|
# compare existing child dictionaries with what is in existing
|
||||||
|
if proposed_children:
|
||||||
|
existing_children = self.result['existing'][0][aci_class].get('children', [])
|
||||||
|
children = [relation for relation in proposed_children if relation not in existing_children]
|
||||||
|
|
||||||
|
if children and config:
|
||||||
|
config[aci_class].update({'children': children})
|
||||||
|
elif children:
|
||||||
|
config = {aci_class: {'attributes': {'name': proposed_config['name']}, 'children': children}}
|
||||||
|
|
||||||
|
else:
|
||||||
|
config = self.result['proposed']
|
||||||
|
|
||||||
|
self.result['config'] = config
|
||||||
|
|
||||||
|
def get_existing(self, filter_string=""):
|
||||||
|
"""
|
||||||
|
This method is used to get the existing object(s) based on the path specified in the module. Each module should
|
||||||
|
build the URL so that if the object's name is supplied, then it will retrieve the configuration for that particular
|
||||||
|
object, but if no name is supplied, then it will retrieve all MOs for the class. Following this method will ensure
|
||||||
|
that this method can be used to supply the existing configuration when using the get_diff method. The response, status,
|
||||||
|
and existing configuration will be added to the self.result dictionary.
|
||||||
|
|
||||||
|
:param filter_string: Type str.
|
||||||
|
The filter to use in order to retrieve the filtered configuration.
|
||||||
|
"""
|
||||||
|
uri = self.result['url'] + filter_string
|
||||||
|
resp, info = fetch_url(self.module, uri,
|
||||||
|
headers=self.headers,
|
||||||
|
method='GET',
|
||||||
|
timeout=self.params['timeout'],
|
||||||
|
use_proxy=self.params['use_proxy'])
|
||||||
|
self.result['response'] = info['msg']
|
||||||
|
self.result['status'] = info['status']
|
||||||
|
self.result['method'] = 'GET'
|
||||||
|
|
||||||
|
# Handle APIC response
|
||||||
|
if info['status'] == 200:
|
||||||
|
self.result['existing'] = json.loads(resp.read())['imdata']
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
# APIC error
|
||||||
|
aci_response_json(self.result, info['body'])
|
||||||
|
self.module.fail_json(msg='Request failed: %(error_code)s %(error_text)s' % self.result, **self.result)
|
||||||
|
except KeyError:
|
||||||
|
# Connection error
|
||||||
|
self.module.fail_json(msg='Request failed for %(url)s. %(msg)s' % info)
|
||||||
|
|
||||||
|
def payload(self, aci_class, class_config, child_configs=None):
|
||||||
|
"""
|
||||||
|
This method is used to dynamically build the proposed configuration dictionary from the config related parameters
|
||||||
|
passed into the module. All values that were not passed values from the playbook task will be removed so as to not
|
||||||
|
inadvertently change configurations.
|
||||||
|
|
||||||
|
:param aci_class: Type str
|
||||||
|
This is the root dictionary key for the MO's configuration body, or the ACI class of the MO.
|
||||||
|
:param class_config: Type dict
|
||||||
|
This is the configuration of the MO using the dictionary keys expected by the API
|
||||||
|
:param child_configs: Type list
|
||||||
|
This is a list of child dictionaries associated with the MOs config. The list should only
|
||||||
|
include child objects that are used to associate two MOs together. Children that represent
|
||||||
|
MOs should have their own module.
|
||||||
|
"""
|
||||||
|
proposed = dict((k, v) for k, v in class_config.items() if v)
|
||||||
|
self.result['proposed'] = {aci_class: {'attributes': proposed}}
|
||||||
|
|
||||||
|
# add child objects to proposed
|
||||||
|
if child_configs:
|
||||||
|
children = []
|
||||||
|
for child in child_configs:
|
||||||
|
for root_key in child.keys():
|
||||||
|
for final_keys, values in child[root_key]['attributes'].items():
|
||||||
|
if values is not None:
|
||||||
|
children.append(child)
|
||||||
|
break
|
||||||
|
|
||||||
|
if children:
|
||||||
|
self.result['proposed'][aci_class].update(dict(children=children))
|
||||||
|
|
||||||
|
def post_config(self):
|
||||||
|
"""
|
||||||
|
This method is used to handle the logic when the modules state is equal to present. The method only pushes a change if
|
||||||
|
the object has differences than what exists on the APIC, and if check_mode is Fasle. A successful change will mark the
|
||||||
|
module as changed.
|
||||||
|
"""
|
||||||
|
if not self.result['config']:
|
||||||
|
return
|
||||||
|
elif not self.module.check_mode:
|
||||||
|
resp, info = fetch_url(self.module, self.result['url'],
|
||||||
|
data=json.dumps(self.result['config']),
|
||||||
|
headers=self.headers,
|
||||||
|
method='POST',
|
||||||
|
timeout=self.params['timeout'],
|
||||||
|
use_proxy=self.params['use_proxy'])
|
||||||
|
|
||||||
|
self.result['response'] = info['msg']
|
||||||
|
self.result['status'] = info['status']
|
||||||
|
self.result['method'] = 'POST'
|
||||||
|
|
||||||
|
# Handle APIC response
|
||||||
|
if info['status'] == 200:
|
||||||
|
self.result['changed'] = True
|
||||||
|
aci_response_json(self.result, resp.read())
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
# APIC error
|
||||||
|
aci_response_json(self.result, info['body'])
|
||||||
|
self.module.fail_json(msg='Request failed: %(error_code)s %(error_text)s' % self.result, **self.result)
|
||||||
|
except KeyError:
|
||||||
|
# Connection error
|
||||||
|
self.module.fail_json(msg='Request failed for %(url)s. %(msg)s' % info)
|
||||||
|
else:
|
||||||
|
self.result['changed'] = True
|
||||||
|
self.result['method'] = 'POST'
|
||||||
|
|
Loading…
Reference in a new issue