#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# pynag - Python Nagios plug-in and configuration environment
# Copyright (C) 2010 Pall Sigurdsson
# 
# This program 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 2 of the License, or
# (at your option) any later version.
# 
# This program 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 this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.


import pynag.Model
import pynag.Parsers

from optparse import OptionParser

import sys

usage = """usage: %prog <sub-command> [arguments]

Examples:
 %prog list host_name service_description where object_type=service and host_name=examplehost.com
 %prog update service set check_period=24x7 where host_name=examplehost.com and object_type=host

Available subcommands:
  list          List host/services, etc.
  update        Update host/service etc.
  add           Create a new object definition
  copy          Copy a current object definition to a new one
  delete        Delete a specific object definition
  config-set    set a specific value for nagios.cfg
  config-append append a specific value to nagios.cfg
  
For help on specific sub-command type:
 %prog <sub-command> --help
 
"""

parser = OptionParser(usage=usage)

def parse_arguments():
    ''' Parse command line arguments and run command specified commands '''
    if len(sys.argv) < 2:
        parser.error("No sub-command specified")
    elif sys.argv[1] == 'list':
        list_objects()
    elif sys.argv[1] == 'update':
        update_objects()
    elif sys.argv[1] == 'add':
        add_object()
    elif sys.argv[1] == 'copy':
        copy_object()
    elif sys.argv[1] == 'delete':
        delete_object()
    elif sys.argv[1] == 'config-set':
        main_config()
    elif sys.argv[1] == 'config-append':
        main_config()
    elif sys.argv[1] == "help":
        parser.print_help()
    elif sys.argv[1] == "--help":
        parser.print_help()
    else:
        parser.error("Unknown sub-command '%s'" % sys.argv[1])

def search_objects(search_statements):
    conditions = {}
    objects = []
    statements = search_statements
    while len(statements) > 0:
        statement = statements.pop(0)
        tmp = statement.split('=',1)
        if statement.lower() == 'and': continue
        if statement.lower() == 'or':
            objects += pynag.Model.ObjectDefinition.objects.filter(**conditions)
            conditions = {}
            continue
        if len(tmp) != 2:
            raise BaseException('Invalid statement: %s' %statement)
        key,value = tmp
        conditions[key] = value
    objects += pynag.Model.ObjectDefinition.objects.filter(**conditions)
    return sorted( set(objects) )

def main_config():
    ''' Get or change a value in main configuration file '''
    parser.add_option("--filename", dest="filename", 
                      default=pynag.Model.cfg_file, help="Path to Nagios configuration file")
    parser.add_option("--verbose", dest="verbose", action="store_true",
                      default=False,help="Increase verbosity")
    (opts,args) = parser.parse_args(sys.argv[1:])
    attributes = {}
    command = args.pop(0)
    while len(args) > 0:
        arg = args.pop(0)
        tmp = arg.split('=',1)
        if len(tmp) != 2: 
            raise BaseException("attributes should be in the form of attribute=new_value for copy (got %s)" % arg )
        attr,value = tmp
        attributes[ attr ] = value
    
    # See if we are appending or replacing
    if command == 'config-append': append = True
    else: append = False
    
    c = pynag.Parsers.config()
    
    for k,v in attributes.items():
        result = c._edit_static_file(filename=opts.filename, attribute=k, new_value=v, append=append)
        if opts.verbose:
            print "%s:%s changed: %s" % (opts.filename, k, result)
def list_objects():
    ''' List nagios objects given a specified search filter '''
    parser.usage = ''' %prog list <attributes> [WHERE <search>]

Examples:
  # %prog list host_name address where object_type=host" 
  # %prog list host_name service_description where host_name=examplehost and object_type=service" 

Run %prog list --help for full list of command line arguments.
'''
    parser.add_option("--print", dest="print_definition", action='store_true',
                      default=False, help="Print actual definition instead of attributes")
    parser.add_option("--width", dest="width", 
                      default=40, help="Column width in output (default 40)")
    (opts,args) = parser.parse_args(sys.argv[2:])
    objects = None
    attributes = []
    
    while len(args) > 0:
        arg = args.pop(0)
        if arg.lower() == 'where':
            objects = search_objects(search_statements=args)
            break
        attributes.append( arg )
    if objects is None:
        objects = pynag.Model.ObjectDefinition.objects.all
    if opts.print_definition:
        for i in objects:
            print i
    else:
        pretty_print(objects=objects, attributes=attributes,width=opts.width)
