diff --git a/puppeter/__init__.py b/puppeter/__init__.py index cd1a16b..40b7145 100644 --- a/puppeter/__init__.py +++ b/puppeter/__init__.py @@ -1,7 +1,19 @@ +import logging + # The version of the app __version__ = '0.1.0.dev0' __program__ = 'puppeter' + +def get_logger(cls): + name = __fullname(cls) + return logging.getLogger(name) + + +def __fullname(cls): + return cls.__module__ + "." + cls.__name__ + + if __name__ == '__main__': from puppeter.main import main main() diff --git a/puppeter/main.py b/puppeter/main.py index 0fcfc26..c82ed2c 100644 --- a/puppeter/main.py +++ b/puppeter/main.py @@ -5,6 +5,15 @@ from puppeter import __program__ from puppeter.presentation.cmdparser import CommandLineParser + +def main(argv=sys.argv): + """Entry point for the puppeter application""" + __load_modules(__program__) + parser = CommandLineParser(argv) + app = parser.parse() + app.run() + + __ROOT_DIR = dirname(dirname(__file__)) @@ -24,11 +33,3 @@ def __load_modules(module_name): # load the modules for module_name_to_import in modules: __import__(module_name_to_import) - - -def main(argv=sys.argv): - """Entry point for the puppeter application""" - __load_modules(__program__) - parser = CommandLineParser(argv) - app = parser.parse() - app.run() diff --git a/puppeter/persistence/gateway/answers.py b/puppeter/persistence/gateway/answers.py index e025d28..abb0ca3 100644 --- a/puppeter/persistence/gateway/answers.py +++ b/puppeter/persistence/gateway/answers.py @@ -1,3 +1,4 @@ +import puppeter from puppeter import container from puppeter.domain.model.installer import Installer from puppeter.domain.model.answers import Answers @@ -17,6 +18,8 @@ def write_answers_to_file(self, answers, file): def read_answers_from_file(self, file): code = ruamel.yaml.load(file.read(), ruamel.yaml.RoundTripLoader) + log = puppeter.get_logger(YamlAnswersGateway) + log.debug("Answers loaded from file: %s", code) installer = self.__load_installer(code['installer']) return Answers( installer=installer diff --git a/puppeter/presentation/app.py b/puppeter/presentation/app.py new file mode 100644 index 0000000..08ca3f3 --- /dev/null +++ b/puppeter/presentation/app.py @@ -0,0 +1,94 @@ +import logging +import os +import sys +from logging import StreamHandler +from logging.handlers import SysLogHandler + +import puppeter +from puppeter.domain.model.os import OsFamily +from puppeter.persistence.os import Facter + + +class App: + + def __init__(self, parsed): + self._parsed = parsed + + def run(self): + root = logging.getLogger() + level = self.__get_log_level(self._parsed.verbose) + root.setLevel(level) + handlers = (self.__syslog_handler(), self.__stderr_handler()) + for hndl in handlers: + root.addHandler(hndl) + + @staticmethod + def __stderr_handler(): + handler = StreamHandler(stream=sys.stderr) + fmt = '%(levelname)s: %(message)s' + handler.setFormatter(ColoredFormatter(fmt=fmt)) + handler.setLevel(logging.NOTSET) + return handler + + @staticmethod + def __syslog_handler(): + osfamily = Facter.get(OsFamily) + if osfamily in (OsFamily.Debian, OsFamily.RedHat, OsFamily.Suse): + handler = SysLogHandler(address='/dev/log') + else: + handler = SysLogHandler() + fmt = puppeter.__program__ + '[%(process)d]: %(levelname)s %(name)s - %(message)s' + handler.setFormatter(logging.Formatter(fmt=fmt)) + handler.setLevel(logging.INFO) + return handler + + @staticmethod + def __get_log_level(verbosity): + if verbosity is None: + verbosity = 0 + if verbosity > 2: + verbosity = 2 + levels = { + 0: logging.WARNING, + 1: logging.INFO, + 2: logging.DEBUG + } + return levels[verbosity] + + +def color_code(fg, bg=None): + if bg is not None: + return FGBG_COLOR_SEQ % (fg, bg) + else: + return FG_COLOR_SEQ % fg + + +RESET_SEQ = "\033[0m" +FG_COLOR_SEQ = "\033[38;5;%dm" +FGBG_COLOR_SEQ = "\033[38;5;%d;48;5;%dm" +# ref: https://unix.stackexchange.com/a/124409/145501 +COLORS = { + 'WARNING': color_code(fg=226), + 'INFO': color_code(fg=155), + 'DEBUG': color_code(fg=244), + 'CRITICAL': color_code(fg=219, bg=196), + 'ERROR': color_code(fg=196) +} +try: + SUPPORTS_256_COLORS = os.environ['XTERM_256_COLORS'] == '1' +except KeyError: + SUPPORTS_256_COLORS = False + + +class ColoredFormatter(logging.Formatter): + + def __init__(self, fmt, use_color=SUPPORTS_256_COLORS): + logging.Formatter.__init__(self, fmt=fmt) + self.use_color = use_color + + def format(self, record): + formatted = logging.Formatter.format(self, record) + levelname = record.levelname + if self.use_color and levelname in COLORS: + formatted = COLORS[levelname] + formatted + RESET_SEQ + return formatted diff --git a/puppeter/presentation/cmdparser.py b/puppeter/presentation/cmdparser.py index 6a4da90..b681d85 100644 --- a/puppeter/presentation/cmdparser.py +++ b/puppeter/presentation/cmdparser.py @@ -52,7 +52,7 @@ def parse(self): metavar='FILE', help='An answer file to be used to perform unattended setup') parser.add_argument('--verbose', '-v', action='count', - help='Print more verbose output (up to 3 verbosity flags are supported)') + help='Print more verbose output (up to 2 verbosity flags are supported)') parser.add_argument('--version', action=_VersionAction, version='%(prog)s ' + puppeter.__version__) parser.add_argument('--execute', '-e', action='store_true', help='Executes setup commands instead of printing them') diff --git a/puppeter/presentation/unattendedapp.py b/puppeter/presentation/unattendedapp.py index 93fb92f..17964b3 100644 --- a/puppeter/presentation/unattendedapp.py +++ b/puppeter/presentation/unattendedapp.py @@ -1,17 +1,28 @@ -from puppeter.domain.gateway.answers import AnswersGateway +import puppeter from puppeter import container -from pprint import pprint +from puppeter.domain.model.answers import Answers # NOQA +from puppeter.domain.gateway.answers import AnswersGateway +from puppeter.presentation.app import App -class UnattendedApp: +class UnattendedApp(App): def __init__(self, parsed): - self.__parsed = parsed + App.__init__(self, parsed) + self.__log = puppeter.get_logger(UnattendedApp) def run(self): - file = self.__parsed.answers - answers = self.__gw().read_answers_from_file(file) - pprint(answers) + App.run(self) + file = self._parsed.answers + answers = self.__load_answers(container.get(AnswersGateway), file) + if self._parsed.execute: + self.__log.warning('Installation will be performed from answer file:' + ' %s. System will be altered!!!', file.name) + else: + self.__log.info('Installation commands will be generated based on answers file' + ' %s and printed out. System will NOT be altered.', file.name) + self.__log.debug(answers) - def __gw(self): - # type: () -> AnswersGateway - return container.get(AnswersGateway) + @staticmethod + def __load_answers(gateway, file): + # type: (AnswersGateway, file) -> Answers + return gateway.read_answers_from_file(file)