Ejemplo n.º 1
0
    def migrate_db(self, ctx, config, from_tree, to_tree):
        def alembic(against, *args, hide='out'):
            alembic_path = Path(against) / 'src'
            with cd(str(alembic_path)):
                return run('alembic -x url={dsn} '.format(dsn=database) + ' '.join(args), hide=hide)

        database = ctx.qabel.block.psql_dsn

        if from_tree:
            current_commit = get_tree_commit(from_tree)
            to_commit = get_tree_commit(to_tree)

            # We are upgrading if the current current commit is an ancestor (precedes) the commit we are deploying.
            # Otherwise it's an downgrade. This is important because we need to use the correct alembic folder
            # which must hold the more recent revision. Alembic also needs to know whether it's an upgrade or
            # downgrade, it can't figure this out on it's own.
            upgrading = commit_is_ancestor(current_commit, to_commit)

            # map "abcdef (head)" to just "abcdef". Empty output means no DB state ("None")
            current_revision = alembic(from_tree, 'current').stdout.strip().split(' ')[0] or None
        else:
            upgrading = True
            current_revision = None

        # map "Rev: 129sdjsakdasd (head)" (+ extra lines) to just "129sdjsakdasd"
        to_revision = alembic(to_tree, 'history -vr +0:').stdout.split('\n')[0].split()[1]

        if current_revision == to_revision:
            print('No database migration required (alembic revision {rev})'.format(rev=current_revision))
            return
        print('Current database is at alembic revision', current_revision)
        print('Migrating to alembic revision', to_revision)
        alembic_verb = 'upgrade' if upgrading else 'downgrade'
        against = to_tree if upgrading else from_tree
        alembic(against, alembic_verb, to_revision, hide=None)
Ejemplo n.º 2
0
    def migrate_db(self, ctx, config, from_tree, to_tree):
        def manage_py(against, args, hide='out'):
            return manage_command(against, config, args, hide=hide)

        def get_migrations(tree):
            # showmigrations output looks like this:
            # <app label>
            #  [X] <migration name>  <-- applied migration
            #  [ ] <migration>       <-- not applied
            line_prefix_length = len(' [X] ')
            all_migrations = manage_py(tree, 'showmigrations').stdout.splitlines()
            app_migrations = {}
            while all_migrations:
                current_app = all_migrations.pop(0)
                migrations = []
                while all_migrations and all_migrations[0].startswith(' ['):
                    migrations.append(all_migrations.pop(0))
                # migrations is now a list of " [x] nnnn_...", find last applied and the last overall migration
                last_migration = migrations[-1][line_prefix_length:]
                applied_migrations = filter(lambda m: m.startswith(' [X] '), migrations)
                last_applied_migration = None
                for last_applied_migration in applied_migrations:
                    pass
                if last_applied_migration:
                    last_applied_migration = last_applied_migration[line_prefix_length:]

                app_migrations[current_app] = last_applied_migration, last_migration
            return app_migrations

        if from_tree:
            current_commit = get_tree_commit(from_tree)
            to_commit = get_tree_commit(to_tree)

            # We are upgrading if the current current commit is an ancestor (precedes) the commit we are deploying.
            # Otherwise it's an downgrade. This is important because we need to use the tree which has the most currently
            # applied migration when downgrading, since the downgraded-to-tree cannot undo migrations it doesn't know.
            upgrading = commit_is_ancestor(current_commit, to_commit)
        else:
            # If we have no old tree it's always a straight upgrade of everything
            upgrading = True

        print('Database is being', 'upgraded' if upgrading else 'downgraded')

        if upgrading:
            manage_py(to_tree, 'migrate')
        else:
            if from_tree:
                from_migrations = get_migrations(from_tree)
            else:
                from_migrations = {}
            to_migrations = get_migrations(to_tree)

            not_migrated = []
            for application, (_, target_migration) in to_migrations.items():
                try:
                    applied_migration, _ = from_migrations[application]
                except KeyError:
                    applied_migration = '<None/Unknown>'
                if applied_migration == target_migration:
                    not_migrated.append(application)
                    continue
                print('Migrating \'{app}\': {applied} -> {target}'.format(app=application, applied=applied_migration,
                                                                          target=target_migration))
                if upgrading:
                    against = to_tree
                else:
                    against = from_tree
                manage_py(against, 'migrate {app} {target}'.format(app=application, target=target_migration))
            print('No migrations needed for:', ', '.join(not_migrated))