]> git.decadent.org.uk Git - dak.git/blob - dak/admin.py
admin: Add version-check subcommands.
[dak.git] / dak / admin.py
1 #!/usr/bin/env python
2
3 """Configure dak parameters in the database"""
4 # Copyright (C) 2009  Mark Hymers <mhy@debian.org>
5
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19
20 ################################################################################
21
22 import sys
23
24 import apt_pkg
25
26 from daklib import utils
27 from daklib.dbconn import *
28 from sqlalchemy.orm.exc import NoResultFound
29
30 ################################################################################
31
32 dispatch = {}
33 dryrun = False
34
35 ################################################################################
36 def warn(msg):
37     print >> sys.stderr, msg
38
39 def die(msg, exit_code=1):
40     print >> sys.stderr, msg
41     sys.exit(exit_code)
42
43 def die_arglen(args, args_needed, msg):
44     if len(args) < args_needed:
45         die(msg)
46
47 def usage(exit_code=0):
48     """Perform administrative work on the dak database."""
49
50     print """Usage: dak admin COMMAND
51 Perform administrative work on the dak database.
52
53   -h, --help          show this help and exit.
54   -n, --dry-run       don't do anything, just show what would have been done
55                       (only applies to add or rm operations).
56
57   Commands can use a long or abbreviated form:
58
59   config / c:
60      c db                   show db config
61      c db-shell             show db config in a usable form for psql
62
63   architecture / a:
64      a list                 show a list of architectures
65      a rm ARCH              remove an architecture (will only work if
66                             no longer linked to any suites)
67      a add ARCH DESCRIPTION [SUITELIST]
68                             add architecture ARCH with DESCRIPTION.
69                             If SUITELIST is given, add to each of the
70                             suites at the same time
71
72   suite / s:
73      s list                 show a list of suites
74      s show SUITE           show config details for a suite
75      s add SUITE VERSION [ label=LABEL ] [ description=DESCRIPTION ]
76                          [ origin=ORIGIN ] [ codename=CODENAME ]
77                             add suite SUITE, version VERSION. label,
78                             description, origin and codename are optional.
79
80   suite-architecture / s-a:
81      s-a list               show the architectures for all suites
82      s-a list-suite ARCH    show the suites an ARCH is in
83      s-a list-arch SUITE    show the architectures in a SUITE
84      s-a add SUITE ARCH     add ARCH to suite
85      s-a rm SUITE ARCH      remove ARCH from suite (will only work if
86                             no packages remain for the arch in the suite)
87
88   version-check / v-c:
89      v-c list                        show version checks for all suites
90      v-c list-suite SUITE            show version checks for suite SUITE
91      v-c add SUITE CHECK REFERENCE   add a version check for suite SUITE
92      v-c rm SUITE CHECK REFERENCE    rmove a version check
93        where
94          CHECK     is one of Enhances, MustBeNewerThan, MustBeOlderThan
95          REFERENCE is another suite name
96 """
97     sys.exit(exit_code)
98
99 ################################################################################
100
101 def __architecture_list(d, args):
102     q = d.session().query(Architecture).order_by('arch_string')
103     for j in q.all():
104         # HACK: We should get rid of source from the arch table
105         if j.arch_string == 'source': continue
106         print j.arch_string
107     sys.exit(0)
108
109 def __architecture_add(d, args):
110     die_arglen(args, 4, "E: adding an architecture requires a name and a description")
111     print "Adding architecture %s" % args[2]
112     suites = [str(x) for x in args[4:]]
113     if len(suites) > 0:
114         print "Adding to suites %s" % ", ".join(suites)
115     if not dryrun:
116         try:
117             s = d.session()
118             a = Architecture()
119             a.arch_string = str(args[2]).lower()
120             a.description = str(args[3])
121             s.add(a)
122             for sn in suites:
123                 su = get_suite(sn, s)
124                 if su is not None:
125                     a.suites.append(su)
126                 else:
127                     warn("W: Cannot find suite %s" % su)
128             s.commit()
129         except IntegrityError, e:
130             die("E: Integrity error adding architecture %s (it probably already exists)" % args[2])
131         except SQLAlchemyError, e:
132             die("E: Error adding architecture %s (%s)" % (args[2], e))
133     print "Architecture %s added" % (args[2])
134
135 def __architecture_rm(d, args):
136     die_arglen(args, 3, "E: removing an architecture requires at least a name")
137     print "Removing architecture %s" % args[2]
138     if not dryrun:
139         try:
140             s = d.session()
141             a = get_architecture(args[2].lower(), s)
142             if a is None:
143                 die("E: Cannot find architecture %s" % args[2])
144             s.delete(a)
145             s.commit()
146         except IntegrityError, e:
147             die("E: Integrity error removing architecture %s (suite-arch entries probably still exist)" % args[2])
148         except SQLAlchemyError, e:
149             die("E: Error removing architecture %s (%s)" % (args[2], e))
150     print "Architecture %s removed" % args[2]
151
152 def architecture(command):
153     args = [str(x) for x in command]
154     Cnf = utils.get_conf()
155     d = DBConn()
156
157     die_arglen(args, 2, "E: architecture needs at least a command")
158
159     mode = args[1].lower()
160     if mode == 'list':
161         __architecture_list(d, args)
162     elif mode == 'add':
163         __architecture_add(d, args)
164     elif mode == 'rm':
165         __architecture_rm(d, args)
166     else:
167         die("E: architecture command unknown")
168
169 dispatch['architecture'] = architecture
170 dispatch['a'] = architecture
171
172 ################################################################################
173
174 def __suite_list(d, args):
175     s = d.session()
176     for j in s.query(Suite).order_by('suite_name').all():
177         print j.suite_name
178
179 def __suite_show(d, args):
180     if len(args) < 2:
181         die("E: showing an suite entry requires a suite")
182
183     s = d.session()
184     su = get_suite(args[2].lower())
185     if su is None:
186         die("E: can't find suite entry for %s" % (args[2].lower()))
187
188     print su.details()
189
190 def __suite_add(d, args):
191     die_arglen(args, 4, "E: adding a suite requires at least a name and a version")
192     suite_name = args[2].lower()
193     version = args[3]
194     rest = args[3:]
195
196     def get_field(field):
197         for varval in args:
198             if varval.startswith(field + '='):
199                 return varval.split('=')[1]
200         return None
201
202     print "Adding suite %s" % suite_name
203     if not dryrun:
204         try:
205             s = d.session()
206             suite = Suite()
207             suite.suite_name = suite_name
208             suite.version = version
209             suite.label = get_field('label')
210             suite.description = get_field('description')
211             suite.origin = get_field('origin')
212             suite.codename = get_field('codename')
213             s.add(suite)
214             s.commit()
215         except IntegrityError, e:
216             die("E: Integrity error adding suite %s (it probably already exists)" % suite_name)
217         except SQLAlchemyError, e:
218             die("E: Error adding suite %s (%s)" % (suite_name, e))
219     print "Suite %s added" % (suite_name)
220
221 def suite(command):
222     args = [str(x) for x in command]
223     Cnf = utils.get_conf()
224     d = DBConn()
225
226     die_arglen(args, 2, "E: suite needs at least a command")
227
228     mode = args[1].lower()
229
230     if mode == 'list':
231         __suite_list(d, args)
232     elif mode == 'show':
233         __suite_show(d, args)
234     elif mode == 'add':
235         __suite_add(d, args)
236     else:
237         die("E: suite command unknown")
238
239 dispatch['suite'] = suite
240 dispatch['s'] = suite
241
242 ################################################################################
243
244 def __suite_architecture_list(d, args):
245     s = d.session()
246     for j in s.query(Suite).order_by('suite_name'):
247         architectures = j.get_architectures(skipsrc = True, skipall = True)
248         print j.suite_name + ': ' + \
249               ', '.join([a.arch_string for a in architectures])
250
251 def __suite_architecture_listarch(d, args):
252     die_arglen(args, 3, "E: suite-architecture list-arch requires a suite")
253     suite = get_suite(args[2].lower(), d.session())
254     if suite is None:
255         die('E: suite %s is invalid' % args[2].lower())
256     a = suite.get_architectures(skipsrc = True, skipall = True)
257     for j in a:
258         print j.arch_string
259
260
261 def __suite_architecture_listsuite(d, args):
262     die_arglen(args, 3, "E: suite-architecture list-suite requires an arch")
263     architecture = get_architecture(args[2].lower(), d.session())
264     if architecture is None:
265         die("E: architecture %s is invalid" % args[2].lower())
266     for j in architecture.suites:
267         print j.suite_name
268
269
270 def __suite_architecture_add(d, args):
271     if len(args) < 3:
272         die("E: adding a suite-architecture entry requires a suite and arch")
273
274     s = d.session()
275
276     suite = get_suite(args[2].lower(), s)
277     if suite is None: die("E: Can't find suite %s" % args[2].lower())
278
279     arch = get_architecture(args[3].lower(), s)
280     if arch is None: die("E: Can't find architecture %s" % args[3].lower())
281
282     if not dryrun:
283         try:
284             suite.architectures.append(arch)
285             s.commit()
286         except IntegrityError, e:
287             die("E: Can't add suite-architecture entry (%s, %s) - probably already exists" % (args[2].lower(), args[3].lower()))
288         except SQLAlchemyError, e:
289             die("E: Can't add suite-architecture entry (%s, %s) - %s" % (args[2].lower(), args[3].lower(), e))
290
291     print "Added suite-architecture entry for %s, %s" % (args[2].lower(), args[3].lower())
292
293
294 def __suite_architecture_rm(d, args):
295     if len(args) < 3:
296         die("E: removing an suite-architecture entry requires a suite and arch")
297
298     s = d.session()
299     if not dryrun:
300         try:
301             suite_name = args[2].lower()
302             suite = get_suite(suite_name, s)
303             if suite is None:
304                 die('E: no such suite %s' % suite_name)
305             arch_string = args[3].lower()
306             architecture = get_architecture(arch_string, s)
307             if architecture not in suite.architectures:
308                 die("E: architecture %s not found in suite %s" % (arch_string, suite_name))
309             suite.architectures.remove(architecture)
310             s.commit()
311         except IntegrityError, e:
312             die("E: Can't remove suite-architecture entry (%s, %s) - it's probably referenced" % (args[2].lower(), args[3].lower()))
313         except SQLAlchemyError, e:
314             die("E: Can't remove suite-architecture entry (%s, %s) - %s" % (args[2].lower(), args[3].lower(), e))
315
316     print "Removed suite-architecture entry for %s, %s" % (args[2].lower(), args[3].lower())
317
318
319 def suite_architecture(command):
320     args = [str(x) for x in command]
321     Cnf = utils.get_conf()
322     d = DBConn()
323
324     die_arglen(args, 2, "E: suite-architecture needs at least a command")
325
326     mode = args[1].lower()
327
328     if mode == 'list':
329         __suite_architecture_list(d, args)
330     elif mode == 'list-arch':
331         __suite_architecture_listarch(d, args)
332     elif mode == 'list-suite':
333         __suite_architecture_listsuite(d, args)
334     elif mode == 'add':
335         __suite_architecture_add(d, args)
336     elif mode == 'rm':
337         __suite_architecture_rm(d, args)
338     else:
339         die("E: suite-architecture command unknown")
340
341 dispatch['suite-architecture'] = suite_architecture
342 dispatch['s-a'] = suite_architecture
343
344 ################################################################################
345
346 def __version_check_list(d):
347     session = d.session()
348     for s in session.query(Suite).order_by('suite_name'):
349         __version_check_list_suite(d, s.suite_name)
350
351 def __version_check_list_suite(d, suite_name):
352     vcs = get_version_checks(suite_name)
353     for vc in vcs:
354         print "%s %s %s" % (suite_name, vc.check, vc.reference.suite_name)
355
356 def __version_check_add(d, suite_name, check, reference_name):
357     suite = get_suite(suite_name)
358     if not suite:
359         die("E: Could not find suite %s." % (suite_name))
360     reference = get_suite(reference_name)
361     if not reference:
362         die("E: Could not find reference suite %s." % (reference_name))
363
364     session = d.session()
365     vc = VersionCheck()
366     vc.suite = suite
367     vc.check = check
368     vc.reference = reference
369     session.add(vc)
370     session.commit()
371
372 def __version_check_rm(d, suite_name, check, reference_name):
373     suite = get_suite(suite_name)
374     if not suite:
375         die("E: Could not find suite %s." % (suite_name))
376     reference = get_suite(reference_name)
377     if not reference:
378         die("E: Could not find reference suite %s." % (reference_name))
379
380     session = d.session()
381     try:
382       vc = session.query(VersionCheck).filter_by(suite=suite, check=check, reference=reference).one()
383       session.delete(vc)
384       session.commit()
385     except NoResultFound:
386       print "W: version-check not found."
387
388 def version_check(command):
389     args = [str(x) for x in command]
390     Cnf = utils.get_conf()
391     d = DBConn()
392
393     die_arglen(args, 2, "E: version-check needs at least a command")
394     mode = args[1].lower()
395
396     if mode == 'list':
397         __version_check_list(d)
398     elif mode == 'list-suite':
399         if len(args) != 3:
400             die("E: version-check list-suite needs a single parameter")
401         __version_check_list_suite(d, args[2])
402     elif mode == 'add':
403         if len(args) != 5:
404             die("E: version-check add needs three parameters")
405         __version_check_add(d, args[2], args[3], args[4])
406     elif mode == 'rm':
407         if len(args) != 5:
408             die("E: version-check rm needs three parameters")
409         __version_check_rm(d, args[2], args[3], args[4])
410     else:
411         die("E: version-check command unknown")
412
413 dispatch['version-check'] = version_check
414 dispatch['v-c'] = version_check
415
416 ################################################################################
417
418 def show_config(command):
419     args = [str(x) for x in command]
420     cnf = utils.get_conf()
421
422     die_arglen(args, 2, "E: config needs at least a command")
423
424     mode = args[1].lower()
425
426     if mode == 'db':
427         connstr = ""
428         if cnf.has_key("DB::Service"):
429             # Service mode
430             connstr = "postgresql://service=%s" % cnf["DB::Service"]
431         elif cnf.has_key("DB::Host"):
432             # TCP/IP
433             connstr = "postgres://%s" % cnf["DB::Host"]
434             if cnf.has_key("DB::Port") and cnf["DB::Port"] != "-1":
435                 connstr += ":%s" % cnf["DB::Port"]
436             connstr += "/%s" % cnf["DB::Name"]
437         else:
438             # Unix Socket
439             connstr = "postgres:///%s" % cnf["DB::Name"]
440             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
441                 connstr += "?port=%s" % cnf["DB::Port"]
442         print connstr
443     elif mode == 'db-shell':
444         e = []
445         if cnf.has_key("DB::Service"):
446             e.append('PGSERVICE')
447             print "PGSERVICE=%s" % cnf["DB::Service"]
448         if cnf.has_key("DB::Name"):
449             e.append('PGDATABASE')
450             print "PGDATABASE=%s" % cnf["DB::Name"]
451         if cnf.has_key("DB::Host"):
452             print "PGHOST=%s" % cnf["DB::Host"]
453             e.append('PGHOST')
454         if cnf.has_key("DB::Port") and cnf["DB::Port"] != "-1":
455             print "PGPORT=%s" % cnf["DB::Port"]
456             e.append('PGPORT')
457         print "export " + " ".join(e)
458     else:
459         die("E: config command unknown")
460
461 dispatch['config'] = show_config
462 dispatch['c'] = show_config
463
464 ################################################################################
465
466 def main():
467     """Perform administrative work on the dak database"""
468     global dryrun
469     Cnf = utils.get_conf()
470     arguments = [('h', "help", "Admin::Options::Help"),
471                  ('n', "dry-run", "Admin::Options::Dry-Run")]
472     for i in [ "help", "dry-run" ]:
473         if not Cnf.has_key("Admin::Options::%s" % (i)):
474             Cnf["Admin::Options::%s" % (i)] = ""
475
476     arguments = apt_pkg.ParseCommandLine(Cnf, arguments, sys.argv)
477
478     options = Cnf.SubTree("Admin::Options")
479     if options["Help"] or len(arguments) < 1:
480         usage()
481     if options["Dry-Run"]:
482         dryrun = True
483
484     subcommand = str(arguments[0])
485
486     if subcommand in dispatch.keys():
487         dispatch[subcommand](arguments)
488     else:
489         die("E: Unknown command")
490
491 ################################################################################
492
493 if __name__ == '__main__':
494     main()