def delete_object():
    ''' Delete one specific nagios object '''
    parser.usage = ''' %prog delete <WHERE statement>

Examples:
  # %prog delete where object_type=service and host_name='mydeprecated_host' 
  # %prog delete where filename__startswith='/etc/nagios/myoldhosts' 

Run %prog delete --help for full list of command line arguments.
'''
    parser.add_option("--verbose", dest="verbose", action="store_true",
                      default=False,help="Increase verbosity")
    parser.add_option("--force", dest="force", action="store_true",
                      default=False,help="Don't prompt for confirmation")
    parser.add_option("--recursive", dest="recursive", action="store_true",
                      default=False,help="Recursively apply to related child objects")
    (opts,args) = parser.parse_args(sys.argv[2:])
    objects = None
    attributes = []    
    while len(args) > 0:
        arg = args.pop(0)
        if arg.lower() == 'where':
            objects = search_objects(search_statements=args)
            break
        attributes.append( arg )
    if objects is None:
        parser.error('Refusing to delete every object. Please trim down the selection with a WHERE statement')
    if len(objects) > 0 and not opts.force:
        pretty_print(objects)
        answer = raw_input('Delete these %s objects ? (y/N) ' % (len(objects)))
        if answer.lower() not in ('y', 'yes'):
            return 
    for i in objects:
        if opts.verbose:
            print i
        try:
            i.delete(recursive=opts.recursive)
            print i.get_shortname(), "deleted."
        except ValueError, e:
            print i.get_shortname(), "not found."
    print len(objects), "objects deleted"
def add_object():
    parser.usage = ''' %prog add <object_type> <attributes>

Examples:
  # %prog add host host_name=examplehost use=generic-host address=127.0.0.1" 
  # %prog add service service_description="Test Service" use="check_nrpe" host_name="localhost" 

Run %prog add --help for full list of command line arguments.
'''
    parser.add_option("--verbose", dest="verbose", action="store_true",
                      default=False,help="Increase verbosity")
    parser.add_option("--filename", dest="filename",
                      help="Write to this specific file")
    (opts,args) = parser.parse_args(sys.argv[2:])
    if len(args) == 0:
        parser.error("You need to specify object_type and attributes for add")
    attributes = {}
    object_type = args.pop(0)
    while len(args) > 0:
        arg = args.pop(0)
        tmp = arg.split('=',1)
        if len(tmp) != 2: 
            raise BaseException("attributes should be in the form of attribute=new_value for update (got %s)" % arg )
        attr,value = tmp
        attributes[ attr ] = value
    
    if attributes == []:
        parser.error('Refusing to write an empty %s definition. Please specify some attributes' % object_type)
    obj = pynag.Model.string_to_class[object_type](filename=opts.filename)
    for k,v in attributes.items():
        obj[k] = v
    obj.save()
    print "Object saved to", obj.get_filename()
    if opts.verbose:
        print "# This is what actual statement looks like"
        print obj

