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