From 4f80c45c51a54d74157955900004e0da689ead6f Mon Sep 17 00:00:00 2001 From: Tim Rupp Date: Thu, 15 Nov 2018 20:01:24 -0800 Subject: [PATCH] Adds revoke functionality to device license (#48772) --- .../network/f5/bigip_device_license.py | 82 +++++++++++++++++-- 1 file changed, 73 insertions(+), 9 deletions(-) diff --git a/lib/ansible/modules/network/f5/bigip_device_license.py b/lib/ansible/modules/network/f5/bigip_device_license.py index 0b04c301024..1d37f667977 100644 --- a/lib/ansible/modules/network/f5/bigip_device_license.py +++ b/lib/ansible/modules/network/f5/bigip_device_license.py @@ -39,10 +39,13 @@ options: - When C(present), only guarantees that a license is there. - When C(latest), ensures that the license is always valid. - When C(absent), removes the license on the system. + - When C(revoked), removes the license on the system and revokes its future usage + on the F5 license servers. default: present choices: - absent - present + - revoked accept_eula: description: - Declares whether you accept the BIG-IP EULA or not. By default, this @@ -385,6 +388,8 @@ class ModuleManager(object): changed = self.present() elif state == "absent": changed = self.absent() + elif state == "revoked": + changed = self.revoke() reportable = ReportableChanges(params=self.changes.to_return()) changes = reportable.to_return() @@ -402,7 +407,7 @@ class ModuleManager(object): ) def present(self): - if self.exists(): + if self.exists() and not self.is_revoked(): return False else: return self.create() @@ -415,11 +420,67 @@ class ModuleManager(object): raise F5ModuleError("Failed to delete the resource.") return True + def revoke(self): + if self.is_revoked(): + return False + else: + # When revoking a license, it should be acceptable to auto-accept the + # license since you accepted it the first time when you activated the + # license you are now revoking. + self.want.update({'accept_eula': True}) + + # Revoking seems to just be another way of saying "get me a new license". + # There appear to be revoke-specific wording in the license and I assume + # some special revoke-like signing is happening, but the process is essentially + # just another form of "create". + return self.create() + + def revoke_from_device(self): + if self.module.check_mode: + return True + + dossier = self.read_dossier_from_device() + if dossier: + self.want.update({'dossier': dossier}) + else: + raise F5ModuleError("Dossier not generated.") + + if self.is_revoked(): + return False + + def is_revoked(self): + command = '-c "egrep Revoked /config/bigip.license"' + params = dict( + command='run', + utilCmdArgs=command + ) + uri = "https://{0}:{1}/mgmt/tm/util/bash".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) + if 'commandResult' in response and 'Revoked' in response['commandResult']: + return True + return False + def read_dossier_from_device(self): params = dict( command='run', utilCmdArgs='-b "{0}"'.format(self.want.license_key) ) + if self.want.state == 'revoked': + params['utilCmdArgs'] = '-r ' + params['utilCmdArgs'] + uri = "https://{0}:{1}/mgmt/tm/util/get-dossier".format( self.client.provider['server'], self.client.provider['server_port'] @@ -436,7 +497,10 @@ class ModuleManager(object): else: raise F5ModuleError(resp.content) try: - return response['commandResult'] + if self.want.state == 'revoked': + return response['commandResult'][8:] + else: + return response['commandResult'] except Exception: return None @@ -455,13 +519,13 @@ class ModuleManager(object): self.want.license_url, data=self.want.license_envelope, ) - except Exception as ex: + except Exception: continue try: - resp = LicenseXmlParser(content=resp._content) + resp = LicenseXmlParser(content=resp.content) result = resp.json() - except F5ModuleError as ex: + except F5ModuleError: # This error occurs when there is a problem with the license server and it # starts returning invalid XML (like if they upgraded something and the server # is redirecting improperly. @@ -469,7 +533,7 @@ class ModuleManager(object): # There's no way to recover from this error except by notifying F5 that there # is an issue with the license server. raise - except Exception as ex: + except Exception: continue if result['state'] == 'EULA_REQUIRED': @@ -554,7 +618,7 @@ class ModuleManager(object): nops += 1 else: nops = 0 - except Exception as ex: + except Exception: pass time.sleep(5) @@ -583,7 +647,7 @@ class ModuleManager(object): if 'commandResult' in response: return True - except Exception as ex: + except Exception: pass return False @@ -790,7 +854,7 @@ class ArgumentSpec(object): default='activate.f5.com' ), state=dict( - choices=['absent', 'present'], + choices=['absent', 'present', 'revoked'], default='present' ), accept_eula=dict(