#!/usr/bin/env python
'''Zabbix API Python CLI tool
print_hostgroup_cpu written by Christoph Haas <email@christoph-haas.de>
'''

from __future__ import print_function

# https://github.com/gescheit/scripts/blob/master/zabbix/zabbix_api.py
import zabbix_api
import sys
import os
import logging
import ConfigParser
import operator
import re
from pprint import pprint
from pydoc import render_doc
from optparse import OptionParser
from types import StringTypes

CONFIG = 'config'
LOGLEVEL = 'loglevel'
NAME = 'name'
PASSWORD = 'password'
URL = 'url'
VERBOSE = 'verbose'
FILTER = 'filter'
SECTION = 'section'

CONFIG_HELP = '''
The Config file used by %s should look like this:
-------------- CUT HERE -----------------
[zabbix]
username='zabbix-api-user'
password='not-your-password'
url='http://zabbix-api.example.com/'
validate_certs='True'

[dev]
username='zabbix-dev-api-user'
password='not-mine-either'
url='https://zabbix-dev.example.com/'
validate_certs=''
-------------- CUT HERE -----------------

By default, %s will use the [zabbix] paragraph, but if you use the
-s/--section option, you can tell %s to use a different section.
''' % (sys.argv[0], sys.argv[0], sys.argv[0])


