diff --git a/nemesis.conf.sample b/nemesis.conf.sample new file mode 100644 index 0000000..362b21e --- /dev/null +++ b/nemesis.conf.sample @@ -0,0 +1,16 @@ +[DEFAULT] + +[keystone_authtoken] +identity_uri = +auth_uri = +admin_tenant_name = +admin_user = +admin_password = +auth_version = +auth_protocol = +delay_auth_decision = True + +[sqlalchemy] +database_uri = sqlite:////tmp/nemesis.db +echo = true + diff --git a/python_nemesis/base_app.py b/python_nemesis/base_app.py new file mode 100644 index 0000000..e9cf39a --- /dev/null +++ b/python_nemesis/base_app.py @@ -0,0 +1,91 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from flask import Flask +import os +from oslo_config import cfg +from python_nemesis.config import collect_sqlalchemy_opts +from python_nemesis.config import register_opts +from python_nemesis.extensions import db +# from python_nemesis.extensions import log + + +def configure_blueprints(app, blueprints): + """Register configured blueprints into app object. + + :param app: The application object to which configuration should + be applied. + :type app: :py:class:`flask.Flask` + :param blueprints: list of blueprints to be registered. + :type blueprints: list(:py:class:`flask.Blueprint`) + """ + for blueprint in blueprints: + app.register_blueprint(blueprint) + + +def configure_app(app): + """Retrieve App Configuration. + + configure_app first loads default configuration and then attempts + to override defaults using a file specified in env:NEMESIS_CONFIG. + + :param app: The application object to which configuration should + be applied. + :type app: :py:class:`flask.Flask` + """ + app.config.from_object('python_nemesis.default_config') + app.config["cfg"] = cfg.CONF + config_file = os.environ.get( + "NEMESIS_CONFIG", + "/etc/nemesis/nemesis.conf") + + register_opts(app.config["cfg"], config_file) + collect_sqlalchemy_opts(app, app.config["cfg"]) + + +def configure_extensions(app): + """Initialize extensions for Flask. + + This function is intended for use with the app factory style + of Flask deployment. + + :param app: The application object to which configuration should + be applied. + :type app: :py:class:`flask.Flask` + """ + db.init_app(app) + # log.init_app(app) + + +def create_app(app_name=None, blueprints=None): + """Create the flask app. + + This function is intended to be used with the app factory + style of Flask deployment. + + :param str app_name: Name to be used internally within flask. + :param blueprints: Blueprints to be registered. + :type blueprints: list(:py:class:`flask.Blueprint`) + :returns: The created app. + :rtype: :py:class:`flask.Flask` + """ + app = Flask(app_name) + + configure_app(app) + configure_extensions(app) + + return app + + +if __name__ == "__main__": # pragma: no cover + app = create_app('nemesis-api') + app.run(threaded=True) diff --git a/python_nemesis/config.py b/python_nemesis/config.py new file mode 100644 index 0000000..ffa9d97 --- /dev/null +++ b/python_nemesis/config.py @@ -0,0 +1,80 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from oslo_config import cfg + + +DEFAULT_OPT_GRP = cfg.OptGroup(name='DEFAULT') +DEFAULT_OPTS = [ + cfg.StrOpt('test_value', default="this is a value") +] + +SQLALCHEMY_OPT_GRP = cfg.OptGroup(name='sqlalchemy') +SQLALCHEMY_OPTS = [ + cfg.StrOpt('database_uri'), + cfg.BoolOpt('echo'), + cfg.IntOpt('pool_size'), + cfg.IntOpt('pool_timeout'), + cfg.IntOpt('pool_recycle'), + cfg.IntOpt('max_overflow'), + cfg.BoolOpt('track_modifications'), +] + +IDENTITY_OPT_GRP = cfg.OptGroup(name='identity') +IDENTITY_OPTS = [ + cfg.StrOpt('username'), + cfg.StrOpt('password') +] + + +def register_opts(conf, config_file): + '''Register Oslo Configuration Options from a provided config file. + + :param conf: oslo config to be populated. + :type conf: :py:obj:`cfg.CONF` + :param str config_file: Location of the config file to be used. + ''' + conf(default_config_files=[config_file]) + conf.register_opts(DEFAULT_OPTS) + conf.register_group(SQLALCHEMY_OPT_GRP) + conf.register_opts(SQLALCHEMY_OPTS, SQLALCHEMY_OPT_GRP) + conf.register_group(IDENTITY_OPT_GRP) + conf.register_opts(IDENTITY_OPTS, IDENTITY_OPT_GRP) + + +def collect_sqlalchemy_opts(app, conf): + """Collect sqlalchemy options from `oslo.config` and apply them. + + This function copies the configuration entries for sqlalchemy + directly into the :py:class:`flask.Flask` object where the + upstream Flask-SqlAlchemy extension expects to find them. + + :param app: The application object to which configuration should + be applied. + :type app: :py:class:`flask.Flask` + :param conf: oslo config to be populated. + :type conf: :py:obj:`cfg.CONF` + """ + def _import_opt_from_oslo(flask_opt, oslo_opt): + if conf.sqlalchemy[oslo_opt] is not None: + app.config[flask_opt] = conf.sqlalchemy[oslo_opt] + + _import_opt_from_oslo('SQLALCHEMY_DATABASE_URI', 'database_uri') + _import_opt_from_oslo('SQLALCHEMY_ECHO', 'echo') + _import_opt_from_oslo('SQLALCHEMY_POOL_SIZE', 'pool_size') + _import_opt_from_oslo('SQLALCHEMY_POOL_TIMEOUT', 'pool_timeout') + _import_opt_from_oslo('SQLALCHEMY_POOL_RECYCLE', 'pool_recycle') + _import_opt_from_oslo('SQLALCHEMY_MAX_OVERFLOW', 'max_overflow') + _import_opt_from_oslo( + 'SQLALCHEMY_TRACK_MODIFICATIONS', + 'track_modifications' + ) diff --git a/python_nemesis/default_config.py b/python_nemesis/default_config.py new file mode 100644 index 0000000..251a507 --- /dev/null +++ b/python_nemesis/default_config.py @@ -0,0 +1,4 @@ +DEBUG = True +TESTING = False +JSONIFY_PRETTYPRINT_REGULAR = True +JSON_SORT_KEYS = True diff --git a/python_nemesis/extensions.py b/python_nemesis/extensions.py new file mode 100644 index 0000000..96f65ff --- /dev/null +++ b/python_nemesis/extensions.py @@ -0,0 +1,6 @@ +# from flask_oslolog import OsloLog +from flask_sqlalchemy import SQLAlchemy + + +db = SQLAlchemy() +# log = OsloLog() diff --git a/requirements.txt b/requirements.txt index 9ee958b..26a59c8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,10 @@ # process, which may cause wedges in the gate later. pbr>=2.0.0 # Apache-2.0 +alembic>=0.8.10 # MIT +Flask!=0.11,<1.0,>=0.10 # BSD +Flask-SQLAlchemy>=2.0 # BSD +oslo.config>=3.22.0 # Apache-2.0 +oslo.messaging>=5.19.0 # Apache-2.0 +oslo.log>=3.11.0 # Apache-2.0 +keystonemiddleware>=4.12.0 # Apache-2.0