Add Nagios plugin.

This commit is contained in:
Bastian Kleineidam 2012-06-20 00:29:46 +02:00
parent a6eaae2c38
commit 2f730f4e13
5 changed files with 329 additions and 100 deletions

View file

@ -6,7 +6,7 @@ PLATFORM:=$(shell $(PYTHON) -c "from distutils.util import get_platform; print g
FILESCHECK_URL:=http://localhost/~calvin/
PYTHONSRC:=${HOME}/src/cpython-hg/Lib
#PYTHONSRC:=/usr/lib/$(PYTHON)
PY_FILES_DIRS:=linkcheck tests *.py linkchecker linkchecker-gui cgi-bin config doc
PY_FILES_DIRS:=linkcheck tests *.py linkchecker linkchecker-nagios linkchecker-gui cgi-bin config doc
TESTS ?= tests/
# set test options, eg. to "--nologcapture"
TESTOPTS=
@ -186,6 +186,7 @@ doccheck:
cgi-bin/lc.wsgi \
linkchecker \
linkchecker-gui \
linkchecker-nagios \
*.py
filescheck: localbuild
@ -252,7 +253,7 @@ gui:
.PHONY: count
count:
@sloccount linkchecker linkchecker-gui linkcheck | grep "Total Physical Source Lines of Code"
@sloccount linkchecker linkchecker-gui linkchecker-nagios linkcheck | grep "Total Physical Source Lines of Code"
# run eclipse ide
.PHONY: ide

132
linkcheck/cmdline.py Normal file
View file

@ -0,0 +1,132 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2000-2012 Bastian Kleineidam
#
# 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.
"""
Utility functions suitable for command line clients.
"""
import sys
import optparse
from . import fileutil, ansicolor, strformat, add_intern_pattern, checker
from .director import console
from .decorators import notimplemented
def print_version(exit_code=0):
"""Print the program version and exit."""
console.print_version()
sys.exit(exit_code)
def print_usage (msg, exit_code=2):
"""Print a program msg text to stderr and exit."""
program = sys.argv[0]
print >> console.stderr, _("Error: %(msg)s") % {"msg": msg}
print >> console.stderr, _("Execute '%(program)s -h' for help") % {"program": program}
sys.exit(exit_code)
class LCHelpFormatter (optparse.IndentedHelpFormatter, object):
"""Help formatter indenting paragraph-wise."""
def __init__ (self):
"""Set current console width for this formatter."""
width = ansicolor.get_columns(sys.stdout)
super(LCHelpFormatter, self).__init__(width=width)
def format_option (self, option):
"""Customized help display with indentation."""
# The help for each option consists of two parts:
# * the opt strings and metavars
# eg. ("-x", or "-fFILENAME, --file=FILENAME")
# * the user-supplied help string
# eg. ("turn on expert mode", "read data from FILENAME")
# If possible, we write both of these on the same line:
# -x turn on expert mode
# But if the opt string list is too long, we put the help
# string on a second line, indented to the same column it would
# start in if it fit on the first line.
# -fFILENAME, --file=FILENAME
# read data from FILENAME
result = []
opts = self.option_strings[option]
opt_width = self.help_position - self.current_indent - 2
if len(opts) > opt_width:
opts = "%*s%s\n" % (self.current_indent, "", opts)
indent_first = self.help_position
else: # start help on same line as opts
opts = "%*s%-*s " % (self.current_indent, "", opt_width, opts)
indent_first = 0
result.append(opts)
if option.help:
text = strformat.wrap(option.help, self.help_width)
help_lines = text.splitlines()
result.append("%*s%s\n" % (indent_first, "", help_lines[0]))
result.extend(["%*s%s\n" % (self.help_position, "", line)
for line in help_lines[1:]])
elif opts[-1] != "\n":
result.append("\n")
return "".join(result)
class LCOptionParser (optparse.OptionParser, object):
"""Option parser with custom help text layout."""
def __init__ (self, err_exit_code=2):
"""Initializing using our own help formatter class."""
super(LCOptionParser, self).__init__(formatter=LCHelpFormatter())
self.err_exit_code = err_exit_code
def error (self, msg):
"""Print usage info and given message."""
print_usage(msg, exit_code=self.err_exit_code)
@notimplemented
def get_usage (self):
pass
def print_help_msg (self, s, out):
"""Print a help message to stdout."""
s = console.encode(s)
if fileutil.is_tty(out):
strformat.paginate(s)
else:
print >>out, s
sys.exit(0)
@notimplemented
def print_help (self, file=None):
pass
def aggregate_url (aggregate, config, url, err_exit_code=2):
"""Append given commandline URL to input queue."""
get_url_from = checker.get_url_from
if url.lower().startswith("www."):
# syntactic sugar
url = "http://%s" % url
elif url.lower().startswith("ftp."):
# syntactic sugar
url = "ftp://%s" % url
url_data = get_url_from(url, 0, aggregate)
try:
add_intern_pattern(url_data, config)
except UnicodeError:
log.error(LOG_CMDLINE,
_("URL has unparsable domain name: %(domain)s") %
{"domain": sys.exc_info()[1]})
sys.exit(err_exit_code)
aggregate.urlqueue.put(url_data)

View file

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python -u
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2000-2012 Bastian Kleineidam
#
@ -33,6 +33,8 @@ import linkcheck
# override optparse gettext method with the one from linkcheck.init_i18n()
optparse._ = _
# now import the rest of the linkchecker gang
from linkcheck.cmdline import print_version, print_usage, LCOptionParser, \
aggregate_url
from linkcheck import log, LOG_CMDLINE, i18n, strformat
import linkcheck.checker
import linkcheck.configuration
@ -195,19 +197,6 @@ file entry:
for tag, desc in sorted(linkcheck.checker.const.Warnings.items())])
def print_version ():
"""Print the program version and exit."""
console.print_version()
sys.exit(0)
def print_usage (msg):
"""Print a program msg text to stderr and exit."""
print >> console.stderr, _("Error: %(msg)s") % {"msg": msg}
print >> console.stderr, _("Execute 'linkchecker -h' for help")
sys.exit(1)
def viewprof ():
"""Print profiling data and exit."""
if not has_pstats:
@ -246,62 +235,8 @@ def has_encoding (encoding):
except LookupError:
return False
class LCHelpFormatter (optparse.IndentedHelpFormatter, object):
"""Help formatter indenting paragraph-wise."""
def __init__ (self):
"""Set current console width for this formatter."""
width = linkcheck.ansicolor.get_columns(sys.stdout)
super(LCHelpFormatter, self).__init__(width=width)
def format_option (self, option):
"""Customized help display with indentation."""
# The help for each option consists of two parts:
# * the opt strings and metavars
# eg. ("-x", or "-fFILENAME, --file=FILENAME")
# * the user-supplied help string
# eg. ("turn on expert mode", "read data from FILENAME")
# If possible, we write both of these on the same line:
# -x turn on expert mode
# But if the opt string list is too long, we put the help
# string on a second line, indented to the same column it would
# start in if it fit on the first line.
# -fFILENAME, --file=FILENAME
# read data from FILENAME
result = []
opts = self.option_strings[option]
opt_width = self.help_position - self.current_indent - 2
if len(opts) > opt_width:
opts = "%*s%s\n" % (self.current_indent, "", opts)
indent_first = self.help_position
else: # start help on same line as opts
opts = "%*s%-*s " % (self.current_indent, "", opt_width, opts)
indent_first = 0
result.append(opts)
if option.help:
text = strformat.wrap(option.help, self.help_width)
help_lines = text.splitlines()
result.append("%*s%s\n" % (indent_first, "", help_lines[0]))
result.extend(["%*s%s\n" % (self.help_position, "", line)
for line in help_lines[1:]])
elif opts[-1] != "\n":
result.append("\n")
return "".join(result)
class LCOptionParser (optparse.OptionParser, object):
"""Option parser with custom help text layout."""
def __init__ (self):
"""Initializing using our own help formatter class."""
super(LCOptionParser, self).__init__(formatter=LCHelpFormatter())
def error (self, msg):
"""Print usage info and given message."""
print_usage(msg)
# instantiate option parser and configure options
class MyOptionParser (LCOptionParser):
def get_usage (self):
"""Return translated usage text."""
@ -312,15 +247,10 @@ class LCOptionParser (optparse.OptionParser, object):
s = u"%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s" % (self.format_help(),
Examples, LoggerTypes, RegularExpressions, CookieFormat,
ProxySupport, Notes, Retval, Warnings)
s = console.encode(s)
if linkcheck.fileutil.is_tty(sys.stdout):
strformat.paginate(s)
else:
print s
sys.exit(0)
self.print_help_msg(s)
# instantiate option parser and configure options
optparser = LCOptionParser()
optparser = MyOptionParser()
# build a config object for this check session
config = linkcheck.configuration.Configuration()
@ -524,26 +454,6 @@ if has_optcomplete:
optcomplete.autocomplete(optparser, arg_completer=FileCompleter)
def aggregate_url (aggregate, config, url):
"""Append given commandline URL to input queue."""
get_url_from = linkcheck.checker.get_url_from
if url.lower().startswith("www."):
# syntactic sugar
url = "http://%s" % url
elif url.lower().startswith("ftp."):
# syntactic sugar
url = "ftp://%s" % url
url_data = get_url_from(url, 0, aggregate)
try:
linkcheck.add_intern_pattern(url_data, config)
except UnicodeError:
log.error(LOG_CMDLINE,
_("URL has unparsable domain name: %(domain)s") %
{"domain": sys.exc_info()[1]})
sys.exit(1)
aggregate.urlqueue.put(url_data)
def read_stdin_urls ():
"""Read list of URLs, separated by white-space, from stdin."""
urls = []

