#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright: (c) 2016, 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

DOCUMENTATION = r'''
---
module: bigip_device_license
short_description: Manage license installation and activation on BIG-IP devices
description:
  - Manage license installation and activation on a BIG-IP.
version_added: "1.0.0"
options:
  license_key:
    description:
      - The registration key to use to license the BIG-IP.
      - This parameter is required if the C(state) is equal to C(present) or C(latest).
      - This parameter is not required when C(state) is C(absent) and will be
        ignored if it is provided.
    type: str
  addon_keys:
    description:
      - The list of addon keys to use to in conjunction with the base license.
      - This parameter will be ignored if no C(license_key) is provided.
      - This parameter is not required when C(state) is C(absent) and will be
        ignored if it is provided.
    type: list
    elements: str
    version_added: "1.2.0"
  license_server:
    description:
      - The F5 license server to use when getting a license and validating a dossier.
      - This parameter is required if the C(state) is equal to C(present) or C(latest).
      - This parameter is not required when C(state) is C(absent) and will be
        ignored if it is provided.
    type: str
    default: activate.f5.com
  state:
    description:
      - The state of the license on the system.
      - When C(present), only guarantees that a license exists.
      - When C(absent), removes the license on the system.
      - When C(latest), ensures that the license is always valid. This is not idempotent state since re-run can modify result.
      - When C(revoked), removes the license on the system and revokes its future usage
        on the F5 license servers.
    type: str
    choices:
      - absent
      - latest
      - present
      - revoked
    default: present
  accept_eula:
    description:
      - Declares whether you accept the BIG-IP EULA or not. By default, this
        value is C(false). You must specifically declare you have viewed and
        accepted the license. This module does not present you with the EULA,
        so it is incumbent on you to read it.
      - The EULA can be found here; https://support.f5.com/csp/article/K12902.
      - This parameter is not required when C(state) is C(absent) and will be
        ignored if it is provided.
    type: bool
    default: false
  force:
    description:
      - Declares whether to force license renewal. By default, this
        value is C(false).
      - This parameter is not required and will be ignored if it is provided.
    type: bool
    default: false
extends_documentation_fragment: f5networks.f5_modules.f5
notes:
  - This module can be used to license BIG-IPs that do not have access to internet.
  - Only the Ansible Controller needs internet access as the license activation is done on the Ansible Controller from
    which the module is running.
author:
  - Tim Rupp (@caphrim007)
  - Wojciech Wypior (@wojtek0806)
  - Andrey Kashcheev (@andreykashcheev)
'''

EXAMPLES = '''
- name: License BIG-IP using a key
  bigip_device_license:
    license_key: "XXXXX-XXXXX-XXXXX-XXXXX-XXXXXXX"
    provider:
      server: "lb.mydomain.com"
      user: "admin"
      password: "secret"
  delegate_to: localhost

- name: License BIG-IP using a key
  bigip_device_license:
    license_key: "XXXXX-XXXXX-XXXXX-XXXXX-XXXXXXX"
    provider:
      server: "lb.mydomain.com"
      user: "admin"
      password: "secret"
  delegate_to: localhost

- name: Remove the license from the system
  bigip_device_license:
    state: "absent"
    provider:
      server: "lb.mydomain.com"
      user: "admin"
      password: "secret"
  delegate_to: localhost
'''

RETURN = r'''
# only common fields returned
'''
import re
import time
import xml.etree.ElementTree
from datetime import datetime

from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.six import iteritems

from ..module_utils.bigip import F5RestClient
from ..module_utils.common import (
    F5ModuleError, AnsibleF5Parameters, is_empty_list, f5_argument_spec
)
from ..module_utils.icontrol import iControlRestSession

from ..module_utils.teem import send_teem


