Source code for dak.update_db

#! /usr/bin/env python3

""" Database Update Main Script

@contact: Debian FTP Master <ftpmaster@debian.org>
# Copyright (C) 2008  Michael Casadevall <mcasadevall@debian.org>
@license: GNU General Public License version 2 or later
"""

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

################################################################################

# <Ganneff> when do you have it written?
# <NCommander> Ganneff, after you make my debian account
# <Ganneff> blackmail wont work
# <NCommander> damn it

################################################################################

import psycopg2
import sys
import fcntl
import os
import apt_pkg
import time
import errno
from glob import glob
from re import findall

from daklib import utils
from daklib.config import Config
from daklib.dak_exceptions import DBUpdateError
from daklib.daklog import Logger

################################################################################

Cnf = None

################################################################################


[docs]class UpdateDB:
[docs] def usage(self, exit_code=0): print("""Usage: dak update-db Updates dak's database schema to the lastest version. You should disable crontabs while this is running -h, --help show this help and exit. -y, --yes do not ask for confirmation""") sys.exit(exit_code)
################################################################################
[docs] def update_db_to_zero(self): """ This function will attempt to update a pre-zero database schema to zero """ # First, do the sure thing, and create the configuration table try: print("Creating configuration table ...") c = self.db.cursor() c.execute("""CREATE TABLE config ( id SERIAL PRIMARY KEY NOT NULL, name TEXT UNIQUE NOT NULL, value TEXT );""") c.execute("INSERT INTO config VALUES ( nextval('config_id_seq'), 'db_revision', '0')") self.db.commit() except psycopg2.ProgrammingError: self.db.rollback() print("Failed to create configuration table.") print("Can the projectB user CREATE TABLE?") print("") print("Aborting update.") sys.exit(-255)
################################################################################
[docs] def get_db_rev(self): # We keep database revision info the config table # Try and access it try: c = self.db.cursor() q = c.execute("SELECT value FROM config WHERE name = 'db_revision';") return c.fetchone()[0] except psycopg2.ProgrammingError: # Whoops .. no config table ... self.db.rollback() print("No configuration table found, assuming dak database revision to be pre-zero") return -1
################################################################################
[docs] def get_transaction_id(self): ''' Returns the current transaction id as a string. ''' cursor = self.db.cursor() cursor.execute("SELECT txid_current();") id = cursor.fetchone()[0] cursor.close() return id
################################################################################
[docs] def update_db(self): # Ok, try and find the configuration table print("Determining dak database revision ...") cnf = Config() logger = Logger('update-db') modules = [] try: # Build a connect string if "DB::Service" in cnf: connect_str = "service=%s" % cnf["DB::Service"] else: connect_str = "dbname=%s" % (cnf["DB::Name"]) if "DB::Host" in cnf and cnf["DB::Host"] != '': connect_str += " host=%s" % (cnf["DB::Host"]) if "DB::Port" in cnf and cnf["DB::Port"] != '-1': connect_str += " port=%d" % (int(cnf["DB::Port"])) self.db = psycopg2.connect(connect_str) db_role = cnf.get("DB::Role") if db_role: self.db.cursor().execute('SET ROLE "{}"'.format(db_role)) except Exception as e: print("FATAL: Failed connect to database (%s)" % str(e)) sys.exit(1) database_revision = int(self.get_db_rev()) logger.log(['transaction id before update: %s' % self.get_transaction_id()]) if database_revision == -1: print("dak database schema predates update-db.") print("") print("This script will attempt to upgrade it to the lastest, but may fail.") print("Please make sure you have a database backup handy. If you don't, press Ctrl-C now!") print("") print("Continuing in five seconds ...") time.sleep(5) print("") print("Attempting to upgrade pre-zero database to zero") self.update_db_to_zero() database_revision = 0 dbfiles = glob(os.path.join(os.path.dirname(__file__), 'dakdb/update*.py')) required_database_schema = max(int(x) for x in findall(r'update(\d+).py', " ".join(dbfiles))) print("dak database schema at %d" % database_revision) print("dak version requires schema %d" % required_database_schema) if database_revision < required_database_schema: print("\nUpdates to be applied:") for i in range(database_revision, required_database_schema): i += 1 dakdb = __import__("dakdb", globals(), locals(), ['update' + str(i)]) update_module = getattr(dakdb, "update" + str(i)) print("Update %d: %s" % (i, next(s for s in update_module.__doc__.split("\n") if s))) modules.append((update_module, i)) if not Config().find_b("Update-DB::Options::Yes", False): prompt = "\nUpdate database? (y/N) " answer = utils.input_or_exit(prompt) if answer.upper() != 'Y': sys.exit(0) else: print("no updates required") logger.log(["no updates required"]) sys.exit(0) for module in modules: (update_module, i) = module try: update_module.do_update(self) message = "updated database schema from %d to %d" % (database_revision, i) print(message) logger.log([message]) except DBUpdateError as e: # Seems the update did not work. print("Was unable to update database schema from %d to %d." % (database_revision, i)) print("The error message received was %s" % (e)) logger.log(["DB Schema upgrade failed"]) logger.close() utils.fubar("DB Schema upgrade failed") database_revision += 1 logger.close()
################################################################################
[docs] def init(self): cnf = Config() arguments = [('h', "help", "Update-DB::Options::Help"), ("y", "yes", "Update-DB::Options::Yes")] for i in ["help"]: key = "Update-DB::Options::%s" % i if key not in cnf: cnf[key] = "" arguments = apt_pkg.parse_commandline(cnf.Cnf, arguments, sys.argv) options = cnf.subtree("Update-DB::Options") if options["Help"]: self.usage() elif arguments: utils.warn("dak update-db takes no arguments.") self.usage(exit_code=1) try: if os.path.isdir(cnf["Dir::Lock"]): lock_fd = os.open(os.path.join(cnf["Dir::Lock"], 'daily.lock'), os.O_RDONLY | os.O_CREAT) fcntl.flock(lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB) else: utils.warn("Lock directory doesn't exist yet - not locking") except OSError as e: if e.errno in (errno.EACCES, errno.EAGAIN): utils.fubar("Couldn't obtain lock, looks like archive is doing something, try again later.") else: raise self.update_db()
################################################################################ if __name__ == '__main__': app = UpdateDB() app.init()
[docs]def main(): app = UpdateDB() app.init()