#!/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': ['preview'], 'supported_by': 'certified'} DOCUMENTATION = r''' --- module: bigiq_regkey_pool short_description: Manages registration key pools on BIG-IQ description: - Manages registration key (regkey) pools on a BIG-IQ. These pools function as a container in-which you will add lists of registration keys. To add registration keys, use the C(bigiq_regkey_license) module. version_added: 2.5 options: name: description: - Specifies the name of the registration key pool. - You must be mindful to name your registration pools unique names. While BIG-IQ does not require this, this module does. If you do not do this, the behavior of the module is undefined and you may end up putting licenses in the wrong registration key pool. required: True description: description: - A description to attach to the pool. state: description: - The state of the regkey pool on the system. - When C(present), guarantees that the pool exists. - When C(absent), removes the pool, and the licenses it contains, from the system. default: present choices: - absent - present requirements: - BIG-IQ >= 5.3.0 extends_documentation_fragment: f5 author: - Tim Rupp (@caphrim007) ''' EXAMPLES = r''' - name: Create a registration key (regkey) pool to hold individual device licenses bigiq_regkey_pool: name: foo-pool password: secret server: lb.mydomain.com state: present user: admin delegate_to: localhost ''' RETURN = r''' description: description: New description of the regkey pool. returned: changed type: string sample: My description ''' from ansible.module_utils.basic import AnsibleModule try: from library.module_utils.network.f5.bigiq 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 except ImportError: from ansible.module_utils.network.f5.bigiq 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 class Parameters(AnsibleF5Parameters): api_map = { } api_attributes = [ 'description' ] returnables = [ 'description' ] updatables = [ '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 class ModuleParameters(Parameters): @property def uuid(self): """Returns UUID of a given name Will search for a given name and return the first one returned to us. If no name, and therefore no ID, is found, will return the string "none". The string "none" is returned because if we were to return the None value, it would cause the license loading code to append a None string to the URI; essentially asking the remote device for its collection (which we dont want and which would cause the SDK to return an False error. :return: """ collection = self.read_current_from_device() resource = next((x for x in collection if x.name == self._values['name']), None) if resource: return resource.id else: return "none" def read_current_from_device(self): uri = "https://{0}:{1}/mgmt/cm/device/licensing/pool/regkey/licenses".format( self.client.provider['server'], self.client.provider['server_port'], ) 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) if 'items' not in response: return [] result = [ApiParameters(params=r) for r in response['items']] return result class ApiParameters(Parameters): @property def uuid(self): return self._values['id'] class Changes(Parameters): pass class ReportableChanges(Changes): pass class UsableChanges(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.module = kwargs.get('module', None) self.client = kwargs.get('client', None) self.client = F5RestClient(**self.module.params) self.want = ModuleParameters(client=self.client, params=self.module.params) self.have = ApiParameters() 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 = Changes(params=changed) return True return False def should_update(self): result = self._update_changed_options() if result: return True return False def exec_module(self): changed = False result = dict() state = self.want.state 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) return result def _announce_deprecations(self, result): warnings = result.pop('__warnings', []) for warning in warnings: self.module.deprecate( msg=warning['msg'], version=warning['version'] ) def present(self): if self.exists(): return self.update() else: return self.create() def exists(self): uri = "https://{0}:{1}/mgmt/cm/device/licensing/pool/regkey/licenses/{2}".format( self.client.provider['server'], self.client.provider['server_port'], self.want.uuid, ) 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() if not self.should_update(): return False if self.module.check_mode: return True self.update_on_device() return True def remove(self): if self.module.check_mode: return True self.remove_from_device() if self.exists(): raise F5ModuleError("Failed to delete the resource.") return True def create(self): self._set_changed_options() if self.module.check_mode: return True self.create_on_device() return True def create_on_device(self): params = self.want.api_params() params['name'] = self.want.name uri = "https://{0}:{1}/mgmt/cm/device/licensing/pool/regkey/licenses/".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.changes.api_params() uri = "https://{0}:{1}/mgmt/cm/device/licensing/pool/regkey/licenses/{2}".format( self.client.provider['server'], self.client.provider['server_port'], self.want.uuid ) 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(): return self.remove() return False def remove_from_device(self): uri = "https://{0}:{1}/mgmt/cm/device/licensing/pool/regkey/licenses/{2}".format( self.client.provider['server'], self.client.provider['server_port'], self.want.uuid ) resp = self.client.api.delete(uri) if resp.status == 200: return True def read_current_from_device(self): uri = "https://{0}:{1}/mgmt/cm/device/licensing/pool/regkey/licenses/{2}".format( self.client.provider['server'], self.client.provider['server_port'], self.want.uuid ) 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): def __init__(self): self.supports_check_mode = True argument_spec = dict( name=dict(required=True), description=dict(), state=dict( default='present', choices=['absent', 'present'] ) ) self.argument_spec = {} self.argument_spec.update(f5_argument_spec) self.argument_spec.update(argument_spec) def main(): spec = ArgumentSpec() module = AnsibleModule( argument_spec=spec.argument_spec, supports_check_mode=spec.supports_check_mode, ) 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()