class Tool(object):

    '''Zabbix API CLI tool.  All public functions should return a list
    which will be printed and summarized.'''
    _APPLICATION = 'application'

    _APPLICATIONID = 'applicationid'
    _APPLICATIONIDS = 'applicationids'
    _DISCOVERYRULE = 'discoveryrule'
    _DESCRIPTION = 'description'
    _ERROR = 'error'
    _EXTEND = 'extend'
    _GRAPH = 'graph'
    _FILTER = 'filter'
    _GROUPID = 'groupid'
    _GROUPIDS = 'groupids'
    _HOSTGROUP = 'hostgroup'
    _HOST = 'host'
    _HOSTID = 'hostid'
    _HOSTIDS = 'hostids'
    _ITEM = 'item'
    _HOSTS = 'hosts'
    _ITEMID = 'itemid'
    _ITEMPROTOTYPE = 'itemprototype'
    _ITEMIDS = 'itemids'
    _KEY = 'key_'
    _LIMIT = 'limit'
    _NAME = 'name'
    _OUTPUT = 'output'
    _PROXY = 'proxy'
    _PROXYID = 'proxyid'
    _PROXYIDS = 'proxyids'
    _SEARCH = 'search'
    _SEARCHWILDCARDSENABLED = 'searchwildcardsenabled'
    _SELECTHOSTS = 'selectHosts'
    _SELECTTRIGGERS = 'selectTriggers'
    _SORTFIELD = 'sortfield'
    _SORTORDER = 'sortorder'
    _STATUS = 'status'
    _TEMPLATE = 'template'
    _TEMPLATEID = 'templateid'
    _TEMPLATEIDS = 'templateids'
    _TEMPLATES = 'templates'
    _TRIGGERIDS = 'triggerids'
    _VALUE_TYPE = 'value_type'
    _TRIGGER = 'trigger'

    _TRIGGERPROTOTYPE = 'triggerprototype'
    _USERMACRO = 'usermacro'

    def login(self, url, username, password, validate_certs, timeout=60, loglevel=logging.ERROR, **_):
        '''Connect to Zabbix server'''
        self.z = zabbix_api.ZabbixAPI(server=url, timeout=timeout, validate_certs=validate_certs)
        self.z.set_log_level(loglevel)
        self.z.login(user=username, password=password)
        return

    def print_hostgroup_cpu(self, hg):
        '''This mostly a demonstration function that will print the
        5-minute load average for all of the hosts in a single
        hostgroup.'''
        item_name = 'system.cpu.load[,avg5]'

        for host in self.get_hostgroup_hosts(hg):
            hostname = host[self._HOST]
            print("Host:", hostname)
            print("Host-ID:", host[self._HOSTID])

            item = self.z.item.get({
                self._OUTPUT: self._EXTEND,
                self._HOSTIDS: host[self._HOSTID],
                self._FILTER: {self._KEY: item_name}})
            if item:
                print(item[0]['lastvalue'])
                print("Item-ID:", item[0][self._ITEMID])

                # Get history
                lastvalue = self.z.history.get({
                    'history': item[0]['value_type'],
                    self._ITEMIDS: item[0][self._ITEMID],
                    self._OUTPUT: self._EXTEND,
                    # Sort by timestamp from new to old
                    self._SORTFIELD: 'clock',
                    self._SORTORDER: 'DESC',
                    self._LIMIT: 1,  # Get only the first (=newest) entry
                })

                # CAVEAT! The history.get function must be told which type the
                # values are (float, text, etc.). The item.value_type contains
                # the number that needs to be passed to history.get.
                if lastvalue:
                    lastvalue = lastvalue[0]['value']

                print("Last value:", lastvalue)

            else:
                print("No item....")

            print("---------------------------")
        return []

    def __generic_getter(self, object_type, object_key, id_attribute,
                         filter_attribute='name', output='extend', **params):
        '''This is a generic getter.  Hopefully, it will reduce most of the other getters to one-liners.
        '''
        logging.debug("Tool.__generic_getter: %s %s %s %s %s %s", object_type, object_key,
                      id_attribute, filter_attribute, output, params)
        thing = getattr(self.z, object_type)
        params[self._OUTPUT] = output
        try:
            int(object_key)
            params[id_attribute] = int(object_key)
        except:
            # Really, this is mostly to handle item and itemprototypes neatly.
            if isinstance(filter_attribute, StringTypes):
                params[self._FILTER] = {filter_attribute: object_key}
            else:
                intermediate_results = []
                for key_name in filter_attribute:
                    params[self._FILTER] = {key_name: object_key}
                    intermediate_results.extend(thing.get(params))
                return intermediate_results

        return thing.get(params)

    def get_hostgroup(self, identifier):
        '''Look up a hostgroup by name or ID number.  Returns a list with a single element'''
        return self.__generic_getter(self._HOSTGROUP, identifier, self._GROUPIDS)

    def get_host(self, identifier):
        '''Look up a host by name or ID number.  Returns a list with a single element'''
        return self.__generic_getter(self._HOST, identifier, self._HOSTIDS)

    def delete_host(self, identifier):
        '''Look up a host by name or ID number, then delete it.  Returns a list with a single element'''
        hostids = [x[self._HOSTID] for x in self.__generic_getter(
            self._HOST, identifier, self._HOSTIDS)]
        return self.z.host.delete(hostids)

    def search_host(self, identifier):
        '''Search for hosts by partial name.  Returns a list.'''
        return self.z.host.get({self._SEARCH: {self._NAME: identifier}, self._OUTPUT: self._EXTEND})

    def get_proxy(self, identifier):
        '''Look up a proxy by name or ID number.  Returns a list with a single element'''
        return self.__generic_getter(self._PROXY, identifier, self._PROXYIDS, self._HOST)

    def get_proxy_interfaces(self, identifier):
        '''Look up a proxy by name or ID number.  Returns a list with a single element'''
        return self.__generic_getter(self._PROXY, identifier, self._PROXYIDS, self._HOST, selectInterfaces=self._EXTEND)

    def get_hosts_assigned_to_proxy(self, identifier):
        '''List hosts assigned to a proxy.  Takes the name of a proxy (e.g. YVR01).'''
        # First, the the proxyid as a list
        return self.__generic_getter(self._PROXY, identifier, None, self._HOST,
                                     selectHosts=self._EXTEND)[0][self._HOSTS]

    def get_item(self, identifier, *hostgroups):
        '''Look up an item by name or ID number.  Returns a list with a single element'''
        if hostgroups:
            return self.__generic_getter(self._ITEM, identifier, self._ITEMIDS, [self._NAME, self._KEY], group=hostgroups[0])
        else:
            return self.__generic_getter(self._ITEM, identifier, self._ITEMIDS, [self._NAME, self._KEY])

    def get_item_host(self, identifier, host):
        '''Look up an item by name or ID number for a given host.  Returns a list with a single element'''
        hosts = [x[self._HOSTID] for x in self.get_host(host)]
        return self.__generic_getter(self._ITEM, identifier, self._ITEMIDS, [self._NAME, self._KEY], hostids=hosts)

    def get_usermacros(self, identifier, filter_expression=None):
        '''Look up a host's macros by hostid.  Returns a list'''
        if filter_expression:
            return self.__generic_getter(self._USERMACRO, identifier, self._HOSTIDS,
                                         filter={self._MACRO: filter_expression})
        else:
            return self.__generic_getter(self._USERMACRO, identifier, self._HOSTIDS)

    def get_template(self, identifier):
        '''Look up a template by name or ID number.  Returns a list with a single element'''
        return self.__generic_getter(self._TEMPLATE, identifier, self._TEMPLATEIDS)

    def get_application(self, identifier, limit=0):
        '''Look up an appliation by name or ID number.  Returns a list.'''
        return self.__generic_getter(self._APPLICATION, identifier, self._APPLICATIONIDS, limit=limit)

    def get_discovery_rule(self, identifier, limit=0):
        '''Look up a discovery rule by name or ID number.  Returns a list.'''
        return self.__generic_getter(self._DISCOVERYRULE, identifier, self._ITEMIDS, limit=limit)

    def get_trigger(self, identifier):
        '''Look up a trigger by name or ID number.  Returns a list.'''
        return self.__generic_getter(self._TRIGGER, identifier, self._TRIGGERIDS)

    def get_graph(self, identifier):
        '''Look up a graph by name or ID number.  Returns a list.'''
        return self.__generic_getter(self._GRAPH, identifier, self._GRAPHIDS)

    def get_item_prototype(self, identifier, limit=0):
        '''Look up an itemprototype by name or ID number.  Returns a list'''
        return self.__generic_getter(self._ITEMPROTOTYPE, identifier, self._ITEMIDS, [self._NAME, self._KEY], limit=limit)

    def get_trigger_prototype(self, identifier, limit=0):
        '''Look up an triggerprototype by name or ID number.  Returns a list'''
        return self.__generic_getter(self._TRIGGERPROTOTYPE, identifier, self._TRIGGERIDS, limit=limit)

    def get_hostgroups(self, match_name=''):
        '''List all hostgroups, filtering via the (optional) parameter'''
        return self.z.hostgroup.get({self._SEARCH: {self._NAME: match_name}, self._OUTPUT: self._EXTEND})

    def get_hostgroup_hosts(self, hg):
        '''Look up all of the hosts which belong to a given hostgroup.'''
        hg = [x[self._GROUPID] for x in self.get_hostgroup(hg)]
        if not hg:
            return hg
        hosts = []
        for x in self.z.hostgroup.get({self._GROUPIDS: hg,
                                       self._SELECTHOSTS: self._EXTEND}):
            hosts.extend(x[self._HOSTS])
        hosts.sort(key=operator.itemgetter(self._HOST))
        return hosts

    def apply_template_to_hostgroup_members(self, template, hostgroup, doit=False):
        '''Given a template and a hostgroup, the template will be added to
        each host within the hostgroup, assuming a third parameter (which
        defaults to False) is set to some true value.  Otherwise, it will
        simply tell you what it *would* have done.'''
        template = self.get_template(template)
        hostlist = self.get_hostgroup_hosts(hostgroup)
        result = []
        if hostlist and doit:
            for host in hostlist:
                result.extend(self.link_template_to_host(template, host))
            return result
        print('would have applied %s to the following %d hosts:' %
              (str(template), len(hostlist)))
        return hostlist

    def get_host_applications(self, hostname):
        '''List all of the applications represented on the given host.'''
        hosts = [x[self._HOSTID] for x in self.get_host(hostname)]
        return self.z.application.get({self._HOSTIDS: hosts, self._OUTPUT: self._EXTEND})

    def get_host_discovery_rule(self, hostname, discoveryrule):
        '''Get the actual discovery rule objects associated with a hostname
        and discovery rule name.'''
        hosts = [x[self._HOSTID] for x in self.get_host(hostname)]
        return self.z.discoveryrule.get({self._FILTER: {self._NAME: discoveryrule},
                                         self._HOSTIDS: hosts, self._OUTPUT: self._EXTEND})

    def get_host_application_items(self, hostname, application):
        '''Given a hostname and an application name, return the list if
        items which match both.'''
        hosts = [x[self._HOSTID] for x in self. get_host(hostname)]
        apps = [x[self._APPLICATIONID] for x in self.z.application.get(
            {self._FILTER: {self._NAME: application}, self._HOSTIDS: hosts})]
        return self.z.item.get({self._HOSTIDS: hosts, self._APPLICATIONIDS: apps, self._OUTPUT: self._EXTEND})

    def get_host_application_triggers(self, hostname, application):
        '''Given a hostname and an application name, return the list if
        triggers which match both.'''
        hosts = [x[self._HOSTID] for x in self.get_host(hostname)]
        apps = self.z.application.get(
            {self._FILTER: {self._NAME: application}, self._HOSTIDS: hosts})
        return self.z.trigger.get({self._HOSTIDS: hosts, self._APPLICATIONIDS: apps})

    def delete_host_application_items(self, hostname, application, doit=False):
        '''Given a hostname and an application name, delete all the items
        which match both if the final parameter is True (defaults to
        False).'''
        items = [x[self._ITEMID]
                 for x in self.get_host_application_items(hostname, application)]
        if not items:
            return None
        if doit:
            return self.z.item.delete(items)
        print('would have deleted the following %d items:' % len(items))
        pprint(items)
        return []

    def delete_host_application_triggers(self, hostname, application, doit=False):
        '''Given a hostname and an application name, delete all the triggers
        which match both if the final parameter is True (defaults to
        False).'''
        triggers = [x[self._ITEMID]
                    for x in self.get_host_application_triggers(hostname, application)]
        if not triggers:
            return None
        if doit:
            return self.z.trigger.delete(triggers)
        print('would have deleted the following %d triggers:' % len(triggers))
        pprint(triggers)
        return []

    def delete_host_application(self, hostname, application, doit=False):
        '''Given a hostname and an application name, delete all the
        triggers and items which match both if the final parameter is True
        (defaults to False).'''
        self.delete_host_application_items(hostname, application, doit)
        self.delete_host_application_triggers(hostname, application, doit)
        hosts = [x[self._HOSTID] for x in self.get_host(hostname)]
        apps = [x[self._APPLICATIONID] for x in self.z.application.get(
            {self._HOSTIDS: hosts, self._FILTER: {self._NAME: application}})]
        if not apps:
            return None
        if doit:
            return self.z.application.delete(apps)
        print('would have deleted the following %d applications:' % len(apps))
        return apps

    def get_host_with_template_and_no_proxy(self, template):
        '''Return a list of hosts with the given template and proxy ID.'''
        template_objects = self.get_template(template)
        if not template_objects:
            print('No such template: %s' % template)
            return []
        template_id = template_objects[0][self._TEMPLATEID]
        host_list = self.z.host.get(
            {self._TEMPLATEIDS: template_id, self._OUTPUT: self._EXTEND})
        print(host_list[0])
        return [x for x in host_list if x['proxy_hostid'] == u'0']

    def assign_proxy_hosts(self, proxyname, *host_list):
        '''Assign hosts to a proxy.  Takes the name of a proxy (e.g. YVR01)
        and a list of hostnames.'''
        if not host_list:
            parser.print_help(
                "no hostname(s) passed in for adding to %s" % proxyname)
            exit()
        # First, the the proxyid as a list
        proxy = self.z.proxy.get(
            {self._FILTER: {self._HOST: proxyname}, self._OUTPUT: self._EXTEND})[0][self._PROXYID]
        # Now update the hosts from the CLI
        results = []
        for a in host_list:
            host = self.z.host.get(
                {self._FILTER: {self._NAME: a}, self._OUTPUT: self._EXTEND})[0]
            host['proxy_hostid'] = proxy
            results.append(self.z.host.update(host))
        return results

    def switch_proxy(self, source, dest):
        '''Move all hosts currently assigned to proxy1 over to proxy2..
        Takes the name of a source proxy and destination proxy (e.g. YVR01
        YVR02).'''
        # First, the the proxyid as a list
        dest = self.z.proxy.get(
            {self._FILTER: {self._HOST: dest}, self._OUTPUT: self._EXTEND})[0][self._PROXYID]
        # Now update the hosts from the CLI
        results = []
        for host in self.get_hosts_assigned_to_proxy(source):
            host['proxy_hostid'] = dest
            results.append(self.z.host.update(host))
        return results

    def get_item_history(self, i, limit=None):
        '''Get the history of data associated with one or more items.
        WARNING: if you pass in the name of an item, you'll probably crush the
        database as it tries to retrieve the history of every item with that name.
        '''
        items = self.get_item(i)
        params = {
            'history': items[0]['value_type'],
            self._ITEMIDS: [x[self._ITEMID] for x in items],
            self._OUTPUT: self._EXTEND,
            # Sort by timestamp from new to old
            self._SORTFIELD: 'clock',
            self._SORTORDER: 'DESC',
        }
        if limit:
            params[self._LIMIT] = limit
        return self.z.history.get(params)

    def get_host_items(self, hostname):
        '''Return all of the items for a single host.'''
        hosts = [x[self._HOSTID] for x in self.get_host(hostname)]
        return self.z.item.get({self._HOSTIDS: hosts, self._OUTPUT: self._EXTEND})

    def get_hostgroup_item(self, item, hostgroup):
        '''Return all of the items of the given name within a hostgroup'''
        hostgroup = self.get_hostgroup(hostgroup)[0][self._GROUPID]
        return self.z.item.get({self._FILTER: {self._NAME: item},
                                self._OUTPUT: self._EXTEND,
                                self._GROUPIDS: [hostgroup, ]})

    def get_host_graphs(self, hostname):
        '''Return all of the items for a single host.'''
        hosts = [x[self._HOSTID] for x in self.get_host(hostname)]
        return self.z.graph.get({self._HOSTIDS: hosts, self._OUTPUT: self._EXTEND})

    def get_items_with_template(self, item, template):
        '''Return matching items from hosts with the given template'''
        hosts = [x[self._HOSTID] for x in self.get_template_hosts(template)]
        return self.z.item.get({self._FILTER: {self._NAME: item}, self._HOSTIDS: hosts, self._OUTPUT: self._EXTEND})

    def mass_host_item_update(self, hostname):
        '''Special-purpose function written specifically to transition from the
        Mikoomi MySQL templates to a home-grown template without data loss.
        This will rename the item keys from the Mikoomi standard to the new
        one.  NEVER USE THIS.
        '''
        results = []
        hosts = [x[self._HOSTID] for x in self.get_host(hostname)]
        for item in self.z.item.get({self._HOSTIDS: hosts, self._OUTPUT: self._EXTEND, 'templated': 0}):
            if not 'mysql' == item[self._KEY][:5]:
                continue
            new_key = item[self._KEY].replace(' tables', '').replace('[', '.').replace(']', '').lower().replace(
                'mysql50', 'mysql').replace('mysql51', 'mysql').replace('mysql55', 'mysql').replace(
                    'mysql56', 'mysql').replace(' ', '_').replace(',', '_').replace(
                        '.setting.', '.var.').replace('mysql.status.state_', 'mysql.plist.state_')
            if new_key.count('.') < 2 and not new_key in [
                    'mysql.auto_increment_max',
                    'mysql.errors',
                    'mysql.errors_error',
                    'mysql.errors_error_warn',
                    'mysql.errors_warn',
            ]:
                new_key = new_key.replace('mysql.', 'mysql.status.')
            if item[self._KEY] != new_key:
                try:
                    results.append(
                        self.z.item.update({self._ITEMID: item[self._ITEMID], self._KEY: new_key.lower()}))
                except:
                    results.append("duplicate key: " + new_key)
        return results

    def mass_rename_host_items(self, host, match_re, replacement):
        '''Look up a host by name or ID number.  Rename all items that match the RE by substituting the replacement.'''
        replacer = re.compile(match_re)
        results = []
        for item in self.get_host_items(host):
            if item.has_key(self._NAME):
                new_name = replacer.sub(replacement, item[self._NAME])
                if new_name != item[self._NAME]:
                    results.append(
                        self.z.item.update({self._ITEMID: item[self._ITEMID], self._NAME: new_name}))
            new_name = replacer.sub(replacement, item[self._DESCRIPTION])
            if new_name != item[self._DESCRIPTION]:
                results.append(self.z.item.update(
                    {self._ITEMID: item[self._ITEMID], self._DESCRIPTION: new_name}))
        return results

    def delete_items_with_template(self, item, template, doit=False):
        '''Delete matching items from hosts with the given template'''
        hosts = [x[self._HOSTID] for x in self.get_template_hosts(template)]
        items = self.z.item.get(
            {self._FILTER: {self._NAME: item}, self._HOSTIDS: hosts, self._OUTPUT: self._EXTEND})
        if not items:
            return []
        itemids = [x[self._ITEMID] for x in items]
        if not itemids:
            return []
        if doit:
            print(self.z.item.delete(itemids))
            return []
        print('would have deleted the following %d items:' % len(items))
        return items

    def delete_host_item(self, hostname, item, doit=False):
        '''From a given host, Delete the item or items that matched the passed in value (ID or name).'''
        hosts = [x[self._HOSTID] for x in self.get_host(hostname)]
        items = [x[self._ITEMID]
                 for x in self.get_item(item) if x[self._HOSTID] in hosts]
        if not items:
            return None
        if doit:
            return self.z.item.delete(items)
        print('would have deleted the following %d items:' % len(items))
        return items

    def delete_items(self, item, doit=False):
        '''Delete the item or items that match the passed in value (ID or name).'''
        items = [x[self._ITEMID] for x in self.get_item(item)]
        if not items:
            return []
        if doit:
            return self.z.item.delete(items)
        print('would have deleted the following %d items:' % len(items))
        pprint(items)
        return items

    def get_unsupported_item_hosts(self, item, error_string):
        '''One-off function for looking for a particular item.  Do not use.'''
        all_items = self.z.item.get({self._FILTER: {self._NAME: item, self._ERROR: error_string},
                                     self._OUTPUT: self._EXTEND})
        for a in all_items:
            print(a[self._NAME], a[self._ERROR], a['key_'])
        items = [x[self._ITEMID] for x in all_items]
        return self.z.host.get({self._ITEMIDS: items, self._OUTPUT: self._EXTEND})

    def get_template_items(self, template):
        '''Look up a template by name or ID number.  Returns a list of items belonging to
        that template (including linked items)'''
        template_objects = [x[self._TEMPLATEID]
                            for x in self.get_template(template)]
        return self.z.item.get({self._HOSTIDS: template_objects, self._OUTPUT: self._EXTEND})

    def mass_rename_template_items(self, template, match_re, replacement):
        '''Look up a template by name or ID number.  Rename all items that match the RE
        by substituting the replacement.'''
        replacer = re.compile(match_re)
        results = []
        for item in self.get_template_items(template):
            if item.has_key(self._NAME):
                new_name = replacer.sub(replacement, item[self._NAME])
                if new_name != item[self._NAME]:
                    results.append(
                        self.z.item.update({self._ITEMID: item[self._ITEMID], self._NAME: new_name}))
            new_name = replacer.sub(replacement, item[self._DESCRIPTION])
            if new_name != item[self._DESCRIPTION]:
                results.append(self.z.item.update(
                    {self._ITEMID: item[self._ITEMID], self._DESCRIPTION: new_name}))
        return results

    def mass_rename_template_item_keys(self, template, match_re, replacement):
        '''Look up a template by name or ID number.  Change all the keys that match
        the RE by substituting the replacement.'''
        replacer = re.compile(match_re)
        results = []
        for item in self.get_template_items(template):
            new_key = replacer.sub(replacement, item[self._KEY])
            if new_key != item[self._KEY]:
                results.append(
                    self.z.item.update({self._ITEMID: item[self._ITEMID], self._KEY: new_key}))
        return results

    def mass_template_item_update(self, template, match_re):
        '''NEVER USE THIS.'''
        matcher = re.compile(match_re)
        results = []
        for item in self.get_template_items(template):
            if matcher.search(item[self._KEY]):
                new_key = item[self._KEY].replace('[', '.').replace(']', '')
                results.append(
                    self.z.item.update({self._ITEMID: item[self._ITEMID], self._KEY: new_key.lower()}))
        return results

    def mass_template_item_replace_attribute(self, template, attribute, old_value, new_value, filter_re=None):
        '''USING THIS CAN DESTROY YOUR TEMPLATE.

        Change ATTRIBUTE on all (or filtered) items from OLD_VALUE to NEW_VALUE.
        :param template: template to update (name or id)
        :param attribute: attribute of each item to update
        :param old_value: current value of the attribute (for limiting purposes)
        :param new_value: new value of the attribute
        :param filter_re: (optional) only update items with name/description matching this.
        '''
        results = []
        if filter_re:
            filter_re = re.compile(filter_re)
        for item in self.get_template_items(template):
            if filter_re:
                if not filter_re.search(item.get(self._NAME, '')) and not filter_re.search(item.get(self._DESCRIPTION, '')):
                    continue
            if item[attribute] == old_value:
                results.append(
                    self.z.item.update({self._ITEMID: item[self._ITEMID], attribute: new_value}))
        return results

    def get_template_hosts(self, t):
        '''Look up all the hosts that use the given template.'''
        template = [x[self._TEMPLATEID] for x in self.get_template(t)]
        return self.z.host.get({self._TEMPLATEIDS: template, self._OUTPUT: self._EXTEND})

    def search_template(self, term, **kwargs):
        '''Search for templates matching the given fragment.  Returns a list'''
        params = {self._SEARCH: {self._NAME: term, self._SEARCHWILDCARDSENABLED: True},
                  self._OUTPUT: self._EXTEND}
        params.update(kwargs)
        data = self.z.template.get(params)
        return data

    def link_template_to_host(self, template, host):
        '''Link the given template to the given host.  Takes names or IDs.'''
        template = self.get_template(template)
        host = self.get_host(host)
        return self.z.template.massadd({self._TEMPLATES: template, self._HOSTS: host})

    def link_template_parent_to_child(self, parent, child):
        '''Makes the child template a linked template of the parent template.'''
        pid = self.get_template(parent)
        if not pid:
            raise Exception('No such template: %s' % pid)
        pid = pid[0]
        cid = self.get_template(child)
        if not cid:
            raise Exception('No such template: %s' % cid)
        pid[self._TEMPLATES] = cid
        pprint(pid)
        return self.z.template.update(pid)

    def get_template_triggers_without_urls(self, group):
        '''Lists Triggers with an empty URL field.  Pass in the name or ID of a Templates hostgroup'''
        tmpl_hg = self.get_hostgroup(group)[0]['groupid']

        output = []
        tmpls = self.z.template.get(
            {self._GROUPIDS: tmpl_hg, self._OUTPUT: self._EXTEND, self._SELECTTRIGGERS: self._EXTEND})
        for tmpl in tmpls:
            for trig in tmpl['triggers']:
                if trig['url'] == '':
                    output.append(tmpl['name'] + ' - ' + trig['description'])
        return output

    def add_item_to_template(self, h, **kwargs):
        '''Create a new item.  Takes a template and dictionary of values.'''
        h = self.get_template(h)[0]
        kwargs[self._HOSTID] = h[self._TEMPLATEID]
        return self.z.item.create(kwargs)

    def get_application_items(self, app, limit=10):
        '''Look up all the items which are part of the given application, limit 10 by default.'''
        appids = [x[self._APPLICATIONID] for x in self.get_application(app)]
        return self.z.item.get({self._APPLICATIONIDS: appids, self._OUTPUT: self._EXTEND, self._LIMIT: limit})

    def delete_application_from_hosts(self, application, limit=1, doit=False):
        '''Delete an application from some number of hosts, limited to one
        by default, and also limited to dry-run by default.'''
        apps = self.get_application(application)
        limit = int(limit)
        count = 0
        resultset = []
        while apps and (count < limit):
            a = apps.pop(0)
            h = self.get_host(a[self._HOSTID])
            if not h:
                continue      # It was a template
            count += 1
            resultset.append(
                self.delete_host_application(h[0][self._NAME], application, doit))
        return resultset

    def delete_discovery_rule(self, hg, doit=False):
        '''Delete a discovery rule from all hosts/templates.  Defaults to dry-run only.'''
        try:
            int(hg)
            rules = [hg, ]
        except:
            rules = [x[self._ITEMID] for x in self.get_discovery_rule(hg)]
        if not rules:
            return None
        if doit:
            return self.z.discoveryrule.delete(rules)
        print('would have deleted the following %d rules:' % len(rules))
        return rules

    def delete_discovery_rule_hosts(self, hg, limit=1, doit=False):
        '''Deletes the indicated discovery rule from hosts, leaving the template untouched.'''
        rules = self.get_discovery_rule(hg)
        limit = int(limit)
        count = 0
        resultset = []
        while rules and (count < limit):
            a = rules.pop(0)
            h = self.get_host(a[self._HOSTID])
            if not h:
                continue      # It was a template
            count += 1
            resultset.append(self.delete_discovery_rule(a[self._ITEMID], doit))
        return resultset

    def get_trigger_hosts(self, trigger):
        '''List all the hosts with the given trigger.'''
        triggers = self.get_trigger(trigger)
        return self.z.trigger.get({self._TRIGGERIDS: triggers, self._OUTPUT: self._EXTEND})

    def delete_trigger(self, trigger, doit=False):
        '''Delete the given trigger.  Defaults to dry-run only.'''
        try:
            int(trigger)
            triggers = [trigger, ]
        except:
            triggers = [x[self._ITEMID] for x in self.get_trigger(trigger)]
        if not triggers:
            return None
        if doit:
            return self.z.trigger.delete(triggers)
        print('would have deleted the following %d triggers:' % len(triggers))
        return triggers

    def find_trigger(self, term, limit=10):
        '''Search trigger descriptions for a given string.'''
        return self.z.trigger.get({self._SEARCH: {self._DESCRIPTION: term, self._SEARCHWILDCARDSENABLED: True},
                                   self._OUTPUT: self._EXTEND, self._LIMIT: limit})

    def special_lld_in_template(self, template_name, identifier, lld_name):
        '''Add a new LLD rule with the requisite, e.g.

        zabbix_tool special_lld_in_template test-template-discovery-geek bletchley 'Discovery - Unlikely Mountpoints'

        (with the current code) will create a discovery rule in
        test-template-discovery-geek that will use BLETCHLEY as the
        differentiator in the key, with 8 items and 4 triggers.  Assuming,
        of course, that it all completes successfully.  There is a great
        deal of contention on the `ids` table in pre-2.2.
        :param template_name: Name of the template to be modified
        :param key: unique value used to differentiate this LLD from others
        :param lld_name: Name for the LLD to be added
        :returns: (lld object, itemprototypes object list, triggerprototypes object list)
        '''
        template = self.get_template(template_name)[0]
        lld = self.z.discoveryrule.create({
            u'key_': "vfs.fs.discovery_llnw[%s]" % identifier,
            self._NAME: lld_name,
            self._HOSTID: template[self._HOSTID],
            'type': "0",
            'delay': 21600,
            'filter': r'{#%s}:^\/d\d+' % identifier.upper(),
            'status': '1',  # 1 is disabled
            'interfaceid': "0",
        })
        lld = self.get_discovery_rule(lld[self._ITEMIDS][0], 1)[0]

        itemprototypes = self.z.itemprototype.create([
            {'ruleid': lld[self._ITEMID],
             'delay': '1800',
             'hostid': lld[self._HOSTID],
             'interfaceid': "0",
             'key_': 'vfs.fs.size[{#%s},free]' % identifier.upper(),
             'name': 'Free disk space on {#%s}' % identifier.upper(),
             'type': '0',
             'value_type': '3',
             'history': '7',
             'units': 'B'},
            {'ruleid': lld[self._ITEMID],
             'hostid': lld[self._HOSTID],
             'interfaceid': "0",
             'type': '0',
             'history': '7',
             'delay': '1800',
             'key_': 'vfs.fs.size[{#%s},pfree]' % identifier.upper(),
             'name': 'Free disk space on {#%s} in %%' % identifier.upper(),
             'units': '%',
             'value_type': '0'},
            {'ruleid': lld[self._ITEMID],
             'hostid': lld[self._HOSTID],
             'interfaceid': "0",
             'type': '0',
             'history': '7',
             'delay': '900',
             'key_': 'vfs.fs.inode[{#%s},pfree]' % identifier.upper(),
             'name': 'Free number of inodes on {#%s} in %%' % identifier.upper(),
             'units': '%',
             'value_type': '0'},
            {'ruleid': lld[self._ITEMID],
             'hostid': lld[self._HOSTID],
             'interfaceid': "0",
             'type': '0',
             'history': '7',
             'delay': '600',
             'key_': 'mountpoint[{#%s}]' % identifier.upper(),
             'name': 'Mount point status for {#%s}' % identifier.upper(),
             'units': '',
             'value_type': '3'},
            {'ruleid': lld[self._ITEMID],
             'hostid': lld[self._HOSTID],
             'interfaceid': "0",
             'type': '0',
             'history': '7',
             'delay': '21600',
             'key_': 'vfs.fs.size[{#%s},total]' % identifier.upper(),
             'name': 'Total disk space on {#%s}' % identifier.upper(),
             'units': 'B',
             'value_type': '3'},
            {'ruleid': lld[self._ITEMID],
             'hostid': lld[self._HOSTID],
             'interfaceid': "0",
             'type': '0',
             'history': '7',
             'delay': '21600',
             'key_': 'vfs.fs.inode[{#%s},total]' % identifier.upper(),
             'name': 'Total number of inodes on {#%s}' % identifier.upper(),
             'units': '',
             'value_type': '3'},
            {'ruleid': lld[self._ITEMID],
             'hostid': lld[self._HOSTID],
             'interfaceid': "0",
             'type': '0',
             'history': '7',
             'delay': '1800',
             'key_': 'vfs.fs.size[{#%s},used]' % identifier.upper(),
             'name': 'Used disk space on {#%s}' % identifier.upper(),
             'units': 'B',
             'value_type': '3'},
            {'ruleid': lld[self._ITEMID],
             'hostid': lld[self._HOSTID],
             'interfaceid': "0",
             'type': '0',
             'history': '7',
             'delay': '600',
             'key_': 'vfs.fs.size[{#%s},pused]' % identifier.upper(),
             'name': 'Used disk space on {#%s} in %%' % identifier.upper(),
             'units': '%',
             'value_type': '0'}
        ])

        triggerprototypes = self.z.triggerprototype.create([
            {'description': 'directory {#%s} does not exist on {HOSTNAME}' % identifier.upper(),
             'expression': '{%s:mountpoint[{#%s}].last(0)}=2' % (template[self._HOST], identifier.upper()),
             'priority': 1},
            {'description': 'directory {#%s} is not a moint point on {HOSTNAME}' % identifier.upper(),
             'expression': '{%s:mountpoint[{#%s}].last(0)}=1' % (template[self._HOST], identifier.upper()),
             'priority': 1},
            {'description': 'Disk utilization on {#%s} above 98%% on {HOSTNAME}' % identifier.upper(),
             'expression': '{%s:vfs.fs.size[{#%s},pused].last(0)}>98' % (template[self._HOST], identifier.upper()),
             'priority': 1},
            {'description': 'Free Inodes on {#%s} below 10%% on {HOSTNAME}' % identifier.upper(),
             'expression': '{%s:vfs.fs.inode[{#%s},pfree].last(0)}<10' % (template[self._HOST], identifier.upper()),
             'priority': 1},   # Information
        ])

        return (lld, itemprototypes, triggerprototypes)


