Implement really basic checking

No arguments, no timeouts, no nothing. But it remembers the return
codes, so that counts.

The example has been updated to work with the library.
master
LEdoian 2 years ago
parent 0125cb9f8f
commit 51e8e1d47b

@ -1,7 +1,10 @@
import sys import sys
import traceback import traceback
import argparse import argparse
from enums import Enum from enum import Enum
from typing import NoReturn
from functools import total_ordering
import abc # Temporary: the only abstract method is Check.check(), until it can fall back to using Check.measure()
""" """
CheckLib: a simple wrapper around Icinga checks dealing with common CheckLib: a simple wrapper around Icinga checks dealing with common
@ -10,18 +13,24 @@ code like correct error codes and output formatting
The main class is `Check`, other classes are just helpers. The main class is `Check`, other classes are just helpers.
""" """
@total_ordering
class Result(Enum): class Result(Enum):
""" """
Enumeration of possible check results. Enumeration of possible check results.
The values are related return codes. The values are related return codes. Results can be compared, "larger"
means worse status. WARNING < UNKNOWN < CRITICAL
""" """
OK = 0 OK = 0
WARNING = 1 WARNING = 1
CRITICAL = 2 CRITICAL = 2
UNKNOWN = 3 UNKNOWN = 3
class Check: def __lt__(self, other):
ordering = (Result.OK, Result.WARNING, Result.UNKNOWN, Result.CRITICAL)
return ordering.index(self) < ordering.index(other)
class Check(abc.ABC):
""" """
The class representing the check. The class representing the check.
@ -38,7 +47,7 @@ class Check:
ArgumentParser, e.g. adding more parameters ArgumentParser, e.g. adding more parameters
1. self.args = argparse.parse_args(sys.argv) 1. self.args = argparse.parse_args(sys.argv)
1. check(): the function to check state and populate 1. check(): the function to check state and populate
self.result, self.short_message and so on. self.result, self.short_status and so on.
1. sys.exit with the relevant exit code according to 1. sys.exit with the relevant exit code according to
self.result self.result
- If anything fails, e.g. by raising an exception, - If anything fails, e.g. by raising an exception,
@ -55,13 +64,101 @@ class Check:
are used. are used.
""" """
# mypy wants __init__ defined before run, because it is otherwise confused
# by using self.result before assignment. I do not like tools dictating me
# how to code, but this seems to be a change small enough to be granted an
# exception. (But I had to at least write this here…)
def __init__(self):
"""
Initialises the check.
This method may be overridden. If so, super().__init__() should be
called early in order for our attributes to be available. (It is not
recommended to not call this initialization.)
"""
# We do not know what the result is unless somebody fills it in.
self.result = Result.UNKNOWN
self.short_status = None
self.long_status = None
# TODO: Initialize argparser
@staticmethod
def _bail_out() -> NoReturn:
"""
A last-resort fail function.
Does not make any assumptions, just prints traceback (if any) and exits
with UNKNOWN status.
Not meant to be called by subclasses.
"""
short_status = 'Error, checklib bailing out!' # FIXME: do not hardcode name.
long_status = 'No details known :-('
e_type, e_value, e_tb = sys.exc_info()
if e_type is not None:
short_status = f'EXCEPTION: {repr(e_value)}'
long_status = traceback.format_exc()
# Let's just hope here that the lines do not contain '|' (which would
# mark performance data following)…
print(short_status)
if long_status: print(long_status)
sys.exit(Result.UNKNOWN.value)
@classmethod @classmethod
def run(cls, *args, **kwargs): def run(cls, *args, **kwargs) -> NoReturn:
""" """
This method implements the checking start-to-end. This method implements the checking start-to-end.
The arguments, if any, are passed to the __init__ of the class. The arguments, if any, are passed to the __init__ of the class.
Please run this method on the class, do not create instances. This
allows to construct the instance in a `try` block that handles possible
exceptions in compliance with the Plugin API.
However, unless we do a dark Python magic with descriptors and stuff,
it is not possible for us to know how you call it. So we cannot even
warn you if you do the wrong thing.
""" """
try: try:
inst = cls(*args, **kwargs)
# TODO: Add argparse arguments
# TODO: Parse arguments into inst.args
# TODO: Prime the timer
inst.check()
# No code from subclass should be run from this moment, so we allow
# ourself to modify the instance. (This is potentially controversial.)
if inst.short_status is None:
inst.short_status = 'UNKNOWN: No status supplied!'
inst.result = max(inst.result, Result.UNKNOWN)
print(inst.short_status)
if inst.long_status is not None:
print(inst.long_status)
return_value = inst.result.value
assert isinstance(return_value, int)
except BaseException:
# We catch everything here, even exits. The idea is that the
# subclass should not exit on its own. This however means that it
# is harder to fail fast from methods other than check() FIXME.
# TODO: Special handling for timeouts
Check._bail_out()
# The only happy-path exit.
sys.exit(return_value)
@abc.abstractmethod
def check(self):
"""
This function does the checking. Subclasses should
"""
# TODO: Fallback for self.measure()
...

@ -1,9 +1,11 @@
#!/usr/bin/env python3
from checklib import Check, Result from checklib import Check, Result
class MyCheck(Check): class MyCheck(Check):
def check(self): def check(self):
self.result.state = Result.UNKNOWN self.result = Result.UNKNOWN
self.result.short_message = 'I don\'t know :-)' self.short_status = 'I don\'t know :-)'
def add_arguments(self): def add_arguments(self):
pass pass
@ -12,3 +14,7 @@ class MyCheck(Check):
# If defining __init__, call super().__init__ first -- it populates # If defining __init__, call super().__init__ first -- it populates
# self.result, self.argumentparser and so on. # self.result, self.argumentparser and so on.
if __name__ == '__main__':
MyCheck.run()

Loading…
Cancel
Save