#! /usr/bin/env python ############################################################################### # Nagios plugin check_actuator_health # # Notes # - The RHEL boxes I work on are currently limited to Python 2.6.6, hence the # use of (deprecated) optparse. If I can ever get them all updated to # Python 2.7 (or better yet, 3.3), I'll switch to argparse # - This template runs in 2.6-3.3. Any changes made will need to be appropriate # to the Python distro you want to use # ############################################################################### __author__ = 'mr.it@cbs.dk - Michael Rasmussen' __version__= 0.1 try: # RHEL8 RHEL9 from optparse import OptionParser, OptionGroup # import logging as log import sys import urllib3 as urllib # python3-urllib3-1.24.2-5.el8.noarch.rpm python3-urllib3-1.26.5-3.el9.noarch.rpm import json except: sys.exit(3) ## These will override any args passed to the script normally. Comment out after testing. #testargs = '--help' #testargs = '--version' #testargs = '-vvv' #testing = True OK_RESPONSE = """ { "status": "UP", "components": { "db": { "status": "UP", "details": { "database": "PostgreSQL", "validationQuery": "isValid()" } }, "diskSpace": { "status": "UP", "details": { "total": 2013582688256, "free": 1635574571008, "threshold": 10485760, "path": "/home/mir/git/soasi-course-catalog-course-target-adapter/.", "exists": true } }, "ping": { "status": "UP" } } }""" DOWN_RESPONSE = """ { "status": "DOWN", "components": { "db": { "status": "DOWN", "components": { "adapterDataSource": { "status": "DOWN", "details": { "error": "org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection" } }, "es3DataSource": { "status": "UP", "details": { "database": "Microsoft SQL Server", "validationQuery": "isValid()" } }, "providersDataSource": { "status": "DOWN", "details": { "error": "org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection" } } } }, "diskSpace": { "status": "UP", "details": { "total": 1013309239296, "free": 882010726400, "threshold": 10485760, "path": "C:\\Users\\hf.it\\Projects\\smart-integrations\\adapters\\target\\soasi-invigilation-report-target-adapter", "exists": true } }, "ping": { "status": "UP" } } } """ def main(): """ Main plugin logic goes here """ ## Parse command-line arguments args, args2 = parse_args() ## Uncomment to test logging levels against verbosity settings # log.debug('debug message') # log.info('info message') # log.warning('warning message') # log.error('error message') # log.critical('critical message') # log.fatal('fatal message') options = vars(args) keyword = options['keyword'] url = options['url'] agent = options['agent'] testing = options['testing'] if keyword is None or url is None: message = "Keywork: {0} url: {1}".format(keyword, url) status = 3 log.fatal(message) else: try: if not testing: req_headers = { 'User-Agent': agent } http = urllib.PoolManager() response = http.request( 'GET', url, headers = req_headers ) data = response.data.decode('utf-8').replace("\\"," ") else: data = DOWN_RESPONSE.replace("\\"," ") data = json.loads(data) if 'status' in data and data['status'] == keyword: message = "UP" status = 2 else: status, message = gather_message(data) except Exception as e: log.fatal(e) message = e status = 3 gtfo(status, message) def gather_message(json_data): """ Assemble error messages """ status = 2 msg = None try: if 'components' in json_data: components = json_data['components'] for component in components: items = components[component] if 'components' in items: for item in items['components']: if 'status' in items['components'][item] and items['components'][item]['status'].upper() == 'DOWN': if msg is not None: msg += "\n{0}: {1}".format(item, items['components'][item]['details']['error']) else: msg = "{0}: {1}".format(item, items['components'][item]['details']['error']) else: if 'status' in items and items['status'].upper() == 'DOWN': if msg is not None: if 'details' in items and 'error' in items['details']: error = items['details']['error'] else: error = "No error message" if msg is not None: msg += "\n{0}: {1}".format(component, error) else: msg = "{0}: {1}".format(component, error) else: return (3, msg) except: return (3, msg) return (status, msg) def parse_args(): """ Parse command-line arguments """ parser = OptionParser(usage='usage: %prog [-v|vv|vvv] [options]', version='{0}: v.{1} by {2}'.format('%prog', __version__, __author__)) ## Verbosity (want this first, so it's right after --help and --version) parser.add_option('-v', help='Set verbosity level', action='count', default=0, dest='v') ## CLI arguments specific to this script group = OptionGroup(parser,'Plugin Options') group.add_option('-a', '--agent', help="User agent for request. Default: Python-nagios", default="Python-nagios", type='string') group.add_option('-k', '--keyword', help="Keyword to search for in response", default=None) group.add_option('-t', '--testing', help="Run in testing mode", default=False, action='store_true') group.add_option('-u', '--url', help="URL to requested resource", default=None) ## Common CLI arguments #parser.add_option('-c', '--critical', help='Set the critical threshold. Default: %(default)s', # default=97, type=float, dest='crit', metavar='##') #parser.add_option('-w', '--warning', help='Set the warning threshold. Default: %(default)s', # default=95, type=float, dest='warn', metavar='##') parser.add_option_group(group) ## Try to parse based on the testargs variable. If it doesn't exist, use args try: args, args2 = parser.parse_args(testargs.split()) except NameError: args, args2 = parser.parse_args() ## Set the logging level based on the -v arg log.getLogger().setLevel([log.ERROR, log.WARN, log.INFO, log.DEBUG][args.v]) log.debug('Parsed arguments: {0}'.format(args)) log.debug('Other arguments: {0}'.format(args2)) return args, args2 def gtfo(exitcode, message=''): """ Exit gracefully with exitcode and (optional) message """ log.debug('Exiting with status {0}. Message: {1}'.format(exitcode, message)) if message: print(message) exit(exitcode) if __name__ == '__main__': ## Initialize logging before hitting main, in case we need extra debuggability log.basicConfig(level=log.DEBUG, format='%(asctime)s - %(funcName)s - %(levelname)s - %(message)s') main()