class LicenseXmlParser(object):
    def __init__(self, content=None):
        self.raw_content = content
        try:
            self.content = xml.etree.ElementTree.fromstring(content)
        except xml.etree.ElementTree.ParseError as ex:
            raise F5ModuleError("Provided XML payload is invalid. Received '{0}'.".format(str(ex)))

    @property
    def namespaces(self):
        result = {
            'xsi': 'http://www.w3.org/2001/XMLSchema-instance'
        }
        return result

    @property
    def eula(self):
        try:
            root = self.content.findall('.//eula', self.namespaces)
            return root[0].text
        except Exception:
            return None

    @property
    def license(self):
        try:
            root = self.content.findall('.//license', self.namespaces)
            return root[0].text
        except Exception:
            return None

    def find_element(self, value):
        root = self.content.findall('.//multiRef', self.namespaces)
        if len(root) == 0:
            return None
        for elem in root:
            for k, v in iteritems(elem.attrib):
                if value in v:
                    return elem

    @property
    def state(self):
        elem = self.find_element('TransactionState')
        if elem is not None:
            return elem.text

    @property
    def fault_number(self):
        fault = self.get_fault()
        return fault.get('faultNumber', None)

    @property
    def fault_text(self):
        fault = self.get_fault()
        return fault.get('faultText', None)

    def get_fault(self):
        result = dict()

        self.set_result_for_license_fault(result)
        self.set_result_for_general_fault(result)

        if 'faultNumber' not in result:
            result['faultNumber'] = None
        return result

    def set_result_for_license_fault(self, result):
        root = self.find_element('LicensingFault')
        if root is None:
            return result
        for elem in root:
            if elem.tag == 'faultNumber':
                result['faultNumber'] = elem.text
            elif elem.tag == 'faultText':
                tmp = elem.attrib.get('{http://www.w3.org/2001/XMLSchema-instance}nil', None)
                if tmp == 'true':
                    result['faultText'] = None
                else:
                    result['faultText'] = elem.text

    def set_result_for_general_fault(self, result):
        namespaces = {
            'ns2': 'http://schemas.xmlsoap.org/soap/envelope/'
        }
        root = self.content.findall('.//ns2:Fault', namespaces)
        if len(root) == 0:
            return None
        for elem in root[0]:
            if elem.tag == 'faultstring':
                result['faultText'] = elem.text

    def json(self):
        result = dict(
            eula=self.eula or None,
            license=self.license or None,
            state=self.state or None,
            fault_number=self.fault_number,
            fault_text=self.fault_text or None
        )
        return result


class Parameters(AnsibleF5Parameters):
    api_map = {
        'licenseEndDateTime': 'license_end_date_time',
    }

    api_attributes = [

    ]

    returnables = [

    ]

    updatables = [

    ]


class ApiParameters(Parameters):
    pass


