docs: def present(name, params, static_host_list=True, **kwargs):
      '''
      Creates Zabbix Template object or if differs update it according defined parameters. See Zabbix API documentation.
  
      Zabbix API version: >3.0
  
      :param name: Zabbix Template name
      :param params: Additional parameters according to Zabbix API documentation
      :param static_host_list: If hosts assigned to the template are controlled
          only by this state or can be also assigned externally
      :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)
  
      .. note::
  
          If there is a need to get a value from current zabbix online (e.g. ids of host groups you want the template
          to be associated with), put a dictionary with two keys "query_object" and "query_name" instead of the value.
          In this example we want to create template named "Testing Template", assign it to hostgroup Templates,
          link it to two ceph nodes and create a macro.
  
      .. note::
  
          IMPORTANT NOTE:
          Objects (except for template name) are identified by name (or by other key in some exceptional cases)
          so changing name of object means deleting old one and creating new one with new ID !!!
  
      .. note::
  
          NOT SUPPORTED FEATURES:
              - linked templates
              - trigger dependencies
              - groups and group prototypes for host prototypes
  
      SLS Example:
  
      .. code-block:: yaml
  
          zabbix-template-present:
              zabbix_template.present:
                  - name: Testing Template
                  # Do not touch existing assigned hosts
                  # True will detach all other hosts than defined here
                  - static_host_list: False
                  - params:
                      description: Template for Ceph nodes
                      groups:
                          # groups must already exist
                          # template must be at least in one hostgroup
                          - groupid:
                              query_object: hostgroup
                              query_name: Templates
                      macros:
                          - macro: "{$CEPH_CLUSTER_NAME}"
                            value: ceph
                      hosts:
                          # hosts must already exist
                          - hostid:
                              query_object: host
                              query_name: ceph-osd-01
                          - hostid:
                              query_object: host
                              query_name: ceph-osd-02
                      # templates:
                      # Linked templates - not supported by state module but can be linked manually (will not be touched)
  
                      applications:
                          - name: Ceph OSD
                      items:
                          - name: Ceph OSD avg fill item
                            key_: ceph.osd_avg_fill
                            type: 2
                            value_type: 0
                            delay: 60
                            units: '%'
                            description: 'Average fill of OSD'
                            applications:
                                - applicationid:
                                    query_object: application
                                    query_name: Ceph OSD
                      triggers:
                          - description: "Ceph OSD filled more that 90%"
                            expression: "{{'{'}}Testing Template:ceph.osd_avg_fill.last(){{'}'}}>90"
                            priority: 4
                      discoveries:
                          - name: Mounted filesystem discovery
                            key_: vfs.fs.discovery
                            type: 0
                            delay: 60
                            itemprototypes:
                                - name: Free disk space on {{'{#'}}FSNAME}
                                  key_: vfs.fs.size[{{'{#'}}FSNAME},free]
                                  type: 0
                                  value_type: 3
                                  delay: 60
                                  applications:
                                      - applicationid:
                                          query_object: application
                                          query_name: Ceph OSD
                            triggerprototypes:
                                - description: "Free disk space is less than 20% on volume {{'{#'}}FSNAME{{'}'}}"
                                  expression: "{{'{'}}Testing Template:vfs.fs.size[{{'{#'}}FSNAME},free].last(){{'}'}}<20"
                      graphs:
                          - name: Ceph OSD avg fill graph
                            width: 900
                            height: 200
                            graphtype: 0
                            gitems:
                                - color: F63100
                                  itemid:
                                    query_object: item
                                    query_name: Ceph OSD avg fill item
                      screens:
                          - name: Ceph
                            hsize: 1
                            vsize: 1
                            screenitems:
                                - x: 0
                                  y: 0
                                  resourcetype: 0
                                  resourceid:
                                      query_object: graph
                                      query_name: Ceph OSD avg fill graph
      '''
      zabbix_id_mapper = __salt__['zabbix.get_zabbix_id_mapper']()
  
      dry_run = __opts__['test']
      ret = {'name': name, 'result': False, 'comment': '', 'changes': {}}
      params['host'] = name
  
      del CHANGE_STACK[:]
  
      # Divide template yaml definition into parts
      # - template definition itself
      # - simple template components
      # - components that have other sub-components
      #   (e.g. discoveries - where parent ID is needed in advance for sub-component manipulation)
      template_definition = {}
      template_components = {}
      discovery_components = []
  
      for attr in params:
          if attr in TEMPLATE_COMPONENT_ORDER and six.text_type(attr) != 'discoveries':
              template_components[attr] = params[attr]
  
          elif six.text_type(attr) == 'discoveries':
              d_rules = []
              for d_rule in params[attr]:
                  d_rule_components = {'query_pid': {'component': attr,
                                                     'filter_val': d_rule[TEMPLATE_COMPONENT_DEF[attr]['filter']]}}
                  for proto_name in DISCOVERYRULE_COMPONENT_ORDER:
                      if proto_name in d_rule:
                          d_rule_components[proto_name] = d_rule[proto_name]
                          del d_rule[proto_name]
  
                  discovery_components.append(d_rule_components)
                  d_rules.append(d_rule)
  
              template_components[attr] = d_rules
  
          else:
              template_definition[attr] = params[attr]
  
      # if a component is not defined, it means to remove existing items during update (empty list)
      for attr in TEMPLATE_COMPONENT_ORDER:
          if attr not in template_components:
              template_components[attr] = []
  
      # if a component is not defined, it means to remove existing items during update (empty list)
      for attr in TEMPLATE_RELATIONS:
          template_definition[attr] = params[attr] if attr in params and params[attr] else []
  
      defined_obj = __salt__['zabbix.substitute_params'](template_definition, **kwargs)
      log.info('SUBSTITUTED template_definition: %s', six.text_type(json.dumps(defined_obj, indent=4)))
  
      tmpl_get = __salt__['zabbix.run_query']('template.get',
                                              {'output': 'extend', 'selectGroups': 'groupid', 'selectHosts': 'hostid',
                                               'selectTemplates': 'templateid', 'selectMacros': 'extend',
                                               'filter': {'host': name}},
                                              **kwargs)
      log.info('TEMPLATE get result: %s', six.text_type(json.dumps(tmpl_get, indent=4)))
  
      existing_obj = __salt__['zabbix.substitute_params'](tmpl_get[0], **kwargs) \
          if tmpl_get and len(tmpl_get) == 1 else False
  
      if existing_obj:
          template_id = existing_obj[zabbix_id_mapper['template']]
  
          if not static_host_list:
              # Prepare objects for comparison
              defined_wo_hosts = defined_obj
              if 'hosts' in defined_obj:
                  defined_hosts = defined_obj['hosts']
                  del defined_wo_hosts['hosts']
              else:
                  defined_hosts = []
  
              existing_wo_hosts = existing_obj
              if 'hosts' in existing_obj:
                  existing_hosts = existing_obj['hosts']
                  del existing_wo_hosts['hosts']
              else:
                  existing_hosts = []
  
              # Compare host list separately from the rest of the object comparison since the merged list is needed for
              # update
              hosts_list = _diff_and_merge_host_list(defined_hosts, existing_hosts)
  
              # Compare objects without hosts
              diff_params = __salt__['zabbix.compare_params'](defined_wo_hosts, existing_wo_hosts, True)
  
              # Merge comparison results together
              if ('new' in diff_params and 'hosts' in diff_params['new']) or hosts_list:
                  diff_params['new']['hosts'] = hosts_list
  
          else:
              diff_params = __salt__['zabbix.compare_params'](defined_obj, existing_obj, True)
  
          if diff_params['new']:
              diff_params['new'][zabbix_id_mapper['template']] = template_id
              diff_params['old'][zabbix_id_mapper['template']] = template_id
              log.info('TEMPLATE: update params: %s', six.text_type(json.dumps(diff_params, indent=4)))
  
              CHANGE_STACK.append({'component': 'template', 'action': 'update', 'params': diff_params['new']})
              if not dry_run:
                  tmpl_update = __salt__['zabbix.run_query']('template.update', diff_params['new'], **kwargs)
                  log.info('TEMPLATE update result: %s', six.text_type(tmpl_update))
  
      else:
          CHANGE_STACK.append({'component': 'template', 'action': 'create', 'params': defined_obj})
          if not dry_run:
              tmpl_create = __salt__['zabbix.run_query']('template.create', defined_obj, **kwargs)
              log.info('TEMPLATE create result: %s', tmpl_create)
              if tmpl_create:
                  template_id = tmpl_create['templateids'][0]
  
      log.info('\n\ntemplate_components: %s', json.dumps(template_components, indent=4))
      log.info('\n\ndiscovery_components: %s', json.dumps(discovery_components, indent=4))
      log.info('\n\nCurrent CHANGE_STACK: %s', six.text_type(json.dumps(CHANGE_STACK, indent=4)))
  
      if existing_obj or not dry_run:
          for component in TEMPLATE_COMPONENT_ORDER:
              log.info('\n\n\n\n\nCOMPONENT: %s\n\n', six.text_type(json.dumps(component)))
              # 1) query for components which belongs to the template
              existing_c_list = _get_existing_template_c_list(component, template_id, **kwargs)
              existing_c_list_subs = __salt__['zabbix.substitute_params'](existing_c_list, **kwargs) \
                  if existing_c_list else []
  
              if component in template_components:
                  defined_c_list_subs = __salt__['zabbix.substitute_params'](
                      template_components[component],
                      extend_params={TEMPLATE_COMPONENT_DEF[component]['qselectpid']: template_id},
                      filter_key=TEMPLATE_COMPONENT_DEF[component]['filter'],
                      **kwargs)
              else:
                  defined_c_list_subs = []
              # 2) take lists of particular component and compare -> do create, update and delete actions
              _manage_component(component, template_id, defined_c_list_subs, existing_c_list_subs, **kwargs)
  
          log.info('\n\nCurrent CHANGE_STACK: %s', six.text_type(json.dumps(CHANGE_STACK, indent=4)))
  
          for d_rule_component in discovery_components:
              # query for parent id -> "query_pid": {"filter_val": "vfs.fs.discovery", "component": "discoveries"}
              q_def = d_rule_component['query_pid']
              c_def = TEMPLATE_COMPONENT_DEF[q_def['component']]
              q_object = c_def['qtype']
              q_params = dict(c_def['output'])
              q_params.update({c_def['qselectpid']: template_id})
              q_params.update({'filter': {c_def['filter']: q_def['filter_val']}})
  
              parent_id = __salt__['zabbix.get_object_id_by_params'](q_object, q_params, **kwargs)
  
              for proto_name in DISCOVERYRULE_COMPONENT_ORDER:
                  log.info('\n\n\n\n\nPROTOTYPE_NAME: %s\n\n', six.text_type(json.dumps(proto_name)))
                  existing_p_list = _get_existing_template_c_list(proto_name, parent_id, **kwargs)
                  existing_p_list_subs = __salt__['zabbix.substitute_params'](existing_p_list, **kwargs)\
                      if existing_p_list else []
  
                  if proto_name in d_rule_component:
                      defined_p_list_subs = __salt__['zabbix.substitute_params'](
                          d_rule_component[proto_name],
                          extend_params={c_def['qselectpid']: template_id},
                          **kwargs)
                  else:
                      defined_p_list_subs = []
  
                  _manage_component(proto_name,
                                    parent_id,
                                    defined_p_list_subs,
                                    existing_p_list_subs,
                                    template_id=template_id,
                                    **kwargs)
  
      log.info('\n\nCurrent CHANGE_STACK: %s', six.text_type(json.dumps(CHANGE_STACK, indent=4)))
  
      if not CHANGE_STACK:
          ret['result'] = True
          ret['comment'] = 'Zabbix Template "{0}" already exists and corresponds to a definition.'.format(name)
      else:
          tmpl_action = next((item for item in CHANGE_STACK
                              if item['component'] == 'template' and item['action'] == 'create'), None)
          if tmpl_action:
              ret['result'] = True
              if dry_run:
                  ret['comment'] = 'Zabbix Template "{0}" would be created.'.format(name)
                  ret['changes'] = {name: {'old': 'Zabbix Template "{0}" does not exist.'.format(name),
                                           'new': 'Zabbix Template "{0}" would be created '
                                                  'according definition.'.format(name)}}
              else:
                  ret['comment'] = 'Zabbix Template "{0}" created.'.format(name)
                  ret['changes'] = {name: {'old': 'Zabbix Template "{0}" did not exist.'.format(name),
                                           'new': 'Zabbix Template "{0}" created according definition.'.format(name)}}
          else:
              ret['result'] = True
              if dry_run:
                  ret['comment'] = 'Zabbix Template "{0}" would be updated.'.format(name)
                  ret['changes'] = {name: {'old': 'Zabbix Template "{0}" differs.'.format(name),
                                           'new': 'Zabbix Template "{0}" would be updated '
                                                  'according definition.'.format(name)}}
              else:
                  ret['comment'] = 'Zabbix Template "{0}" updated.'.format(name)
                  ret['changes'] = {name: {'old': 'Zabbix Template "{0}" differed.'.format(name),
                                           'new': 'Zabbix Template "{0}" updated according definition.'.format(name)}}
  
      return ret