class MyParser(OptionParser):

    '''Standard parser with a non-re-formatted epilog
    '''

    def format_epilog(self, formatter):
        return self.epilog


def parse_config_file(option_set):
    '''Parse the config file out here.
    '''
    config = ConfigParser.ConfigParser()
    try:
        config.read(option_set[CONFIG])
    except Exception, e:
        raise Exception('Unable to parse the configuration file(%s): %s\n\n' %
                        (option_set[CONFIG], e))
    if not option_set[SECTION] in config.sections():
        raise SystemExit(
            'No [%s] section in the configuration file' % option_set[SECTION])

    for (option_key, option_value) in config.items(option_set[SECTION], 1):
        option_set[option_key] = option_value.strip('"').strip("'")

if __name__ == '__main__':
    tool = Tool()
    # Handle command-line options
    parser = MyParser(epilog=render_doc(tool, '%s') + CONFIG_HELP)
    parser.add_option("-c", "--config", dest="config", default=os.environ['HOME'] + '/.zabbix',
                      help="CONFIG file", metavar="CONFIG")
    parser.add_option("-s", "--section", dest="section", default='zabbix',
                      help="Section of config file to use file", metavar="SECTION")
    parser.add_option("-l", "--loglevel",
                      dest=LOGLEVEL, default=logging.ERROR, type="int",
                      help="Set logging level (0-50), defaults to 40 (only errors)")
    parser.add_option("--filter",
                      dest=FILTER, action="append",
                      help="Only print the named field of the returned items")

    (options, args) = parser.parse_args()
    options = vars(options)
    parse_config_file(options)
    tool.login(**options)      # Cheating!
    if not args:
        parser.print_help()
        exit()
    func = args.pop(0)
    try:
        cmd = getattr(tool, func)
    except:
        print('No such function: %s' % func)
        parser.print_help()
        exit()

    try:
        kwargs = {}
        func_args = []
        for arg in args:
            if '=' in arg:
                key, value = arg.split('=', 1)
                kwargs[key] = value
            else:
                func_args.append(arg)
        func_args = tuple(func_args)
        function_result = cmd(*func_args, **kwargs)
    except TypeError:
        print(render_doc(cmd))
        exit()
    if options[FILTER]:
        for name in function_result:
            filtered_result = ' '.join(
                [name.get(xxxx, None) for xxxx in options[FILTER]])
            if filtered_result:
                print(filtered_result)
    else:
        print('********* Results ********')
        pprint(function_result)
        print(len(function_result))