def copy_object():
    parser.usage = ''' %prog copy object_id <attribute1=new_value1> [--filename /path/to/file]

Examples:
  # %prog list host_name id where host_name=examplehost and object_type=host
  >>> 8eed3f47c230afae8b3747b1fd230ae4
  # %prog copy 8eed3f47c230afae8b3747b1fd230ae4 host_name=newhostname address=new_address" 

Run %prog copy --help for full list of command line arguments.
'''
    parser.add_option("--verbose", dest="verbose", action="store_true",
                      default=False,help="Increase verbosity")
    parser.add_option("--filename", dest="filename",
                      help="Write to this specific file")
    parser.add_option("--recursive", dest="recursive", action="store_true",
                      default=False,help="Recursively apply to related child objects")
    (opts,args) = parser.parse_args(sys.argv[2:])
    if len(args) == 0:
        parser.error("You need to specify an object id of the object to copy")
    attributes = {}
    object_id = args.pop(0)
    while len(args) > 0:
        arg = args.pop(0)
        tmp = arg.split('=',1)
        if len(tmp) != 2: 
            raise BaseException("attributes should be in the form of attribute=new_value for copy (got %s)" % arg )
        attr,value = tmp
        attributes[ attr ] = value
    
    if attributes == []:
        parser.error('Refusing to write an identical definition. Please specify some attributes' )
    # Here we actually copy the object
    obj = pynag.Model.ObjectDefinition.objects.get_by_id(object_id)
    new_obj = obj.copy(filename=opts.filename,recursive=opts.recursive,**attributes)
    print "Object saved to", new_obj.get_filename()
    if opts.verbose:
        print "# This is what actual statement looks like"
        print new_obj


def update_objects():
    parser.usage = ''' %prog list <attributes> [WHERE <search>]

Examples:
  # %prog update host_name=newhostname where host_name=oldhostname" 
  # %prog update address=127.0.0.1 where host_name='examplehost.example.com' and object_type=host" 
  
Run %prog update --help for full list of command line arguments.
'''
    parser.add_option("--verbose", dest="verbose", action='store_true',
                      default=False, help="Print updated definition after changing")
    parser.add_option("--force", dest="force", action="store_true",
                      default=False,help="Don't prompt for confirmation")
    (opts,args) = parser.parse_args(sys.argv[2:])
    objects = None
    attributes = {}
    while len(args) > 0:
        arg = args.pop(0)
        if arg.lower() == 'set': continue
        if arg.lower() == 'where':
            objects = search_objects(search_statements=args)
            break
        tmp = arg.split('=',1)
        if len(tmp) != 2: 
            raise BaseException("attributes should be in the form of attribute=new_value for update (got %s)" % arg )
        attributes[tmp[0]] = tmp[1]
    if objects is None :
        return
    if len(objects) > 0 and not opts.force:
        pretty_print(objects)
        answer = raw_input('Update these %s objects ? (y/N) ' % (len(objects)))
        if answer.lower() not in ('y', 'yes'):
            return 
    
    update_many(objects=objects, **attributes)

    if opts.verbose:
        for i in objects: print i
    print "Updated %s objects" % (len(objects))

def pretty_print(objects,width=40, attributes=None):
    ''' Takes in a list of objects and prints them in a pretty table format '''
    # Set default attributes if none are specified
    if not attributes:
        attributes = ['object_type','shortname', 'filename']
    
    width = "%%-%ss" % width # by default should turn into "%-30s"    
    # Print header
    for attr in attributes:
        print width % attr,
    print
    print "-"*80
    
    # Print one row for each object
    for i in objects:
        for attr in attributes:
            if attr == 'shortname': value = i.get_shortname()
            elif attr == 'effective_command_line': value = i.get_effective_command_line()
            elif attr == "id": value = i.get_id()
            else: value = i.get(attr,"null")
            print width % value,
        print
    message = "%s objects matches search condition" % ( len(objects) )
    pre = "-"*10
    post = "-"*(80-10-len(message))
    print pre + message + post
    
def update_many(objects, **kwargs):
    for i in objects:
        for attr,value in kwargs.items():
            i[attr] = value
            i.save()
            print "%s (%s): changed %s to %s" % (i.get_filename(), i.get_shortname(),attr, value)

def parse_configfile():
    ''' Parse configuration file '''


if __name__ == '__main__':
#    config = ConfigParser.ConfigParser()
#    config.read('/etc/pynag.conf')
#    pynag.Model.cfg_file = config.options('pynag')['cfg_file']
    try:
        parse_arguments()
    except KeyboardInterrupt, k:
        print
        sys.exit()
