From 5d0805b25a1c764319a7bc8ba3e77f25f4f8a134 Mon Sep 17 00:00:00 2001 From: Yannig Date: Mon, 20 Feb 2017 19:44:07 +0100 Subject: [PATCH] Add Nagios livestatus inventory plugin. (#12342) * Add Nagios livestatus inventory plugin. * Add new capabilities for the nagios_livestatus inventory: - host_field: set the name returned (default: 'name') - group_field: set the field used for group (default: 'groups') - host_filter: filter host using this filter (default: None) To be more consistent, prefix was renamed into var_prefix. * Fix py34 runtests errors against print call. --- contrib/inventory/nagios_livestatus.ini | 41 ++++++ contrib/inventory/nagios_livestatus.py | 175 ++++++++++++++++++++++++ 2 files changed, 216 insertions(+) create mode 100644 contrib/inventory/nagios_livestatus.ini create mode 100755 contrib/inventory/nagios_livestatus.py diff --git a/contrib/inventory/nagios_livestatus.ini b/contrib/inventory/nagios_livestatus.ini new file mode 100644 index 0000000000..3ff7408cd7 --- /dev/null +++ b/contrib/inventory/nagios_livestatus.ini @@ -0,0 +1,41 @@ +# Ansible Nagios external inventory script settings +# +# To get all available possibilities, check following URL: +# http://www.naemon.org/documentation/usersguide/livestatus.html +# https://mathias-kettner.de/checkmk_livestatus.html +# + +[local] +# Livestatus URI +# Example for default naemon livestatus unix socket : +# livestatus_uri=unix:/var/cache/naemon/live + +[remote] + +# default field name for host: name +# Uncomment to override: +# host_field=address +# +# default field group for host: groups +# Uncomment to override: +# group_field=state +# default fields retrieved: address, alias, display_name, childs, parents +# To override, uncomment the following line +# fields_to_retrieve=address,alias,display_name +# +# default variable prefix: livestatus_ +# To override, uncomment the following line +# var_prefix=naemon_ +# +# default filter: None +# +# Uncomment to override +# +# All host with state = OK +# host_filter=state = 0 +# Warning: for the moment, you can use only one filter at a time. You cannot combine various conditions. +# +# All host in groups Linux +# host_filter=groups >= Linux +# +livestatus_uri=tcp:192.168.66.137:6557 diff --git a/contrib/inventory/nagios_livestatus.py b/contrib/inventory/nagios_livestatus.py new file mode 100755 index 0000000000..7315322f16 --- /dev/null +++ b/contrib/inventory/nagios_livestatus.py @@ -0,0 +1,175 @@ +#!/usr/bin/env python + +# (c) 2015, Yannig Perre +# +# This file is part of Ansible, +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +''' +Nagios livestatus inventory script. Before using this script, please +update nagios_livestatus.ini file. + +Livestatus is a nagios/naemon/shinken module which let you retrieve +informations stored in the monitoring core. + +This plugin inventory need livestatus API for python. Please install it +before using this script (apt/pip/yum/...). + +Checkmk livestatus: https://mathias-kettner.de/checkmk_livestatus.html +Livestatus API: http://www.naemon.org/documentation/usersguide/livestatus.html +''' + +import os +import re +import argparse +try: + import configparser +except ImportError: + import ConfigParser + configparser = ConfigParser +import json + +try: + from mk_livestatus import Socket +except ImportError: + print("Error: mk_livestatus is needed. Try something like: pip install python-mk-livestatus") + exit(1) + +class NagiosLivestatusInventory(object): + + def parse_ini_file(self): + config = configparser.SafeConfigParser() + config.read(os.path.dirname(os.path.realpath(__file__)) + '/nagios_livestatus.ini') + for section in config.sections(): + if not config.has_option(section, 'livestatus_uri'): continue + + # If fields_to_retrieve is not set, using default fields + fields_to_retrieve = self.default_fields_to_retrieve + if config.has_option(section, 'fields_to_retrieve'): + fields_to_retrieve = [field.strip() for field in config.get(section, 'fields_to_retrieve').split(',')] + fields_to_retrieve = tuple(fields_to_retrieve) + + # default section values + section_values = { + 'var_prefix' : 'livestatus_', + 'host_filter': None, + 'host_field' : 'name', + 'group_field': 'groups' + } + for key,value in section_values.iteritems(): + if config.has_option(section, key): + section_values[key] = config.get(section, key).strip() + + # Retrieving livestatus string connection + livestatus_uri = config.get(section, 'livestatus_uri') + backend_definition = None + + # Local unix socket + unix_match = re.match('unix:(.*)', livestatus_uri) + if unix_match is not None: + backend_definition = { 'connection': unix_match.group(1) } + + # Remote tcp connection + tcp_match = re.match('tcp:(.*):([^:]*)', livestatus_uri) + if tcp_match is not None: + backend_definition = { 'connection': (tcp_match.group(1), int(tcp_match.group(2))) } + + # No valid livestatus_uri => exiting + if backend_definition is None: + raise Exception('livestatus_uri field is invalid (%s). Expected: unix:/path/to/live or tcp:host:port' % livestatus_uri) + + # Updating backend_definition with current value + backend_definition['name'] = section + backend_definition['fields'] = fields_to_retrieve + for key, value in section_values.iteritems(): + backend_definition[key] = value + + self.backends.append(backend_definition) + + def parse_options(self): + parser = argparse.ArgumentParser() + parser.add_argument('--host', nargs=1) + parser.add_argument('--list', action='store_true') + parser.add_argument('--pretty', action='store_true') + self.options = parser.parse_args() + + def add_host(self, hostname, group): + if group not in self.result: + self.result[group] = {} + self.result[group]['hosts'] = [] + if hostname not in self.result[group]['hosts']: + self.result[group]['hosts'].append(hostname) + + def query_backend(self, backend, host = None): + '''Query a livestatus backend''' + hosts_request = Socket(backend['connection']).hosts.columns(backend['host_field'], backend['group_field']) + + if backend['host_filter'] is not None: + hosts_request = hosts_request.filter(backend['host_filter']) + + if host is not None: + hosts_request = hosts_request.filter('name = ' + host[0]) + + hosts_request._columns += backend['fields'] + + hosts = hosts_request.call() + for host in hosts: + hostname = host[backend['host_field']] + hostgroups = host[backend['group_field']] + if not isinstance(hostgroups, list): + hostgroups = [ hostgroups ] + self.add_host(hostname, 'all') + self.add_host(hostname, backend['name']) + for group in hostgroups: + self.add_host(hostname, group) + for field in backend['fields']: + var_name = backend['var_prefix'] + field + if hostname not in self.result['_meta']['hostvars']: + self.result['_meta']['hostvars'][hostname] = {} + self.result['_meta']['hostvars'][hostname][var_name] = host[field] + + def __init__(self): + + self.defaultgroup = 'group_all' + self.default_fields_to_retrieve = ('address', 'alias', 'display_name', 'childs', 'parents') + self.backends = [] + self.options = None + + self.parse_ini_file() + self.parse_options() + + self.result = {} + self.result['_meta'] = {} + self.result['_meta']['hostvars'] = {} + self.json_indent = None + if self.options.pretty: + self.json_indent = 2 + + if len(self.backends) == 0: + print("Error: Livestatus configuration is missing. See nagios_livestatus.ini.") + exit(1) + + for backend in self.backends: + self.query_backend(backend, self.options.host) + + if self.options.host: + print(json.dumps(self.result['_meta']['hostvars'][self.options.host[0]], indent = self.json_indent)) + elif self.options.list: + print(json.dumps(self.result, indent = self.json_indent)) + else: + print("usage: --list or --host HOSTNAME [--pretty]") + exit(1) + +NagiosLivestatusInventory()