class ModuleParameters(Parameters):
    @property
    def license_options(self):
        result = dict(
            eula=self.eula or '',
            email=self.email or '',
            first_name=self.first_name or '',
            last_name=self.last_name or '',
            company=self.company or '',
            phone=self.phone or '',
            job_title=self.job_title or '',
            address=self.address or '',
            city=self.city or '',
            state=self.state or '',
            postal_code=self.postal_code or '',
            country=self.country or ''
        )
        return result

    @property
    def license_url(self):
        result = 'https://{0}/license/services/urn:com.f5.license.v5b.ActivationService'.format(self.license_server)
        return result

    @property
    def license_envelope(self):
        result = """<?xml version="1.0" encoding="UTF-8"?>
        <SOAP-ENV:Envelope xmlns:ns3="http://www.w3.org/2001/XMLSchema"
                           xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
                           xmlns:ns0="http://schemas.xmlsoap.org/soap/encoding/"
                           xmlns:ns1="https://{0}/license/services/urn:com.f5.license.v5b.ActivationService"
                           xmlns:ns2="http://schemas.xmlsoap.org/soap/envelope/"
                           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                           xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
                           SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
          <SOAP-ENV:Header/>
          <ns2:Body>
            <ns1:getLicense>
              <dossier xsi:type="ns3:string">{1}</dossier>
              <eula xsi:type="ns3:string">{eula}</eula>
              <email xsi:type="ns3:string">{email}</email>
              <firstName xsi:type="ns3:string">{first_name}</firstName>
              <lastName xsi:type="ns3:string">{last_name}</lastName>
              <companyName xsi:type="ns3:string">{company}</companyName>
              <phone xsi:type="ns3:string">{phone}</phone>
              <jobTitle xsi:type="ns3:string">{job_title}</jobTitle>
              <address xsi:type="ns3:string">{address}</address>
              <city xsi:type="ns3:string">{city}</city>
              <stateProvince xsi:type="ns3:string">{state}</stateProvince>
              <postalCode xsi:type="ns3:string">{postal_code}</postalCode>
              <country xsi:type="ns3:string">{country}</country>
            </ns1:getLicense>
          </ns2:Body>
        </SOAP-ENV:Envelope>"""
        result = result.format(self.license_server, self.dossier, **self.license_options)
        return result

    @property
    def addon_keys(self):
        if self._values['license_key'] is None:
            return None
        if self._values['addon_keys'] is None or is_empty_list(self._values['addon_keys']):
            return None
        result = ','.join(self._values['addon_keys'])
        return result


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:
            raise
        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.module = kwargs.get('module', None)
        self.client = F5RestClient(**self.module.params)
        self.want = ModuleParameters(params=self.module.params, client=self.client)
        self.have = ApiParameters(client=self.client)
        self.changes = UsableChanges()
        self.escape_patterns = r'([$"' + "'])"

    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 should_update(self):
        result = self._update_changed_options()
        if result:
            return True
        return False

    def exec_module(self):
        start = datetime.now().isoformat()
        changed = False
        result = dict()
        state = self.want.state
        if state == "present":
            changed = self.present()
        elif state == "latest":
            changed = self.valid()
        elif state == "absent":
            changed = self.absent()
        elif state == "revoked":
            changed = self.revoke()

        reportable = ReportableChanges(params=self.changes.to_return())
        changes = reportable.to_return()
        result.update(**changes)
        result.update(dict(changed=changed))
        self._announce_deprecations(result)
        send_teem(start, self.client, self.module, None)
        return result

    def _announce_deprecations(self, result):
        warnings = result.pop('__warnings', [])
        for warning in warnings:
            self.client.module.deprecate(
                msg=warning['msg'],
                version=warning['version']
            )

    def present(self):
        if self.exists() and not self.is_revoked():
            return False
        else:
            return self.create()

    def valid(self):
        if self.exists() and not self.is_revoked():
            if self.want.force or not self.license_valid():
                return self.create()
            else:
                return False
        else:
            return self.create()

    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 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 resp.status not in [200, 201] or 'code' in response and response['code'] not in [200, 201]:
            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.addon_keys:
            addons = self.want.addon_keys
            params['utilCmdArgs'] = '-a {0} '.format(addons) + params['utilCmdArgs']

        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']
        )
        resp = self.client.api.post(uri, json=params)
        try:
            response = resp.json()
        except ValueError as ex:
            raise F5ModuleError(str(ex))

        if resp.status not in [200, 201] or 'code' in response and response['code'] not in [200, 201]:
            raise F5ModuleError(resp.content)
        try:
            if self.want.state == 'revoked':
                return response['commandResult'][8:]
            else:
                return response['commandResult']
        except Exception:
            return None

    def generate_license_from_remote(self):
        mgmt = iControlRestSession(
            validate_certs=False,
            headers={
                'SOAPAction': '""',
                'Content-Type': 'text/xml; charset=utf-8',
            }
        )

        for x in range(0, 10):
            try:
                resp = mgmt.post(
                    self.want.license_url,
                    data=self.want.license_envelope,
                )
            except Exception:
                # Failures to connect to licensing server must be passed upstream not supressed
                raise

            try:
                resp = LicenseXmlParser(content=resp.content)
                result = resp.json()
            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.
                #
                # 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:
                # Any other exceptions must be raised also
                raise

            if result['state'] == 'EULA_REQUIRED':
                self.want.update({'eula': result['eula']})
                continue
            if result['state'] == 'LICENSE_RETURNED':
                return result
            elif result['state'] == 'EMAIL_REQUIRED':
                raise F5ModuleError("Email must be provided")
            elif result['state'] == 'CONTACT_INFO_REQUIRED':
                raise F5ModuleError("Contact info must be provided")
            else:
                raise F5ModuleError(result['fault_text'])

    def create(self):
        self._set_changed_options()
        if not self.want.accept_eula:
            raise F5ModuleError(
                "You must read and accept the product EULA to license the box."
            )
        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.")

        self.create_on_device()
        self.wait_for_mcpd()
        if not self.exists():
            raise F5ModuleError(
                "Failed to license the device."
            )
        return True

    def absent(self):
        if self.any_license_exists():
            self.remove()
            self.wait_for_mcpd()
            if self.exists():
                raise F5ModuleError(
                    "Failed to remove the license from the device."
                )
            return True
        return False

    def license_valid(self):
        errors = [401, 403, 409, 500, 501, 502, 503, 504]
        uri = "https://{0}:{1}/mgmt/tm/shared/licensing/registration".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 resp.status in errors or 'code' in response and response['code'] in errors:
            if 'message' in response:
                raise F5ModuleError(response['message'])
            else:
                raise F5ModuleError(resp.content)
        try:
            return int(float(response['expiresInDays'])) > 0
        except Exception:
            pass
        return False

    def exists(self):
        errors = [401, 403, 409, 500, 501, 502, 503, 504]
        uri = "https://{0}:{1}/mgmt/tm/shared/licensing/registration".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 resp.status in errors or 'code' in response and response['code'] in errors:
            if 'message' in response:
                raise F5ModuleError(response['message'])
            else:
                raise F5ModuleError(resp.content)

        try:
            if response['registrationKey'] == self.want.license_key:
                return True
        except Exception:
            pass
        return False

    def wait_for_mcpd(self):
        nops = 0

        # Sleep a little to let mcpd settle and begin properly
        time.sleep(5)

        while nops < 4:
            try:
                if self._is_mcpd_ready_on_device():
                    nops += 1
                else:
                    nops = 0
            except Exception as ex:
                if '"message":"X-F5-Auth-Token has expired."' in str(ex):
                    raise F5ModuleError("X-F5-Auth-Token has expired.")
                pass
            time.sleep(5)

    def _is_mcpd_ready_on_device(self):
        try:
            command = "tmsh show sys mcp-state | grep running"
            params = dict(
                command='run',
                utilCmdArgs='-c "{0}"'.format(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 resp.status not in [200, 201] or 'code' in response and response['code'] not in [200, 201]:
                raise F5ModuleError(resp.content)

            if 'commandResult' in response:
                return True
        except Exception as ex:
            if '"message":"X-F5-Auth-Token has expired."' in str(ex):
                raise
            pass
        return False

    def any_license_exists(self):
        errors = [401, 403, 409, 500, 501, 502, 503, 504]
        uri = "https://{0}:{1}/mgmt/tm/shared/licensing/registration".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 resp.status in errors or 'code' in response and response['code'] in errors:
            if 'message' in response:
                raise F5ModuleError(response['message'])
            else:
                raise F5ModuleError(resp.content)
        try:
            if response['registrationKey'] is not None:
                return True
        except Exception:
            pass
        return False

    def create_on_device(self):
        license = self.generate_license_from_remote()
        if license is None:
            raise F5ModuleError(
                "Failed to generate license from F5 activation servers."
            )
        result = self.upload_license_to_device(license)
        if not result:
            raise F5ModuleError(
                "Failed to install license on device."
            )
        result = self.upload_eula_to_device(license)
        if not result:
            raise F5ModuleError(
                "Failed to upload EULA file to device."
            )
        result = self.reload_license()
        if not result:
            raise F5ModuleError(
                "Failed to reload license configuration."
            )

    def upload_license_to_device(self, license):
        license_payload = re.sub(self.escape_patterns, r'\\\1', license['license'])
        command_arg = """cat > /config/bigip.license <<EOF\n{0}\nEOF""".format(license_payload)
        command = '-c "{0}"'.format(command_arg)
        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 resp.status in [200, 201] or 'code' in response and response['code'] in [200, 201]:
            return True
        raise F5ModuleError(resp.content)

    def upload_eula_to_device(self, license):
        eula_payload = re.sub(self.escape_patterns, r'\\\1', license['eula'])
        command_arg = """cat > /LICENSE.F5 <<EOF\n{0}\nEOF""".format(eula_payload)
        command = '-c "{0}"'.format(command_arg)
        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 resp.status in [200, 201] or 'code' in response and response['code'] in [200, 201]:
            return True
        raise F5ModuleError(resp.content)

    def reload_license(self):
        command = '-c "{0}"'.format("/usr/bin/reloadlic")
        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 resp.status in [200, 201] or 'code' in response and response['code'] in [200, 201]:
            return True
        raise F5ModuleError(resp.content)

    def remove_from_device(self):
        result = self.remove_license_from_device()
        if not result:
            raise F5ModuleError(
                "Failed to remove license from device."
            )
        result = self.remove_eula_from_device()
        if not result:
            raise F5ModuleError(
                "Failed to remove EULA file from device."
            )
        result = self.reload_license()
        if not result:
            raise F5ModuleError(
                "Failed to reload the empty license configuration."
            )

    def remove_license_from_device(self):
        params = dict(
            command='run',
            utilCmdArgs='/config/bigip.license'
        )
        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 resp.status not in [200, 201] or 'code' in response and response['code'] not in [200, 201]:
            raise F5ModuleError(resp.content)

        if 'commandResult' in response:
            if 'No such file or directory' in response['commandResult']:
                return True
            else:
                raise F5ModuleError(response['commandResult'])
        return True

    def remove_eula_from_device(self):
        params = dict(
            command='run',
            utilCmdArgs='/LICENSE.F5'
        )
        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 resp.status not in [200, 201] or 'code' in response and response['code'] not in [200, 201]:
            raise F5ModuleError(resp.content)

        if 'commandResult' in response:
            if 'No such file or directory' in response['commandResult']:
                return True
            else:
                raise F5ModuleError(response['commandResult'])
        return True


class ArgumentSpec(object):
    def __init__(self):
        self.supports_check_mode = True
        argument_spec = dict(
            license_key=dict(no_log=True),
            addon_keys=dict(
                type='list',
                elements='str',
                no_log=True
            ),
            license_server=dict(
                default='activate.f5.com'
            ),
            state=dict(
                choices=['absent', 'present', 'revoked', 'latest'],
                default='present'
            ),
            accept_eula=dict(
                type='bool',
                default='no'
            ),
            force=dict(
                type='bool',
                default='no'
            )
        )
        self.argument_spec = {}
        self.argument_spec.update(f5_argument_spec)
        self.argument_spec.update(argument_spec)
        self.required_if = [
            ['state', 'present', ['accept_eula', 'license_key']],
            ['state', 'latest', ['accept_eula', 'license_key']]
        ]


def main():
    spec = ArgumentSpec()

    module = AnsibleModule(
        argument_spec=spec.argument_spec,
        supports_check_mode=spec.supports_check_mode,
        required_if=spec.required_if
    )

    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()
