From: Frank Brehm Date: Tue, 14 Feb 2017 16:20:42 +0000 (+0100) Subject: Reorganized object inheritance X-Git-Tag: 0.8.4~33 X-Git-Url: https://git.uhu-banane.de/?a=commitdiff_plain;h=bf8dc871d18c7b7f28acf34310f6929362b5e470;p=pixelpark%2Fpuppetmaster-webhooks.git Reorganized object inheritance --- diff --git a/hooks.yaml b/hooks.yaml new file mode 100644 index 0000000..00fccb9 --- /dev/null +++ b/hooks.yaml @@ -0,0 +1,12 @@ +--- +verbose: 0 +do_sudo: true +log_dir: '/var/log/webhooks' +default_email: 'frank@brehm-online.com' +default_parent_dir: '/www/data' +smtp_server: 'smtp.pixelpark.com' +smtp_port: 25 +mail_cc_addresses: + - 'webmaster@pixelpark.com' + - 'frank.brehm@pixelpark.com' +sender_address: 'Puppetmaster ' diff --git a/lib/webhooks/base_app.py b/lib/webhooks/base_app.py index ad545fa..3e73999 100644 --- a/lib/webhooks/base_app.py +++ b/lib/webhooks/base_app.py @@ -42,7 +42,7 @@ class BaseHookApp(object): dev_re = re.compile(r'^dev') # ------------------------------------------------------------------------- - def __init__(self, appname=None, version=__version__): + def __init__(self, appname=None, verbose=0, version=__version__): """Constructor.""" self._appname = None @@ -63,11 +63,12 @@ class BaseHookApp(object): @type: str """ - self._verbose = 1 + self._verbose = verbose """ @ivar: verbosity level (0 - 9) @type: int """ + self._start_verbose = verbose self.data = None self.json_data = None @@ -145,7 +146,7 @@ class BaseHookApp(object): @property def error_logfile(self): """The logfile for STDERR of this application.""" - return os.path.join(self.log_directory, self.appname + '.log') + return os.path.join(self.log_directory, self.appname + '.error.log') # ------------------------------------------------------------------------- def __str__(self): @@ -168,7 +169,6 @@ class BaseHookApp(object): @rtype: dict """ - res = self.__dict__ res = {} for key in self.__dict__: if key.startswith('_') and not key.startswith('__'): @@ -190,9 +190,13 @@ class BaseHookApp(object): """Reading configuration from different YAML files.""" yaml_files = [] - # ./deploy.yaml + # ./hooks.yaml + yaml_files.append(os.path.join(self.base_dir, 'hooks.yaml')) + # ./.yaml yaml_files.append(os.path.join(self.base_dir, self.appname + '.yaml')) - # /etc/pixelpark/deploy.yaml + # /etc/pixelpark/hooks.yaml + yaml_files.append(os.sep + os.path.join('etc', 'pixelpark', 'hooks.yaml')) + # /etc/pixelpark/.yaml yaml_files.append(os.sep + os.path.join('etc', 'pixelpark', self.appname + '.yaml')) for yaml_file in yaml_files: @@ -202,18 +206,36 @@ class BaseHookApp(object): def read_from_yaml(self, yaml_file): """Reading configuration from given YAML file.""" - # LOG.debug("Trying to read config from {!r} ...".format(yaml_file)) + if self._start_verbose > 1: + self.print_err("Trying to read config from {!r} ...".format(yaml_file)) if not os.access(yaml_file, os.F_OK): - # LOG.debug("File {!r} does not exists.".format(yaml_file)) return - # LOG.debug("Reading config from {!r} ...".format(yaml_file)) + if self._start_verbose > 1: + self.print_err("Reading config from {!r} ...".format(yaml_file)) config = {} with open(yaml_file, 'rb') as fh: config = yaml.load(fh.read()) - # LOG.debug("Read config:\n{}".format(pp(config))) + if self._start_verbose > 2: + self.print_err("Read config:\n{}".format(pp(config))) + if config and isinstance(config, dict): + self.evaluate_config(config, yaml_file) + + # ------------------------------------------------------------------------- + def evaluate_config(self, config, yaml_file): if 'verbose' in config: - self.verbose = config['verbose'] + try: + v = int(config['verbose']) + if v >= 0: + if v >= self.verbose: + self._verbose = v + else: + LOG.warn("Wrong verbose level {v!d} in file {f!r}, must be >= 0".format( + v=v, f=yaml_file)) + except ValueError as e: + msg = "Wrong verbose level {v!r} in file {f!r}: {e}".format( + v=config['verbose'], f=yaml_file, e=e) + LOG.warn(msg) if 'do_sudo' in config: self.do_sudo = to_bool(config['do_sudo']) @@ -322,6 +344,10 @@ class BaseHookApp(object): return + # ------------------------------------------------------------------------- + def print_err(self, *objects, sep=' ', end='\n', file=sys.stderr.buffer, flush=True): + self.print_out(*objects, sep=sep, end=end, file=file, flush=flush) + # ------------------------------------------------------------------------- def print_out(self, *objects, sep=' ', end='\n', file=sys.stdout.buffer, flush=True): @@ -341,6 +367,15 @@ class BaseHookApp(object): if self.verbose > 1: LOG.debug("Base directory: {!r}".format(self.base_dir)) + self.run() + + # ------------------------------------------------------------------------- + def run(self): + """Main routine, must be overridden in descendant classes.""" + + msg = "Method run() must be overridden in descendant classes of {!r}.".format( + self.__class__.__name__) + raise NotImplementedError(msg) # ============================================================================= diff --git a/lib/webhooks/r10k.py b/lib/webhooks/r10k.py index 5cd6b56..224f14b 100644 --- a/lib/webhooks/r10k.py +++ b/lib/webhooks/r10k.py @@ -23,6 +23,8 @@ import webhooks from webhooks.common import pp, to_bytes, to_str, to_bool +from webhooks.base_app import BaseHookApp + __version__ = webhooks.__version__ LOG = logging.getLogger(__name__) DEFAULT_EMAIL = 'frank.brehm@pixelpark.com' @@ -30,130 +32,19 @@ DEFAULT_SENDER = 'Puppetmaster <{}>'.format(DEFAULT_EMAIL) # ============================================================================= -class R10kHookApp(object): +class R10kHookApp(BaseHookApp): """ - Class for the application objects. + Class for the r10k-hook application object. """ - cgi_bin_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) - base_dir = os.path.dirname(cgi_bin_dir) - - special_chars_re = re.compile(r'[^a-z0-9_\-]', re.IGNORECASE) - dev_re = re.compile(r'^dev') - # ------------------------------------------------------------------------- - def __init__(self, appname=None, version=__version__): + def __init__(self, appname=None, verbose=0, version=__version__): """Constructor.""" - self._appname = None - """ - @ivar: name of the current running application - @type: str - """ - if appname: - v = str(appname).strip() - if v: - self._appname = v - if not self._appname: - self._appname = os.path.basename(sys.argv[0]) - - self._version = version - """ - @ivar: version string of the current object or application - @type: str - """ - - self._verbose = 1 - """ - @ivar: verbosity level (0 - 9) - @type: int - """ - - self.data = None - self.json_data = None - self.ref = None - self.namespace = None - self.name = None - self.full_name = None - self.git_ssh_url = None - self.do_sudo = True - self.ignore_projects = [] - self.error_data = [] - self.smtp_server = 'smtp.pixelpark.com' - self.smtp_port = 25 - - self.default_parent_dir = '/www/data' - self.default_email = DEFAULT_EMAIL - self.mail_to_addresses = [] - self.mail_cc_addresses = [ - 'webmaster@pixelpark.com', - self.default_email, - ] - self.sender_address = DEFAULT_SENDER - - self._log_directory = os.sep + os.path.join('var', 'log', 'webhooks') - - self.read_config() - self.init_logging() - - # ----------------------------------------------------------- - @property - def appname(self): - """The name of the current running application.""" - return self._appname - - @appname.setter - def appname(self, value): - if value: - v = str(value).strip() - if v: - self._appname = v - - # ----------------------------------------------------------- - @property - def version(self): - """The version string of the current object or application.""" - return self._version - - # ----------------------------------------------------------- - @property - def verbose(self): - """The verbosity level.""" - return getattr(self, '_verbose', 0) - - @verbose.setter - def verbose(self, value): - v = int(value) - if v >= 0: - self._verbose = v - else: - LOG.warn("Wrong verbose level %r, must be >= 0", value) - - # ----------------------------------------------------------- - @property - def log_directory(self): - """The directory containing the logfiles of this application.""" - return self._log_directory - - # ----------------------------------------------------------- - @property - def logfile(self): - """The logfile of this application.""" - return os.path.join(self.log_directory, self.appname + '.log') - - # ------------------------------------------------------------------------- - def __str__(self): - """ - Typecasting function for translating object structure - into a string - - @return: structure as string - @rtype: str - """ - - return pp(self.as_dict()) + super(R10kHookApp, self).__init__( + appname=appname, verbose=verbose, version=version) # ------------------------------------------------------------------------- def as_dict(self): @@ -164,166 +55,35 @@ class R10kHookApp(object): @rtype: dict """ - res = self.__dict__ - res = {} - for key in self.__dict__: - if key.startswith('_') and not key.startswith('__'): - continue - res[key] = self.__dict__[key] - res['__class_name__'] = self.__class__.__name__ - res['appname'] = self.appname - res['verbose'] = self.verbose - res['base_dir'] = self.base_dir - res['cgi_bin_dir'] = self.cgi_bin_dir - res['log_directory'] = self.log_directory - res['logfile'] = self.logfile + res = super(R10kHookApp, self).as_dict() return res # ------------------------------------------------------------------------- - def read_config(self): - """Reading configuration from different YAML files.""" - - yaml_files = [] - # ./deploy.yaml - yaml_files.append(os.path.join(self.base_dir, self.appname + '.yaml')) - # /etc/pixelpark/deploy.yaml - yaml_files.append(os.sep + os.path.join('etc', 'pixelpark', self.appname + '.yaml')) - - for yaml_file in yaml_files: - self.read_from_yaml(yaml_file) - - # ------------------------------------------------------------------------- - def read_from_yaml(self, yaml_file): - """Reading configuration from given YAML file.""" - - # LOG.debug("Trying to read config from {!r} ...".format(yaml_file)) - if not os.access(yaml_file, os.F_OK): - # LOG.debug("File {!r} does not exists.".format(yaml_file)) - return - # LOG.debug("Reading config from {!r} ...".format(yaml_file)) - config = {} - with open(yaml_file, 'rb') as fh: - config = yaml.load(fh.read()) - # LOG.debug("Read config:\n{}".format(pp(config))) - - if 'verbose' in config: - self.verbose = config['verbose'] - - if 'do_sudo' in config: - self.do_sudo = to_bool(config['do_sudo']) - - if 'log_dir' in config and config['log_dir']: - self._log_directory = config['log_dir'] - - if 'default_email' in config and config['default_email']: - self.default_email = config['default_email'] - - if 'smtp_server' in config and config['smtp_server'].strip(): - self.smtp_server = config['smtp_server'].strip() - - if 'smtp_port' in config and config['smtp_port']: - msg = "Invalid port {p!r} for SMTP in {f!r} found.".format( - p=config['smtp_port'], f=yaml_file) - try: - port = int(config['smtp_port']) - if port > 0 and port < 2**16: - self.smtp_port = port - else: - self.error_data.append(msg) - except ValueError: - self.error_data.append(msg) - - if 'default_parent_dir' in config and config['default_parent_dir']: - pdir = config['default_parent_dir'] - if os.path.isabs(pdir): - self.default_parent_dir = pdir - - if 'mail_cc_addresses' in config: - if config['mail_cc_addresses'] is None: - self.mail_cc_addresses = [] - elif isinstance(config['mail_cc_addresses'], str): - if config['mail_cc_addresses']: - self.mail_cc_addresses = [config['mail_cc_addresses']] - else: - self.mail_cc_addresses = [] - elif isinstance(config['mail_cc_addresses'], list): - self.mail_cc_addresses = config['mail_cc_addresses'] - - # ------------------------------------------------------------------------- - def init_logging(self): - """ - Initialize the logger object. - It creates a colored loghandler with all output to STDERR. - Maybe overridden in descendant classes. - - @return: None - """ - - root_log = logging.getLogger() - root_log.setLevel(logging.INFO) - if self.verbose: - root_log.setLevel(logging.DEBUG) - - # create formatter - format_str = '' - if 'REQUEST_METHOD' in os.environ or self.verbose > 1: - format_str = '[%(asctime)s]: ' - format_str += self.appname + ': ' - if self.verbose: - if self.verbose > 1: - format_str += '%(name)s(%(lineno)d) %(funcName)s() ' - else: - format_str += '%(name)s ' - format_str += '%(levelname)s - %(message)s' - formatter = logging.Formatter(format_str) + def evaluate_config(self, config, yaml_file): - if 'REQUEST_METHOD' in os.environ: + super(R10kHookApp, self).evaluate_config(config, yaml_file) - #sys.stderr.write("Trying to open logfile {!r} ...\n".format(self.logfile)) - # we are in a CGI environment - if os.path.isdir(self.log_directory) and os.access(self.log_directory, os.W_OK): - lh_file = logging.FileHandler( - self.logfile, mode='a', encoding='utf-8', delay=True) - if self.verbose: - lh_file.setLevel(logging.DEBUG) - else: - lh_file.setLevel(logging.INFO) - lh_file.setFormatter(formatter) - root_log.addHandler(lh_file) + if 'ignore_projects' in config: + if config['ignore_projects'] is None: + self.ignore_projects = [] + elif isinstance(config['ignore_projects'], str): + if config['ignore_projects']: + self.ignore_projects = [config['ignore_projects']] + elif isinstance(config['ignore_projects'], list): + self.ignore_projects = config['ignore_projects'] - else: - # create log handler for console output - lh_console = logging.StreamHandler(sys.stderr) - if self.verbose: - lh_console.setLevel(logging.DEBUG) - else: - lh_console.setLevel(logging.INFO) - lh_console.setFormatter(formatter) - - root_log.addHandler(lh_console) - - return + if 'add_ignore_projects' in config and config['add_ignore_projects']: + if isinstance(config['add_ignore_projects'], str): + self.ignore_projects.append(config['add_ignore_projects']) + elif isinstance(config['add_ignore_projects'], list): + self.ignore_projects += config['add_ignore_projects'] # ------------------------------------------------------------------------- - def print_out(self, *objects, sep=' ', end='\n', file=sys.stdout.buffer, flush=True): - - file.write(to_bytes(sep.join(map(lambda x: str(x), objects)))) - if end: - file.write(to_bytes(end)) - if flush: - file.flush() - - # ------------------------------------------------------------------------- - def __call__(self): - """Helper method to make the resulting object callable.""" - - self.print_out("Content-Type: text/plain;charset=utf-8\n") - self.print_out("Python CGI läuft.\n") - - if self.verbose > 1: - LOG.debug("Base directory: {!r}".format(self.base_dir)) + def run(self): + """Main routine.""" + LOG.info("Starting {} ...".format(self.appname)) # ============================================================================= diff --git a/r10k-hook.yaml b/r10k-hook.yaml new file mode 100644 index 0000000..aecbeec --- /dev/null +++ b/r10k-hook.yaml @@ -0,0 +1,4 @@ +--- +#add_ignore_projects: +# - nova + diff --git a/r10k_hook-new.py b/r10k_hook-new.py index b7a3189..8091d8b 100755 --- a/r10k_hook-new.py +++ b/r10k_hook-new.py @@ -20,7 +20,7 @@ from webhooks.r10k import R10kHookApp MY_APPNAME = 'r10k-hook' LOG = logging.getLogger(MY_APPNAME) -app = R10kHookApp(appname=MY_APPNAME) +app = R10kHookApp(appname=MY_APPNAME, verbose=3) if app.verbose > 2: LOG.debug("{c} object:\n{o}".format(c=app.__class__.__name__, o=app))