text: #!/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
  
  DOCUMENTATION = r'''
  ---
  module: bigip_gtm_monitor_https
  short_description: Manages F5 BIG-IP GTM HTTPS monitors
  description:
    - Manages F5 BIG-IP GTM (now BIG-IP DNS) HTTPS monitors.
  version_added: "1.0.0"
  options:
    name:
      description:
        - Monitor name.
      type: str
      required: True
    parent:
      description:
        - The parent template of this monitor template. Once this value has
          been set, it cannot be changed. By default, this value is the C(https)
          parent on the C(Common) partition.
      type: str
      default: /Common/https
    send:
      description:
        - The send string for the monitor call.
        - When creating a new monitor, if this parameter is not provided, the
          default of C(GET /\r\n) will be used.
      type: str
    receive:
      description:
        - The receive string for the monitor call.
      type: str
    ip:
      description:
        - IP address part of the IP/port definition. If this parameter is not
          provided when creating a new monitor, the default value will be
          '*'.
        - If this value is an IP address, a C(port) number must be specified.
      type: str
    port:
      description:
        - Port address part of the IP/port definition. If this parameter is not
          provided when creating a new monitor, the default value will be
          '*'. If specifying an IP address, you must specify a value between 1 and 65535.
      type: str
    interval:
      description:
        - The interval specifying how frequently the monitor instance of this
          template will run.
        - If this parameter is not provided when creating a new monitor,
          the default value will be 30.
        - This value B(must) be less than the C(timeout) value.
      type: int
    timeout:
      description:
        - The number of seconds in which the node or service must respond to
          the monitor request. If the target responds within the set time
          period, it is considered up. If the target does not respond within
          the set time period, it is considered down. You can change this
          to any number, however, it should be 3 times the
          interval number of seconds plus 1 second.
        - If this parameter is not provided when creating a new monitor, the
          default value will be 120.
      type: int
    partition:
      description:
        - Device partition to manage resources on.
      type: str
      default: Common
    state:
      description:
        - When C(present), ensures the monitor exists.
        - When C(absent), ensures the monitor is removed.
      type: str
      choices:
        - present
        - absent
      default: present
    probe_timeout:
      description:
        - Specifies the number of seconds after which the system times out the probe request
          to the system.
        - When creating a new monitor, if this parameter is not provided, then the default
          value will be C(5).
      type: int
    ignore_down_response:
      description:
        - Specifies the monitor allows more than one probe attempt per interval.
        - When C(yes), specifies the monitor ignores down responses for the duration of
          the monitor timeout. Once the monitor timeout is reached without the system receiving
          an up response, the system marks the object down.
        - When C(no), specifies the monitor immediately marks an object down when it
          receives a down response.
        - When creating a new monitor, if this parameter is not provided, the default
          value will be C(no).
      type: bool
    transparent:
      description:
        - Specifies whether the monitor operates in transparent mode.
        - A monitor in transparent mode directs traffic through the associated pool members
          or nodes (usually a router or firewall) to the aliased destination (that is, it
          probes the C(ip)-C(port) combination specified in the monitor).
        - If the monitor cannot successfully reach the aliased destination, the pool member
          or node through which the monitor traffic was sent is marked down.
        - When creating a new monitor, if this parameter is not provided, the default
          value will be C(no).
      type: bool
    reverse:
      description:
        - Instructs the system to mark the target resource down when the test is successful.
          This setting is useful, for example, if the content on your web site home page is
          dynamic and changes frequently, you may want to set up a reverse ECV service check
          that looks for the string Error.
        - A match for this string means the web server was down.
        - To use this option, you must specify values for C(send) and C(receive).
      type: bool
    target_username:
      description:
        - Specifies the user name, if the monitored target requires authentication.
      type: str
    target_password:
      description:
        - Specifies the password, if the monitored target requires authentication.
      type: str
    update_password:
      description:
        - C(always) will update passwords if the C(target_password) is specified.
        - C(on_create) will only set the password for newly created monitors.
      type: str
      choices:
        - always
        - on_create
      default: always
    cipher_list:
      description:
        - Specifies the list of ciphers for this monitor.
        - The items in the cipher list are separated with the colon C(:) symbol.
        - When creating a new monitor, if this parameter is not specified, the default
          list is C(DEFAULT:+SHA:+3DES:+kEDH).
      type: str
    compatibility:
      description:
        - Specifies, when enabled, the SSL options setting (in OpenSSL) is set to B(all).
        - When creating a new monitor, if this value is not specified, the default is
          C(yes)
      type: bool
    client_cert:
      description:
        - Specifies a fully-qualified path for a client certificate the monitor sends to
          the target SSL server.
      type: str
    client_key:
      description:
        - Specifies a key for a client certificate the monitor sends to the target SSL server.
      type: str
  extends_documentation_fragment: f5networks.f5_modules.f5
  author:
    - Tim Rupp (@caphrim007)
    - Wojciech Wypior (@wojtek0806)
  '''
  
  EXAMPLES = r'''
  - name: Create a GTM HTTPS monitor
    bigip_gtm_monitor_https:
      name: my_monitor
      ip: 1.1.1.1
      port: 80
      send: my send string
      receive: my receive string
      state: present
      provider:
        user: admin
        password: secret
        server: lb.mydomain.com
    delegate_to: localhost
  
  - name: Remove HTTPS Monitor
    bigip_gtm_monitor_https:
      name: my_monitor
      state: absent
      provider:
        user: admin
        password: secret
        server: lb.mydomain.com
    delegate_to: localhost
  
  - name: Add HTTPS monitor for all addresses, port 514
    bigip_gtm_monitor_https:
      name: my_monitor
      provider:
        user: admin
        password: secret
        server: lb.mydomain.com
      port: 514
    delegate_to: localhost
  '''
  
  RETURN = r'''
  parent:
    description: New parent template of the monitor.
    returned: changed
    type: str
    sample: https
  ip:
    description: The new IP of IP/port definition.
    returned: changed
    type: str
    sample: 10.12.13.14
  port:
    description: The new port the monitor checks the resource on.
    returned: changed
    type: str
    sample: 8080
  interval:
    description: The new interval in which to run the monitor check.
    returned: changed
    type: int
    sample: 2
  timeout:
    description: The new timeout in which the remote system must respond to the monitor.
    returned: changed
    type: int
    sample: 10
  ignore_down_response:
    description: Whether to ignore the down response or not.
    returned: changed
    type: bool
    sample: True
  send:
    description: The new send string for this monitor.
    returned: changed
    type: str
    sample: tcp string to send
  receive:
    description: The new receive string for this monitor.
    returned: changed
    type: str
    sample: tcp string to receive
  probe_timeout:
    description: The new timeout in which the system will timeout the monitor probe.
    returned: changed
    type: int
    sample: 10
  reverse:
    description: The new value for whether the monitor operates in reverse mode.
    returned: changed
    type: bool
    sample: False
  transparent:
    description: The new value for whether the monitor operates in transparent mode.
    returned: changed
    type: bool
    sample: False
  cipher_list:
    description: The new value for the cipher list.
    returned: changed
    type: str
    sample: +3DES:+kEDH
  compatibility:
    description: The new SSL compatibility setting.
    returned: changed
    type: bool
    sample: True
  client_cert:
    description: The new client cert setting.
    returned: changed
    type: str
    sample: /Common/default
  client_key:
    description: The new client key setting.
    returned: changed
    type: str
    sample: /Common/default
  '''
  from datetime import datetime
  
  from ansible.module_utils.basic import (
      AnsibleModule, env_fallback
  )
  
  from ..module_utils.bigip import F5RestClient
  from ..module_utils.common import (
      F5ModuleError, AnsibleF5Parameters, transform_name, f5_argument_spec, fq_name
  )
  from ..module_utils.icontrol import (
      module_provisioned, tmos_version
  )
  from ..module_utils.ipaddress import is_valid_ip
  from ..module_utils.teem import send_teem
  
  
  class Parameters(AnsibleF5Parameters):
      api_map = {
          'defaultsFrom': 'parent',
          'ignoreDownResponse': 'ignore_down_response',
          'probeTimeout': 'probe_timeout',
          'recv': 'receive',
          'username': 'target_username',
          'password': 'target_password',
          'cipherlist': 'cipher_list',
          'cert': 'client_cert',
          'key': 'client_key',
      }
  
      api_attributes = [
          'defaultsFrom',
          'interval',
          'timeout',
          'destination',
          'transparent',
          'probeTimeout',
          'ignoreDownResponse',
          'reverse',
          'send',
          'recv',
          'username',
          'password',
          'cipherlist',
          'compatibility',
          'cert',
          'key',
      ]
  
      returnables = [
          'parent',
          'ip',
          'port',
          'interval',
          'timeout',
          'transparent',
          'probe_timeout',
          'ignore_down_response',
          'send',
          'receive',
          'reverse',
          'cipher_list',
          'compatibility',
          'client_cert',
          'client_key',
      ]
  
      updatables = [
          'destination',
          'interval',
          'timeout',
          'transparent',
          'probe_timeout',
          'ignore_down_response',
          'send',
          'receive',
          'reverse',
          'ip',
          'port',
          'target_username',
          'target_password',
          'cipher_list',
          'compatibility',
          'client_cert',
          'client_key',
      ]
  
  
  class ApiParameters(Parameters):
      @property
      def ip(self):
          ip, port = self._values['destination'].split(':')
          return ip
  
      @property
      def port(self):
          ip, port = self._values['destination'].split(':')
          try:
              return int(port)
          except ValueError:
              return port
  
      @property
      def ignore_down_response(self):
          if self._values['ignore_down_response'] is None:
              return None
          if self._values['ignore_down_response'] == 'disabled':
              return False
          return True
  
      @property
      def transparent(self):
          if self._values['transparent'] is None:
              return None
          if self._values['transparent'] == 'disabled':
              return False
          return True
  
      @property
      def reverse(self):
          if self._values['reverse'] is None:
              return None
          if self._values['reverse'] == 'disabled':
              return False
          return True
  
      @property
      def compatibility(self):
          if self._values['compatibility'] is None:
              return None
          if self._values['compatibility'] == 'disabled':
              return False
          return True
  
  
  class ModuleParameters(Parameters):
      @property
      def interval(self):
          if self._values['interval'] is None:
              return None
          if 1 > int(self._values['interval']) > 86400:
              raise F5ModuleError(
                  "Interval value must be between 1 and 86400"
              )
          return int(self._values['interval'])
  
      @property
      def timeout(self):
          if self._values['timeout'] is None:
              return None
          return int(self._values['timeout'])
  
      @property
      def ip(self):
          if self._values['ip'] is None:
              return None
          if self._values['ip'] in ['*', '0.0.0.0']:
              return '*'
          elif is_valid_ip(self._values['ip']):
              return self._values['ip']
          else:
              raise F5ModuleError(
                  "The provided 'ip' parameter is not an IP address."
              )
  
      @property
      def parent(self):
          if self._values['parent'] is None:
              return None
          result = fq_name(self.partition, self._values['parent'])
          return result
  
      @property
      def port(self):
          if self._values['port'] is None:
              return None
          elif self._values['port'] == '*':
              return '*'
          return int(self._values['port'])
  
      @property
      def destination(self):
          if self.ip is None and self.port is None:
              return None
          destination = '{0}:{1}'.format(self.ip, self.port)
          return destination
  
      @destination.setter
      def destination(self, value):
          ip, port = value.split(':')
          self._values['ip'] = ip
          self._values['port'] = port
  
      @property
      def probe_timeout(self):
          if self._values['probe_timeout'] is None:
              return None
          return int(self._values['probe_timeout'])
  
      @property
      def type(self):
          return 'https'
  
      @property
      def client_cert(self):
          if self._values['client_cert'] is None:
              return None
          if self._values['client_cert'] == '':
              return ''
          result = fq_name(self.partition, self._values['client_cert'])
          if not result.endswith('.crt'):
              result += '.crt'
          return result
  
      @property
      def client_key(self):
          if self._values['client_key'] is None:
              return None
          if self._values['client_key'] == '':
              return ''
          result = fq_name(self.partition, self._values['client_key'])
          if not result.endswith('.key'):
              result += '.key'
          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):
      @property
      def transparent(self):
          if self._values['transparent'] is None:
              return None
          elif self._values['transparent'] is True:
              return 'enabled'
          return 'disabled'
  
      @property
      def ignore_down_response(self):
          if self._values['ignore_down_response'] is None:
              return None
          elif self._values['ignore_down_response'] is True:
              return 'enabled'
          return 'disabled'
  
      @property
      def reverse(self):
          if self._values['reverse'] is None:
              return None
          elif self._values['reverse'] is True:
              return 'enabled'
          return 'disabled'
  
      @property
      def compatibility(self):
          if self._values['compatibility'] is None:
              return None
          elif self._values['compatibility'] is True:
              return 'enabled'
          return 'disabled'
  
  
  class ReportableChanges(Changes):
      @property
      def ip(self):
          ip, port = self._values['destination'].split(':')
          return ip
  
      @property
      def port(self):
          ip, port = self._values['destination'].split(':')
          return int(port)
  
      @property
      def transparent(self):
          if self._values['transparent'] == 'enabled':
              return True
          return False
  
      @property
      def ignore_down_response(self):
          if self._values['ignore_down_response'] == 'enabled':
              return True
          return False
  
      @property
      def reverse(self):
          if self._values['reverse'] == 'enabled':
              return True
          return False
  
      @property
      def compatibility(self):
          if self._values['compatibility'] == 'enabled':
              return True
          return False
  
  
  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
  
      @property
      def parent(self):
          if self.want.parent != self.have.parent:
              raise F5ModuleError(
                  "The parent monitor cannot be changed"
              )
  
      @property
      def destination(self):
          if self.want.ip is None and self.want.port is None:
              return None
          if self.want.port is None:
              self.want.update({'port': self.have.port})
          if self.want.ip is None:
              self.want.update({'ip': self.have.ip})
  
          if self.want.port in [None, '*'] and self.want.ip != '*':
              raise F5ModuleError(
                  "Specifying an IP address requires that a port number be specified"
              )
  
          if self.want.destination != self.have.destination:
              return self.want.destination
  
      @property
      def interval(self):
          if self.want.timeout is not None and self.want.interval is not None:
              if self.want.interval >= self.want.timeout:
                  raise F5ModuleError(
                      "Parameter 'interval' must be less than 'timeout'."
                  )
          elif self.want.timeout is not None:
              if self.have.interval >= self.want.timeout:
                  raise F5ModuleError(
                      "Parameter 'interval' must be less than 'timeout'."
                  )
          elif self.want.interval is not None:
              if self.want.interval >= self.have.timeout:
                  raise F5ModuleError(
                      "Parameter 'interval' must be less than 'timeout'."
                  )
          if self.want.interval != self.have.interval:
              return self.want.interval
  
      @property
      def target_password(self):
          if self.want.target_password != self.have.target_password:
              if self.want.update_password == 'always':
                  result = self.want.target_password
                  return result
  
      @property
      def client_cert(self):
          if self.have.client_cert is None and self.want.client_cert == '':
              return None
          if self.have.client_cert != self.want.client_cert:
              return self.want.client_cert
  
      @property
      def client_key(self):
          if self.have.client_key is None and self.want.client_key == '':
              return None
          if self.have.client_key != self.want.client_key:
              return self.want.client_key
  
  
  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)
          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 = UsableChanges(params=changed)
              return True
          return False
  
      def should_update(self):
          result = self._update_changed_options()
          if result:
              return True
          return False
  
      def _announce_deprecations(self, result):
          warnings = result.pop('__warnings', [])
          for warning in warnings:
              self.client.module.deprecate(
                  msg=warning['msg'],
                  version=warning['version']
              )
  
      def _set_default_creation_values(self):
          if self.want.timeout is None:
              self.want.update({'timeout': 120})
          if self.want.interval is None:
              self.want.update({'interval': 30})
          if self.want.probe_timeout is None:
              self.want.update({'probe_timeout': 5})
          if self.want.ip is None:
              self.want.update({'ip': '*'})
          if self.want.port is None:
              self.want.update({'port': '*'})
          if self.want.ignore_down_response is None:
              self.want.update({'ignore_down_response': False})
          if self.want.transparent is None:
              self.want.update({'transparent': False})
          if self.want.send is None:
              self.want.update({'send': 'GET /\r\n'})
          if self.want.cipher_list is None:
              self.want.update({'cipher_list': 'DEFAULT:+SHA:+3DES:+kEDH'})
          if self.want.compatibility is None:
              self.want.update({'compatibility': True})
  
      def exec_module(self):
          start = datetime.now().isoformat()
          version = tmos_version(self.client)
          if not module_provisioned(self.client, 'gtm'):
              raise F5ModuleError(
                  "GTM must be provisioned to use this module."
              )
          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)
          send_teem(start, self.client, self.module, version)
          return result
  
      def present(self):
          if self.exists():
              return self.update()
          else:
              return self.create()
  
      def absent(self):
          if self.exists():
              return self.remove()
          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 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_default_creation_values()
          self._set_changed_options()
          if self.module.check_mode:
              return True
          self.create_on_device()
          return True
  
      def exists(self):
          uri = "https://{0}:{1}/mgmt/tm/gtm/monitor/https/{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 resp.status == 404 or 'code' in response and response['code'] == 404:
              return False
          if resp.status in [200, 201] or 'code' in response and response['code'] in [200, 201]:
              return True
  
          errors = [401, 403, 409, 500, 501, 502, 503, 504]
  
          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)
  
      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/monitor/https/".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 update_on_device(self):
          params = self.changes.api_params()
          uri = "https://{0}:{1}/mgmt/tm/gtm/monitor/https/{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 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):
          uri = "https://{0}:{1}/mgmt/tm/gtm/monitor/https/{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)
  
      def read_current_from_device(self):
          uri = "https://{0}:{1}/mgmt/tm/gtm/monitor/https/{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 resp.status in [200, 201] or 'code' in response and response['code'] in [200, 201]:
              return ApiParameters(params=response)
          raise F5ModuleError(resp.content)
  
  
  class ArgumentSpec(object):
      def __init__(self):
          self.supports_check_mode = True
          argument_spec = dict(
              name=dict(required=True),
              parent=dict(default='/Common/https'),
              send=dict(),
              receive=dict(),
              ip=dict(),
              port=dict(),
              interval=dict(type='int'),
              timeout=dict(type='int'),
              ignore_down_response=dict(type='bool'),
              transparent=dict(type='bool'),
              probe_timeout=dict(type='int'),
              reverse=dict(type='bool'),
              target_username=dict(),
              target_password=dict(no_log=True),
              cipher_list=dict(),
              compatibility=dict(type='bool'),
              client_cert=dict(),
              client_key=dict(),
              update_password=dict(
                  default='always',
                  choices=['always', 'on_create']
              ),
              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)
  
  
  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()
  