#!/usr/bin/python # -*- coding: utf-8 -*- # # Copyright: (c) 2017, F5 Networks Inc. # GNU General Public License v3.0 (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function __metaclass__ = type ANSIBLE_METADATA = {'metadata_version': '1.1', 'status': ['stableinterface'], 'supported_by': 'certified'} DOCUMENTATION = r''' --- module: bigip_irule short_description: Manage iRules across different modules on a BIG-IP description: - Manage iRules across different modules on a BIG-IP. version_added: 2.2 options: content: description: - When used instead of 'src', sets the contents of an iRule directly to the specified value. This is for simple values, but can be used with lookup plugins for anything complex or with formatting. Either one of C(src) or C(content) must be provided. type: str module: description: - The BIG-IP module to add the iRule to. type: str required: True choices: - ltm - gtm name: description: - The name of the iRule. type: str required: True src: description: - The iRule file to interpret and upload to the BIG-IP. Either one of C(src) or C(content) must be provided. type: path required: True state: description: - Whether the iRule should exist or not. type: str choices: - present - absent default: present partition: description: - Device partition to manage resources on. type: str default: Common version_added: 2.5 extends_documentation_fragment: f5 author: - Tim Rupp (@caphrim007) - Wojciech Wypior (@wojtek0806) ''' EXAMPLES = r''' - name: Add the iRule contained in template irule.tcl to the LTM module bigip_irule: content: "{{ lookup('template', 'irule.tcl') }}" module: ltm name: MyiRule state: present provider: user: admin password: secret server: lb.mydomain.com delegate_to: localhost - name: Add the iRule contained in static file irule.tcl to the LTM module bigip_irule: module: ltm name: MyiRule src: irule.tcl state: present provider: user: admin password: secret server: lb.mydomain.com delegate_to: localhost ''' RETURN = r''' module: description: The module that the iRule was added to returned: changed and success type: str sample: gtm src: description: The filename that included the iRule source returned: changed and success, when provided type: str sample: /opt/src/irules/example1.tcl content: description: The content of the iRule that was managed returned: changed and success type: str sample: "when LB_FAILED { set wipHost [LB::server addr] }" ''' 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 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 f5_argument_spec from library.module_utils.network.f5.common import transform_name except ImportError: 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 f5_argument_spec from ansible.module_utils.network.f5.common import transform_name class Parameters(AnsibleF5Parameters): api_map = { 'apiAnonymous': 'content', } updatables = [ 'content', ] api_attributes = [ 'apiAnonymous', ] returnables = [ 'content', 'src', 'module', ] class ApiParameters(Parameters): pass class ModuleParameters(Parameters): @property def content(self): if self._values['content'] is None: result = self.src_content else: result = self._values['content'] return str(result).strip() @property def src(self): if self._values['src'] is None: return None return self._values['src'] @property def src_content(self): if not os.path.exists(self._values['src']): raise F5ModuleError( "The specified 'src' was not found." ) with open(self._values['src']) as f: result = f.read() return result class Changes(Parameters): def to_return(self): result = {} for returnable in self.returnables: result[returnable] = getattr(self, returnable) result = self._filter_params(result) return result class UsableChanges(Changes): pass class ReportableChanges(Changes): pass class Difference(object): def __init__(self, want, have=None): self.want = want self.have = have def compare(self, param): try: result = getattr(self, param) return result except AttributeError: return self.__default(param) def __default(self, param): attr1 = getattr(self.want, param) try: attr2 = getattr(self.have, param) if attr1 != attr2: return attr1 except AttributeError: return attr1 class ModuleManager(object): def __init__(self, *args, **kwargs): self.client = kwargs.get('client', None) self.module = kwargs.get('module', None) self.kwargs = kwargs def exec_module(self): if self.module.params['module'] == 'ltm': manager = self.get_manager('ltm') elif self.module.params['module'] == 'gtm': manager = self.get_manager('gtm') else: raise F5ModuleError( "An unknown iRule module type was specified" ) return manager.exec_module() def get_manager(self, type): if type == 'ltm': return LtmManager(**self.kwargs) elif type == 'gtm': return GtmManager(**self.kwargs) class BaseManager(object): def __init__(self, *args, **kwargs): self.module = kwargs.get('module', None) self.client = F5RestClient(**self.module.params) self.have = None self.want = ModuleParameters(params=self.module.params) self.changes = UsableChanges() def _set_changed_options(self): changed = {} for key in Parameters.returnables: if getattr(self.want, key) is not None: changed[key] = getattr(self.want, key) if changed: self.changes = UsableChanges(params=changed) def _update_changed_options(self): diff = Difference(self.want, self.have) updatables = Parameters.updatables changed = dict() for k in updatables: change = diff.compare(k) if change is None: continue else: if isinstance(change, dict): changed.update(change) else: changed[k] = change if changed: self.changes = UsableChanges(params=changed) return True return False def _announce_deprecations(self, result): warnings = result.pop('__warnings', []) for warning in warnings: self.module.deprecate( msg=warning['msg'], version=warning['version'] ) def exec_module(self): changed = False result = dict() state = self.want.state if state in ["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) return result def present(self): if not self.want.content and not self.want.src: raise F5ModuleError( "Either 'content' or 'src' must be provided" ) if self.exists(): return self.update() else: return self.create() def create(self): self._set_changed_options() if self.module.check_mode: return True self.create_on_device() if not self.exists(): raise F5ModuleError("Failed to create the iRule") return True def should_update(self): result = self._update_changed_options() if result: return True return False def update(self): self.have = self.read_current_from_device() if not self.should_update(): return False if self.module.check_mode: return True 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 iRule") return True class LtmManager(BaseManager): def exists(self): uri = "https://{0}:{1}/mgmt/tm/ltm/rule/{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_on_device(self): params = self.changes.api_params() uri = "https://{0}:{1}/mgmt/tm/ltm/rule/{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) def create_on_device(self): params = self.changes.api_params() params['name'] = self.want.name params['partition'] = self.want.partition uri = "https://{0}:{1}/mgmt/tm/ltm/rule/".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) return response['selfLink'] def read_current_from_device(self): uri = "https://{0}:{1}/mgmt/tm/ltm/rule/{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) def remove_from_device(self): uri = "https://{0}:{1}/mgmt/tm/ltm/rule/{2}".format( self.client.provider['server'], self.client.provider['server_port'], transform_name(self.want.partition, self.want.name) ) response = self.client.api.delete(uri) if response.status == 200: return True raise F5ModuleError(response.content) class GtmManager(BaseManager): def exists(self): uri = "https://{0}:{1}/mgmt/tm/gtm/rule/{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_on_device(self): params = self.changes.api_params() uri = "https://{0}:{1}/mgmt/tm/gtm/rule/{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) def create_on_device(self): params = self.changes.api_params() params['name'] = self.want.name params['partition'] = self.want.partition uri = "https://{0}:{1}/mgmt/tm/gtm/rule/".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) return response['selfLink'] def read_current_from_device(self): uri = "https://{0}:{1}/mgmt/tm/gtm/rule/{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) def remove_from_device(self): uri = "https://{0}:{1}/mgmt/tm/gtm/rule/{2}".format( self.client.provider['server'], self.client.provider['server_port'], transform_name(self.want.partition, self.want.name) ) response = self.client.api.delete(uri) if response.status == 200: return True raise F5ModuleError(response.content) class ArgumentSpec(object): def __init__(self): self.supports_check_mode = True argument_spec = dict( content=dict(), src=dict( type='path', ), name=dict(required=True), module=dict( required=True, choices=['gtm', 'ltm'] ), state=dict( default='present', choices=['present', 'absent'] ), partition=dict( default='Common', fallback=(env_fallback, ['F5_PARTITION']) ) ) self.argument_spec = {} self.argument_spec.update(f5_argument_spec) self.argument_spec.update(argument_spec) self.mutually_exclusive = [ ['content', 'src'] ] def main(): spec = ArgumentSpec() module = AnsibleModule( argument_spec=spec.argument_spec, supports_check_mode=spec.supports_check_mode, mutually_exclusive=spec.mutually_exclusive ) try: mm = ModuleManager(module=module) results = mm.exec_module() module.exit_json(**results) except F5ModuleError as ex: module.fail_json(msg=str(ex)) if __name__ == '__main__': main()