text: # -*- coding: utf-8 -*-
  '''
  Management of Zabbix hosts.
  
  :codeauthor: Jiri Kotlin <jiri.kotlin@ultimum.io>
  
  
  '''
  from __future__ import absolute_import, print_function, unicode_literals
  from salt.utils.json import loads, dumps
  from copy import deepcopy
  from salt.ext import six
  
  
  def __virtual__():
      '''
      Only make these states available if Zabbix module is available.
      '''
      return 'zabbix.host_create' in __salt__
  
  
  def present(host, groups, interfaces, **kwargs):
      '''
      Ensures that the host exists, eventually creates new host.
      NOTE: please use argument visible_name instead of name to not mess with name from salt sls. This function accepts
      all standard host properties: keyword argument names differ depending on your zabbix version, see:
      https://www.zabbix.com/documentation/2.4/manual/api/reference/host/object#host
  
      .. versionadded:: 2016.3.0
  
      :param host: technical name of the host
      :param groups: groupids of host groups to add the host to
      :param interfaces: interfaces to be created for the host
      :param proxy_host: Optional proxy name or proxyid to monitor host
      :param inventory: Optional list of inventory names and values
      :param _connection_user: Optional - zabbix user (can also be set in opts or pillar, see module's docstring)
      :param _connection_password: Optional - zabbix password (can also be set in opts or pillar, see module's docstring)
      :param _connection_url: Optional - url of zabbix frontend (can also be set in opts, pillar, see module's docstring)
      :param visible_name: Optional - string with visible name of the host, use 'visible_name' instead of 'name' \
      parameter to not mess with value supplied from Salt sls file.
  
      .. code-block:: yaml
  
          create_test_host:
              zabbix_host.present:
                  - host: TestHostWithInterfaces
                  - proxy_host: 12345
                  - groups:
                      - 5
                      - 6
                      - 7
                  - interfaces:
                      - test1.example.com:
                          - ip: '192.168.1.8'
                          - type: 'Agent'
                          - port: 92
                      - testing2_create:
                          - ip: '192.168.1.9'
                          - dns: 'test2.example.com'
                          - type: 'agent'
                          - main: false
                      - testovaci1_ipmi:
                          - ip: '192.168.100.111'
                          - type: 'ipmi'
                  - inventory:
                      - alias: some alias
                      - asset_tag: jlm3937
  
  
      '''
      connection_args = {}
      if '_connection_user' in kwargs:
          connection_args['_connection_user'] = kwargs['_connection_user']
      if '_connection_password' in kwargs:
          connection_args['_connection_password'] = kwargs['_connection_password']
      if '_connection_url' in kwargs:
          connection_args['_connection_url'] = kwargs['_connection_url']
  
      ret = {'name': host, 'changes': {}, 'result': False, 'comment': ''}
  
      # Comment and change messages
      comment_host_created = 'Host {0} created.'.format(host)
      comment_host_updated = 'Host {0} updated.'.format(host)
      comment_host_notcreated = 'Unable to create host: {0}. '.format(host)
      comment_host_exists = 'Host {0} already exists.'.format(host)
      changes_host_created = {host: {'old': 'Host {0} does not exist.'.format(host),
                                     'new': 'Host {0} created.'.format(host),
                                     }
                              }
  
      def _interface_format(interfaces_data):
          '''
          Formats interfaces from SLS file into valid JSON usable for zabbix API.
          Completes JSON with default values.
  
          :param interfaces_data: list of interfaces data from SLS file
  
          '''
  
          if not interfaces_data:
              return list()
  
          interface_attrs = ('ip', 'dns', 'main', 'type', 'useip', 'port')
          interfaces_json = loads(dumps(interfaces_data))
          interfaces_dict = dict()
  
          for interface in interfaces_json:
              for intf in interface:
                  intf_name = intf
                  interfaces_dict[intf_name] = dict()
                  for intf_val in interface[intf]:
                      for key, value in intf_val.items():
                          if key in interface_attrs:
                              interfaces_dict[intf_name][key] = value
  
          interfaces_list = list()
          interface_ports = {'agent': ['1', '10050'], 'snmp': ['2', '161'], 'ipmi': ['3', '623'],
                             'jmx': ['4', '12345']}
  
          for key, value in interfaces_dict.items():
              # Load interface values or default values
              interface_type = interface_ports[value['type'].lower()][0]
              main = '1' if six.text_type(value.get('main', 'true')).lower() == 'true' else '0'
              useip = '1' if six.text_type(value.get('useip', 'true')).lower() == 'true' else '0'
              interface_ip = value.get('ip', '')
              dns = value.get('dns', key)
              port = six.text_type(value.get('port', interface_ports[value['type'].lower()][1]))
  
              interfaces_list.append({'type': interface_type,
                                      'main': main,
                                      'useip': useip,
                                      'ip': interface_ip,
                                      'dns': dns,
                                      'port': port})
  
          interfaces_list = interfaces_list
          interfaces_list_sorted = sorted(interfaces_list, key=lambda k: k['main'], reverse=True)
  
          return interfaces_list_sorted
  
      interfaces_formated = _interface_format(interfaces)
  
      # Ensure groups are all groupid
      groupids = []
      for group in groups:
          if isinstance(group, six.string_types):
              groupid = __salt__['zabbix.hostgroup_get'](name=group, **connection_args)
              try:
                  groupids.append(int(groupid[0]['groupid']))
              except TypeError:
                  ret['comment'] = 'Invalid group {0}'.format(group)
                  return ret
          else:
              groupids.append(group)
      groups = groupids
  
      # Get and validate proxyid
      proxy_hostid = "0"
      if 'proxy_host' in kwargs:
          # Test if proxy_host given as name
          if isinstance(kwargs['proxy_host'], six.string_types):
              try:
                  proxy_hostid = __salt__['zabbix.run_query']('proxy.get', {"output": "proxyid",
                                                              "selectInterface": "extend",
                                                              "filter": {"host": "{0}".format(kwargs['proxy_host'])}},
                                                              **connection_args)[0]['proxyid']
              except TypeError:
                  ret['comment'] = 'Invalid proxy_host {0}'.format(kwargs['proxy_host'])
                  return ret
          # Otherwise lookup proxy_host as proxyid
          else:
              try:
                  proxy_hostid = __salt__['zabbix.run_query']('proxy.get', {"proxyids":
                                                              "{0}".format(kwargs['proxy_host']),
                                                              "output": "proxyid"},
                                                              **connection_args)[0]['proxyid']
              except TypeError:
                  ret['comment'] = 'Invalid proxy_host {0}'.format(kwargs['proxy_host'])
                  return ret
  
      if 'inventory' not in kwargs:
          inventory = {}
      else:
          inventory = kwargs['inventory']
      if inventory is None:
          inventory = {}
      # Create dict of requested inventory items
      new_inventory = {}
      for inv_item in inventory:
          for k, v in inv_item.items():
              new_inventory[k] = str(v)
  
      host_exists = __salt__['zabbix.host_exists'](host, **connection_args)
  
      if host_exists:
          host = __salt__['zabbix.host_get'](host=host, **connection_args)[0]
          hostid = host['hostid']
  
          update_proxy = False
          update_hostgroups = False
          update_interfaces = False
          update_inventory = False
  
          cur_proxy_hostid = host['proxy_hostid']
          if proxy_hostid != cur_proxy_hostid:
              update_proxy = True
  
          hostgroups = __salt__['zabbix.hostgroup_get'](hostids=hostid, **connection_args)
          cur_hostgroups = list()
  
          for hostgroup in hostgroups:
              cur_hostgroups.append(int(hostgroup['groupid']))
  
          if set(groups) != set(cur_hostgroups):
              update_hostgroups = True
  
          hostinterfaces = __salt__['zabbix.hostinterface_get'](hostids=hostid, **connection_args)
  
          if hostinterfaces:
              hostinterfaces = sorted(hostinterfaces, key=lambda k: k['main'])
              hostinterfaces_copy = deepcopy(hostinterfaces)
              for hostintf in hostinterfaces_copy:
                  hostintf.pop('interfaceid')
                  hostintf.pop('bulk')
                  hostintf.pop('hostid')
              interface_diff = [x for x in interfaces_formated if x not in hostinterfaces_copy] + \
                               [y for y in hostinterfaces_copy if y not in interfaces_formated]
              if interface_diff:
                  update_interfaces = True
  
          elif not hostinterfaces and interfaces:
              update_interfaces = True
  
          cur_inventory = __salt__['zabbix.host_inventory_get'](hostids=hostid, **connection_args)
          if cur_inventory:
              # Remove blank inventory items
              cur_inventory = {k: v for k, v in cur_inventory.items() if v}
              # Remove persistent inventory keys for comparison
              cur_inventory.pop('hostid', None)
              cur_inventory.pop('inventory_mode', None)
  
          if new_inventory and not cur_inventory:
              update_inventory = True
          elif set(cur_inventory) != set(new_inventory):
              update_inventory = True
  
      # Dry run, test=true mode
      if __opts__['test']:
          if host_exists:
              if update_hostgroups or update_interfaces or update_proxy or update_inventory:
                  ret['result'] = None
                  ret['comment'] = comment_host_updated
              else:
                  ret['result'] = True
                  ret['comment'] = comment_host_exists
          else:
              ret['result'] = None
              ret['comment'] = comment_host_created
              ret['changes'] = changes_host_created
          return ret
  
      error = []
  
      if host_exists:
          ret['result'] = True
          if update_hostgroups or update_interfaces or update_proxy or update_inventory:
  
              if update_inventory:
                  # combine connection_args, inventory, and clear_old
                  sum_kwargs = dict(new_inventory)
                  sum_kwargs.update(connection_args)
                  sum_kwargs['clear_old'] = True
  
                  hostupdate = __salt__['zabbix.host_inventory_set'](hostid, **sum_kwargs)
                  ret['changes']['inventory'] = str(new_inventory)
                  if 'error' in hostupdate:
                      error.append(hostupdate['error'])
              if update_proxy:
                  hostupdate = __salt__['zabbix.host_update'](hostid, proxy_hostid=proxy_hostid, **connection_args)
                  ret['changes']['proxy_hostid'] = six.text_type(proxy_hostid)
                  if 'error' in hostupdate:
                      error.append(hostupdate['error'])
              if update_hostgroups:
                  hostupdate = __salt__['zabbix.host_update'](hostid, groups=groups, **connection_args)
                  ret['changes']['groups'] = six.text_type(groups)
                  if 'error' in hostupdate:
                      error.append(hostupdate['error'])
              if update_interfaces:
                  if hostinterfaces:
                      for interface in hostinterfaces:
                          __salt__['zabbix.hostinterface_delete'](interfaceids=interface['interfaceid'],
                                                                  **connection_args)
  
                  hostid = __salt__['zabbix.host_get'](name=host, **connection_args)[0]['hostid']
  
                  for interface in interfaces_formated:
                      updatedint = __salt__['zabbix.hostinterface_create'](hostid=hostid,
                                                                           ip=interface['ip'],
                                                                           dns=interface['dns'],
                                                                           main=interface['main'],
                                                                           type=interface['type'],
                                                                           useip=interface['useip'],
                                                                           port=interface['port'],
                                                                           **connection_args)
  
                      if 'error' in updatedint:
                          error.append(updatedint['error'])
  
                  ret['changes']['interfaces'] = six.text_type(interfaces_formated)
  
              ret['comment'] = comment_host_updated
  
          else:
              ret['comment'] = comment_host_exists
      else:
          host_create = __salt__['zabbix.host_create'](host,
                                                       groups,
                                                       interfaces_formated,
                                                       proxy_hostid=proxy_hostid,
                                                       inventory=new_inventory,
                                                       **connection_args)
  
          if 'error' not in host_create:
              ret['result'] = True
              ret['comment'] = comment_host_created
              ret['changes'] = changes_host_created
          else:
              ret['result'] = False
              ret['comment'] = comment_host_notcreated + six.text_type(host_create['error'])
  
      # error detected
      if error:
          ret['changes'] = {}
          ret['result'] = False
          ret['comment'] = six.text_type(error)
  
      return ret
  
  
  def absent(name, **kwargs):
      """
      Ensures that the host does not exists, eventually deletes host.
  
      .. versionadded:: 2016.3.0
  
      :param: name: technical name of the host
      :param _connection_user: Optional - zabbix user (can also be set in opts or pillar, see module's docstring)
      :param _connection_password: Optional - zabbix password (can also be set in opts or pillar, see module's docstring)
      :param _connection_url: Optional - url of zabbix frontend (can also be set in opts, pillar, see module's docstring)
  
      .. code-block:: yaml
  
          TestHostWithInterfaces:
              zabbix_host.absent
  
      """
      ret = {'name': name, 'changes': {}, 'result': False, 'comment': ''}
  
      # Comment and change messages
      comment_host_deleted = 'Host {0} deleted.'.format(name)
      comment_host_notdeleted = 'Unable to delete host: {0}. '.format(name)
      comment_host_notexists = 'Host {0} does not exist.'.format(name)
      changes_host_deleted = {name: {'old': 'Host {0} exists.'.format(name),
                                     'new': 'Host {0} deleted.'.format(name),
                                     }
                              }
      connection_args = {}
      if '_connection_user' in kwargs:
          connection_args['_connection_user'] = kwargs['_connection_user']
      if '_connection_password' in kwargs:
          connection_args['_connection_password'] = kwargs['_connection_password']
      if '_connection_url' in kwargs:
          connection_args['_connection_url'] = kwargs['_connection_url']
  
      host_exists = __salt__['zabbix.host_exists'](name, **connection_args)
  
      # Dry run, test=true mode
      if __opts__['test']:
          if not host_exists:
              ret['result'] = True
              ret['comment'] = comment_host_notexists
          else:
              ret['result'] = None
              ret['comment'] = comment_host_deleted
          return ret
  
      host_get = __salt__['zabbix.host_get'](name, **connection_args)
  
      if not host_get:
          ret['result'] = True
          ret['comment'] = comment_host_notexists
      else:
          try:
              hostid = host_get[0]['hostid']
              host_delete = __salt__['zabbix.host_delete'](hostid, **connection_args)
          except KeyError:
              host_delete = False
  
          if host_delete and 'error' not in host_delete:
              ret['result'] = True
              ret['comment'] = comment_host_deleted
              ret['changes'] = changes_host_deleted
          else:
              ret['result'] = False
              ret['comment'] = comment_host_notdeleted + six.text_type(host_delete['error'])
  
      return ret
  
  
  def assign_templates(host, templates, **kwargs):
      '''
      Ensures that templates are assigned to the host.
  
      .. versionadded:: 2017.7.0
  
      :param host: technical name of the host
      :param _connection_user: Optional - zabbix user (can also be set in opts or pillar, see module's docstring)
      :param _connection_password: Optional - zabbix password (can also be set in opts or pillar, see module's docstring)
      :param _connection_url: Optional - url of zabbix frontend (can also be set in opts, pillar, see module's docstring)
  
      .. code-block:: yaml
  
          add_zabbix_templates_to_host:
              zabbix_host.assign_templates:
                  - host: TestHost
                  - templates:
                      - "Template OS Linux"
                      - "Template App MySQL"
  
      '''
      connection_args = {}
      if '_connection_user' in kwargs:
          connection_args['_connection_user'] = kwargs['_connection_user']
      if '_connection_password' in kwargs:
          connection_args['_connection_password'] = kwargs['_connection_password']
      if '_connection_url' in kwargs:
          connection_args['_connection_url'] = kwargs['_connection_url']
  
      ret = {'name': host, 'changes': {}, 'result': False, 'comment': ''}
  
      # Set comments
      comment_host_templates_updated = 'Templates updated.'
      comment_host_templ_notupdated = 'Unable to update templates on host: {0}.'.format(host)
      comment_host_templates_in_sync = 'Templates already synced.'
  
      update_host_templates = False
      curr_template_ids = list()
      requested_template_ids = list()
      hostid = ''
  
      host_exists = __salt__['zabbix.host_exists'](host, **connection_args)
  
      # Fail out if host does not exist
      if not host_exists:
          ret['result'] = False
          ret['comment'] = comment_host_templ_notupdated
          return ret
  
      host_info = __salt__['zabbix.host_get'](host=host, **connection_args)[0]
      hostid = host_info['hostid']
  
      if not templates:
          templates = list()
  
      # Get current templateids for host
      host_templates = __salt__['zabbix.host_get'](hostids=hostid,
                                                   output='[{"hostid"}]',
                                                   selectParentTemplates='["templateid"]',
                                                   **connection_args)
      for template_id in host_templates[0]['parentTemplates']:
          curr_template_ids.append(template_id['templateid'])
  
      # Get requested templateids
      for template in templates:
          try:
              template_id = __salt__['zabbix.template_get'](host=template, **connection_args)[0]['templateid']
              requested_template_ids.append(template_id)
          except TypeError:
              ret['result'] = False
              ret['comment'] = 'Unable to find template: {0}.'.format(template)
              return ret
  
      # remove any duplications
      requested_template_ids = list(set(requested_template_ids))
  
      if set(curr_template_ids) != set(requested_template_ids):
          update_host_templates = True
  
      # Set change output
      changes_host_templates_modified = {host: {'old': 'Host templates: ' + ", ".join(curr_template_ids),
                                                'new': 'Host templates: ' + ', '.join(requested_template_ids)}}
  
      # Dry run, test=true mode
      if __opts__['test']:
          if update_host_templates:
              ret['result'] = None
              ret['comment'] = comment_host_templates_updated
          else:
              ret['result'] = True
              ret['comment'] = comment_host_templates_in_sync
          return ret
  
      # Attempt to perform update
      ret['result'] = True
      if update_host_templates:
          update_output = __salt__['zabbix.host_update'](hostid, templates=(requested_template_ids), **connection_args)
          if update_output is False:
              ret['result'] = False
              ret['comment'] = comment_host_templ_notupdated
              return ret
          ret['comment'] = comment_host_templates_updated
          ret['changes'] = changes_host_templates_modified
      else:
          ret['comment'] = comment_host_templates_in_sync
  
      return ret
  