Implement a database migration manager using yoyo

This commit is contained in:
Bilal Elmoussaoui 2019-05-25 18:12:15 +00:00
parent 1531eab89b
commit 9721f4c645
7 changed files with 153 additions and 37 deletions

View file

@ -213,6 +213,44 @@
}
]
},
{
"name" : "yoyo-migrations",
"buildsystem" : "simple",
"build-commands" : [
"pip3 install --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} yoyo-migrations"
],
"ensure-writable" : [
"/lib/python*/site-packages/easy-install.pth",
"/lib/python*/site-packages/setuptools.pth",
"/app/lib/python*/site-packages/easy-install.pth",
"/app/lib/python*/site-packages/setuptools.pth"
],
"sources" : [
{
"type" : "file",
"url" : "https://files.pythonhosted.org/packages/5f/cf/f6d468c6929e8739cd12bf1a9cf3719e0be739e09acfaddc0f9ade67e67c/yoyo_migrations-6.1.0-py2.py3-none-any.whl",
"sha256" : "95e5c49a797873d3b86e5a7714c1a714bea8728a6fc1d6f6f5019d3d058471e5"
},
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/79/42/d717cc2b4520fb09e45b344b1b0b4e81aa672001dd128c180fabc655c341/text_unidecode-1.2-py2.py3-none-any.whl",
"sha256": "801e38bd550b943563660a91de8d4b6fa5df60a542be9093f7abf819f86050cc"
}
],
"modules": [{
"name": "iniherit",
"buildsystem" : "simple",
"build-commands" : [
"python3 setup.py install --prefix=/app"
],
"sources": [
{
"type": "archive",
"url": "https://files.pythonhosted.org/packages/65/a5/5bb95059c92c23560a80bcd599bc737a4175b275b3a577cb19f66bd302e3/iniherit-0.3.9.tar.gz",
"sha256": "06d90849ff0c4fadb7e255ce31e7c8e188a99af90d761435c72b79b36adbb67a"
}]
}]
},
{
"name" : "libhandy",
"buildsystem" : "meson",

View file

@ -2,4 +2,5 @@ beautifulsoup4==4.7.0
Pillow==5.0.0
pyotp==2.2.7
pyzbar==0.1.8
requests==2.21.0
requests==2.21.0
yoyo-migrations==6.1.0

View file

@ -0,0 +1,19 @@
"""
Create table accounts
"""
from yoyo import step
__depends__ = {}
steps = [
step('''
CREATE TABLE "accounts" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE,
"username" VARCHAR NOT NULL,
"provider" VARCHAR NOT NULL,
"secret_id" VARCHAR NOT NULL UNIQUE
''',
'DROP TABLE "accounts"',
ignore_errors='apply')
]

View file

@ -0,0 +1,22 @@
"""
Create table providers
"""
from yoyo import step
__depends__ = {'authenticator_20190525_01_GdUDU-create-table-accounts'}
steps = [
step(
'''CREATE TABLE "providers" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE,
"name" VARCHAR NOT NULL,
"website" VARCHAR NULL,
"doc_url" VARCHAR NULL,
"image" VARCHAR NULL
)
''',
'DROP TABLE "providers"',
ignore_errors='apply'
)
]

View file

@ -0,0 +1,33 @@
"""
Restore old accounts
"""
from yoyo import step
__depends__ = {'authenticator_20190525_02_mdR2o-create-table-providers'}
def do_step(conn):
accounts = conn.execute("SELECT * FROM accounts").fetchall()
_accounts = []
cursor = conn.cursor()
for account_id, username, provider_name, secret_id in accounts:
cursor.execute("INSERT INTO providers (name) VALUES (?)", (provider_name, ))
provider_id = cursor.lastrowid
_accounts.append((account_id, username, provider_id, secret_id))
cursor.execute(" ALTER TABLE accounts RENAME TO tmp;")
cursor.execute(''' CREATE TABLE "accounts" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE,
"username" VARCHAR NOT NULL,
"token_id" VARCHAR NOT NULL UNIQUE,
"provider" INTEGER NOT NULL
)''')
cursor.execute("DROP TABLE tmp;")
for account_id, username, provider_id, token_id in _accounts:
cursor.execute("INSERT INTO accounts (username, provider, token_id) VALUES (?, ?, ?)", (username, provider_id, token_id))
steps = [
step(do_step, ignore_errors='apply')
]

View file

@ -20,6 +20,7 @@ import sqlite3
from os import path, makedirs
from gi.repository import GLib, Gio
from collections import namedtuple
from shutil import move
from Authenticator.models import Logger
import json
@ -37,11 +38,12 @@ class Database:
db_version = 7
def __init__(self):
self.migrations_dir = path.join(path.dirname(__file__), '../migrations')
database_created = self.__create_database_file()
self.conn = sqlite3.connect(self.db_file)
if database_created:
self.__create_tables()
self.__fill_providers()
self.__apply_migrations()
self.conn = sqlite3.connect(self.db_file)
self.__fill_providers()
@staticmethod
def get_default():
@ -51,9 +53,13 @@ class Database:
return Database.instance
@property
def db_file(self):
def db_dir(self):
return path.join(GLib.get_user_config_dir(),
'Authenticator',
'Authenticator')
@property
def db_file(self):
return path.join(self.db_dir,
'database-{}.db'.format(str(Database.db_version))
)
@ -249,41 +255,31 @@ class Database:
"""
Create an empty database file for the first start of the application.
"""
makedirs(path.dirname(self.db_file), exist_ok=True)
if not path.exists(self.db_file):
with open(self.db_file, 'w') as file_obj:
file_obj.write('')
return True
return False
created = False
for i in range(3, self.db_version + 1):
db_file = path.join(self.db_dir, "database-{}.db".format(i))
if path.exists(db_file):
move(db_file, self.db_file)
created = True
break
if not created:
makedirs(path.dirname(self.db_file), exist_ok=True)
if not path.exists(self.db_file):
with open(self.db_file, 'w') as file_obj:
file_obj.write('')
created = True
return created
def __create_tables(self):
def __apply_migrations(self):
"""
Create the needed tables to run the application.
"""
accounts_table = '''
CREATE TABLE "accounts" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE,
"username" VARCHAR NOT NULL,
"token_id" VARCHAR NOT NULL UNIQUE,
"provider" INTEGER NOT NULL
);
'''
providers_table = '''
CREATE TABLE "providers" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE,
"name" VARCHAR NOT NULL,
"website" VARCHAR NULL,
"doc_url" VARCHAR NULL,
"image" VARCHAR NULL
)
'''
try:
self.conn.execute(accounts_table)
self.conn.execute(providers_table)
self.conn.commit()
except Exception as error:
Logger.error("[SQL] Impossible to create table 'accounts'")
Logger.error(str(error))
from yoyo import read_migrations
from yoyo import get_backend
backend = get_backend('sqlite:///' + self.db_file)
migrations = read_migrations(self.migrations_dir)
with backend.lock():
backend.apply_migrations(backend.to_apply(migrations))
def __count(self, table_name):
query = "SELECT COUNT(id) AS count FROM " + table_name

7
yoyo.ini Normal file
View file

@ -0,0 +1,7 @@
[DEFAULT]
sources = ./src/Authenticator/migrations
migration_table = _yoyo_migration
batch_mode = on
verbosity = 0
editor = code {}
prefix = authenticator_