183
linkchecker-nagios Executable file
View file

@ -0,0 +1,183 @@
#!/usr/bin/python -u
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2012 Bastian Kleineidam
#
# 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.
"""
Check HTML pages for broken links. This is the commandline nagios plugin.
Run this file without options to see how it's done.
"""
import sys
import os
import socket
import optparse
import pprint
# installs _() and _n() gettext functions into global namespace
import linkcheck
# override optparse gettext method with the one from linkcheck.init_i18n()
optparse._ = _
# now import the rest of the linkchecker gang
from linkcheck.cmdline import print_version, print_usage, LCOptionParser, \
aggregate_url
from linkcheck import log, LOG_CMDLINE, strformat
import linkcheck.checker
import linkcheck.configuration
import linkcheck.fileutil
import linkcheck.logger
import linkcheck.ansicolor
from linkcheck.director import console, check_urls, get_aggregate
# usage texts
Usage = _("""USAGE\tlinkchecker-nagios [options] [file-or-url]...""")
Retval = _(r"""RETURN VALUE
0 - everything checked ok without errors or warnings
1 - URL check warnings were found
2 - URL check errors were found
3 - internal or config error
""")
Examples = _(r"""EXAMPLES
linkchecker-nagios -v www.example.org
""")
class MyOptionParser (LCOptionParser):
"""Option parser for LinkChecker nagios plugin."""
def get_usage (self):
"""Return translated usage text."""
return Usage
def print_help (self, file=None):
"""Print translated help text."""
s = u"%s\n%s\n%s" % (self.format_help(), Examples, Retval)
if file is None:
file = sys.stdout
self.print_help_msg(s, file)
# instantiate option parser and configure options
optparser = MyOptionParser(err_exit_code=3)
# build a config object for this check session
config = linkcheck.configuration.Configuration()
# default recursion level is 1
config['recursionlevel'] = 1
# Standard options
group = optparse.OptionGroup(optparser, _("Standard nagios options"))
group.add_option("-v", "--verbose", action="count", default=0, dest="verbose",
help=_("""Increase verbosity. This option can be given multiple times."""))
group.add_option("-V", "--version", action="store_true", dest="version",
help=_("""Print version and exit."""))
group.add_option("-t", "--timeout", type="int", dest="timeout",
metavar="NUMBER",
help=_(
"""Set the timeout for connection attempts in seconds. The default
timeout is %d seconds.""") % config["timeout"])
# Checking options
group = optparse.OptionGroup(optparser, _("Checking options"))
group.add_option("-f", "--config", type="string", dest="configfile",
metavar="FILENAME",
help=_(
"""Use FILENAME as configuration file. Per default LinkChecker uses
~/.linkchecker/linkcheckerrc (under Windows
%HOMEPATH%\\.linkchecker\\linkcheckerrc)."""))
# read and parse command line options and arguments
(options, args) = optparser.parse_args()
if options.version is not None:
print_version()
if options.timeout is not None:
if options.timeout > 0:
config["timeout"] = options.timeout
else:
print_usage(_("Illegal argument %(arg)r for option %(option)s") % \
{"arg": options.timeout, "option": "'--timeout'"},
exit_code=3)
socket.setdefaulttimeout(config["timeout"])
if options.verbose >= 3:
debug = ['all']
else:
debug = None
# initialize logging
config.init_logging(console.StatusLogger(), debug=debug)
log.debug(LOG_CMDLINE, _("Python %(version)s on %(platform)s") % \
{"version": sys.version, "platform": sys.platform})
# read configuration files
try:
files = []
if options.configfile:
path = linkcheck.configuration.normpath(options.configfile)
if os.path.isfile(path):
files.append(path)
else:
log.warn(LOG_CMDLINE,
_("Unreadable config file: %r"), options.configfile)
sys.exit(3)
config.read(files=files)
except linkcheck.LinkCheckerError, msg:
# config error
print_usage(str(msg), exit_code=3)
linkcheck.drop_privileges()
socket.setdefaulttimeout(config["timeout"])
if options.verbose < 0:
config['logger'] = config.logger_new('none')
else:
config["verbose"] = True
config["warnings"] = True
if options.verbose >= 2:
config["complete"] = True
# check missing passwords
for entry in config["authentication"]:
if entry["password"] is None:
pattern = entry["pattern"].pattern
log.warn(LOG_CMDLINE, _("missing password for pattern %(pattern)s") % dict(pattern=pattern))
sys.exit(3)
# sanitize the configuration
config.sanitize()
log.debug(LOG_CMDLINE, "configuration: %s",
pprint.pformat(sorted(config.items())))
# prepare checking queue
aggregate = get_aggregate(config)
# add urls to queue
if args:
for url in args:
aggregate_url(aggregate, config, strformat.stripurl(url), err_exit_code=3)
else:
log.warn(LOG_CMDLINE, _("no files or URLs given"))
sys.exit(3)
# finally, start checking
check_urls(aggregate)
stats = config['logger'].stats
# on internal errors, exit with status 3
if stats.internal_errors:
sys.exit(3)
# on errors, exit with status 2
if stats.errors:
sys.exit(2)
# on warnings, exit with status 1
if stats.warnings_printed and config['warnings']:
sys.exit(1)

View file

@ -29,6 +29,9 @@ It includes the following features:
- automatic MANIFEST.in check
- automatic generation of .mo locale files
- automatic permission setting on POSIX systems for installed files
Because of all the features, this script is nasty and big.
Change it very careful.
"""
import sys
@ -581,7 +584,7 @@ library_dirs = []
# libraries
libraries = []
# scripts
scripts = ['linkchecker', 'linkchecker-gui']
scripts = ['linkchecker', 'linkchecker-gui', 'linkchecker-nagios']
if os.name == 'nt':
# windows does not have unistd.h