├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.rst ├── mk_livestatus ├── __init__.py └── livestatus.py ├── setup.py ├── tests ├── test_query.py └── test_socket.py └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | # Template taken from: 2 | # https://github.com/github/gitignore/blob/master/Python.gitignore 3 | 4 | *.py[co] 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | 19 | # Installer logs 20 | pip-log.txt 21 | 22 | # Unit test / coverage reports 23 | .coverage 24 | .tox 25 | 26 | #Translations 27 | *.mo 28 | 29 | MANIFEST 30 | 31 | *.swp 32 | 33 | coverage* 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011, Michael Fladischer 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 3. The name of the author may not be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 16 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 17 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 18 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 19 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 20 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 21 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 22 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 24 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | include LICENSE 3 | include MANIFEST.in 4 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Python MK Livestatus parser 2 | =========================== 3 | 4 | :Author: Michael Fladischer 5 | :Version: 0.5-dev0 6 | 7 | .. contents:: 8 | 9 | Access the data returned from MK Livestatus queries as Python lists or dictionaries. 10 | It does this by sending queries to the MK Livestatus UNIX socket and parses the returned rows. 11 | Read/write permission to the UNIX socket are required. 12 | 13 | This package is known to be compatible with Python 2.7, 3.3, 34, pypy and pypy3. 14 | 15 | Usage 16 | ----- 17 | 18 | Here a simple example to fetch the name and hostgroups for all servers in the UP (0) state: 19 | 20 | >>> from mk_livestatus import Socket 21 | >>> s = Socket("/var/lib/icinga/rw/live") 22 | >>> q = s.hosts.columns('name', 'groups').filter('state = 0') 23 | >>> print q 24 | GET hosts 25 | Columns: name groups 26 | Filter: state = 0 27 | 28 | 29 | >>> q.call() 30 | [{'name': 'example.com', 'groups': ['ssh', 'snmp', 'smtp-server', 'ping-server', 'http-server', 'debian-server', 'apache2']}] 31 | 32 | ``s.hosts`` returns a Query to the ``hosts`` resources on Nagios. The ``columns`` and ``filter`` methods modify our query and return it, so we can chain the calls. The call to `call` method returns the rows as a list of dictionaries. 33 | 34 | If you use xinetd to bind the Unix socket to a TCP socket (like explained `here `_), you can create the socket like : 35 | 36 | >>> s = Socket(('192.168.1.1', 6557)) 37 | 38 | For more information please visit the `python-mk-livestatus website`_. Information about MK Livestatus and it's query syntax is available at the `mk-livestatus website`_. 39 | 40 | .. _python-mk-livestatus website: https://github.com/arthru/python-mk-livestatus 41 | .. _mk-livestatus website: http://mathias-kettner.de/checkmk_livestatus.html 42 | 43 | -------------------------------------------------------------------------------- /mk_livestatus/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from .livestatus import Query, Socket 5 | 6 | __version__ = "0.5-dev0" 7 | -------------------------------------------------------------------------------- /mk_livestatus/livestatus.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from __future__ import unicode_literals 5 | 6 | import socket 7 | import json 8 | 9 | 10 | __all__ = ['Query', 'Socket'] 11 | 12 | 13 | class Query(object): 14 | def __init__(self, conn, resource): 15 | self._conn = conn 16 | self._resource = resource 17 | self._columns = [] 18 | self._filters = [] 19 | 20 | def call(self): 21 | try: 22 | data = bytes(str(self), 'utf-8') 23 | except TypeError: 24 | data = str(self) 25 | return self._conn.call(data) 26 | 27 | __call__ = call 28 | 29 | def __str__(self): 30 | request = 'GET %s' % (self._resource) 31 | if self._columns and any(self._columns): 32 | request += '\nColumns: %s' % (' '.join(self._columns)) 33 | if self._filters: 34 | for filter_line in self._filters: 35 | request += '\nFilter: %s' % (filter_line) 36 | request += '\nOutputFormat: json\nColumnHeaders: on\n' 37 | return request 38 | 39 | def columns(self, *args): 40 | self._columns = args 41 | return self 42 | 43 | def filter(self, filter_str): 44 | self._filters.append(filter_str) 45 | return self 46 | 47 | 48 | class Socket(object): 49 | def __init__(self, peer): 50 | self.peer = peer 51 | 52 | def __getattr__(self, name): 53 | return Query(self, name) 54 | 55 | def call(self, request): 56 | try: 57 | if len(self.peer) == 2: 58 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 59 | else: 60 | s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 61 | s.connect(self.peer) 62 | s.send(request) 63 | s.shutdown(socket.SHUT_WR) 64 | rawdata = s.makefile().read() 65 | if not rawdata: 66 | return [] 67 | data = json.loads(rawdata) 68 | return [dict(zip(data[0], value)) for value in data[1:]] 69 | finally: 70 | s.close() 71 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from os.path import join, dirname 4 | from setuptools import setup 5 | 6 | import mk_livestatus 7 | 8 | 9 | def read(filename): 10 | filepath = join(dirname(__file__), filename) 11 | with open(filepath) as f: 12 | return f.read() 13 | 14 | 15 | setup( 16 | name='python-mk-livestatus', 17 | version=mk_livestatus.__version__, 18 | description='Helps to query MK livestatus and get results', 19 | long_description=read('README.rst'), 20 | author='Michael Fladischer', 21 | author_email='michael@fladi.at', 22 | url='https://github.com/arthru/python-mk-livestatus', 23 | download_url='http://pypi.python.org/pypi/python-mk-livestatus', 24 | packages=['mk_livestatus'], 25 | license='BSD', 26 | test_requires=['tox', 'mock', 'pytest'], 27 | classifiers=[ 28 | 'Development Status :: 4 - Beta', 29 | 'Intended Audience :: Developers', 30 | 'Intended Audience :: System Administrators', 31 | 'License :: OSI Approved :: BSD License', 32 | 'Operating System :: OS Independent', 33 | 'Programming Language :: Python', 34 | 'Topic :: Software Development :: Libraries :: Python Modules', 35 | 'Topic :: System :: Monitoring', 36 | ], 37 | ) 38 | -------------------------------------------------------------------------------- /tests/test_query.py: -------------------------------------------------------------------------------- 1 | from mk_livestatus import Query 2 | 3 | 4 | def test_query_1(): 5 | q = Query(None, 'hosts') 6 | q.columns('name', 'groups') 7 | q.filter('state = 0') 8 | expected = '''GET hosts 9 | Columns: name groups 10 | Filter: state = 0 11 | OutputFormat: json 12 | ColumnHeaders: on 13 | ''' 14 | assert str(q) == expected 15 | 16 | 17 | def test_query_2(): 18 | q = Query(None, 'services') 19 | q.columns('host_name', 'service_description', 'plugin_output', 'state') 20 | q.filter('host_name = localhost') 21 | expected = '''GET services 22 | Columns: host_name service_description plugin_output state 23 | Filter: host_name = localhost 24 | OutputFormat: json 25 | ColumnHeaders: on 26 | ''' 27 | assert str(q) == expected 28 | -------------------------------------------------------------------------------- /tests/test_socket.py: -------------------------------------------------------------------------------- 1 | import mock 2 | 3 | from mk_livestatus import Socket 4 | 5 | 6 | @mock.patch('mk_livestatus.livestatus.socket.socket') 7 | def test_socket(socket_mock): 8 | socket_mock.return_value.makefile.return_value.read.return_value = ( 9 | '[["name","groups","perf_data"],' 10 | '["hbops",["archlinux","linux","hashbang"],' 11 | '"rta=0.168000ms;1000.000000;3000.000000;0.000000 pl=0%;100;100;0"]]' 12 | ) 13 | s = Socket("/var/lib/icinga/rw/live") 14 | q = s.hosts 15 | r = q.call() 16 | expected = [{ 17 | 'name': 'hbops', 18 | 'groups': ['archlinux', 'linux', 'hashbang'], 19 | 'perf_data': "rta=0.168000ms;1000.000000;3000.000000;0.000000 pl=0%;" 20 | "100;100;0", 21 | }] 22 | assert r == expected 23 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py27,py33,py34,pypy,pypy3 3 | 4 | [testenv] 5 | deps = 6 | pytest 7 | mock 8 | commands= 9 | {envbindir}/py.test 10 | --------------------------------------------------------------------------------