From 5bdcaff92167aa78cf5792bf93c1bd59f0e52aaa Mon Sep 17 00:00:00 2001 From: Tim Rupp Date: Wed, 29 Aug 2018 16:08:37 -0700 Subject: [PATCH] Various f5 fixes (#44858) * Remove sdk from modules * Correct IP address bugs in data_group * Correct compare_dictionary bug in several modules --- lib/ansible/module_utils/network/f5/common.py | 42 +-- .../modules/network/f5/bigip_data_group.py | 89 +++---- .../network/f5/bigip_gtm_monitor_external.py | 2 +- .../network/f5/bigip_gtm_virtual_server.py | 12 +- .../network/f5/bigip_monitor_external.py | 246 ++++++++++-------- .../modules/network/f5/bigip_monitor_tcp.py | 6 - .../modules/network/f5/bigip_monitor_udp.py | 244 ++++++++++------- .../modules/network/f5/bigip_timer_policy.py | 6 +- 8 files changed, 361 insertions(+), 286 deletions(-) diff --git a/lib/ansible/module_utils/network/f5/common.py b/lib/ansible/module_utils/network/f5/common.py index 3c5ee73984b..e330484c26f 100644 --- a/lib/ansible/module_utils/network/f5/common.py +++ b/lib/ansible/module_utils/network/f5/common.py @@ -335,28 +335,34 @@ def transform_name(partition='', name='', sub_path=''): return result -def dict2tuple(items): - """Convert a dictionary to a list of tuples +def compare_complex_list(want, have): + """Performs a complex list comparison - This method is used in cases where dictionaries need to be compared. Due - to dictionaries inherently having no order, it is easier to compare list - of tuples because these lists can be converted to sets. - - This conversion only supports dicts of simple values. Do not give it dicts - that contain sub-dicts. This will not give you the result you want when using - the returned tuple for comparison. + A complex list is a list of dictionaries Args: - items (dict): The dictionary of items that should be converted + want (list): List of dictionaries to compare with second parameter. + have (list): List of dictionaries compare with first parameter. Returns: - list: Returns a list of tuples upon success. Otherwise, an empty list. + bool: """ - result = [] - for x in items: + if want == [] and have is None: + return None + if want is None: + return None + w = [] + h = [] + for x in want: tmp = [(str(k), str(v)) for k, v in iteritems(x)] - result += tmp - return result + w += tmp + for x in have: + tmp = [(str(k), str(v)) for k, v in iteritems(x)] + h += tmp + if set(w) == set(h): + return None + else: + return want def compare_dictionary(want, have): @@ -369,12 +375,12 @@ def compare_dictionary(want, have): Returns: bool: """ - if want == [] and have is None: + if want == {} and have is None: return None if want is None: return None - w = dict2tuple(want) - h = dict2tuple(have) + w = [(str(k), str(v)) for k, v in iteritems(want)] + h = [(str(k), str(v)) for k, v in iteritems(have)] if set(w) == set(h): return None else: diff --git a/lib/ansible/modules/network/f5/bigip_data_group.py b/lib/ansible/modules/network/f5/bigip_data_group.py index 5e3e59a9581..4fb605f68b7 100644 --- a/lib/ansible/modules/network/f5/bigip_data_group.py +++ b/lib/ansible/modules/network/f5/bigip_data_group.py @@ -265,7 +265,7 @@ try: from library.module_utils.network.f5.common import F5ModuleError from library.module_utils.network.f5.common import AnsibleF5Parameters from library.module_utils.network.f5.common import cleanup_tokens - from library.module_utils.network.f5.common import compare_dictionary + from library.module_utils.network.f5.common import compare_complex_list from library.module_utils.network.f5.common import f5_argument_spec from library.module_utils.network.f5.ipaddress import is_valid_ip from library.module_utils.network.f5.ipaddress import is_valid_ip_network @@ -283,7 +283,7 @@ except ImportError: from ansible.module_utils.network.f5.common import F5ModuleError from ansible.module_utils.network.f5.common import AnsibleF5Parameters from ansible.module_utils.network.f5.common import cleanup_tokens - from ansible.module_utils.network.f5.common import compare_dictionary + from ansible.module_utils.network.f5.common import compare_complex_list from ansible.module_utils.network.f5.common import f5_argument_spec from ansible.module_utils.network.f5.ipaddress import is_valid_ip from ansible.module_utils.network.f5.ipaddress import is_valid_ip_network @@ -351,33 +351,24 @@ class RecordsEncoder(object): return self.encode_string_from_dict(record) def encode_address_from_dict(self, record): - if is_valid_ip_network(record['key']): - key = ip_network(u"{0}".format(str(record['key']))) - elif is_valid_ip(record['key']): - key = ip_address(u"{0}".format(str(record['key']))) - elif is_valid_ip_interface(record['key']): + if is_valid_ip_interface(record['key']): key = ip_interface(u"{0}".format(str(record['key']))) else: raise F5ModuleError( "When specifying an 'address' type, the value to the left of the separator must be an IP." ) if key and 'value' in record: - try: - # Only ip_address's have max_prefixlen - if key.max_prefixlen in [32, 128]: - return self.encode_host(str(key), record['value']) - except ValueError: - return self.encode_network( - str(key.network_address), key.prefixlen, record['value']) + if key.network.prefixlen in [32, 128]: + return self.encode_host(str(key.ip), record['value']) + return self.encode_network( + str(key.network.network_address), key.network.prefixlen, record['value'] + ) elif key: - try: - # Only ip_address's have max_prefixlen - if key.max_prefixlen in [32, 128]: - return self.encode_host(str(key), str(key)) - except ValueError: - return self.encode_network( - str(key.network_address), key.prefixlen, str(key.network_address) - ) + if key.network.prefixlen in [32, 128]: + return self.encode_host(str(key.ip), str(key.ip)) + return self.encode_network( + str(key.network.network_address), key.network.prefixlen, str(key.network.network_address) + ) def encode_integer_from_dict(self, record): try: @@ -418,37 +409,27 @@ class RecordsEncoder(object): else: # 192.168.0.0/16 := "Network3", # 2402:9400:1000:0::/64 := "Network4", - parts = record.split(self._separator) - if is_valid_ip_network(parts[0]): - key = ip_network(u"{0}".format(str(parts[0]))) - elif is_valid_ip(parts[0]): - key = ip_address(u"{0}".format(str(parts[0]))) - elif is_valid_ip_interface(parts[0]): - key = ip_interface(u"{0}".format(str(parts[0]))) - elif parts[0] == '': - pass - else: - raise F5ModuleError( - "When specifying an 'address' type, the value to the left of the separator must be an IP." - ) - - if len(parts) == 2: - try: - # Only ip_address's have max_prefixlen - if key.max_prefixlen in [32, 128]: - return self.encode_host(str(key), parts[1]) - except ValueError: - return self.encode_network( - str(key.network_address), key.prefixlen, parts[1]) - elif len(parts) == 1 and parts[0] != '': - try: - # Only ip_address's have max_prefixlen - if key.max_prefixlen in [32, 128]: - return self.encode_host(str(key), str(key)) - except ValueError: - return self.encode_network( - str(key.network_address), key.prefixlen, str(key.network_address) - ) + parts = record.split(self._separator) + if parts[0] == '': + return + if not is_valid_ip_interface(parts[0]): + raise F5ModuleError( + "When specifying an 'address' type, the value to the left of the separator must be an IP." + ) + key = ip_interface(u"{0}".format(str(parts[0]))) + + if len(parts) == 2: + if key.network.prefixlen in [32, 128]: + return self.encode_host(str(key.ip), parts[1]) + return self.encode_network( + str(key.network.network_address), key.network.prefixlen, parts[1] + ) + elif len(parts) == 1 and parts[0] != '': + if key.network.prefixlen in [32, 128]: + return self.encode_host(str(key.ip), str(key.ip)) + return self.encode_network( + str(key.network.network_address), key.network.prefixlen, str(key.network.network_address) + ) def encode_host(self, key, value): return 'host {0} {1} {2}'.format(str(key), self._separator, str(value)) @@ -706,7 +687,7 @@ class Difference(object): return None if self.have.records is None: return self.want.records - result = compare_dictionary(self.want.records, self.have.records) + result = compare_complex_list(self.want.records, self.have.records) return result @property diff --git a/lib/ansible/modules/network/f5/bigip_gtm_monitor_external.py b/lib/ansible/modules/network/f5/bigip_gtm_monitor_external.py index a3d5b235c5d..8cda980066c 100644 --- a/lib/ansible/modules/network/f5/bigip_gtm_monitor_external.py +++ b/lib/ansible/modules/network/f5/bigip_gtm_monitor_external.py @@ -415,7 +415,7 @@ class Difference(object): ) result = dict() - different = compare_dictionary([self.want.variables], [self.have.variables]) + different = compare_dictionary(self.want.variables, self.have.variables) if not different: return None diff --git a/lib/ansible/modules/network/f5/bigip_gtm_virtual_server.py b/lib/ansible/modules/network/f5/bigip_gtm_virtual_server.py index 8dc4118e5e3..88560b41b18 100644 --- a/lib/ansible/modules/network/f5/bigip_gtm_virtual_server.py +++ b/lib/ansible/modules/network/f5/bigip_gtm_virtual_server.py @@ -244,13 +244,14 @@ from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import env_fallback try: + from library.module_utils.compat.ipaddress import ip_address from library.module_utils.network.f5.bigip import HAS_F5SDK from library.module_utils.network.f5.bigip import F5Client from library.module_utils.network.f5.common import F5ModuleError from library.module_utils.network.f5.common import AnsibleF5Parameters from library.module_utils.network.f5.common import cleanup_tokens from library.module_utils.network.f5.common import fq_name - from library.module_utils.network.f5.common import compare_dictionary + from library.module_utils.network.f5.common import compare_complex_list from library.module_utils.network.f5.common import f5_argument_spec from library.module_utils.network.f5.ipaddress import is_valid_ip from library.module_utils.network.f5.ipaddress import validate_ip_v6_address @@ -260,13 +261,14 @@ try: except ImportError: HAS_F5SDK = False except ImportError: + from ansible.module_utils.compat.ipaddress import ip_address from ansible.module_utils.network.f5.bigip import HAS_F5SDK from ansible.module_utils.network.f5.bigip import F5Client from ansible.module_utils.network.f5.common import F5ModuleError from ansible.module_utils.network.f5.common import AnsibleF5Parameters from ansible.module_utils.network.f5.common import cleanup_tokens from ansible.module_utils.network.f5.common import fq_name - from ansible.module_utils.network.f5.common import compare_dictionary + from ansible.module_utils.network.f5.common import compare_complex_list from ansible.module_utils.network.f5.common import f5_argument_spec from ansible.module_utils.network.f5.ipaddress import is_valid_ip from ansible.module_utils.network.f5.ipaddress import validate_ip_v6_address @@ -534,7 +536,8 @@ class ModuleParameters(Parameters): if self._values['address'] is None: return None if is_valid_ip(self._values['address']): - return self._values['address'] + ip = str(ip_address(u'{0}'.format(self._values['address']))) + return ip raise F5ModuleError( "Specified 'address' is not an IP address." ) @@ -758,7 +761,8 @@ class Difference(object): return None if self.want.virtual_server_dependencies is None: return None - return compare_dictionary(self.want.virtual_server_dependencies, self.have.virtual_server_dependencies) + result = compare_complex_list(self.want.virtual_server_dependencies, self.have.virtual_server_dependencies) + return result @property def enabled(self): diff --git a/lib/ansible/modules/network/f5/bigip_monitor_external.py b/lib/ansible/modules/network/f5/bigip_monitor_external.py index efeec55bbff..108fa2eaf8e 100644 --- a/lib/ansible/modules/network/f5/bigip_monitor_external.py +++ b/lib/ansible/modules/network/f5/bigip_monitor_external.py @@ -7,7 +7,6 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type - ANSIBLE_METADATA = {'metadata_version': '1.1', 'status': ['preview'], 'supported_by': 'community'} @@ -159,7 +158,6 @@ timeout: sample: 10 ''' -import os import re from ansible.module_utils.basic import AnsibleModule @@ -167,33 +165,29 @@ from ansible.module_utils.basic import env_fallback from ansible.module_utils.six import iteritems try: - from library.module_utils.network.f5.bigip import HAS_F5SDK - from library.module_utils.network.f5.bigip import F5Client + from library.module_utils.network.f5.bigip import F5RestClient from library.module_utils.network.f5.common import F5ModuleError from library.module_utils.network.f5.common import AnsibleF5Parameters from library.module_utils.network.f5.common import cleanup_tokens from library.module_utils.network.f5.common import fq_name from library.module_utils.network.f5.common import f5_argument_spec + from library.module_utils.network.f5.common import transform_name + from library.module_utils.network.f5.common import exit_json + from library.module_utils.network.f5.common import fail_json from library.module_utils.network.f5.common import compare_dictionary from library.module_utils.network.f5.ipaddress import is_valid_ip - try: - from library.module_utils.network.f5.common import iControlUnexpectedHTTPError - except ImportError: - HAS_F5SDK = False except ImportError: - from ansible.module_utils.network.f5.bigip import HAS_F5SDK - from ansible.module_utils.network.f5.bigip import F5Client + from ansible.module_utils.network.f5.bigip import F5RestClient from ansible.module_utils.network.f5.common import F5ModuleError from ansible.module_utils.network.f5.common import AnsibleF5Parameters from ansible.module_utils.network.f5.common import cleanup_tokens from ansible.module_utils.network.f5.common import fq_name from ansible.module_utils.network.f5.common import f5_argument_spec + from ansible.module_utils.network.f5.common import transform_name from ansible.module_utils.network.f5.common import compare_dictionary + from ansible.module_utils.network.f5.common import exit_json + from ansible.module_utils.network.f5.common import fail_json from ansible.module_utils.network.f5.ipaddress import is_valid_ip - try: - from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError - except ImportError: - HAS_F5SDK = False class Parameters(AnsibleF5Parameters): @@ -201,33 +195,24 @@ class Parameters(AnsibleF5Parameters): 'defaultsFrom': 'parent', 'apiRawValues': 'variables', 'run': 'external_program', - 'args': 'arguments' + 'args': 'arguments', } api_attributes = [ - 'defaultsFrom', 'interval', 'timeout', 'destination', 'run', 'args', 'description' + 'defaultsFrom', 'interval', 'timeout', 'destination', 'run', 'args', + 'description', ] returnables = [ 'parent', 'ip', 'port', 'interval', 'timeout', 'variables', 'external_program', - 'arguments', 'description' + 'arguments', 'description', ] updatables = [ 'destination', 'interval', 'timeout', 'variables', 'external_program', - 'arguments', 'description' + 'arguments', 'description', ] - def to_return(self): - result = {} - try: - for returnable in self.returnables: - result[returnable] = getattr(self, returnable) - result = self._filter_params(result) - except Exception: - pass - return result - @property def destination(self): if self.ip is None and self.port is None: @@ -285,11 +270,7 @@ class Parameters(AnsibleF5Parameters): def parent(self): if self._values['parent'] is None: return None - if self._values['parent'].startswith('/'): - parent = os.path.basename(self._values['parent']) - result = '/{0}/{1}'.format(self.partition, parent) - else: - result = '/{0}/{1}'.format(self.partition, self._values['parent']) + result = fq_name(self.partition, self._values['parent']) return result @property @@ -426,8 +407,7 @@ class Difference(object): variables=self.want.variables ) result = dict() - - different = compare_dictionary([self.want.variables], [self.have.variables]) + different = compare_dictionary(self.want.variables, self.have.variables) if not different: return None @@ -491,13 +471,10 @@ class ModuleManager(object): result = dict() state = self.want.state - try: - if state == "present": - changed = self.present() - elif state == "absent": - changed = self.absent() - except iControlUnexpectedHTTPError as e: - raise F5ModuleError(str(e)) + if state == "present": + changed = self.present() + elif state == "absent": + changed = self.absent() reportable = ReportableChanges(params=self.changes.to_return()) changes = reportable.to_return() @@ -520,19 +497,19 @@ class ModuleManager(object): else: return self.create() - def create(self): - self._set_changed_options() - if self.want.timeout is None: - self.want.update({'timeout': 16}) - if self.want.interval is None: - self.want.update({'interval': 5}) - if self.want.ip is None: - self.want.update({'ip': '*'}) - if self.want.port is None: - self.want.update({'port': '*'}) - if self.module.check_mode: - return True - self.create_on_device() + def exists(self): + uri = "https://{0}:{1}/mgmt/tm/ltm/monitor/external/{2}".format( + self.client.provider['server'], + self.client.provider['server_port'], + transform_name(self.want.partition, self.want.name) + ) + resp = self.client.api.get(uri) + try: + response = resp.json() + except ValueError: + return False + if resp.status == 404 or 'code' in response and response['code'] == 404: + return False return True def update(self): @@ -544,70 +521,132 @@ class ModuleManager(object): self.update_on_device() return True - def absent(self): - if self.exists(): - return self.remove() - return False - def remove(self): if self.module.check_mode: return True self.remove_from_device() if self.exists(): - raise F5ModuleError("Failed to delete the monitor.") + raise F5ModuleError("Failed to delete the resource.") return True - def read_current_from_device(self): - resource = self.client.api.tm.ltm.monitor.externals.external.load( - name=self.want.name, - partition=self.want.partition - ) - result = resource.attrs - return ApiParameters(params=result) + def create(self): + self._set_changed_options() + self._set_default_creation_values() + if self.module.check_mode: + return True + self.create_on_device() + return True - def exists(self): - result = self.client.api.tm.ltm.monitor.externals.external.exists( - name=self.want.name, - partition=self.want.partition - ) - return result + def _set_default_creation_values(self): + if self.want.timeout is None: + self.want.update({'timeout': 16}) + if self.want.interval is None: + self.want.update({'interval': 5}) + if self.want.ip is None: + self.want.update({'ip': '*'}) + if self.want.port is None: + self.want.update({'port': '*'}) - def update_on_device(self): + def create_on_device(self): params = self.changes.api_params() - result = self.client.api.tm.ltm.monitor.externals.external.load( - name=self.want.name, - partition=self.want.partition + params['name'] = self.want.name + params['partition'] = self.want.partition + uri = "https://{0}:{1}/mgmt/tm/ltm/monitor/external/".format( + self.client.provider['server'], + self.client.provider['server_port'] ) - if params: - result.modify(**params) - if self.changes.variables: - self.set_variable_on_device(self.changes.variables) + resp = self.client.api.post(uri, json=params) + try: + response = resp.json() + except ValueError as ex: + raise F5ModuleError(str(ex)) + + if 'code' in response and response['code'] in [400, 403]: + if 'message' in response: + raise F5ModuleError(response['message']) + else: + raise F5ModuleError(resp.content) + + if self.want.variables: + self.set_variable_on_device(self.want.variables) def set_variable_on_device(self, commands): command = ' '.join(['user-defined {0} \\\"{1}\\\"'.format(k, v) for k, v in iteritems(commands)]) command = 'tmsh modify ltm monitor external {0} {1}'.format(self.want.name, command) - self.client.api.tm.util.bash.exec_cmd( - 'run', + uri = "https://{0}:{1}/mgmt/tm/util/bash".format( + self.client.provider['server'], + self.client.provider['server_port'], + ) + args = dict( + command='run', utilCmdArgs='-c "{0}"'.format(command) ) + resp = self.client.api.post(uri, json=args) + try: + response = resp.json() + except ValueError as ex: + raise F5ModuleError(str(ex)) + if 'code' in response and response['code'] == 400: + if 'message' in response: + raise F5ModuleError(response['message']) + else: + raise F5ModuleError(resp.content) - def create_on_device(self): - params = self.want.api_params() - self.client.api.tm.ltm.monitor.externals.external.create( - name=self.want.name, - partition=self.want.partition, - **params - ) - if self.want.variables: - self.set_variable_on_device(self.want.variables) + def update_on_device(self): + params = self.changes.api_params() + if params: + uri = "https://{0}:{1}/mgmt/tm/ltm/monitor/external/{2}".format( + self.client.provider['server'], + self.client.provider['server_port'], + transform_name(self.want.partition, self.want.name) + ) + resp = self.client.api.patch(uri, json=params) + try: + response = resp.json() + except ValueError as ex: + raise F5ModuleError(str(ex)) + + if 'code' in response and response['code'] == 400: + if 'message' in response: + raise F5ModuleError(response['message']) + else: + raise F5ModuleError(resp.content) + if self.changes.variables: + self.set_variable_on_device(self.changes.variables) + + def absent(self): + if self.exists(): + return self.remove() + return False def remove_from_device(self): - result = self.client.api.tm.ltm.monitor.externals.external.load( - name=self.want.name, - partition=self.want.partition + uri = "https://{0}:{1}/mgmt/tm/ltm/monitor/external/{2}".format( + self.client.provider['server'], + self.client.provider['server_port'], + transform_name(self.want.partition, self.want.name) ) - if result: - result.delete() + resp = self.client.api.delete(uri) + if resp.status == 200: + return True + + def read_current_from_device(self): + uri = "https://{0}:{1}/mgmt/tm/ltm/monitor/external/{2}".format( + self.client.provider['server'], + self.client.provider['server_port'], + transform_name(self.want.partition, self.want.name) + ) + resp = self.client.api.get(uri) + try: + response = resp.json() + except ValueError as ex: + raise F5ModuleError(str(ex)) + + if 'code' in response and response['code'] == 400: + if 'message' in response: + raise F5ModuleError(response['message']) + else: + raise F5ModuleError(resp.content) + return ApiParameters(params=response) class ArgumentSpec(object): @@ -643,20 +682,19 @@ def main(): module = AnsibleModule( argument_spec=spec.argument_spec, - supports_check_mode=spec.supports_check_mode + supports_check_mode=spec.supports_check_mode, ) - if not HAS_F5SDK: - module.fail_json(msg="The python f5-sdk module is required") + + client = F5RestClient(**module.params) try: - client = F5Client(**module.params) mm = ModuleManager(module=module, client=client) results = mm.exec_module() cleanup_tokens(client) - module.exit_json(**results) + exit_json(module, results, client) except F5ModuleError as ex: cleanup_tokens(client) - module.fail_json(msg=str(ex)) + fail_json(module, ex, client) if __name__ == '__main__': diff --git a/lib/ansible/modules/network/f5/bigip_monitor_tcp.py b/lib/ansible/modules/network/f5/bigip_monitor_tcp.py index 07d475a16a2..cabfc740fe7 100644 --- a/lib/ansible/modules/network/f5/bigip_monitor_tcp.py +++ b/lib/ansible/modules/network/f5/bigip_monitor_tcp.py @@ -478,12 +478,6 @@ class ModuleManager(object): self.update_on_device() return True - def should_update(self): - result = self._update_changed_options() - if result: - return True - return False - def remove(self): if self.module.check_mode: return True diff --git a/lib/ansible/modules/network/f5/bigip_monitor_udp.py b/lib/ansible/modules/network/f5/bigip_monitor_udp.py index 6769dffb71e..6d5d096d4c5 100644 --- a/lib/ansible/modules/network/f5/bigip_monitor_udp.py +++ b/lib/ansible/modules/network/f5/bigip_monitor_udp.py @@ -155,69 +155,57 @@ time_until_up: sample: 2 ''' -import os from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import env_fallback try: - from library.module_utils.network.f5.bigip import HAS_F5SDK - from library.module_utils.network.f5.bigip import F5Client + from library.module_utils.network.f5.bigip import F5RestClient from library.module_utils.network.f5.common import F5ModuleError from library.module_utils.network.f5.common import AnsibleF5Parameters from library.module_utils.network.f5.common import cleanup_tokens + from library.module_utils.network.f5.common import fq_name from library.module_utils.network.f5.common import f5_argument_spec + from library.module_utils.network.f5.common import transform_name + from library.module_utils.network.f5.common import exit_json + from library.module_utils.network.f5.common import fail_json from library.module_utils.network.f5.ipaddress import is_valid_ip - try: - from library.module_utils.network.f5.common import iControlUnexpectedHTTPError - except ImportError: - HAS_F5SDK = False + except ImportError: - from ansible.module_utils.network.f5.bigip import HAS_F5SDK - from ansible.module_utils.network.f5.bigip import F5Client + from ansible.module_utils.network.f5.bigip import F5RestClient from ansible.module_utils.network.f5.common import F5ModuleError from ansible.module_utils.network.f5.common import AnsibleF5Parameters from ansible.module_utils.network.f5.common import cleanup_tokens + from ansible.module_utils.network.f5.common import fq_name from ansible.module_utils.network.f5.common import f5_argument_spec + from ansible.module_utils.network.f5.common import transform_name + from ansible.module_utils.network.f5.common import exit_json + from ansible.module_utils.network.f5.common import fail_json from ansible.module_utils.network.f5.ipaddress import is_valid_ip - try: - from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError - except ImportError: - HAS_F5SDK = False class Parameters(AnsibleF5Parameters): api_map = { 'timeUntilUp': 'time_until_up', 'defaultsFrom': 'parent', - 'recv': 'receive' + 'recv': 'receive', } api_attributes = [ 'timeUntilUp', 'defaultsFrom', 'interval', 'timeout', 'recv', 'send', - 'destination', 'description' + 'destination', 'description', ] returnables = [ 'parent', 'send', 'receive', 'ip', 'port', 'interval', 'timeout', - 'time_until_up', 'description' + 'time_until_up', 'description', ] updatables = [ 'destination', 'send', 'receive', 'interval', 'timeout', 'time_until_up', - 'description' + 'description', ] - def to_return(self): - result = {} - try: - for returnable in self.returnables: - result[returnable] = getattr(self, returnable) - result = self._filter_params(result) - except Exception: - pass - return result - @property def destination(self): if self.ip is None and self.port is None: @@ -281,11 +269,7 @@ class Parameters(AnsibleF5Parameters): def parent(self): if self._values['parent'] is None: return None - if self._values['parent'].startswith('/'): - parent = os.path.basename(self._values['parent']) - result = '/{0}/{1}'.format(self.partition, parent) - else: - result = '/{0}/{1}'.format(self.partition, self._values['parent']) + result = fq_name(self.partition, self._values['parent']) return result @property @@ -293,7 +277,31 @@ class Parameters(AnsibleF5Parameters): return 'udp' +class ApiParameters(Parameters): + pass + + +class ModuleParameters(Parameters): + pass + + class Changes(Parameters): + def to_return(self): + result = {} + try: + for returnable in self.returnables: + result[returnable] = getattr(self, returnable) + result = self._filter_params(result) + except Exception: + pass + return result + + +class UsableChanges(Changes): + pass + + +class ReportableChanges(Changes): pass @@ -368,9 +376,9 @@ class ModuleManager(object): def __init__(self, *args, **kwargs): self.module = kwargs.get('module', None) self.client = kwargs.get('client', None) - self.have = None - self.want = Parameters(params=self.module.params) - self.changes = Changes() + self.want = ModuleParameters(params=self.module.params) + self.have = ApiParameters() + self.changes = UsableChanges() def _set_changed_options(self): changed = {} @@ -378,7 +386,7 @@ class ModuleManager(object): if getattr(self.want, key) is not None: changed[key] = getattr(self.want, key) if changed: - self.changes = Changes(params=changed) + self.changes = UsableChanges(params=changed) def _update_changed_options(self): diff = Difference(self.want, self.have) @@ -394,7 +402,7 @@ class ModuleManager(object): else: changed[k] = change if changed: - self.changes = Changes(params=changed) + self.changes = UsableChanges(params=changed) return True return False @@ -409,15 +417,13 @@ class ModuleManager(object): result = dict() state = self.want.state - try: - if state == "present": - changed = self.present() - elif state == "absent": - changed = self.absent() - except iControlUnexpectedHTTPError as e: - raise F5ModuleError(str(e)) - - changes = self.changes.to_return() + if state == "present": + changed = self.present() + elif state == "absent": + changed = self.absent() + + reportable = ReportableChanges(params=self.changes.to_return()) + changes = reportable.to_return() result.update(**changes) result.update(dict(changed=changed)) self._announce_deprecations(result) @@ -426,7 +432,7 @@ class ModuleManager(object): def _announce_deprecations(self, result): warnings = result.pop('__warnings', []) for warning in warnings: - self.module.deprecate( + self.client.module.deprecate( msg=warning['msg'], version=warning['version'] ) @@ -437,31 +443,20 @@ class ModuleManager(object): else: return self.create() - def create(self): - self._set_changed_options() - if self.want.timeout is None: - self.want.update({'timeout': 16}) - if self.want.interval is None: - self.want.update({'interval': 5}) - if self.want.time_until_up is None: - self.want.update({'time_until_up': 0}) - if self.want.ip is None: - self.want.update({'ip': '*'}) - if self.want.port is None: - self.want.update({'port': '*'}) - if self.want.send is None: - self.want.update({'send': 'default send string'}) - if self.module.check_mode: - return True - self.create_on_device() - return True - def exists(self): - result = self.client.api.tm.ltm.monitor.udps.udp.exists( - name=self.want.name, - partition=self.want.partition + uri = "https://{0}:{1}/mgmt/tm/ltm/monitor/udp/{2}".format( + self.client.provider['server'], + self.client.provider['server_port'], + transform_name(self.want.partition, self.want.name) ) - return result + resp = self.client.api.get(uri) + try: + response = resp.json() + except ValueError: + return False + if resp.status == 404 or 'code' in response and response['code'] == 404: + return False + return True def update(self): self.have = self.read_current_from_device() @@ -480,21 +475,66 @@ class ModuleManager(object): raise F5ModuleError("Failed to delete the resource.") return True + def create(self): + self._set_changed_options() + self._set_default_creation_values() + if self.module.check_mode: + return True + self.create_on_device() + return True + + def _set_default_creation_values(self): + if self.want.timeout is None: + self.want.update({'timeout': 16}) + if self.want.interval is None: + self.want.update({'interval': 5}) + if self.want.time_until_up is None: + self.want.update({'time_until_up': 0}) + if self.want.ip is None: + self.want.update({'ip': '*'}) + if self.want.port is None: + self.want.update({'port': '*'}) + if self.want.send is None: + self.want.update({'send': 'default send string'}) + def create_on_device(self): - params = self.want.api_params() - self.client.api.tm.ltm.monitor.udps.udp.create( - name=self.want.name, - partition=self.want.partition, - **params + params = self.changes.api_params() + params['name'] = self.want.name + params['partition'] = self.want.partition + uri = "https://{0}:{1}/mgmt/tm/ltm/monitor/udp/".format( + self.client.provider['server'], + self.client.provider['server_port'] ) + resp = self.client.api.post(uri, json=params) + try: + response = resp.json() + except ValueError as ex: + raise F5ModuleError(str(ex)) + + if 'code' in response and response['code'] in [400, 403]: + if 'message' in response: + raise F5ModuleError(response['message']) + else: + raise F5ModuleError(resp.content) def update_on_device(self): - params = self.want.api_params() - resource = self.client.api.tm.ltm.monitor.udps.udp.load( - name=self.want.name, - partition=self.want.partition + params = self.changes.api_params() + uri = "https://{0}:{1}/mgmt/tm/ltm/monitor/udp/{2}".format( + self.client.provider['server'], + self.client.provider['server_port'], + transform_name(self.want.partition, self.want.name) ) - resource.modify(**params) + resp = self.client.api.patch(uri, json=params) + try: + response = resp.json() + except ValueError as ex: + raise F5ModuleError(str(ex)) + + if 'code' in response and response['code'] == 400: + if 'message' in response: + raise F5ModuleError(response['message']) + else: + raise F5ModuleError(resp.content) def absent(self): if self.exists(): @@ -502,20 +542,33 @@ class ModuleManager(object): return False def remove_from_device(self): - resource = self.client.api.tm.ltm.monitor.udps.udp.load( - name=self.want.name, - partition=self.want.partition + uri = "https://{0}:{1}/mgmt/tm/ltm/monitor/udp/{2}".format( + self.client.provider['server'], + self.client.provider['server_port'], + transform_name(self.want.partition, self.want.name) ) - if resource: - resource.delete() + resp = self.client.api.delete(uri) + if resp.status == 200: + return True def read_current_from_device(self): - resource = self.client.api.tm.ltm.monitor.udps.udp.load( - name=self.want.name, - partition=self.want.partition + uri = "https://{0}:{1}/mgmt/tm/ltm/monitor/udp/{2}".format( + self.client.provider['server'], + self.client.provider['server_port'], + transform_name(self.want.partition, self.want.name) ) - result = resource.attrs - return Parameters(params=result) + resp = self.client.api.get(uri) + try: + response = resp.json() + except ValueError as ex: + raise F5ModuleError(str(ex)) + + if 'code' in response and response['code'] == 400: + if 'message' in response: + raise F5ModuleError(response['message']) + else: + raise F5ModuleError(resp.content) + return ApiParameters(params=response) class ArgumentSpec(object): @@ -552,20 +605,19 @@ def main(): module = AnsibleModule( argument_spec=spec.argument_spec, - supports_check_mode=spec.supports_check_mode + supports_check_mode=spec.supports_check_mode, ) - if not HAS_F5SDK: - module.fail_json(msg="The python f5-sdk module is required") + + client = F5RestClient(**module.params) try: - client = F5Client(**module.params) mm = ModuleManager(module=module, client=client) results = mm.exec_module() cleanup_tokens(client) - module.exit_json(**results) + exit_json(module, results, client) except F5ModuleError as ex: cleanup_tokens(client) - module.fail_json(msg=str(ex)) + fail_json(module, ex, client) if __name__ == '__main__': diff --git a/lib/ansible/modules/network/f5/bigip_timer_policy.py b/lib/ansible/modules/network/f5/bigip_timer_policy.py index f1f0b128a2f..458c7917ba3 100644 --- a/lib/ansible/modules/network/f5/bigip_timer_policy.py +++ b/lib/ansible/modules/network/f5/bigip_timer_policy.py @@ -152,7 +152,7 @@ try: from library.module_utils.network.f5.common import cleanup_tokens from library.module_utils.network.f5.common import fq_name from library.module_utils.network.f5.common import f5_argument_spec - from library.module_utils.network.f5.common import compare_dictionary + from library.module_utils.network.f5.common import compare_complex_list try: from library.module_utils.network.f5.common import iControlUnexpectedHTTPError except ImportError: @@ -165,7 +165,7 @@ except ImportError: from ansible.module_utils.network.f5.common import cleanup_tokens from ansible.module_utils.network.f5.common import fq_name from ansible.module_utils.network.f5.common import f5_argument_spec - from ansible.module_utils.network.f5.common import compare_dictionary + from ansible.module_utils.network.f5.common import compare_complex_list try: from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError except ImportError: @@ -371,7 +371,7 @@ class Difference(object): have = [tuple(x.pop('destination_ports')) for x in self.have.rules if 'destination_ports' in x] if set(want) != set(have): return self.want.rules - if compare_dictionary(self.want.rules, self.have.rules): + if compare_complex_list(self.want.rules, self.have.rules): return self.want.rules