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 errno
34 import fcntl
35 import os
36 import sys
37 import time
38 from glob import glob
39 from re import findall
40
41 import apt_pkg
42 import psycopg2
43
44 from daklib import utils
45 from daklib.config import Config
46 from daklib.dak_exceptions import DBUpdateError
47 from daklib.daklog import Logger
48
49
50
51 Cnf = None
52
53
54
55
57 - def usage(self, exit_code=0):
58 print(
59 """Usage: dak update-db
60 Updates dak's database schema to the lastest version. You should disable crontabs while this is running
61
62 -h, --help show this help and exit.
63 -y, --yes do not ask for confirmation"""
64 )
65 sys.exit(exit_code)
66
67
68
70 """This function will attempt to update a pre-zero database schema to zero"""
71
72
73 try:
74 print("Creating configuration table ...")
75 c = self.db.cursor()
76 c.execute(
77 """CREATE TABLE config (
78 id SERIAL PRIMARY KEY NOT NULL,
79 name TEXT UNIQUE NOT NULL,
80 value TEXT
81 );"""
82 )
83 c.execute(
84 "INSERT INTO config VALUES ( nextval('config_id_seq'), 'db_revision', '0')"
85 )
86 self.db.commit()
87
88 except psycopg2.ProgrammingError:
89 self.db.rollback()
90 print("Failed to create configuration table.")
91 print("Can the projectB user CREATE TABLE?")
92 print("")
93 print("Aborting update.")
94 sys.exit(-255)
95
96
97
99
100
101
102 try:
103 c = self.db.cursor()
104 c.execute("SELECT value FROM config WHERE name = 'db_revision';")
105 return c.fetchone()[0]
106
107 except psycopg2.ProgrammingError:
108
109 self.db.rollback()
110 print(
111 "No configuration table found, assuming dak database revision to be pre-zero"
112 )
113 return -1
114
115
116
118 """
119 Returns the current transaction id as a string.
120 """
121 cursor = self.db.cursor()
122 cursor.execute("SELECT txid_current();")
123 id = cursor.fetchone()[0]
124 cursor.close()
125 return id
126
127
128
130
131 print("Determining dak database revision ...")
132 cnf = Config()
133 logger = Logger("update-db")
134 modules = []
135
136 try:
137
138 if "DB::Service" in cnf:
139 connect_str = "service=%s" % cnf["DB::Service"]
140 else:
141 connect_str = "dbname=%s" % (cnf["DB::Name"])
142 if "DB::Host" in cnf and cnf["DB::Host"] != "":
143 connect_str += " host=%s" % (cnf["DB::Host"])
144 if "DB::Port" in cnf and cnf["DB::Port"] != "-1":
145 connect_str += " port=%d" % (int(cnf["DB::Port"]))
146
147 self.db = psycopg2.connect(connect_str)
148
149 db_role = cnf.get("DB::Role")
150 if db_role:
151 self.db.cursor().execute('SET ROLE "{}"'.format(db_role))
152
153 except Exception as e:
154 print("FATAL: Failed connect to database (%s)" % str(e))
155 sys.exit(1)
156
157 database_revision = int(self.get_db_rev())
158 logger.log(["transaction id before update: %s" % self.get_transaction_id()])
159
160 if database_revision == -1:
161 print("dak database schema predates update-db.")
162 print("")
163 print(
164 "This script will attempt to upgrade it to the lastest, but may fail."
165 )
166 print(
167 "Please make sure you have a database backup handy. If you don't, press Ctrl-C now!"
168 )
169 print("")
170 print("Continuing in five seconds ...")
171 time.sleep(5)
172 print("")
173 print("Attempting to upgrade pre-zero database to zero")
174
175 self.update_db_to_zero()
176 database_revision = 0
177
178 dbfiles = glob(os.path.join(os.path.dirname(__file__), "dakdb/update*.py"))
179 required_database_schema = max(
180 int(x) for x in findall(r"update(\d+).py", " ".join(dbfiles))
181 )
182
183 print("dak database schema at %d" % database_revision)
184 print("dak version requires schema %d" % required_database_schema)
185
186 if database_revision < required_database_schema:
187 print("\nUpdates to be applied:")
188 for i in range(database_revision, required_database_schema):
189 i += 1
190 dakdb = __import__("dakdb", globals(), locals(), ["update" + str(i)])
191 update_module = getattr(dakdb, "update" + str(i))
192 print(
193 "Update %d: %s"
194 % (i, next(s for s in update_module.__doc__.split("\n") if s))
195 )
196 modules.append((update_module, i))
197 if not Config().find_b("Update-DB::Options::Yes", False):
198 prompt = "\nUpdate database? (y/N) "
199 answer = utils.input_or_exit(prompt)
200 if answer.upper() != "Y":
201 sys.exit(0)
202 else:
203 print("no updates required")
204 logger.log(["no updates required"])
205 sys.exit(0)
206
207 for module in modules:
208 (update_module, i) = module
209 try:
210 update_module.do_update(self)
211 message = "updated database schema from %d to %d" % (
212 database_revision,
213 i,
214 )
215 print(message)
216 logger.log([message])
217 except DBUpdateError as e:
218
219 print(
220 "Was unable to update database schema from %d to %d."
221 % (database_revision, i)
222 )
223 print("The error message received was %s" % (e))
224 logger.log(["DB Schema upgrade failed"])
225 logger.close()
226 utils.fubar("DB Schema upgrade failed")
227 database_revision += 1
228 logger.close()
229
230
231
233 cnf = Config()
234 arguments = [
235 ("h", "help", "Update-DB::Options::Help"),
236 ("y", "yes", "Update-DB::Options::Yes"),
237 ]
238 for i in ["help"]:
239 key = "Update-DB::Options::%s" % i
240 if key not in cnf:
241 cnf[key] = ""
242
243 arguments = apt_pkg.parse_commandline(cnf.Cnf, arguments, sys.argv)
244
245 options = cnf.subtree("Update-DB::Options")
246 if options["Help"]:
247 self.usage()
248 elif arguments:
249 utils.warn("dak update-db takes no arguments.")
250 self.usage(exit_code=1)
251
252 try:
253 if os.path.isdir(cnf["Dir::Lock"]):
254 lock_fd = os.open(
255 os.path.join(cnf["Dir::Lock"], "daily.lock"),
256 os.O_RDONLY | os.O_CREAT,
257 )
258 fcntl.flock(lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
259 else:
260 utils.warn("Lock directory doesn't exist yet - not locking")
261 except OSError as e:
262 if e.errno in (errno.EACCES, errno.EAGAIN):
263 utils.fubar(
264 "Couldn't obtain lock, looks like archive is doing something, try again later."
265 )
266 else:
267 raise
268
269 self.update_db()
270
271
272
273
274 if __name__ == "__main__":
275 app = UpdateDB()
276 app.init()
277
278
280 app = UpdateDB()
281 app.init()
282