"""New accounts table Revision ID: cfb0ed4cced9 Revises: 1edcdfbc7780 Create Date: 2018-07-10 13:26:01.588708 """ from json import dumps as to_json from alembic import op import sqlalchemy as sa from sqlalchemy import text, inspect from sqlalchemy.dialects import mysql # revision identifiers, used by Alembic. revision = 'cfb0ed4cced9' down_revision = '1edcdfbc7780' select_ai = 'SELECT AUTO_INCREMENT FROM information_schema.TABLES WHERE TABLE_SCHEMA = :db AND TABLE_NAME = :table' select_cfg_item = 'SELECT value FROM config_items WHERE namespace_prefix = :ns AND `key` = :key' select_acct_types = 'SELECT account_type_id, account_type FROM account_types' insert_acct_type = 'INSERT INTO account_types (account_type) VALUES (:name)' insert_acct = ( 'INSERT INTO accounts_new (account_id, account_name, account_type_id, contacts, enabled, required_roles)' ' VALUES(:id, :name, :type_id, :contacts, :enabled, :required_roles)' ) insert_acct_prop = 'INSERT INTO account_properties (account_id, name, value) VALUES (:id, :name, :value)' def upgrade(): create_new_tables() migrate_data() switch_tables() def downgrade(): raise Exception('You cannot downgrade from this version') def create_new_tables(): op.create_table('account_types', sa.Column('account_type_id', mysql.INTEGER(unsigned=True), nullable=False, autoincrement=True), sa.Column('account_type', sa.String(length=100), nullable=False), sa.PrimaryKeyConstraint('account_type_id') ) op.create_index(op.f('ix_account_types_account_type'), 'account_types', ['account_type'], unique=True) op.create_table('accounts_new', sa.Column('account_id', mysql.INTEGER(unsigned=True), nullable=False), sa.Column('account_name', sa.String(length=256), nullable=False), sa.Column('account_type_id', mysql.INTEGER(unsigned=True), nullable=False), sa.Column('contacts', mysql.JSON(), nullable=False), sa.Column('enabled', mysql.SMALLINT(unsigned=True), nullable=False), sa.Column('required_roles', mysql.JSON(), nullable=True), sa.ForeignKeyConstraint( ('account_type_id',), ['account_types.account_type_id'], name='fk_account_account_type_id', ondelete='CASCADE' ), sa.PrimaryKeyConstraint('account_id') ) op.create_index(op.f('ix_accounts_new_account_name'), 'accounts_new', ['account_name'], unique=True) op.create_index(op.f('ix_accounts_new_account_type_id'), 'accounts_new', ['account_type_id'], unique=False) op.create_table('account_properties', sa.Column('property_id', mysql.INTEGER(unsigned=True), nullable=False, autoincrement=True), sa.Column('account_id', mysql.INTEGER(unsigned=True), nullable=False), sa.Column('name', sa.String(length=50), nullable=False), sa.Column('value', mysql.JSON(), nullable=False), sa.ForeignKeyConstraint( ('account_id',), ['accounts_new.account_id'], name='fk_account_properties_account_id', ondelete='CASCADE' ), sa.PrimaryKeyConstraint('property_id', 'account_id') ) op.create_index(op.f('ix_account_properties_account_id'), 'account_properties', ['account_id'], unique=False) op.create_index(op.f('ix_account_properties_name'), 'account_properties', ['name'], unique=False) def migrate_data(): conn = op.get_bind() account_types = {x['account_type']: x['account_type_id'] for x in conn.execute(text(select_acct_types))} try: schema = inspect(conn.engine).default_schema_name conn.execute('SET FOREIGN_KEY_CHECKS=0') res = conn.execute(text(select_ai), {'db': schema, 'table': 'accounts'}) acct_auto_increment = next(res)['AUTO_INCREMENT'] for acct_type in ('AWS', 'DNS: AXFR', 'DNS: CloudFlare'): if acct_type not in account_types: conn.execute(text(insert_acct_type), {'name': acct_type}) account_types[acct_type] = get_insert_id(conn) res = conn.execute('SELECT * FROM accounts') for acct in res: if acct['account_type'] == 'AWS': conn.execute( text(insert_acct), { 'id': acct['account_id'], 'name': acct['account_name'], 'type_id': account_types['AWS'], 'contacts': acct['contacts'], 'enabled': acct['enabled'], 'required_roles': acct['required_roles'] } ) conn.execute( text(insert_acct_prop), { 'id': acct['account_id'], 'name': 'account_number', 'value': to_json(acct['account_number']) } ) conn.execute( text(insert_acct_prop), { 'id': acct['account_id'], 'name': 'ad_group_base', 'value': to_json(acct['ad_group_base'] or '') } ) print('Migrated {} account {}'.format(acct['account_type'], acct['account_name'])) elif acct['account_type'] == 'DNS_AXFR': conn.execute( text(insert_acct), { 'id': acct['account_id'], 'name': acct['account_name'], 'type_id': account_types['DNS: AXFR'], 'contacts': acct['contacts'], 'enabled': acct['enabled'], 'required_roles': acct['required_roles'] } ) server = get_config_value(conn, 'collector_dns', 'axfr_server') domains = get_config_value(conn, 'collector_dns', 'axfr_domains') conn.execute(text(insert_acct_prop), {'id': acct['account_id'], 'name': 'server', 'value': [server]}) conn.execute(text(insert_acct_prop), {'id': acct['account_id'], 'name': 'domains', 'value': domains}) print('Migrated {} account {}'.format(acct['account_type'], acct['account_name'])) elif acct['account_type'] == 'DNS_CLOUDFLARE': conn.execute( text(insert_acct), { 'id': acct['account_id'], 'name': acct['account_name'], 'type_id': account_types['DNS: CloudFlare'], 'contacts': acct['contacts'], 'enabled': acct['enabled'], 'required_roles': acct['required_roles'] } ) api_key = get_config_value(conn, 'collector_dns', 'cloudflare_api_key') email = get_config_value(conn, 'collector_dns', 'cloudflare_email') endpoint = get_config_value(conn, 'collector_dns', 'cloudflare_endpoint') conn.execute(text(insert_acct_prop), {'id': acct['account_id'], 'name': 'api_key', 'value': api_key}) conn.execute(text(insert_acct_prop), {'id': acct['account_id'], 'name': 'email', 'value': email}) conn.execute(text(insert_acct_prop), {'id': acct['account_id'], 'name': 'endpoint', 'value': endpoint}) print('Migrated {} account {}'.format(acct['account_type'], acct['account_name'])) else: print('Invalid account type: {}'.format(acct['account_type'])) conn.execute(text('ALTER TABLE accounts_new AUTO_INCREMENT = :counter'), {'counter': acct_auto_increment}) finally: conn.execute('SET FOREIGN_KEY_CHECKS=1') def switch_tables(): conn = op.get_bind() conn.execute('SET FOREIGN_KEY_CHECKS=0') conn.execute('DROP TABLE accounts') conn.execute('ALTER TABLE resources MODIFY `account_id` int(10) unsigned') conn.execute('ALTER TABLE accounts_new RENAME accounts') conn.execute('ALTER TABLE accounts RENAME INDEX `ix_accounts_new_account_name` TO `ix_accounts_account_name`') conn.execute('ALTER TABLE accounts RENAME INDEX `ix_accounts_new_account_type_id` TO `ix_accounts_account_type_id`') conn.execute('SET FOREIGN_KEY_CHECKS=1') def get_insert_id(conn): return next(conn.execute('SELECT LAST_INSERT_ID()'))[0] def get_config_value(conn, ns, item): return next(conn.execute(text(select_cfg_item), {'ns': ns, 'key': item}))['value']