]> git.decadent.org.uk Git - dak.git/blob - dak/admin.py
dak/admin.py: suite add has a archive=ARCHIVE option.
[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      c NAME                 show option NAME as set in configuration table
63
64   keyring / k:
65      k list-all             list all keyrings
66      k list-binary          list all keyrings with a NULL source acl
67      k list-source          list all keyrings with a non NULL source acl
68      k add-buildd NAME ARCH...   add buildd keyring with upload permission
69                                  for the given architectures
70
71   architecture / a:
72      a list                 show a list of architectures
73      a rm ARCH              remove an architecture (will only work if
74                             no longer linked to any suites)
75      a add ARCH DESCRIPTION [SUITELIST]
76                             add architecture ARCH with DESCRIPTION.
77                             If SUITELIST is given, add to each of the
78                             suites at the same time
79
80   suite / s:
81      s list                 show a list of suites
82      s show SUITE           show config details for a suite
83      s add SUITE VERSION [ label=LABEL ] [ description=DESCRIPTION ]
84                          [ origin=ORIGIN ] [ codename=CODENAME ]
85                          [ signingkey=SIGNINGKEY ] [ archive=ARCHIVE ]
86                             add suite SUITE, version VERSION.
87                             label, description, origin, codename
88                             and signingkey are optional.
89
90      s add-all-arches SUITE VERSION... as "s add" but adds suite-architecture
91                             relationships for all architectures
92
93   suite-architecture / s-a:
94      s-a list               show the architectures for all suites
95      s-a list-suite ARCH    show the suites an ARCH is in
96      s-a list-arch SUITE    show the architectures in a SUITE
97      s-a add SUITE ARCH     add ARCH to suite
98      s-a rm SUITE ARCH      remove ARCH from suite (will only work if
99                             no packages remain for the arch in the suite)
100
101   archive:
102      archive list           list all archives
103      archive add NAME ROOT DESCRIPTION [primary-mirror=MIRROR] [tainted=1]
104                             add archive NAME with path ROOT,
105                             primary mirror MIRROR.
106      archive rm NAME        remove archive NAME (will only work if there are
107                             no files and no suites in the archive)
108      archive rename OLD NEW rename archive OLD to NEW
109
110   version-check / v-c:
111      v-c list                        show version checks for all suites
112      v-c list-suite SUITE            show version checks for suite SUITE
113      v-c add SUITE CHECK REFERENCE   add a version check for suite SUITE
114      v-c rm SUITE CHECK REFERENCE    remove a version check
115        where
116          CHECK     is one of Enhances, MustBeNewerThan, MustBeOlderThan
117          REFERENCE is another suite name
118 """
119     sys.exit(exit_code)
120
121 ################################################################################
122
123 def __architecture_list(d, args):
124     q = d.session().query(Architecture).order_by(Architecture.arch_string)
125     for j in q.all():
126         # HACK: We should get rid of source from the arch table
127         if j.arch_string == 'source': continue
128         print j.arch_string
129     sys.exit(0)
130
131 def __architecture_add(d, args):
132     die_arglen(args, 4, "E: adding an architecture requires a name and a description")
133     print "Adding architecture %s" % args[2]
134     suites = [str(x) for x in args[4:]]
135     if len(suites) > 0:
136         print "Adding to suites %s" % ", ".join(suites)
137     if not dryrun:
138         try:
139             s = d.session()
140             a = Architecture()
141             a.arch_string = str(args[2]).lower()
142             a.description = str(args[3])
143             s.add(a)
144             for sn in suites:
145                 su = get_suite(sn, s)
146                 if su is not None:
147                     a.suites.append(su)
148                 else:
149                     warn("W: Cannot find suite %s" % su)
150             s.commit()
151         except IntegrityError as e:
152             die("E: Integrity error adding architecture %s (it probably already exists)" % args[2])
153         except SQLAlchemyError as e:
154             die("E: Error adding architecture %s (%s)" % (args[2], e))
155     print "Architecture %s added" % (args[2])
156
157 def __architecture_rm(d, args):
158     die_arglen(args, 3, "E: removing an architecture requires at least a name")
159     print "Removing architecture %s" % args[2]
160     if not dryrun:
161         try:
162             s = d.session()
163             a = get_architecture(args[2].lower(), s)
164             if a is None:
165                 die("E: Cannot find architecture %s" % args[2])
166             s.delete(a)
167             s.commit()
168         except IntegrityError as e:
169             die("E: Integrity error removing architecture %s (suite-arch entries probably still exist)" % args[2])
170         except SQLAlchemyError as e:
171             die("E: Error removing architecture %s (%s)" % (args[2], e))
172     print "Architecture %s removed" % args[2]
173
174 def architecture(command):
175     args = [str(x) for x in command]
176     Cnf = utils.get_conf()
177     d = DBConn()
178
179     die_arglen(args, 2, "E: architecture needs at least a command")
180
181     mode = args[1].lower()
182     if mode == 'list':
183         __architecture_list(d, args)
184     elif mode == 'add':
185         __architecture_add(d, args)
186     elif mode == 'rm':
187         __architecture_rm(d, args)
188     else:
189         die("E: architecture command unknown")
190
191 dispatch['architecture'] = architecture
192 dispatch['a'] = architecture
193
194 ################################################################################
195
196 def __suite_list(d, args):
197     s = d.session()
198     for j in s.query(Suite).order_by(Suite.suite_name).all():
199         print j.suite_name
200
201 def __suite_show(d, args):
202     if len(args) < 2:
203         die("E: showing an suite entry requires a suite")
204
205     s = d.session()
206     su = get_suite(args[2].lower())
207     if su is None:
208         die("E: can't find suite entry for %s" % (args[2].lower()))
209
210     print su.details()
211
212 def __suite_add(d, args, addallarches=False):
213     die_arglen(args, 4, "E: adding a suite requires at least a name and a version")
214     suite_name = args[2].lower()
215     version = args[3]
216     rest = args[3:]
217
218     def get_field(field):
219         for varval in args:
220             if varval.startswith(field + '='):
221                 return varval.split('=')[1]
222         return None
223
224     print "Adding suite %s" % suite_name
225     if not dryrun:
226         try:
227             s = d.session()
228             suite = Suite()
229             suite.suite_name = suite_name
230             suite.overridecodename = suite_name
231             suite.version = version
232             suite.label = get_field('label')
233             suite.description = get_field('description')
234             suite.origin = get_field('origin')
235             suite.codename = get_field('codename')
236             signingkey = get_field('signingkey')
237             if signingkey is not None:
238                 suite.signingkeys = [signingkey.upper()]
239             archive_name = get_field('archive')
240             if archive_name is not None:
241                 suite.archive = get_archive(archive_name, s)
242             else:
243                 suite.archive = s.query(Archive).filter(~Archive.archive_name.in_(['build-queues', 'new', 'policy'])).one()
244             suite.srcformats = s.query(SrcFormat).all()
245             s.add(suite)
246             s.flush()
247         except IntegrityError as e:
248             die("E: Integrity error adding suite %s (it probably already exists)" % suite_name)
249         except SQLAlchemyError as e:
250             die("E: Error adding suite %s (%s)" % (suite_name, e))
251     print "Suite %s added" % (suite_name)
252
253     if addallarches:
254         arches = []
255         q = s.query(Architecture).order_by(Architecture.arch_string)
256         for arch in q.all():
257             suite.architectures.append(arch)
258             arches.append(arch.arch_string)
259
260         print "Architectures %s added to %s" % (','.join(arches), suite_name)
261
262     s.commit()
263
264
265 def suite(command):
266     args = [str(x) for x in command]
267     Cnf = utils.get_conf()
268     d = DBConn()
269
270     die_arglen(args, 2, "E: suite needs at least a command")
271
272     mode = args[1].lower()
273
274     if mode == 'list':
275         __suite_list(d, args)
276     elif mode == 'show':
277         __suite_show(d, args)
278     elif mode == 'add':
279         __suite_add(d, args, False)
280     elif mode == 'add-all-arches':
281         __suite_add(d, args, True)
282     else:
283         die("E: suite command unknown")
284
285 dispatch['suite'] = suite
286 dispatch['s'] = suite
287
288 ################################################################################
289
290 def __suite_architecture_list(d, args):
291     s = d.session()
292     for j in s.query(Suite).order_by(Suite.suite_name):
293         architectures = j.get_architectures(skipsrc = True, skipall = True)
294         print j.suite_name + ': ' + \
295               ', '.join([a.arch_string for a in architectures])
296
297 def __suite_architecture_listarch(d, args):
298     die_arglen(args, 3, "E: suite-architecture list-arch requires a suite")
299     suite = get_suite(args[2].lower(), d.session())
300     if suite is None:
301         die('E: suite %s is invalid' % args[2].lower())
302     a = suite.get_architectures(skipsrc = True, skipall = True)
303     for j in a:
304         print j.arch_string
305
306
307 def __suite_architecture_listsuite(d, args):
308     die_arglen(args, 3, "E: suite-architecture list-suite requires an arch")
309     architecture = get_architecture(args[2].lower(), d.session())
310     if architecture is None:
311         die("E: architecture %s is invalid" % args[2].lower())
312     for j in architecture.suites:
313         print j.suite_name
314
315
316 def __suite_architecture_add(d, args):
317     if len(args) < 3:
318         die("E: adding a suite-architecture entry requires a suite and arch")
319
320     s = d.session()
321
322     suite = get_suite(args[2].lower(), s)
323     if suite is None: die("E: Can't find suite %s" % args[2].lower())
324
325     arch = get_architecture(args[3].lower(), s)
326     if arch is None: die("E: Can't find architecture %s" % args[3].lower())
327
328     if not dryrun:
329         try:
330             suite.architectures.append(arch)
331             s.commit()
332         except IntegrityError as e:
333             die("E: Can't add suite-architecture entry (%s, %s) - probably already exists" % (args[2].lower(), args[3].lower()))
334         except SQLAlchemyError as e:
335             die("E: Can't add suite-architecture entry (%s, %s) - %s" % (args[2].lower(), args[3].lower(), e))
336
337     print "Added suite-architecture entry for %s, %s" % (args[2].lower(), args[3].lower())
338
339
340 def __suite_architecture_rm(d, args):
341     if len(args) < 3:
342         die("E: removing an suite-architecture entry requires a suite and arch")
343
344     s = d.session()
345     if not dryrun:
346         try:
347             suite_name = args[2].lower()
348             suite = get_suite(suite_name, s)
349             if suite is None:
350                 die('E: no such suite %s' % suite_name)
351             arch_string = args[3].lower()
352             architecture = get_architecture(arch_string, s)
353             if architecture not in suite.architectures:
354                 die("E: architecture %s not found in suite %s" % (arch_string, suite_name))
355             suite.architectures.remove(architecture)
356             s.commit()
357         except IntegrityError as e:
358             die("E: Can't remove suite-architecture entry (%s, %s) - it's probably referenced" % (args[2].lower(), args[3].lower()))
359         except SQLAlchemyError as e:
360             die("E: Can't remove suite-architecture entry (%s, %s) - %s" % (args[2].lower(), args[3].lower(), e))
361
362     print "Removed suite-architecture entry for %s, %s" % (args[2].lower(), args[3].lower())
363
364
365 def suite_architecture(command):
366     args = [str(x) for x in command]
367     Cnf = utils.get_conf()
368     d = DBConn()
369
370     die_arglen(args, 2, "E: suite-architecture needs at least a command")
371
372     mode = args[1].lower()
373
374     if mode == 'list':
375         __suite_architecture_list(d, args)
376     elif mode == 'list-arch':
377         __suite_architecture_listarch(d, args)
378     elif mode == 'list-suite':
379         __suite_architecture_listsuite(d, args)
380     elif mode == 'add':
381         __suite_architecture_add(d, args)
382     elif mode == 'rm':
383         __suite_architecture_rm(d, args)
384     else:
385         die("E: suite-architecture command unknown")
386
387 dispatch['suite-architecture'] = suite_architecture
388 dispatch['s-a'] = suite_architecture
389
390 ################################################################################
391
392 def archive_list():
393     session = DBConn().session()
394     for archive in session.query(Archive).order_by(Archive.archive_name):
395         print "{0} path={1} description={2} tainted={3}".format(archive.archive_name, archive.path, archive.description, archive.tainted)
396
397 def archive_add(args):
398     (name, path, description) = args[0:3]
399
400     attributes = dict(
401         archive_name=name,
402         path=path,
403         description=description,
404         )
405
406     for option in args[3:]:
407         (key, value) = option.split('=')
408         attributes[key] = value
409
410     session = DBConn().session()
411
412     archive = Archive()
413     for key, value in attributes.iteritems():
414         setattr(archive, key, value)
415
416     session.add(archive)
417     session.flush()
418
419     if dryrun:
420         session.rollback()
421     else:
422         session.commit()
423
424 def archive_rm(name):
425     session = DBConn().session()
426     archive = session.query(Archive).filter_by(archive_name=name).one()
427     session.delete(archive)
428     session.flush()
429
430     if dryrun:
431         session.rollback()
432     else:
433         session.commit()
434
435 def archive_rename(oldname, newname):
436     session = DBConn().session()
437     archive = get_archive(oldname, session)
438     archive.archive_name = newname
439     session.flush()
440
441     if dryrun:
442         session.rollback()
443     else:
444         session.commit()
445
446 def archive(command):
447     mode = command[1]
448     if mode == 'list':
449         archive_list()
450     elif mode == 'rename':
451         archive_rename(command[2], command[3])
452     elif mode == 'add':
453         archive_add(command[2:])
454     elif mode == 'rm':
455         archive_rm(command[2])
456     else:
457         die("E: archive command unknown")
458
459 dispatch['archive'] = archive
460
461 ################################################################################
462
463 def __version_check_list(d):
464     session = d.session()
465     for s in session.query(Suite).order_by(Suite.suite_name):
466         __version_check_list_suite(d, s.suite_name)
467
468 def __version_check_list_suite(d, suite_name):
469     vcs = get_version_checks(suite_name)
470     for vc in vcs:
471         print "%s %s %s" % (suite_name, vc.check, vc.reference.suite_name)
472
473 def __version_check_add(d, suite_name, check, reference_name):
474     suite = get_suite(suite_name)
475     if not suite:
476         die("E: Could not find suite %s." % (suite_name))
477     reference = get_suite(reference_name)
478     if not reference:
479         die("E: Could not find reference suite %s." % (reference_name))
480
481     session = d.session()
482     vc = VersionCheck()
483     vc.suite = suite
484     vc.check = check
485     vc.reference = reference
486     session.add(vc)
487     session.commit()
488
489 def __version_check_rm(d, suite_name, check, reference_name):
490     suite = get_suite(suite_name)
491     if not suite:
492         die("E: Could not find suite %s." % (suite_name))
493     reference = get_suite(reference_name)
494     if not reference:
495         die("E: Could not find reference suite %s." % (reference_name))
496
497     session = d.session()
498     try:
499       vc = session.query(VersionCheck).filter_by(suite=suite, check=check, reference=reference).one()
500       session.delete(vc)
501       session.commit()
502     except NoResultFound:
503       print "W: version-check not found."
504
505 def version_check(command):
506     args = [str(x) for x in command]
507     Cnf = utils.get_conf()
508     d = DBConn()
509
510     die_arglen(args, 2, "E: version-check needs at least a command")
511     mode = args[1].lower()
512
513     if mode == 'list':
514         __version_check_list(d)
515     elif mode == 'list-suite':
516         if len(args) != 3:
517             die("E: version-check list-suite needs a single parameter")
518         __version_check_list_suite(d, args[2])
519     elif mode == 'add':
520         if len(args) != 5:
521             die("E: version-check add needs three parameters")
522         __version_check_add(d, args[2], args[3], args[4])
523     elif mode == 'rm':
524         if len(args) != 5:
525             die("E: version-check rm needs three parameters")
526         __version_check_rm(d, args[2], args[3], args[4])
527     else:
528         die("E: version-check command unknown")
529
530 dispatch['version-check'] = version_check
531 dispatch['v-c'] = version_check
532
533 ################################################################################
534
535 def show_config(command):
536     args = [str(x) for x in command]
537     cnf = utils.get_conf()
538
539     die_arglen(args, 2, "E: config needs at least a command")
540
541     mode = args[1].lower()
542
543     if mode == 'db':
544         connstr = ""
545         if cnf.has_key("DB::Service"):
546             # Service mode
547             connstr = "postgresql://service=%s" % cnf["DB::Service"]
548         elif cnf.has_key("DB::Host"):
549             # TCP/IP
550             connstr = "postgres://%s" % cnf["DB::Host"]
551             if cnf.has_key("DB::Port") and cnf["DB::Port"] != "-1":
552                 connstr += ":%s" % cnf["DB::Port"]
553             connstr += "/%s" % cnf["DB::Name"]
554         else:
555             # Unix Socket
556             connstr = "postgres:///%s" % cnf["DB::Name"]
557             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
558                 connstr += "?port=%s" % cnf["DB::Port"]
559         print connstr
560     elif mode == 'db-shell':
561         e = []
562         if cnf.has_key("DB::Service"):
563             e.append('PGSERVICE')
564             print "PGSERVICE=%s" % cnf["DB::Service"]
565         if cnf.has_key("DB::Name"):
566             e.append('PGDATABASE')
567             print "PGDATABASE=%s" % cnf["DB::Name"]
568         if cnf.has_key("DB::Host"):
569             print "PGHOST=%s" % cnf["DB::Host"]
570             e.append('PGHOST')
571         if cnf.has_key("DB::Port") and cnf["DB::Port"] != "-1":
572             print "PGPORT=%s" % cnf["DB::Port"]
573             e.append('PGPORT')
574         print "export " + " ".join(e)
575     elif mode == 'get':
576         print cnf.get(args[2])
577     else:
578         session = DBConn().session()
579         try:
580             o = session.query(DBConfig).filter_by(name = mode).one()
581             print o.value
582         except NoResultFound:
583             print "W: option '%s' not set" % mode
584
585 dispatch['config'] = show_config
586 dispatch['c'] = show_config
587
588 ################################################################################
589
590 def show_keyring(command):
591     args = [str(x) for x in command]
592     cnf = utils.get_conf()
593
594     die_arglen(args, 2, "E: keyring needs at least a command")
595
596     mode = args[1].lower()
597
598     d = DBConn()
599
600     q = d.session().query(Keyring).filter(Keyring.active == True)
601
602     if mode == 'list-all':
603         pass
604     elif mode == 'list-binary':
605         q = q.join(Keyring.acl).filter(ACL.allow_source == False)
606     elif mode == 'list-source':
607         q = q.join(Keyring.acl).filter(ACL.allow_source == True)
608     else:
609         die("E: keyring command unknown")
610
611     for k in q.all():
612         print k.keyring_name
613
614 def keyring_add_buildd(command):
615     name = command[2]
616     arch_names = command[3:]
617
618     session = DBConn().session()
619     arches = session.query(Architecture).filter(Architecture.arch_string.in_(arch_names))
620
621     acl = ACL()
622     acl.name = 'buildd-{0}'.format('+'.join(arch_names))
623     acl.architectures.update(arches)
624     acl.allow_new = True
625     acl.allow_binary = True
626     acl.allow_binary_only = True
627     acl.allow_hijack = True
628     session.add(acl)
629
630     k = Keyring()
631     k.keyring_name = name
632     k.acl = acl
633     k.priority = 10
634     session.add(k)
635
636     session.commit()
637
638 def keyring(command):
639     if command[1].startswith('list-'):
640         show_keyring(command)
641     elif command[1] == 'add-buildd':
642         keyring_add_buildd(command)
643     else:
644         die("E: keyring command unknown")
645
646 dispatch['keyring'] = keyring
647 dispatch['k'] = keyring
648
649 ################################################################################
650
651 def main():
652     """Perform administrative work on the dak database"""
653     global dryrun
654     Cnf = utils.get_conf()
655     arguments = [('h', "help", "Admin::Options::Help"),
656                  ('n', "dry-run", "Admin::Options::Dry-Run")]
657     for i in [ "help", "dry-run" ]:
658         if not Cnf.has_key("Admin::Options::%s" % (i)):
659             Cnf["Admin::Options::%s" % (i)] = ""
660
661     arguments = apt_pkg.parse_commandline(Cnf, arguments, sys.argv)
662
663     options = Cnf.subtree("Admin::Options")
664     if options["Help"] or len(arguments) < 1:
665         usage()
666     if options["Dry-Run"]:
667         dryrun = True
668
669     subcommand = str(arguments[0])
670
671     if subcommand in dispatch.keys():
672         dispatch[subcommand](arguments)
673     else:
674         die("E: Unknown command")
675
676 ################################################################################
677
678 if __name__ == '__main__':
679     main()