]> git.decadent.org.uk Git - dak.git/blob - dak/update_db.py
Debug suites might also miss the source package
[dak.git] / dak / update_db.py
1 #!/usr/bin/env python
2
3 """ Database Update Main Script
4
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 """
9
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.
14
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.
19
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
23
24 ################################################################################
25
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
30
31 ################################################################################
32
33 import psycopg2
34 import sys
35 import fcntl
36 import os
37 import apt_pkg
38 import time
39 import errno
40 from glob import glob
41 from re import findall
42
43 from daklib import utils
44 from daklib.config import Config
45 from daklib.dak_exceptions import DBUpdateError
46 from daklib.daklog import Logger
47
48 ################################################################################
49
50 Cnf = None
51
52 ################################################################################
53
54 class UpdateDB:
55     def usage (self, exit_code=0):
56         print """Usage: dak update-db
57 Updates dak's database schema to the lastest version. You should disable crontabs while this is running
58
59   -h, --help                show this help and exit."""
60         sys.exit(exit_code)
61
62
63 ################################################################################
64
65     def update_db_to_zero(self):
66         """ This function will attempt to update a pre-zero database schema to zero """
67
68         # First, do the sure thing, and create the configuration table
69         try:
70             print "Creating configuration table ..."
71             c = self.db.cursor()
72             c.execute("""CREATE TABLE config (
73                                   id SERIAL PRIMARY KEY NOT NULL,
74                                   name TEXT UNIQUE NOT NULL,
75                                   value TEXT
76                                 );""")
77             c.execute("INSERT INTO config VALUES ( nextval('config_id_seq'), 'db_revision', '0')")
78             self.db.commit()
79
80         except psycopg2.ProgrammingError:
81             self.db.rollback()
82             print "Failed to create configuration table."
83             print "Can the projectB user CREATE TABLE?"
84             print ""
85             print "Aborting update."
86             sys.exit(-255)
87
88 ################################################################################
89
90     def get_db_rev(self):
91         # We keep database revision info the config table
92         # Try and access it
93
94         try:
95             c = self.db.cursor()
96             q = c.execute("SELECT value FROM config WHERE name = 'db_revision';")
97             return c.fetchone()[0]
98
99         except psycopg2.ProgrammingError:
100             # Whoops .. no config table ...
101             self.db.rollback()
102             print "No configuration table found, assuming dak database revision to be pre-zero"
103             return -1
104
105 ################################################################################
106
107     def get_transaction_id(self):
108         '''
109         Returns the current transaction id as a string.
110         '''
111         cursor = self.db.cursor()
112         cursor.execute("SELECT txid_current();")
113         id = cursor.fetchone()[0]
114         cursor.close()
115         return id
116
117 ################################################################################
118
119     def update_db(self):
120         # Ok, try and find the configuration table
121         print "Determining dak database revision ..."
122         cnf = Config()
123         logger = Logger('update-db')
124         modules = []
125
126         try:
127             # Build a connect string
128             if cnf.has_key("DB::Service"):
129                 connect_str = "service=%s" % cnf["DB::Service"]
130             else:
131                 connect_str = "dbname=%s"% (cnf["DB::Name"])
132                 if cnf.has_key("DB::Host") and cnf["DB::Host"] != '':
133                     connect_str += " host=%s" % (cnf["DB::Host"])
134                 if cnf.has_key("DB::Port") and cnf["DB::Port"] != '-1':
135                     connect_str += " port=%d" % (int(cnf["DB::Port"]))
136
137             self.db = psycopg2.connect(connect_str)
138
139         except Exception as e:
140             print "FATAL: Failed connect to database (%s)" % str(e)
141             sys.exit(1)
142
143         database_revision = int(self.get_db_rev())
144         logger.log(['transaction id before update: %s' % self.get_transaction_id()])
145
146         if database_revision == -1:
147             print "dak database schema predates update-db."
148             print ""
149             print "This script will attempt to upgrade it to the lastest, but may fail."
150             print "Please make sure you have a database backup handy. If you don't, press Ctrl-C now!"
151             print ""
152             print "Continuing in five seconds ..."
153             time.sleep(5)
154             print ""
155             print "Attempting to upgrade pre-zero database to zero"
156
157             self.update_db_to_zero()
158             database_revision = 0
159
160         dbfiles = glob(os.path.join(os.path.dirname(__file__), 'dakdb/update*.py'))
161         required_database_schema = max(map(int, findall('update(\d+).py', " ".join(dbfiles))))
162
163         print "dak database schema at %d" % database_revision
164         print "dak version requires schema %d"  % required_database_schema
165
166         if database_revision < required_database_schema:
167             print "\nUpdates to be applied:"
168             for i in range(database_revision, required_database_schema):
169                 i += 1
170                 dakdb = __import__("dakdb", globals(), locals(), ['update'+str(i)])
171                 update_module = getattr(dakdb, "update"+str(i))
172                 print "Update %d: %s" % (i, next(s for s in update_module.__doc__.split("\n") if s))
173                 modules.append((update_module, i))
174             prompt = "\nUpdate database? (y/N) "
175             answer = utils.our_raw_input(prompt)
176             if answer.upper() != 'Y':
177                 sys.exit(0)
178         else:
179             print "no updates required"
180             logger.log(["no updates required"])
181             sys.exit(0)
182
183         for module in modules:
184             (update_module, i) = module
185             try:
186                 update_module.do_update(self)
187                 message = "updated database schema from %d to %d" % (database_revision, i)
188                 print message
189                 logger.log([message])
190             except DBUpdateError as e:
191                 # Seems the update did not work.
192                 print "Was unable to update database schema from %d to %d." % (database_revision, i)
193                 print "The error message received was %s" % (e)
194                 logger.log(["DB Schema upgrade failed"])
195                 logger.close()
196                 utils.fubar("DB Schema upgrade failed")
197             database_revision += 1
198         logger.close()
199
200 ################################################################################
201
202     def init (self):
203         cnf = Config()
204         arguments = [('h', "help", "Update-DB::Options::Help")]
205         for i in [ "help" ]:
206             if not cnf.has_key("Update-DB::Options::%s" % (i)):
207                 cnf["Update-DB::Options::%s" % (i)] = ""
208
209         arguments = apt_pkg.parse_commandline(cnf.Cnf, arguments, sys.argv)
210
211         options = cnf.subtree("Update-DB::Options")
212         if options["Help"]:
213             self.usage()
214         elif arguments:
215             utils.warn("dak update-db takes no arguments.")
216             self.usage(exit_code=1)
217
218         try:
219             if os.path.isdir(cnf["Dir::Lock"]):
220                 lock_fd = os.open(os.path.join(cnf["Dir::Lock"], 'dinstall.lock'), os.O_RDWR | os.O_CREAT)
221                 fcntl.lockf(lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
222             else:
223                 utils.warn("Lock directory doesn't exist yet - not locking")
224         except IOError as e:
225             if errno.errorcode[e.errno] == 'EACCES' or errno.errorcode[e.errno] == 'EAGAIN':
226                 utils.fubar("Couldn't obtain lock; assuming another 'dak process-unchecked' is already running.")
227
228         self.update_db()
229
230
231 ################################################################################
232
233 if __name__ == '__main__':
234     app = UpdateDB()
235     app.init()
236
237 def main():
238     app = UpdateDB()
239     app.init()