1#! /usr/bin/env python3
3""" Database Update Main Script
5@contact: Debian FTP Master <ftpmaster@debian.org>
6# Copyright (C) 2008 Michael Casadevall <mcasadevall@debian.org>
7@license: GNU General Public License version 2 or later
8"""
10# This program is free software; you can redistribute it and/or modify
11# it under the terms of the GNU General Public License as published by
12# the Free Software Foundation; either version 2 of the License, or
13# (at your option) any later version.
15# This program is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18# GNU General Public License for more details.
20# You should have received a copy of the GNU General Public License
21# along with this program; if not, write to the Free Software
22# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24################################################################################
26# <Ganneff> when do you have it written?
27# <NCommander> Ganneff, after you make my debian account
28# <Ganneff> blackmail wont work
29# <NCommander> damn it
31################################################################################
33import psycopg2
34import sys
35import fcntl
36import os
37import apt_pkg
38import time
39import errno
40from glob import glob
41from re import findall
43from daklib import utils
44from daklib.config import Config
45from daklib.dak_exceptions import DBUpdateError
46from daklib.daklog import Logger
48################################################################################
50Cnf = None
52################################################################################
55class UpdateDB:
56 def usage(self, exit_code=0):
57 print("""Usage: dak update-db
58Updates dak's database schema to the lastest version. You should disable crontabs while this is running
60 -h, --help show this help and exit.
61 -y, --yes do not ask for confirmation""")
62 sys.exit(exit_code)
64################################################################################
66 def update_db_to_zero(self):
67 """ This function will attempt to update a pre-zero database schema to zero """
69 # First, do the sure thing, and create the configuration table
70 try:
71 print("Creating configuration table ...")
72 c = self.db.cursor()
73 c.execute("""CREATE TABLE config (
74 id SERIAL PRIMARY KEY NOT NULL,
75 name TEXT UNIQUE NOT NULL,
76 value TEXT
77 );""")
78 c.execute("INSERT INTO config VALUES ( nextval('config_id_seq'), 'db_revision', '0')")
79 self.db.commit()
81 except psycopg2.ProgrammingError:
82 self.db.rollback()
83 print("Failed to create configuration table.")
84 print("Can the projectB user CREATE TABLE?")
85 print("")
86 print("Aborting update.")
87 sys.exit(-255)
89################################################################################
91 def get_db_rev(self):
92 # We keep database revision info the config table
93 # Try and access it
95 try:
96 c = self.db.cursor()
97 q = c.execute("SELECT value FROM config WHERE name = 'db_revision';")
98 return c.fetchone()[0]
100 except psycopg2.ProgrammingError:
101 # Whoops .. no config table ...
102 self.db.rollback()
103 print("No configuration table found, assuming dak database revision to be pre-zero")
104 return -1
106################################################################################
108 def get_transaction_id(self):
109 '''
110 Returns the current transaction id as a string.
111 '''
112 cursor = self.db.cursor()
113 cursor.execute("SELECT txid_current();")
114 id = cursor.fetchone()[0]
115 cursor.close()
116 return id
118################################################################################
120 def update_db(self):
121 # Ok, try and find the configuration table
122 print("Determining dak database revision ...")
123 cnf = Config()
124 logger = Logger('update-db')
125 modules = []
127 try:
128 # Build a connect string
129 if "DB::Service" in cnf: 129 ↛ 130line 129 didn't jump to line 130, because the condition on line 129 was never true
130 connect_str = "service=%s" % cnf["DB::Service"]
131 else:
132 connect_str = "dbname=%s" % (cnf["DB::Name"])
133 if "DB::Host" in cnf and cnf["DB::Host"] != '': 133 ↛ 134line 133 didn't jump to line 134, because the condition on line 133 was never true
134 connect_str += " host=%s" % (cnf["DB::Host"])
135 if "DB::Port" in cnf and cnf["DB::Port"] != '-1': 135 ↛ 136line 135 didn't jump to line 136, because the condition on line 135 was never true
136 connect_str += " port=%d" % (int(cnf["DB::Port"]))
138 self.db = psycopg2.connect(connect_str)
140 db_role = cnf.get("DB::Role")
141 if db_role: 141 ↛ 148line 141 didn't jump to line 148, because the condition on line 141 was never false
142 self.db.cursor().execute('SET ROLE "{}"'.format(db_role))
144 except Exception as e:
145 print("FATAL: Failed connect to database (%s)" % str(e))
146 sys.exit(1)
148 database_revision = int(self.get_db_rev())
149 logger.log(['transaction id before update: %s' % self.get_transaction_id()])
151 if database_revision == -1: 151 ↛ 152line 151 didn't jump to line 152, because the condition on line 151 was never true
152 print("dak database schema predates update-db.")
153 print("")
154 print("This script will attempt to upgrade it to the lastest, but may fail.")
155 print("Please make sure you have a database backup handy. If you don't, press Ctrl-C now!")
156 print("")
157 print("Continuing in five seconds ...")
158 time.sleep(5)
159 print("")
160 print("Attempting to upgrade pre-zero database to zero")
162 self.update_db_to_zero()
163 database_revision = 0
165 dbfiles = glob(os.path.join(os.path.dirname(__file__), 'dakdb/update*.py'))
166 required_database_schema = max(int(x) for x in findall(r'update(\d+).py', " ".join(dbfiles)))
168 print("dak database schema at %d" % database_revision)
169 print("dak version requires schema %d" % required_database_schema)
171 if database_revision < required_database_schema: 171 ↛ 185line 171 didn't jump to line 185, because the condition on line 171 was never false
172 print("\nUpdates to be applied:")
173 for i in range(database_revision, required_database_schema):
174 i += 1
175 dakdb = __import__("dakdb", globals(), locals(), ['update' + str(i)])
176 update_module = getattr(dakdb, "update" + str(i))
177 print("Update %d: %s" % (i, next(s for s in update_module.__doc__.split("\n") if s))) 177 ↛ exitline 177 didn't finish the generator expression on line 177
178 modules.append((update_module, i))
179 if not Config().find_b("Update-DB::Options::Yes", False): 179 ↛ 180line 179 didn't jump to line 180, because the condition on line 179 was never true
180 prompt = "\nUpdate database? (y/N) "
181 answer = utils.input_or_exit(prompt)
182 if answer.upper() != 'Y':
183 sys.exit(0)
184 else:
185 print("no updates required")
186 logger.log(["no updates required"])
187 sys.exit(0)
189 for module in modules:
190 (update_module, i) = module
191 try:
192 update_module.do_update(self)
193 message = "updated database schema from %d to %d" % (database_revision, i)
194 print(message)
195 logger.log([message])
196 except DBUpdateError as e:
197 # Seems the update did not work.
198 print("Was unable to update database schema from %d to %d." % (database_revision, i))
199 print("The error message received was %s" % (e))
200 logger.log(["DB Schema upgrade failed"])
201 logger.close()
202 utils.fubar("DB Schema upgrade failed")
203 database_revision += 1
204 logger.close()
206################################################################################
208 def init(self):
209 cnf = Config()
210 arguments = [('h', "help", "Update-DB::Options::Help"),
211 ("y", "yes", "Update-DB::Options::Yes")]
212 for i in ["help"]:
213 key = "Update-DB::Options::%s" % i
214 if key not in cnf: 214 ↛ 212line 214 didn't jump to line 212, because the condition on line 214 was never false
215 cnf[key] = ""
217 arguments = apt_pkg.parse_commandline(cnf.Cnf, arguments, sys.argv)
219 options = cnf.subtree("Update-DB::Options")
220 if options["Help"]:
221 self.usage()
222 elif arguments: 222 ↛ 223line 222 didn't jump to line 223, because the condition on line 222 was never true
223 utils.warn("dak update-db takes no arguments.")
224 self.usage(exit_code=1)
226 try:
227 if os.path.isdir(cnf["Dir::Lock"]): 227 ↛ 228line 227 didn't jump to line 228, because the condition on line 227 was never true
228 lock_fd = os.open(os.path.join(cnf["Dir::Lock"], 'daily.lock'), os.O_RDONLY | os.O_CREAT)
229 fcntl.flock(lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
230 else:
231 utils.warn("Lock directory doesn't exist yet - not locking")
232 except OSError as e:
233 if e.errno in (errno.EACCES, errno.EAGAIN):
234 utils.fubar("Couldn't obtain lock, looks like archive is doing something, try again later.")
235 else:
236 raise
238 self.update_db()
241################################################################################
243if __name__ == '__main__':
244 app = UpdateDB()
245 app.init()
248def main():
249 app = UpdateDB()
250 app.init()