Permalink
Browse files

Initial import

  • Loading branch information...
0 parents commit bdf9e6d25e9f684ba7fbb7a6418953e2b5882191 @tehmaze committed Jan 28, 2012
Showing with 654 additions and 0 deletions.
  1. +3 −0 .gitignore
  2. +26 −0 nagios-cli
  3. 0 nagios_cli/__init__.py
  4. +191 −0 nagios_cli/cli.py
  5. +163 −0 nagios_cli/command.py
  6. +98 −0 nagios_cli/config.py
  7. +43 −0 nagios_cli/nagios.py
  8. +57 −0 nagios_cli/objects.py
  9. +73 −0 nagios_cli/ui.py
3 .gitignore
@@ -0,0 +1,3 @@
+*.pyc
+*.pyo
+*.swp
26 nagios-cli
@@ -0,0 +1,26 @@
+#! /usr/bin/env python
+
+import sys
+from nagios_cli.cli import CLI
+from nagios_cli.config import Config
+
+def run():
+ import optparse
+
+ parser = optparse.OptionParser(usage='%prog [<options>] [<host> [<service>]]')
+ parser.add_option('-c', '--config',
+ help='Configuration file')
+
+ options, args = parser.parse_args()
+
+ config = Config()
+ config.load_default()
+ if options.config:
+ config.load(options.config)
+
+ cli = CLI(config)
+ return cli.run()
+
+if __name__ == '__main__':
+ sys.exit(run())
+
0 nagios_cli/__init__.py
No changes.
191 nagios_cli/cli.py
@@ -0,0 +1,191 @@
+import os
+import re
+import readline
+import sys
+from nagios_cli import command
+from nagios_cli.ui import UI
+from nagios_cli.objects import Objects
+
+RE_SPACING = re.compile(r'\s+')
+
+
+class Context(object):
+ def __init__(self, stack=[]):
+ self.stack = stack
+
+ def add(self, item):
+ self.stack.append(item)
+
+ def pop(self, item=-1, default=None):
+ if self.stack:
+ return self.stack.pop(item)
+ else:
+ return default
+
+ def get(self, item=-1):
+ return self.stack and self.stack[item]
+
+ def set(self, item):
+ self.stack = [item]
+
+
+class CLI(object):
+ def __init__(self, config):
+ self.config = config
+ self.ui = UI(config)
+ self.commands = {}
+ self.context = Context()
+ self.objects = Objects(self, config)
+ self.matches = []
+
+ self.setup_prompt()
+ self.setup_readline()
+ self.setup_commands()
+
+ # Setup
+
+ def setup_prompt(self):
+ prompt = self.config.get('ui.prompt_separator', ':').join(
+ map(str, self.context.stack)
+ )
+ self.prompt = self.ui.color('prompt',
+ self.config.get('ui.prompt') % (
+ self.ui.color('prompt.object',
+ self.config.get('ui.prompt_separator', ':').join(
+ map(str, self.context.stack)
+ )
+ )
+ )
+ ) + ' '
+
+ def setup_readline(self):
+ history = self.config.get('cli.history')
+ if history:
+ # Load history
+ history = os.path.expanduser(history)
+ try:
+ readline.read_history_file(history)
+ except IOError:
+ pass
+
+ # Save history at exit
+ import atexit
+ atexit.register(readline.write_history_file, history)
+
+ readline.set_completer(self.complete)
+ readline.parse_and_bind('tab: complete')
+
+ # We need hyphen in hostnames
+ readline.set_completer_delims(readline.get_completer_delims().replace('-', ''))
+
+ def setup_commands(self):
+ for item in command.DEFAULT:
+ self.command_add(item)
+
+ # I/O
+
+ def flush(self):
+ sys.stdout.flush()
+
+ def send(self, text):
+ sys.stdout.write(text)
+
+ def sendline(self, text):
+ self.send(text + '\n')
+
+ def error(self, text):
+ self.sendline(self.ui.color('error', text))
+
+ def warn(self, text):
+ self.sendline(self.ui.color('warn', text))
+
+ # Commands
+
+ def command_add(self, obj):
+ self.commands[obj.name] = obj
+ self.commands[obj.name].cli = self
+
+ def command_run(self, name, args):
+ if name not in self.commands:
+ matches = []
+ for state in xrange(0, len(self.commands)):
+ match = self.complete(name, state)
+ if match is None:
+ break
+ else:
+ matches.append(match)
+
+ # One possible match, complete it
+ if len(matches) == 1:
+ return self.command_run(matches[0], args)
+
+ return False
+
+ if not self.commands[name].valid_in_context(self.context):
+ print 'Not valid in context'
+ return False
+
+ try:
+ self.commands[name].run(*args)
+ except TypeError, e:
+ msg = e.message
+ if msg.startswith('run()') and (
+ 'takes exactly' in msg or \
+ 'takes at least' in msg or \
+ 'takes at most' in msg):
+ self.error('Syntax error')
+ return True
+ else:
+ raise
+
+ return True
+
+ def complete(self, text, state):
+ if state == 0:
+ line = readline.get_line_buffer()
+ part = RE_SPACING.split(line)
+
+ if part and part[0]:
+ cmnd = part[0]
+ if len(part) == 1:
+ self.matches = [key
+ for key in self.commands.keys()
+ if key.startswith(text) and
+ self.commands[key].valid_in_context(self.context)]
+ elif cmnd in self.commands:
+ text = text.replace(cmnd, '').lstrip()
+ self.matches = self.commands[cmnd].complete(text, state)
+ else:
+ self.matches = []
+ else:
+ self.matches = self.commands.keys()
+
+ try:
+ return self.matches[state]
+ except IndexError:
+ pass
+
+ def dispatch(self, line):
+ part = RE_SPACING.split(line)
+ if not part:
+ return
+
+ cmnd, args = part[0], part[1:]
+ if not self.command_run(cmnd, args):
+ self.error('Invalid command "%s"' % (cmnd,))
+
+ def run(self, banner='Welcome to the nagios command line interface'):
+ self.sendline(banner)
+ self.running = True
+ while self.running:
+ try:
+ line = raw_input(self.prompt)
+ except EOFError:
+ line = 'EOF'
+ except KeyboardInterrupt:
+ line = 'exit'
+
+ stop = self.dispatch(line)
+ if stop:
+ self.running = False
+
163 nagios_cli/command.py
@@ -0,0 +1,163 @@
+import readline
+from nagios_cli import objects
+
+class Command(object):
+ def __init__(self, name):
+ self.name = name
+ self.cli = None
+ # For completion
+ self.matches = []
+
+ def valid_in_context(self, context):
+ return True
+
+ def complete(self, text, state):
+ '''
+ Return the next possible completion for 'text'.
+
+ This is called successively with state == 0, 1, 2, ... until it
+ returns None. The completion should begin with 'text'.
+ '''
+ return None
+
+ def run(self, *args):
+ return False
+
+
+class Version(Command):
+ def run(self):
+ self.cli.sendline('nagios-cli version 0.1')
+ self.cli.sendline('nagios version %s' % (self.cli.objects.info.version,))
+
+class About(Command):
+ def run(self):
+ self.cli.sendline('nagios-cli command line interface for nagios')
+ self.cli.sendline('(c) 2012 Wijnand Modderman-Lenstra, https://maze.io/')
+
+
+class Exit(Command):
+ def run(self):
+ self.cli.sendline('Bye')
+ self.cli.running = False
+
+
+class EOF(Command):
+ def run(self):
+ self.cli.sendline('')
+ if self.cli.context.pop():
+ self.cli.setup_prompt()
+ else:
+ self.cli.dispatch('exit')
+
+
+class Host(Command):
+ def run(self, hostname=None):
+ '''
+ [<hostname>]
+ '''
+ if not hostname:
+ self.cli.sendline('%d hosts available' % (len(self.cli.objects.hosts),))
+ self.cli.sendline('')
+ self.cli.sendline('To switch objects, use: host <hostname>')
+ return
+
+ if hostname in self.cli.objects.hosts:
+ self.cli.context.set(self.cli.objects.hosts[hostname])
+ self.cli.setup_prompt()
+ else:
+ self.cli.error('Host "%s" not found' % (hostname,))
+
+ def complete(self, text, state):
+ print 'Host.complete', state, text
+ if state == 0:
+ part = text.split()
+ print 'part', part
+ if not part:
+ self.matches = self.cli.objects.hosts.keys()
+ elif len(part) == 1:
+ self.matches = [hostname
+ for hostname in self.cli.objects.hosts
+ if hostname.startswith(part[0])]
+ else:
+ self.matches = []
+
+ self.matches.sort()
+
+ print self.matches
+ return self.matches
+
+
+class Service(Command):
+ def valid_in_context(self, context):
+ obj = context.get()
+ return isinstance(obj, objects.Host)
+
+ def run(self, service=None):
+ '''
+ [<service>]
+
+ Shows information about services, if you specify an argument you
+ will switch the active object to the given service.
+ '''
+
+ host = self.cli.context.get()
+ if not service:
+ self.cli.sendline('%s services for this host' % (len(host.services),))
+ for service in sorted(host.services):
+ self.cli.sendline(' - "%s" (%s)' % (service, host.services[service].plugin_output))
+
+ elif service in host.services:
+ self.cli.context.add(host.services[service])
+ self.cli.setup_prompt()
+
+ else:
+ self.cli.error('Service "%s" not found' % (service,))
+
+ def complete(self, text, state):
+ print 'Service.complete', state, text
+ if state == 0:
+ part = text.split()
+ host = self.cli.context.get()
+ print 'part', part
+ if not part:
+ self.matches = host.services.keys()
+ elif len(part) == 1:
+ self.matches = [service
+ for service in host.services
+ if service.startswith(part[0])]
+ else:
+ self.matches = []
+
+ self.matches.sort()
+
+ print self.matches
+ return self.matches
+
+
+class Info(Command):
+ def valid_in_context(self, context):
+ obj = context.get()
+ return isinstance(obj, objects.Object)
+
+ def run(self):
+ obj = self.cli.context.get()
+ if isinstance(obj, objects.Host):
+ self.cli.sendline('Host')
+ elif isinstance(obj, objects.Service):
+ self.cli.sendline('Service')
+
+# Default commands
+DEFAULT = (
+ # Global commands
+ About('about'),
+ Version('version'),
+ EOF('EOF'),
+ Exit('exit'),
+ Exit('quit'),
+ Host('host'),
+ # Host context
+ Service('service'),
+ # Host/Service context
+ Info('info'),
+ Info('show'),
+)
98 nagios_cli/config.py
@@ -0,0 +1,98 @@
+# -*- coding: utf8; -*-
+
+import ConfigParser
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
+
+DEFAULT_CONFIG = u'''
+[cli]
+history = ~/.nagios_cli_history
+
+[ui]
+color = 1
+prompt = nagios %s>
+prompt_separator = " → "
+
+[nagios]
+log = /var/log/nagios
+objects.cache = %(log)s/objects.cache
+status.dat = %(log)s/nagios/status.dat
+
+[string]
+level.ok = ↑ OK
+level.warning = ↓ WARNING
+level.critical = ⚠ CRITICAL
+level.unknown = ↕ UNKNOWN
+
+[color]
+error = bold_red
+
+prompt = normal
+prompt.object = bold
+
+host.host_name = bold
+host.plugin_output = bold
+service.plugin_output = bold
+
+level.ok = bold_green
+level.warning = bold_yellow
+level.critical = bold_red
+level.unknown = bold_magenta
+'''
+
+class Config(dict):
+ def load(self, filename):
+ fp = open(filename, 'rb')
+ self.load_file(fp)
+ fp.close()
+
+ def load_default(self):
+ self.load_str(DEFAULT_CONFIG.encode('utf-8'))
+
+ def load_str(self, config):
+ fp = StringIO(config)
+ self.load_file(fp)
+ fp.close()
+
+ def load_file(self, fp):
+ parser = ConfigParser.ConfigParser()
+ parser.readfp(fp)
+ for section in parser.sections():
+ for option, value in parser.items(section):
+ # Support quoted strings
+ if isinstance(value, basestring):
+ if value.startswith('"') and value.endswith('"'):
+ value = value[1:-1]
+ elif value.startswith("'") and value.endswith("'"):
+ value = value[1:-1]
+
+ self['.'.join([section, option])] = value
+
+ def as_ini(self):
+ curr = None
+ ini = []
+ for key, value in sorted(self.iteritems()):
+ section, option = key.split('.', 1)
+ if section != curr:
+ if ini:
+ ini.append('')
+
+ ini.append('[%s]' % (section,))
+ curr = section
+
+ if type(value) in [int, long, float]:
+ ini.append('%s = %s' % (option, value))
+ elif isinstance(value, basestring):
+ # Quoted string
+ if value.startswith(' ') or value.endswith(' '):
+ value = '"%s"' % (value,)
+ ini.append('%s = %s' % (option, value))
+ elif type(value) is bool:
+ ini.append('%s = %s' % (option, int(value)))
+
+ return '\n'.join(ini)
+
+
+
43 nagios_cli/nagios.py
@@ -0,0 +1,43 @@
+class Section(dict):
+ def __init__(self, name):
+ self.name = name
+
+ def __str__(self):
+ return self.name
+
+
+class Parser(object):
+ def __init__(self, define=True):
+ self.section = None
+ self.define = define
+
+ def parse(self, filename):
+ handle = open(filename, 'rb')
+ for line in handle:
+ line = line.strip()
+ part = line.split()
+ #print part
+
+ if not part:
+ continue
+
+ elif self.section is None:
+ if self.define:
+ if part[0] == 'define' and part[-1] == '{':
+ self.section = Section(part[1])
+ elif part[-1] == '{':
+ self.section = Section(part[0])
+
+ elif line == '}':
+ yield self.section
+ self.section = None
+
+ elif line.startswith('#'):
+ continue
+
+ else:
+ if self.define:
+ self.section[part[0]] = ' '.join(part[1:])
+ elif '=' in line:
+ part = line.split('=', 1)
+ self.section[part[0]] = part[1]
57 nagios_cli/objects.py
@@ -0,0 +1,57 @@
+import fnmatch
+from nagios_cli import nagios
+from nagios_cli.ui import Spinner
+
+
+class Object(dict):
+ def __getattr__(self, attr):
+ if attr in self:
+ return self[attr]
+ else:
+ raise AttributeError
+
+class Host(Object):
+ def __str__(self):
+ return self.host_name
+
+
+class Service(Object):
+ def __str__(self):
+ return self.service_description
+
+
+class Objects(object):
+ def __init__(self, cli, config):
+ self.cli = cli
+ self.config = config
+ self.info = None
+ self.hosts = {}
+
+ self.parser = nagios.Parser(define=False)
+ self.parse_status()
+
+ def filter(self, pattern):
+ return fnmatch.filter(self.hosts.keys(), pattern)
+
+ def parse_status(self):
+ spinner = Spinner(self.cli, 'Loading nagios objects')
+ filename = self.config.get('nagios.status.dat')
+ for item in self.parser.parse(filename):
+ if item.name == 'info':
+ self.info = Object(item)
+
+ elif item.name == 'hoststatus':
+ host = Host(item)
+ host['services'] = {}
+ self.hosts[host.host_name] = host
+
+ elif item.name == 'servicestatus':
+ host = self.hosts.get(item.get('host_name', None), None)
+ name = item.get('service_description', None)
+ if host and name:
+ host.services[name] = Service(item)
+ spinner.tick()
+
+ # Finished
+ spinner.stop()
+
73 nagios_cli/ui.py
@@ -0,0 +1,73 @@
+class Color(object):
+ ANSI = dict(
+ normal = '',
+ reset = '\033[m',
+ bold = '\033[1m',
+ red = '\033[31m',
+ green = '\033[32m',
+ yellow = '\033[33m',
+ blue = '\033[34m',
+ magenta = '\033[35m',
+ cyan = '\033[36m',
+ bold_red = '\033[1;31m',
+ bold_green = '\033[1;32m',
+ bold_yellow = '\033[1;33m',
+ bold_blue = '\033[1;34m',
+ bold_magenta = '\033[1;35m',
+ bold_cyan = '\033[1;36m',
+ bg_red = '\033[41m',
+ bg_green = '\033[42m',
+ bg_yellow = '\033[43m',
+ bg_blue = '\033[44m',
+ bg_magenta = '\033[45m',
+ bg_cyan = '\033[46m',
+ )
+
+ def __init__(self, ui):
+ self.ui = ui
+
+ def __call__(self, color, format):
+ if self.ui.config.get('color.enabled', True):
+ return self.color_str(color, format)
+ else:
+ return format
+
+ def color_str(self, color, format):
+ color = self.ui.config.get('color.%s' % (color,), 'normal')
+ return ''.join([
+ self.ANSI.get(color),
+ format,
+ self.ANSI.get('reset')
+ ])
+
+
+class UI(object):
+ def __init__(self, config):
+ self.config = config
+ self.color = Color(self)
+
+
+class Spinner(object):
+ chars = '-\|/'
+
+ def __init__(self, cli, prompt):
+ self.cli = cli
+ self.cli.send(prompt + ' ')
+ self.pos = -1
+ self.prompt = prompt
+ self.tick()
+
+ def __add__(self, x):
+ self.tick()
+
+ def tick(self):
+ self.pos += 1
+ if self.pos >= len(self.chars):
+ self.pos = 0
+
+ self.cli.send('\b%s' % (self.chars[self.pos]))
+ self.cli.flush()
+
+ def stop(self):
+ self.cli.send('\r\033[K')
+ self.cli.flush()

0 comments on commit bdf9e6d

Please sign in to comment.