diff --git a/alembic.ini b/alembic.ini new file mode 100644 index 0000000..5f5ffd2 --- /dev/null +++ b/alembic.ini @@ -0,0 +1,40 @@ +[alembic] +script_location = tuning_box/migrations +# use in-memory sqlite to generate revisions +sqlalchemy.url = sqlite:/// + + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/requirements.txt b/requirements.txt index 6823e76..ddfb7b9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,3 +6,4 @@ pbr>=1.6 flask flask-sqlalchemy flask-restful +alembic diff --git a/tuning_box/migrations/env.py b/tuning_box/migrations/env.py new file mode 100644 index 0000000..4704170 --- /dev/null +++ b/tuning_box/migrations/env.py @@ -0,0 +1,75 @@ +# 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. + +import logging.config + +from alembic import context +import sqlalchemy + +from tuning_box import db + +config = context.config +if config.get_main_option('table_prefix') is None: + config.set_main_option('table_prefix', '') +logging.config.fileConfig(config.config_file_name) +target_metadata = db.db.metadata + + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + context.configure( + url=config.get_main_option('sqlalchemy.url'), + version_table=config.get_main_option('version_table'), + literal_binds=True, + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + connectable = sqlalchemy.engine_from_config( + config.get_section(config.config_ini_section), + prefix='sqlalchemy.', + poolclass=sqlalchemy.pool.NullPool, + ) + + with connectable.connect() as connection: + context.configure( + connection=connection, + target_metadata=target_metadata, + version_table=config.get_main_option('version_table'), + ) + + with context.begin_transaction(): + context.run_migrations() + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/tuning_box/migrations/script.py.mako b/tuning_box/migrations/script.py.mako new file mode 100644 index 0000000..7f993a7 --- /dev/null +++ b/tuning_box/migrations/script.py.mako @@ -0,0 +1,41 @@ +# 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. + +"""${message} + +Revision ID: ${up_revision} +Revises:${" " if down_revision else ""}${down_revision | comma,n} +Create Date: ${create_date} + +""" + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + +from alembic import context +from alembic import op +import sqlalchemy as sa + +import tuning_box.db +${imports if imports else ""} + +def upgrade(): + table_prefix = context.config.get_main_option('table_prefix') + ${upgrades if upgrades else "pass"} + + +def downgrade(): + table_prefix = context.config.get_main_option('table_prefix') + ${downgrades if downgrades else "pass"} diff --git a/tuning_box/migrations/versions/f16eb4eff7c_initial_revision.py b/tuning_box/migrations/versions/f16eb4eff7c_initial_revision.py new file mode 100644 index 0000000..8e70f3d --- /dev/null +++ b/tuning_box/migrations/versions/f16eb4eff7c_initial_revision.py @@ -0,0 +1,140 @@ +# 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. + +"""Initial revision + +Revision ID: f16eb4eff7c +Revises: +Create Date: 2016-03-02 17:10:04.750584 + +""" + +# revision identifiers, used by Alembic. +revision = 'f16eb4eff7c' +down_revision = None +branch_labels = None +depends_on = None + +from alembic import context +from alembic import op +import sqlalchemy as sa + +import tuning_box.db + + +def upgrade(): + table_prefix = context.config.get_main_option('table_prefix') + op.create_table( + table_prefix + 'component', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=128), nullable=True), + sa.PrimaryKeyConstraint('id'), + ) + op.create_table( + table_prefix + 'environment', + sa.Column('id', sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint('id'), + ) + op.create_table( + table_prefix + 'namespace', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=128), nullable=True), + sa.PrimaryKeyConstraint('id'), + ) + op.create_table( + table_prefix + 'environment_components', + sa.Column('environment_id', sa.Integer(), nullable=True), + sa.Column('component_id', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint( + ['component_id'], [table_prefix + 'component.id']), + sa.ForeignKeyConstraint( + ['environment_id'], [table_prefix + 'environment.id']), + ) + op.create_table( + table_prefix + 'environment_hierarchy_level', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('environment_id', sa.Integer(), nullable=True), + sa.Column('name', sa.String(length=128), nullable=True), + sa.Column('parent_id', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint( + ['environment_id'], [table_prefix + 'environment.id']), + sa.ForeignKeyConstraint( + ['parent_id'], [table_prefix + 'environment_hierarchy_level.id']), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('environment_id', 'name'), + sa.UniqueConstraint('environment_id', 'parent_id'), + ) + op.create_table( + table_prefix + 'schema', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=128), nullable=True), + sa.Column('component_id', sa.Integer(), nullable=True), + sa.Column('namespace_id', sa.Integer(), nullable=True), + sa.Column('content', tuning_box.db.Json(), nullable=True), + sa.ForeignKeyConstraint( + ['component_id'], [table_prefix + 'component.id']), + sa.ForeignKeyConstraint( + ['namespace_id'], [table_prefix + 'namespace.id']), + sa.PrimaryKeyConstraint('id'), + ) + op.create_table( + table_prefix + 'template', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=128), nullable=True), + sa.Column('component_id', sa.Integer(), nullable=True), + sa.Column('content', tuning_box.db.Json(), nullable=True), + sa.ForeignKeyConstraint( + ['component_id'], [table_prefix + 'component.id']), + sa.PrimaryKeyConstraint('id'), + ) + op.create_table( + table_prefix + 'environment_hierarchy_level_value', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('level_id', sa.Integer(), nullable=True), + sa.Column('parent_id', sa.Integer(), nullable=True), + sa.Column('value', sa.String(length=128), nullable=True), + sa.ForeignKeyConstraint( + ['level_id'], [table_prefix + 'environment_hierarchy_level.id']), + sa.ForeignKeyConstraint( + ['parent_id'], + [table_prefix + 'environment_hierarchy_level_value.id']), + sa.PrimaryKeyConstraint('id'), + ) + op.create_table( + table_prefix + 'environment_schema_values', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('environment_id', sa.Integer(), nullable=True), + sa.Column('schema_id', sa.Integer(), nullable=True), + sa.Column('level_value_id', sa.Integer(), nullable=True), + sa.Column('values', tuning_box.db.Json(), nullable=True), + sa.ForeignKeyConstraint( + ['environment_id'], [table_prefix + 'environment.id']), + sa.ForeignKeyConstraint( + ['level_value_id'], + [table_prefix + 'environment_hierarchy_level_value.id']), + sa.ForeignKeyConstraint(['schema_id'], [table_prefix + 'schema.id']), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('environment_id', 'schema_id', 'level_value_id'), + ) + + +def downgrade(): + table_prefix = context.config.get_main_option('table_prefix') + op.drop_table(table_prefix + 'environment_schema_values') + op.drop_table(table_prefix + 'environment_hierarchy_level_value') + op.drop_table(table_prefix + 'template') + op.drop_table(table_prefix + 'schema') + op.drop_table(table_prefix + 'environment_hierarchy_level') + op.drop_table(table_prefix + 'environment_components') + op.drop_table(table_prefix + 'namespace') + op.drop_table(table_prefix + 'environment') + op.drop_table(table_prefix + 'component') diff --git a/tuning_box/nailgun.py b/tuning_box/nailgun.py index 8cb6bde..59cf716 100644 --- a/tuning_box/nailgun.py +++ b/tuning_box/nailgun.py @@ -12,6 +12,8 @@ from __future__ import absolute_import +import os + from nailgun import extensions import tuning_box @@ -21,3 +23,7 @@ class Extension(extensions.BaseExtension): name = 'tuning_box' version = tuning_box.__version__ description = 'Plug tuning_box endpoints into Nailgun itself' + + @classmethod + def alembic_migrations_path(cls): + return os.path.join(os.path.dirname(__file__), 'migrations')