#! /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 errno
import fcntl
import os
import sys
import time
from glob import glob
from re import findall
import apt_pkg
import psycopg2
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()
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()