1
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
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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
56 - def usage(self, exit_code=0):
57 print("""Usage: dak update-db
58 Updates dak's database schema to the lastest version. You should disable crontabs while this is running
59
60 -h, --help show this help and exit.
61 -y, --yes do not ask for confirmation""")
62 sys.exit(exit_code)
63
64
65
67 """ This function will attempt to update a pre-zero database schema to zero """
68
69
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()
80
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)
88
89
90
92
93
94
95 try:
96 c = self.db.cursor()
97 q = c.execute("SELECT value FROM config WHERE name = 'db_revision';")
98 return c.fetchone()[0]
99
100 except psycopg2.ProgrammingError:
101
102 self.db.rollback()
103 print("No configuration table found, assuming dak database revision to be pre-zero")
104 return -1
105
106
107
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
117
118
119
121
122 print("Determining dak database revision ...")
123 cnf = Config()
124 logger = Logger('update-db')
125 modules = []
126
127 try:
128
129 if "DB::Service" in cnf:
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"] != '':
134 connect_str += " host=%s" % (cnf["DB::Host"])
135 if "DB::Port" in cnf and cnf["DB::Port"] != '-1':
136 connect_str += " port=%d" % (int(cnf["DB::Port"]))
137
138 self.db = psycopg2.connect(connect_str)
139
140 db_role = cnf.get("DB::Role")
141 if db_role:
142 self.db.cursor().execute('SET ROLE "{}"'.format(db_role))
143
144 except Exception as e:
145 print("FATAL: Failed connect to database (%s)" % str(e))
146 sys.exit(1)
147
148 database_revision = int(self.get_db_rev())
149 logger.log(['transaction id before update: %s' % self.get_transaction_id()])
150
151 if database_revision == -1:
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")
161
162 self.update_db_to_zero()
163 database_revision = 0
164
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)))
167
168 print("dak database schema at %d" % database_revision)
169 print("dak version requires schema %d" % required_database_schema)
170
171 if database_revision < required_database_schema:
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)))
178 modules.append((update_module, i))
179 if not Config().find_b("Update-DB::Options::Yes", False):
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)
188
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
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()
205
206
207
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:
215 cnf[key] = ""
216
217 arguments = apt_pkg.parse_commandline(cnf.Cnf, arguments, sys.argv)
218
219 options = cnf.subtree("Update-DB::Options")
220 if options["Help"]:
221 self.usage()
222 elif arguments:
223 utils.warn("dak update-db takes no arguments.")
224 self.usage(exit_code=1)
225
226 try:
227 if os.path.isdir(cnf["Dir::Lock"]):
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
237
238 self.update_db()
239
240
241
242
243 if __name__ == '__main__':
244 app = UpdateDB()
245 app.init()
246
247
249 app = UpdateDB()
250 app.init()
251