]> git.decadent.org.uk Git - dak.git/blob - dak/update_db.py
update-db: automatically find the schema nunmber to upgrade to
[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
125         try:
126             # Build a connect string
127             if cnf.has_key("DB::Service"):
128                 connect_str = "service=%s" % cnf["DB::Service"]
129             else:
130                 connect_str = "dbname=%s"% (cnf["DB::Name"])
131                 if cnf.has_key("DB::Host") and cnf["DB::Host"] != '':
132                     connect_str += " host=%s" % (cnf["DB::Host"])
133                 if cnf.has_key("DB::Port") and cnf["DB::Port"] != '-1':
134                     connect_str += " port=%d" % (int(cnf["DB::Port"]))
135
136             self.db = psycopg2.connect(connect_str)
137
138         except Exception as e:
139             print "FATAL: Failed connect to database (%s)" % str(e)
140             sys.exit(1)
141
142         database_revision = int(self.get_db_rev())
143         logger.log(['transaction id before update: %s' % self.get_transaction_id()])
144
145         if database_revision == -1:
146             print "dak database schema predates update-db."
147             print ""
148             print "This script will attempt to upgrade it to the lastest, but may fail."
149             print "Please make sure you have a database backup handy. If you don't, press Ctrl-C now!"
150             print ""
151             print "Continuing in five seconds ..."
152             time.sleep(5)
153             print ""
154             print "Attempting to upgrade pre-zero database to zero"
155
156             self.update_db_to_zero()
157             database_revision = 0
158
159         dbfiles = glob(os.path.join(os.path.dirname(__file__), 'dakdb/update*.py'))
160         required_database_schema = int(max(findall('update(\d+).py', " ".join(dbfiles))))
161
162         print "dak database schema at %d" % database_revision
163         print "dak version requires schema %d"  % required_database_schema
164
165         if database_revision < required_database_schema:
166             prompt = "Update database? (y/N) "
167             answer = utils.our_raw_input(prompt)
168             if answer.upper() != 'Y':
169                 sys.exit(0)
170         else:
171             print "no updates required"
172             logger.log(["no updates required"])
173             sys.exit(0)
174
175         for i in range (database_revision, required_database_schema):
176             try:
177                 dakdb = __import__("dakdb", globals(), locals(), ['update'+str(i+1)])
178                 update_module = getattr(dakdb, "update"+str(i+1))
179                 update_module.do_update(self)
180                 message = "updated database schema from %d to %d" % (database_revision, i+1)
181                 print message
182                 logger.log([message])
183             except DBUpdateError as e:
184                 # Seems the update did not work.
185                 print "Was unable to update database schema from %d to %d." % (database_revision, i+1)
186                 print "The error message received was %s" % (e)
187                 logger.log(["DB Schema upgrade failed"])
188                 logger.close()
189                 utils.fubar("DB Schema upgrade failed")
190             database_revision += 1
191         logger.close()
192
193 ################################################################################
194
195     def init (self):
196         cnf = Config()
197         arguments = [('h', "help", "Update-DB::Options::Help")]
198         for i in [ "help" ]:
199             if not cnf.has_key("Update-DB::Options::%s" % (i)):
200                 cnf["Update-DB::Options::%s" % (i)] = ""
201
202         arguments = apt_pkg.parse_commandline(cnf.Cnf, arguments, sys.argv)
203
204         options = cnf.subtree("Update-DB::Options")
205         if options["Help"]:
206             self.usage()
207         elif arguments:
208             utils.warn("dak update-db takes no arguments.")
209             self.usage(exit_code=1)
210
211         try:
212             if os.path.isdir(cnf["Dir::Lock"]):
213                 lock_fd = os.open(os.path.join(cnf["Dir::Lock"], 'dinstall.lock'), os.O_RDWR | os.O_CREAT)
214                 fcntl.lockf(lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
215             else:
216                 utils.warn("Lock directory doesn't exist yet - not locking")
217         except IOError as e:
218             if errno.errorcode[e.errno] == 'EACCES' or errno.errorcode[e.errno] == 'EAGAIN':
219                 utils.fubar("Couldn't obtain lock; assuming another 'dak process-unchecked' is already running.")
220
221         self.update_db()
222
223
224 ################################################################################
225
226 if __name__ == '__main__':
227     app = UpdateDB()
228     app.init()
229
230 def main():
231     app = UpdateDB()
232     app.init()