From 218f93d000129026df8c5445ab2236bbada94313 Mon Sep 17 00:00:00 2001 From: David Moreau Simard Date: Wed, 4 Apr 2018 17:22:26 -0400 Subject: [PATCH] 1.0 backport: Refactor configuration and instanciate it This is a squash of 4 commits backported from 1.0 and adapted to work against the stable release of ARA: - Instanciate the configuration loading into classes - Refactor configuration - Import ARA configuration and views "just-in-time" - The callback no longer persists a json file to cache the playbook id for the purpose of ara_record and ara_read, this is instead done in-memory in a new _cache key of the flask application context. In summary: - ARA now ships a default (and vastly improved!) logging configuration - Flask context_processors, filters and errorhandlers have been folded back into the webapp module - The configuration has been exploded into submodules that are instanciated on a need basis rather than imported. - Since the configuration is now instanciated, this resolves issues with the configuration "leaking" into what was thought to be different processes/forks/instances of the ARA application. Parts of the configuration are now loaded/imported/instanciated "just in time" because we do not need to load and configure all the components all the time. For example, if we're working with an application context, we don't want to re-import/re-configure everything. (cherry picked from commit 0db256a7b0f3e5a71dc1029b597f17dc7d44c652) (cherry picked from commit e34ee7ff2953cd5db0308d6446724fcb5603e13e) (cherry picked from commit 2c418edee5600787f9b2e979964897e0d7cdf619) (cherry picked from commit 71ef3236753ff9592b29f2b40a666b2398c50d37) Change-Id: Ifee5ec6d1251cd6d0c6933b7c9c371cf43591213 --- ara/config.py | 171 ------------ ara/{plugins/modules => config}/__init__.py | 0 ara/config/base.py | 79 ++++++ ara/config/compat.py | 65 +++++ ara/config/logger.py | 142 ++++++++++ ara/config/webapp.py | 64 +++++ ara/context_processors.py | 45 ---- ara/errorhandlers.py | 24 -- ara/filters.py | 122 --------- ara/plugins/actions/ara_read.py | 20 +- ara/plugins/actions/ara_record.py | 20 +- ara/plugins/callbacks/log_ara.py | 15 +- ara/plugins/modules/ara_read.py | 74 ------ ara/plugins/modules/ara_record.py | 92 ------- ara/templates/about.html | 4 +- ara/tests/unit/common.py | 4 +- ara/tests/unit/test_callback.py | 10 - ara/tests/unit/test_cli.py | 70 +---- ara/tests/unit/test_config.py | 63 ++--- ara/views/about.py | 66 +++-- ara/webapp.py | 275 ++++++++++++++------ 21 files changed, 628 insertions(+), 797 deletions(-) delete mode 100644 ara/config.py rename ara/{plugins/modules => config}/__init__.py (100%) create mode 100644 ara/config/base.py create mode 100644 ara/config/compat.py create mode 100644 ara/config/logger.py create mode 100644 ara/config/webapp.py delete mode 100644 ara/context_processors.py delete mode 100644 ara/errorhandlers.py delete mode 100644 ara/filters.py delete mode 100644 ara/plugins/modules/ara_read.py delete mode 100644 ara/plugins/modules/ara_record.py diff --git a/ara/config.py b/ara/config.py deleted file mode 100644 index e8d7587d..00000000 --- a/ara/config.py +++ /dev/null @@ -1,171 +0,0 @@ -# Copyright (c) 2017 Red Hat, Inc. -# -# This file is part of ARA: Ansible Run Analysis. -# -# ARA 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 3 of the License, or -# (at your option) any later version. -# -# ARA 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 ARA. If not, see . - -import os -import xstatic.main -import xstatic.pkg.bootstrap_scss -import xstatic.pkg.datatables -import xstatic.pkg.jquery -import xstatic.pkg.patternfly -import xstatic.pkg.patternfly_bootstrap_treeview - -from ansible import __version__ as ansible_version -from ansible.constants import get_config -try: - from ansible.constants import load_config_file -except ImportError: - # Ansible 2.4 no longer provides load_config_file, this is handled further - # down - from ansible.config.manager import find_ini_config_file - # Also, don't scream deprecated things at us - import ansible.constants - ansible.constants._deprecated = lambda *args: None -from distutils.version import LooseVersion -from six.moves import configparser - - -def _ara_config(config, key, env_var, default=None, section='ara', - value_type=None): - """ - Wrapper around Ansible's get_config backward/forward compatibility - """ - if default is None: - try: - # We're using env_var as keys in the DEFAULTS dict - default = DEFAULTS.get(env_var) - except KeyError as e: - msg = 'There is no default value for {0}: {1}'.format(key, str(e)) - raise KeyError(msg) - - # >= 2.3.0.0 (NOTE: Ansible trunk versioning scheme has 3 digits, not 4) - if LooseVersion(ansible_version) >= LooseVersion('2.3.0'): - return get_config(config, section, key, env_var, default, - value_type=value_type) - - # < 2.3.0.0 compatibility - if value_type is None: - return get_config(config, section, key, env_var, default) - - args = { - 'boolean': dict(boolean=True), - 'integer': dict(integer=True), - 'list': dict(islist=True), - 'tmppath': dict(istmppath=True) - } - return get_config(config, section, key, env_var, default, - **args[value_type]) - - -DEFAULTS = { - 'ARA_AUTOCREATE_DATABASE': True, - 'ARA_DIR': os.path.expanduser('~/.ara'), - 'ARA_ENABLE_DEBUG_VIEW': False, - 'ARA_HOST': '127.0.0.1', - 'ARA_IGNORE_EMPTY_GENERATION': True, - 'ARA_IGNORE_MIMETYPE_WARNINGS': True, - 'ARA_IGNORE_PARAMETERS': ['extra_vars'], - 'ARA_LOG_CONFIG': None, - 'ARA_LOG_FORMAT': '%(asctime)s - %(name)s - %(levelname)s - %(message)s', - 'ARA_LOG_LEVEL': 'INFO', - 'ARA_PATH_MAX': 40, - 'ARA_PLAYBOOK_OVERRIDE': None, - 'ARA_PLAYBOOK_PER_PAGE': 10, - 'ARA_PORT': '9191', - 'ARA_RESULT_PER_PAGE': 25, - 'ARA_SQL_DEBUG': False, - 'ARA_TMP_DIR': os.path.expanduser('~/.ansible/tmp') -} - -# Bootstrap Ansible configuration -# Ansible >=2.4 takes care of loading the configuration file itself -if LooseVersion(ansible_version) < LooseVersion('2.4.0'): - config, path = load_config_file() -else: - path = find_ini_config_file() - config = configparser.ConfigParser() - if path is not None: - config.read(path) - -# Some defaults need to be based on top of a "processed" ARA_DIR -ARA_DIR = _ara_config(config, 'dir', 'ARA_DIR') -database_path = os.path.join(ARA_DIR, 'ansible.sqlite') -DEFAULTS.update({ - 'ARA_LOG_FILE': os.path.join(ARA_DIR, 'ara.log'), - 'ARA_DATABASE': 'sqlite:///{}'.format(database_path) -}) - -ARA_AUTOCREATE_DATABASE = _ara_config(config, 'autocreate_database', - 'ARA_AUTOCREATE_DATABASE', - value_type='boolean') -ARA_ENABLE_DEBUG_VIEW = _ara_config(config, 'enable_debug_view', - 'ARA_ENABLE_DEBUG_VIEW', - value_type='boolean') -ARA_HOST = _ara_config(config, 'host', 'ARA_HOST') -ARA_IGNORE_PARAMETERS = _ara_config(config, 'ignore_parameters', - 'ARA_IGNORE_PARAMETERS', - value_type='list') -ARA_LOG_CONFIG = _ara_config(config, 'logconfig', 'ARA_LOG_CONFIG') -ARA_LOG_FILE = _ara_config(config, 'logfile', 'ARA_LOG_FILE') -ARA_LOG_FORMAT = _ara_config(config, 'logformat', 'ARA_LOG_FORMAT') -ARA_LOG_LEVEL = _ara_config(config, 'loglevel', 'ARA_LOG_LEVEL') -ARA_PLAYBOOK_OVERRIDE = _ara_config(config, 'playbook_override', - 'ARA_PLAYBOOK_OVERRIDE', - value_type='list') -ARA_PLAYBOOK_PER_PAGE = _ara_config(config, 'playbook_per_page', - 'ARA_PLAYBOOK_PER_PAGE', - value_type='integer') -ARA_PORT = _ara_config(config, 'port', 'ARA_PORT') -ARA_RESULT_PER_PAGE = _ara_config(config, 'result_per_page', - 'ARA_RESULT_PER_PAGE', - value_type='integer') -ARA_TMP_DIR = _ara_config(config, 'local_tmp', 'ANSIBLE_LOCAL_TEMP', - default=DEFAULTS['ARA_TMP_DIR'], - section='defaults', - value_type='tmppath') - -# Static generation with flask-frozen -ARA_IGNORE_EMPTY_GENERATION = _ara_config(config, - 'ignore_empty_generation', - 'ARA_IGNORE_EMPTY_GENERATION', - value_type='boolean') -FREEZER_DEFAULT_MIMETYPE = 'text/html' -FREEZER_IGNORE_MIMETYPE_WARNINGS = _ara_config(config, - 'ignore_mimetype_warnings', - 'ARA_IGNORE_MIMETYPE_WARNINGS', - value_type='boolean') -FREEZER_RELATIVE_URLS = True -FREEZER_IGNORE_404_NOT_FOUND = True - -# SQLAlchemy/Alembic settings -SQLALCHEMY_DATABASE_URI = _ara_config(config, 'database', 'ARA_DATABASE') -SQLALCHEMY_ECHO = _ara_config(config, 'sqldebug', - 'ARA_SQL_DEBUG', - value_type='boolean') -SQLALCHEMY_TRACK_MODIFICATIONS = False - -INSTALL_PATH = os.path.dirname(os.path.realpath(__file__)) -DB_MIGRATIONS = os.path.join(INSTALL_PATH, 'db') - -# Xstatic configuration -treeview = xstatic.pkg.patternfly_bootstrap_treeview -XSTATIC = dict( - bootstrap=xstatic.main.XStatic(xstatic.pkg.bootstrap_scss).base_dir, - datatables=xstatic.main.XStatic(xstatic.pkg.datatables).base_dir, - jquery=xstatic.main.XStatic(xstatic.pkg.jquery).base_dir, - patternfly=xstatic.main.XStatic(xstatic.pkg.patternfly).base_dir, - patternfly_bootstrap_treeview=xstatic.main.XStatic(treeview).base_dir, -) diff --git a/ara/plugins/modules/__init__.py b/ara/config/__init__.py similarity index 100% rename from ara/plugins/modules/__init__.py rename to ara/config/__init__.py diff --git a/ara/config/base.py b/ara/config/base.py new file mode 100644 index 00000000..c5986335 --- /dev/null +++ b/ara/config/base.py @@ -0,0 +1,79 @@ +# Copyright (c) 2017 Red Hat, Inc. +# +# This file is part of ARA: Ansible Run Analysis. +# +# ARA 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 3 of the License, or +# (at your option) any later version. +# +# ARA 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 ARA. If not, see . + +import os +from ara.config.compat import ara_config +from ara.setup import path as ara_location + + +class BaseConfig(object): + def __init__(self): + self.ARA_DIR = ara_config( + 'dir', + 'ARA_DIR', + os.path.expanduser('~/.ara') + ) + database_path = os.path.join(self.ARA_DIR, 'ansible.sqlite') + self.ARA_DATABASE = ara_config( + 'database', + 'ARA_DATABASE', + 'sqlite:///%s' % database_path + ) + self.ARA_AUTOCREATE_DATABASE = ara_config( + 'autocreate_database', + 'ARA_AUTOCREATE_DATABASE', + True, + value_type='boolean' + ) + self.SQLALCHEMY_DATABASE_URI = self.ARA_DATABASE + self.SQLALCHEMY_TRACK_MODIFICATIONS = False + self.DB_MIGRATIONS = os.path.join(ara_location, 'db') + + self.ARA_HOST = ara_config('host', 'ARA_HOST', '127.0.0.1') + self.ARA_PORT = ara_config('port', 'ARA_PORT', '9191') + self.ARA_IGNORE_PARAMETERS = ara_config( + 'ignore_parameters', + 'ARA_IGNORE_PARAMETERS', + ['extra_vars'], + value_type='list' + ) + + # Static generation with flask-frozen + self.ARA_IGNORE_EMPTY_GENERATION = ara_config( + 'ignore_empty_generation', + 'ARA_IGNORE_EMPTY_GENERATION', + True, + value_type='boolean' + ) + self.FREEZER_DEFAULT_MIMETYPE = 'text/html' + self.FREEZER_IGNORE_MIMETYPE_WARNINGS = ara_config( + 'ignore_mimetype_warnings', + 'ARA_IGNORE_MIMETYPE_WARNINGS', + True, + value_type='boolean' + ) + self.FREEZER_RELATIVE_URLS = True + self.FREEZER_IGNORE_404_NOT_FOUND = True + + @property + def config(self): + """ Returns a dictionary for the loaded configuration """ + return { + key: self.__dict__[key] + for key in dir(self) + if key.isupper() + } diff --git a/ara/config/compat.py b/ara/config/compat.py new file mode 100644 index 00000000..0708aaca --- /dev/null +++ b/ara/config/compat.py @@ -0,0 +1,65 @@ +# Copyright (c) 2017 Red Hat, Inc. +# +# This file is part of ARA: Ansible Run Analysis. +# +# ARA 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 3 of the License, or +# (at your option) any later version. +# +# ARA 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 ARA. If not, see . + +# Compatibility layer between ARA and the different version of Ansible + +from ansible import __version__ as ansible_version +from ansible.constants import get_config +try: + from ansible.constants import load_config_file +except ImportError: + # Ansible 2.4 no longer provides load_config_file, this is handled further + # down + from ansible.config.manager import find_ini_config_file + # Also, don't scream deprecated things at us + import ansible.constants + ansible.constants._deprecated = lambda *args: None +from distutils.version import LooseVersion +from six.moves import configparser + + +def ara_config(key, env_var, default, section='ara', value_type=None): + """ + Wrapper around Ansible's get_config backward/forward compatibility + """ + # Bootstrap Ansible configuration + # Ansible >=2.4 takes care of loading the configuration file itself + if LooseVersion(ansible_version) < LooseVersion('2.4.0'): + config, path = load_config_file() + else: + path = find_ini_config_file() + config = configparser.ConfigParser() + if path is not None: + config.read(path) + + # >= 2.3.0.0 (NOTE: Ansible trunk versioning scheme has 3 digits, not 4) + if LooseVersion(ansible_version) >= LooseVersion('2.3.0'): + return get_config(config, section, key, env_var, default, + value_type=value_type) + + # < 2.3.0.0 compatibility + if value_type is None: + return get_config(config, section, key, env_var, default) + + args = { + 'boolean': dict(boolean=True), + 'integer': dict(integer=True), + 'list': dict(islist=True), + 'tmppath': dict(istmppath=True) + } + return get_config(config, section, key, env_var, default, + **args[value_type]) diff --git a/ara/config/logger.py b/ara/config/logger.py new file mode 100644 index 00000000..23759e90 --- /dev/null +++ b/ara/config/logger.py @@ -0,0 +1,142 @@ +# Copyright (c) 2017 Red Hat, Inc. +# +# This file is part of ARA: Ansible Run Analysis. +# +# ARA 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 3 of the License, or +# (at your option) any later version. +# +# ARA 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 ARA. If not, see . + +# Note: This file tries to import itself if it's named logging, thus, logger. + +import logging +import logging.config +import os +import yaml +from ara.config.compat import ara_config + + +DEFAULT_LOG_CONFIG = """ +--- +version: 1 +formatters: + normal: + format: '%(asctime)s %(levelname)s %(name)s: %(message)s' + console: + format: '%(asctime)s %(levelname)s %(name)s: %(message)s' +handlers: + console: + class: logging.StreamHandler + formatter: console + level: INFO + stream: ext://sys.stdout + normal: + class: logging.handlers.TimedRotatingFileHandler + formatter: normal + level: DEBUG + filename: '{dir}/{file}' + when: 'midnight' + interval: 1 + backupCount: 30 +loggers: + ara: + handlers: + - console + - normal + level: {level} + propagate: 0 + alembic: + handlers: + - console + - normal + level: WARN + propagate: 0 + sqlalchemy.engine: + handlers: + - console + - normal + level: WARN + propagate: 0 + werkzeug: + handlers: + - console + - normal + level: INFO + propagate: 0 +root: + handlers: + - normal + level: {level} +""" + + +class LogConfig(object): + def __init__(self): + default_dir = ara_config('dir', 'ARA_DIR', + os.path.expanduser('~/.ara')) + self.ARA_LOG_CONFIG = ara_config( + 'logconfig', 'ARA_LOG_CONFIG', os.path.join(default_dir, + 'logging.yml') + ) + self.ARA_LOG_DIR = ara_config('logdir', 'ARA_LOG_DIR', default_dir) + self.ARA_LOG_FILE = ara_config('logfile', 'ARA_LOG_FILE', 'ara.log') + self.ARA_LOG_LEVEL = ara_config('loglevel', 'ARA_LOG_LEVEL', 'INFO') + if self.ARA_LOG_LEVEL == 'DEBUG': + self.SQLALCHEMY_ECHO = True + self.ARA_ENABLE_DEBUG_VIEW = True + else: + self.SQLALCHEMY_ECHO = False + self.ARA_ENABLE_DEBUG_VIEW = False + + @property + def config(self): + """ Returns a dictionary for the loaded configuration """ + return { + key: self.__dict__[key] + for key in dir(self) + if key.isupper() + } + + +def setup_logging(config=None): + if config is None: + config = LogConfig().config + + if not os.path.isdir(config['ARA_LOG_DIR']): + os.makedirs(config['ARA_LOG_DIR'], mode=0o750) + + if not os.path.exists(config['ARA_LOG_CONFIG']): + default_config = DEFAULT_LOG_CONFIG.format( + dir=config['ARA_LOG_DIR'], + file=config['ARA_LOG_FILE'], + level=config['ARA_LOG_LEVEL'] + ) + with open(config['ARA_LOG_CONFIG'], 'w') as log_config: + log_config.write(default_config.lstrip()) + + ext = os.path.splitext(config['ARA_LOG_CONFIG'])[1] + if ext in ('.yml', '.yaml', '.json'): + # yaml.safe_load can load json as well as yaml + logging.config.dictConfig(yaml.safe_load( + open(config['ARA_LOG_CONFIG'], 'r') + )) + else: + logging.config.fileConfig(config['ARA_LOG_CONFIG']) + + logger = logging.getLogger('ara.logging') + msg = 'Logging: Level {level} from {config}, logging to {dir}/{file}' + msg = msg.format( + level=config['ARA_LOG_LEVEL'], + config=config['ARA_LOG_CONFIG'], + dir=config['ARA_LOG_DIR'], + file=config['ARA_LOG_FILE'], + ) + logger.debug(msg) diff --git a/ara/config/webapp.py b/ara/config/webapp.py new file mode 100644 index 00000000..57c4f3cf --- /dev/null +++ b/ara/config/webapp.py @@ -0,0 +1,64 @@ +# Copyright (c) 2017 Red Hat, Inc. +# +# This file is part of ARA: Ansible Run Analysis. +# +# ARA 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 3 of the License, or +# (at your option) any later version. +# +# ARA 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 ARA. If not, see . + +from xstatic import main as xs +import xstatic.pkg.bootstrap_scss +import xstatic.pkg.datatables +import xstatic.pkg.jquery +import xstatic.pkg.patternfly +import xstatic.pkg.patternfly_bootstrap_treeview +from ara.config.compat import ara_config + + +class WebAppConfig(object): + def __init__(self): + self.ARA_PLAYBOOK_PER_PAGE = ara_config( + 'playbook_per_page', + 'ARA_PLAYBOOK_PER_PAGE', + 10, + value_type='integer' + ) + self.ARA_RESULT_PER_PAGE = ara_config( + 'result_per_page', + 'ARA_RESULT_PER_PAGE', + 25, + value_type='integer' + ) + self.ARA_PLAYBOOK_OVERRIDE = ara_config( + 'playbook_override', + 'ARA_PLAYBOOK_OVERRIDE', + None, + value_type='list' + ) + + treeview = xstatic.pkg.patternfly_bootstrap_treeview + self.XSTATIC = dict( + bootstrap=xs.XStatic(xstatic.pkg.bootstrap_scss).base_dir, + datatables=xs.XStatic(xstatic.pkg.datatables).base_dir, + jquery=xs.XStatic(xstatic.pkg.jquery).base_dir, + patternfly=xs.XStatic(xstatic.pkg.patternfly).base_dir, + patternfly_bootstrap_treeview=xs.XStatic(treeview).base_dir, + ) + + @property + def config(self): + """ Returns a dictionary for the loaded configuration """ + return { + key: self.__dict__[key] + for key in dir(self) + if key.isupper() + } diff --git a/ara/context_processors.py b/ara/context_processors.py deleted file mode 100644 index a223e96a..00000000 --- a/ara/context_processors.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright (c) 2017 Red Hat, Inc. -# -# This file is part of ARA: Ansible Run Analysis. -# -# ARA 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 3 of the License, or -# (at your option) any later version. -# -# ARA 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 ARA. If not, see . - -import sys - -from ansible import __version__ as ansible_version -from ara import __release__ as ara_release -from ara import models - - -def configure_context_processors(app): - @app.context_processor - def ctx_add_nav_data(): - """ - Returns standard data that will be available in every template view. - """ - try: - models.Playbook.query.one() - empty_database = False - except models.MultipleResultsFound: - empty_database = False - except models.NoResultFound: - empty_database = True - - # Get python version info - major, minor, micro, release, serial = sys.version_info - - return dict(ara_version=ara_release, - ansible_version=ansible_version, - python_version="{0}.{1}".format(major, minor), - empty_database=empty_database) diff --git a/ara/errorhandlers.py b/ara/errorhandlers.py deleted file mode 100644 index 47773a9f..00000000 --- a/ara/errorhandlers.py +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright (c) 2017 Red Hat, Inc. -# -# This file is part of ARA: Ansible Run Analysis. -# -# ARA 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 3 of the License, or -# (at your option) any later version. -# -# ARA 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 ARA. If not, see . - -from flask import render_template - - -def configure_errorhandlers(app): - @app.errorhandler(404) - def page_not_found(error): - return render_template('errors/404.html', error=error), 404 diff --git a/ara/filters.py b/ara/filters.py deleted file mode 100644 index 80a29cca..00000000 --- a/ara/filters.py +++ /dev/null @@ -1,122 +0,0 @@ -# Copyright (c) 2017 Red Hat, Inc. -# -# This file is part of ARA: Ansible Run Analysis. -# -# ARA 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 3 of the License, or -# (at your option) any later version. -# -# ARA 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 ARA. If not, see . - -import datetime -import logging -import six - -from ara.utils import fast_count -from ara.utils import playbook_treeview -from jinja2 import Markup -from os import path -from oslo_serialization import jsonutils -from pygments import highlight -from pygments.formatters import HtmlFormatter -from pygments.lexers import YamlLexer -from pygments.lexers import JsonLexer -from pygments.lexers.special import TextLexer - - -def configure_template_filters(app): - log = logging.getLogger('%s.filters' % app.logger_name) - - @app.template_filter('datefmt') - def jinja_date_formatter(timestamp, format='%Y-%m-%d %H:%M:%S'): - """ Reformats a datetime timestamp from str(datetime.datetime) """ - if timestamp is None: - return 'n/a' - else: - return datetime.datetime.strftime(timestamp, format) - - @app.template_filter('timefmt') - def jinja_time_formatter(timestamp): - """ Reformats a datetime timedelta """ - if timestamp is None: - return 'n/a' - else: - date = datetime.timedelta(seconds=int(timestamp.total_seconds())) - return str(date) - - @app.template_filter('to_nice_json') - def jinja_to_nice_json(result): - """ Tries to format a result as a pretty printed JSON. """ - try: - return jsonutils.dumps(jsonutils.loads(result), - indent=4, - sort_keys=True) - except (ValueError, TypeError): - try: - return jsonutils.dumps(result, indent=4, sort_keys=True) - except TypeError as err: - log.error('failed to dump json: %s', err) - return result - - @app.template_filter('from_json') - def jinja_from_json(val): - try: - return jsonutils.loads(val) - except ValueError as err: - log.error('failed to load json: %s', err) - return val - - @app.template_filter('yamlhighlight') - def jinja_yamlhighlight(code): - formatter = HtmlFormatter(linenos='table', - anchorlinenos=True, - lineanchors='line', - linespans='line', - cssclass='codehilite') - - if not code: - code = '' - - return highlight(Markup(code).unescape(), - YamlLexer(stripall=True), - formatter) - - @app.template_filter('pygments_formatter') - def jinja_pygments_formatter(data): - formatter = HtmlFormatter(cssclass='codehilite') - - if isinstance(data, dict) or isinstance(data, list): - data = jsonutils.dumps(data, indent=4, sort_keys=True) - lexer = JsonLexer() - elif six.string_types or six.text_type: - try: - data = jsonutils.dumps(jsonutils.loads(data), - indent=4, - sort_keys=True) - lexer = JsonLexer() - except (ValueError, TypeError): - lexer = TextLexer() - else: - lexer = TextLexer() - - lexer.stripall = True - return highlight(Markup(data).unescape(), lexer, formatter) - - @app.template_filter('fast_count') - def jinja_fast_count(query): - return fast_count(query) - - @app.template_filter('basename') - def jinja_basename(pathname): - return path.basename(pathname) - - @app.template_filter('treeview') - def jinja_treeview(playbook): - return playbook_treeview(playbook) diff --git a/ara/plugins/actions/ara_read.py b/ara/plugins/actions/ara_read.py index 870991f0..d90ee348 100644 --- a/ara/plugins/actions/ara_read.py +++ b/ara/plugins/actions/ara_read.py @@ -15,10 +15,7 @@ # You should have received a copy of the GNU General Public License # along with ARA. If not, see . -import os - from ansible.plugins.action import ActionBase -from oslo_serialization import jsonutils try: from ara import models @@ -109,11 +106,6 @@ class ActionModule(ActionBase): } return result - app = create_app() - if not current_app: - context = app.app_context() - context.push() - for arg in self._task.args: if arg not in self.VALID_ARGS: result = { @@ -134,12 +126,14 @@ class ActionModule(ActionBase): result['msg'] = '{0} parameter is required'.format(parameter) return result + app = create_app() + if not current_app: + context = app.app_context() + context.push() + if playbook_id is None: - # Retrieve the persisted playbook_id from tmpfile - tmpfile = os.path.join(app.config['ARA_TMP_DIR'], 'ara.json') - with open(tmpfile, 'rb') as file: - data = jsonutils.load(file) - playbook_id = data['playbook']['id'] + # Retrieve playbook_id from the cached context + playbook_id = current_app._cache['playbook'] try: data = self.get_key(playbook_id, key) diff --git a/ara/plugins/actions/ara_record.py b/ara/plugins/actions/ara_record.py index 1c20e4c6..e95b94d5 100644 --- a/ara/plugins/actions/ara_record.py +++ b/ara/plugins/actions/ara_record.py @@ -15,10 +15,7 @@ # You should have received a copy of the GNU General Public License # along with ARA. If not, see . -import os - from ansible.plugins.action import ActionBase -from oslo_serialization import jsonutils try: from ara import models @@ -143,11 +140,6 @@ class ActionModule(ActionBase): } return result - app = create_app() - if not current_app: - context = app.app_context() - context.push() - for arg in self._task.args: if arg not in self.VALID_ARGS: result = { @@ -178,12 +170,14 @@ class ActionModule(ActionBase): result['msg'] = msg return result + app = create_app() + if not current_app: + context = app.app_context() + context.push() + if playbook_id is None: - # Retrieve the persisted playbook_id from tmpfile - tmpfile = os.path.join(app.config['ARA_TMP_DIR'], 'ara.json') - with open(tmpfile, 'rb') as file: - data = jsonutils.load(file) - playbook_id = data['playbook']['id'] + # Retrieve playbook_id from the cached context + playbook_id = current_app._cache['playbook'] try: self.create_or_update_key(playbook_id, key, value, type) diff --git a/ara/plugins/callbacks/log_ara.py b/ara/plugins/callbacks/log_ara.py index 95b9154d..bf3237b5 100644 --- a/ara/plugins/callbacks/log_ara.py +++ b/ara/plugins/callbacks/log_ara.py @@ -17,7 +17,6 @@ from __future__ import (absolute_import, division, print_function) -import flask import itertools import logging import os @@ -29,6 +28,7 @@ from ara.models import db from ara.webapp import create_app from datetime import datetime from distutils.version import LooseVersion +from flask import current_app from oslo_serialization import jsonutils # To retrieve Ansible CLI options @@ -62,7 +62,7 @@ class CallbackModule(CallbackBase): def __init__(self): super(CallbackModule, self).__init__() - if not flask.current_app: + if not current_app: ctx = app.app_context() ctx.push() @@ -333,15 +333,8 @@ class CallbackModule(CallbackBase): file_ = self.get_or_create_file(path) file_.is_playbook = True - # We need to persist the playbook id so it can be used by the modules - data = { - 'playbook': { - 'id': self.playbook.id - } - } - tmpfile = os.path.join(app.config['ARA_TMP_DIR'], 'ara.json') - with open(tmpfile, 'w') as file: - file.write(jsonutils.dumps(data)) + # Cache the playbook data in memory for ara_record/ara_read + current_app._cache['playbook'] = self.playbook.id def v2_playbook_on_play_start(self, play): self.close_task() diff --git a/ara/plugins/modules/ara_read.py b/ara/plugins/modules/ara_read.py deleted file mode 100644 index 7adf7150..00000000 --- a/ara/plugins/modules/ara_read.py +++ /dev/null @@ -1,74 +0,0 @@ -# Copyright (c) 2017 Red Hat, Inc. -# -# This file is part of ARA: Ansible Run Analysis. -# -# ARA 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 3 of the License, or -# (at your option) any later version. -# -# ARA 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 ARA. If not, see . - -# This file is purposefully left empty due to an Ansible issue -# Details at: https://github.com/ansible/ansible/pull/18208 - -# TODO: Remove this file and update the documentation when the issue is fixed, -# released and present in all supported versions. - -DOCUMENTATION = """ ---- -module: ara_read -short_description: Ansible module to read recorded persistent data with ARA. -version_added: "2.0" -author: "RDO Community " -description: - - Ansible module to read recorded persistent data with ARA. -options: - playbook: - description: - - uuid of the playbook to read the key from - required: false - version_added: 0.13.2 - key: - description: - - Name of the key to read from - required: true - -requirements: - - "python >= 2.6" - - "ara >= 0.10.0" -""" - -EXAMPLES = """ -# Write data -- ara_record: - key: "foo" - value: "bar" - -# Read data -- ara_read: - key: "foo" - register: foo - -# Read data from a specific playbook -# (Retrieve playbook uuid's with 'ara playbook list') -- ara_read: - playbook: uuuu-iiii-dddd-0000 - key: logs - register: logs - -# Use data -- debug: - msg: "{{ item }}" - with_items: - - foo.key - - foo.value - - foo.type - - foo.playbook_id -""" diff --git a/ara/plugins/modules/ara_record.py b/ara/plugins/modules/ara_record.py deleted file mode 100644 index 20a30d9b..00000000 --- a/ara/plugins/modules/ara_record.py +++ /dev/null @@ -1,92 +0,0 @@ -# Copyright (c) 2017 Red Hat, Inc. -# -# This file is part of ARA: Ansible Run Analysis. -# -# ARA 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 3 of the License, or -# (at your option) any later version. -# -# ARA 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 ARA. If not, see . - -# This file is purposefully left empty due to an Ansible issue -# Details at: https://github.com/ansible/ansible/pull/18208 - -# TODO: Remove this file and update the documentation when the issue is fixed, -# released and present in all supported versions. - -DOCUMENTATION = """ ---- -module: ara_record -short_description: Ansible module to record persistent data with ARA. -version_added: "2.0" -author: "RDO Community " -description: - - Ansible module to record persistent data with ARA. -options: - playbook: - description: - - uuid of the playbook to write the key to - required: false - version_added: 0.13.2 - key: - description: - - Name of the key to write data to - required: true - value: - description: - - Value of the key written to - required: true - type: - description: - - Type of the key - choices: [text, url, json, list, dict] - default: text - -requirements: - - "python >= 2.6" - - "ara >= 0.10.0" -""" - -EXAMPLES = """ -# Write static data -- ara_record: - key: "foo" - value: "bar" - -# Write data to a specific (previously run) playbook -# (Retrieve playbook uuid's with 'ara playbook list') -- ara_record: - playbook: uuuu-iiii-dddd-0000 - key: logs - value: "{{ lookup('file', '/var/log/ansible.log') }}" - type: text - -# Write dynamic data -- shell: cd dev && git rev-parse HEAD - register: git_version - delegate_to: localhost - -- ara_record: - key: "git_version" - value: "{{ git_version.stdout }}" - -# Write data with a type (otherwise defaults to "text") -# This changes the behavior on how the value is presented in the web interface -- ara_record: - key: "{{ item.key }}" - value: "{{ item.value }}" - type: "{{ item.type }}" - with_items: - - { key: "log", value: "error", type: "text" } - - { key: "website", value: "http://domain.tld", type: "url" } - - { key: "data", value: "{ 'key': 'value' }", type: "json" } - - { key: "somelist", value: ['one', 'two'], type: "list" } - - { key: "somedict", value: {'key': 'value' }, type: "dict" } -""" diff --git a/ara/templates/about.html b/ara/templates/about.html index d4c94bb3..f0666462 100644 --- a/ara/templates/about.html +++ b/ara/templates/about.html @@ -33,7 +33,7 @@ Tasks