]> git.decadent.org.uk Git - dak.git/blob - dak/admin.py
Add support for rm-ing suites
[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 def __suite_rm(d, args):
265     die_arglen(args, 3, "E: removing a suite requires at least a name")
266     name = args[2]
267     print "Removing suite {0}".format(name)
268     if not dryrun:
269         try:
270             s = d.session()
271             su = get_suite(name.lower())
272             if su is None:
273                 die("E: Cannot find suite {0}".format(name))
274             s.delete(su)
275             s.commit()
276         except IntegrityError as e:
277             die("E: Integrity error removing suite {0} (suite-arch entries probably still exist)".format(name))
278         except SQLAlchemyError as e:
279             die("E: Error removing suite {0} ({1})".format(name, e))
280     print "Suite {0} removed".format(name)
281
282 def suite(command):
283     args = [str(x) for x in command]
284     Cnf = utils.get_conf()
285     d = DBConn()
286
287     die_arglen(args, 2, "E: suite needs at least a command")
288
289     mode = args[1].lower()
290
291     if mode == 'list':
292         __suite_list(d, args)
293     elif mode == 'show':
294         __suite_show(d, args)
295     elif mode == 'rm':
296         __suite_rm(d, args)
297     elif mode == 'add':
298         __suite_add(d, args, False)
299     elif mode == 'add-all-arches':
300         __suite_add(d, args, True)
301     else:
302         die("E: suite command unknown")
303
304 dispatch['suite'] = suite
305 dispatch['s'] = suite
306
307 ################################################################################
308
309 def __suite_architecture_list(d, args):
310     s = d.session()
311     for j in s.query(Suite).order_by(Suite.suite_name):
312         architectures = j.get_architectures(skipsrc = True, skipall = True)
313         print j.suite_name + ': ' + \
314               ', '.join([a.arch_string for a in architectures])
315
316 def __suite_architecture_listarch(d, args):
317     die_arglen(args, 3, "E: suite-architecture list-arch requires a suite")
318     suite = get_suite(args[2].lower(), d.session())
319     if suite is None:
320         die('E: suite %s is invalid' % args[2].lower())
321     a = suite.get_architectures(skipsrc = True, skipall = True)
322     for j in a:
323         print j.arch_string
324
325
326 def __suite_architecture_listsuite(d, args):
327     die_arglen(args, 3, "E: suite-architecture list-suite requires an arch")
328     architecture = get_architecture(args[2].lower(), d.session())
329     if architecture is None:
330         die("E: architecture %s is invalid" % args[2].lower())
331     for j in architecture.suites:
332         print j.suite_name
333
334
335 def __suite_architecture_add(d, args):
336     if len(args) < 3:
337         die("E: adding a suite-architecture entry requires a suite and arch")
338
339     s = d.session()
340
341     suite = get_suite(args[2].lower(), s)
342     if suite is None: die("E: Can't find suite %s" % args[2].lower())
343
344     arch = get_architecture(args[3].lower(), s)
345     if arch is None: die("E: Can't find architecture %s" % args[3].lower())
346
347     if not dryrun:
348         try:
349             suite.architectures.append(arch)
350             s.commit()
351         except IntegrityError as e:
352             die("E: Can't add suite-architecture entry (%s, %s) - probably already exists" % (args[2].lower(), args[3].lower()))
353         except SQLAlchemyError as e:
354             die("E: Can't add suite-architecture entry (%s, %s) - %s" % (args[2].lower(), args[3].lower(), e))
355
356     print "Added suite-architecture entry for %s, %s" % (args[2].lower(), args[3].lower())
357
358
359 def __suite_architecture_rm(d, args):
360     if len(args) < 3:
361         die("E: removing an suite-architecture entry requires a suite and arch")
362
363     s = d.session()
364     if not dryrun:
365         try:
366             suite_name = args[2].lower()
367             suite = get_suite(suite_name, s)
368             if suite is None:
369                 die('E: no such suite %s' % suite_name)
370             arch_string = args[3].lower()
371             architecture = get_architecture(arch_string, s)
372             if architecture not in suite.architectures:
373                 die("E: architecture %s not found in suite %s" % (arch_string, suite_name))
374             suite.architectures.remove(architecture)
375             s.commit()
376         except IntegrityError as e:
377             die("E: Can't remove suite-architecture entry (%s, %s) - it's probably referenced" % (args[2].lower(), args[3].lower()))
378         except SQLAlchemyError as e:
379             die("E: Can't remove suite-architecture entry (%s, %s) - %s" % (args[2].lower(), args[3].lower(), e))
380
381     print "Removed suite-architecture entry for %s, %s" % (args[2].lower(), args[3].lower())
382
383
384 def suite_architecture(command):
385     args = [str(x) for x in command]
386     Cnf = utils.get_conf()
387     d = DBConn()
388
389     die_arglen(args, 2, "E: suite-architecture needs at least a command")
390
391     mode = args[1].lower()
392
393     if mode == 'list':
394         __suite_architecture_list(d, args)
395     elif mode == 'list-arch':
396         __suite_architecture_listarch(d, args)
397     elif mode == 'list-suite':
398         __suite_architecture_listsuite(d, args)
399     elif mode == 'add':
400         __suite_architecture_add(d, args)
401     elif mode == 'rm':
402         __suite_architecture_rm(d, args)
403     else:
404         die("E: suite-architecture command unknown")
405
406 dispatch['suite-architecture'] = suite_architecture
407 dispatch['s-a'] = suite_architecture
408
409 ################################################################################
410
411 def archive_list():
412     session = DBConn().session()
413     for archive in session.query(Archive).order_by(Archive.archive_name):
414         print "{0} path={1} description={2} tainted={3}".format(archive.archive_name, archive.path, archive.description, archive.tainted)
415
416 def archive_add(args):
417     (name, path, description) = args[0:3]
418
419     attributes = dict(
420         archive_name=name,
421         path=path,
422         description=description,
423         )
424
425     for option in args[3:]:
426         (key, value) = option.split('=')
427         attributes[key] = value
428
429     session = DBConn().session()
430
431     archive = Archive()
432     for key, value in attributes.iteritems():
433         setattr(archive, key, value)
434
435     session.add(archive)
436     session.flush()
437
438     if dryrun:
439         session.rollback()
440     else:
441         session.commit()
442
443 def archive_rm(name):
444     session = DBConn().session()
445     archive = session.query(Archive).filter_by(archive_name=name).one()
446     session.delete(archive)
447     session.flush()
448
449     if dryrun:
450         session.rollback()
451     else:
452         session.commit()
453
454 def archive_rename(oldname, newname):
455     session = DBConn().session()
456     archive = get_archive(oldname, session)
457     archive.archive_name = newname
458     session.flush()
459
460     if dryrun:
461         session.rollback()
462     else:
463         session.commit()
464
465 def archive(command):
466     mode = command[1]
467     if mode == 'list':
468         archive_list()
469     elif mode == 'rename':
470         archive_rename(command[2], command[3])
471     elif mode == 'add':
472         archive_add(command[2:])
473     elif mode == 'rm':
474         archive_rm(command[2])
475     else:
476         die("E: archive command unknown")
477
478 dispatch['archive'] = archive
479
480 ################################################################################
481
482 def __version_check_list(d):
483     session = d.session()
484     for s in session.query(Suite).order_by(Suite.suite_name):
485         __version_check_list_suite(d, s.suite_name)
486
487 def __version_check_list_suite(d, suite_name):
488     vcs = get_version_checks(suite_name)
489     for vc in vcs:
490         print "%s %s %s" % (suite_name, vc.check, vc.reference.suite_name)
491
492 def __version_check_add(d, suite_name, check, reference_name):
493     suite = get_suite(suite_name)
494     if not suite:
495         die("E: Could not find suite %s." % (suite_name))
496     reference = get_suite(reference_name)
497     if not reference:
498         die("E: Could not find reference suite %s." % (reference_name))
499
500     session = d.session()
501     vc = VersionCheck()
502     vc.suite = suite
503     vc.check = check
504     vc.reference = reference
505     session.add(vc)
506     session.commit()
507
508 def __version_check_rm(d, suite_name, check, reference_name):
509     suite = get_suite(suite_name)
510     if not suite:
511         die("E: Could not find suite %s." % (suite_name))
512     reference = get_suite(reference_name)
513     if not reference:
514         die("E: Could not find reference suite %s." % (reference_name))
515
516     session = d.session()
517     try:
518       vc = session.query(VersionCheck).filter_by(suite=suite, check=check, reference=reference).one()
519       session.delete(vc)
520       session.commit()
521     except NoResultFound:
522       print "W: version-check not found."
523
524 def version_check(command):
525     args = [str(x) for x in command]
526     Cnf = utils.get_conf()
527     d = DBConn()
528
529     die_arglen(args, 2, "E: version-check needs at least a command")
530     mode = args[1].lower()
531
532     if mode == 'list':
533         __version_check_list(d)
534     elif mode == 'list-suite':
535         if len(args) != 3:
536             die("E: version-check list-suite needs a single parameter")
537         __version_check_list_suite(d, args[2])
538     elif mode == 'add':
539         if len(args) != 5:
540             die("E: version-check add needs three parameters")
541         __version_check_add(d, args[2], args[3], args[4])
542     elif mode == 'rm':
543         if len(args) != 5:
544             die("E: version-check rm needs three parameters")
545         __version_check_rm(d, args[2], args[3], args[4])
546     else:
547         die("E: version-check command unknown")
548
549 dispatch['version-check'] = version_check
550 dispatch['v-c'] = version_check
551
552 ################################################################################
553
554 def show_config(command):
555     args = [str(x) for x in command]
556     cnf = utils.get_conf()
557
558     die_arglen(args, 2, "E: config needs at least a command")
559
560     mode = args[1].lower()
561
562     if mode == 'db':
563         connstr = ""
564         if cnf.has_key("DB::Service"):
565             # Service mode
566             connstr = "postgresql://service=%s" % cnf["DB::Service"]
567         elif cnf.has_key("DB::Host"):
568             # TCP/IP
569             connstr = "postgres://%s" % cnf["DB::Host"]
570             if cnf.has_key("DB::Port") and cnf["DB::Port"] != "-1":
571                 connstr += ":%s" % cnf["DB::Port"]
572             connstr += "/%s" % cnf["DB::Name"]
573         else:
574             # Unix Socket
575             connstr = "postgres:///%s" % cnf["DB::Name"]
576             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
577                 connstr += "?port=%s" % cnf["DB::Port"]
578         print connstr
579     elif mode == 'db-shell':
580         e = []
581         if cnf.has_key("DB::Service"):
582             e.append('PGSERVICE')
583             print "PGSERVICE=%s" % cnf["DB::Service"]
584         if cnf.has_key("DB::Name"):
585             e.append('PGDATABASE')
586             print "PGDATABASE=%s" % cnf["DB::Name"]
587         if cnf.has_key("DB::Host"):
588             print "PGHOST=%s" % cnf["DB::Host"]
589             e.append('PGHOST')
590         if cnf.has_key("DB::Port") and cnf["DB::Port"] != "-1":
591             print "PGPORT=%s" % cnf["DB::Port"]
592             e.append('PGPORT')
593         print "export " + " ".join(e)
594     elif mode == 'get':
595         print cnf.get(args[2])
596     else:
597         session = DBConn().session()
598         try:
599             o = session.query(DBConfig).filter_by(name = mode).one()
600             print o.value
601         except NoResultFound:
602             print "W: option '%s' not set" % mode
603
604 dispatch['config'] = show_config
605 dispatch['c'] = show_config
606
607 ################################################################################
608
609 def show_keyring(command):
610     args = [str(x) for x in command]
611     cnf = utils.get_conf()
612
613     die_arglen(args, 2, "E: keyring needs at least a command")
614
615     mode = args[1].lower()
616
617     d = DBConn()
618
619     q = d.session().query(Keyring).filter(Keyring.active == True)
620
621     if mode == 'list-all':
622         pass
623     elif mode == 'list-binary':
624         q = q.join(Keyring.acl).filter(ACL.allow_source == False)
625     elif mode == 'list-source':
626         q = q.join(Keyring.acl).filter(ACL.allow_source == True)
627     else:
628         die("E: keyring command unknown")
629
630     for k in q.all():
631         print k.keyring_name
632
633 def keyring_add_buildd(command):
634     name = command[2]
635     arch_names = command[3:]
636
637     session = DBConn().session()
638     arches = session.query(Architecture).filter(Architecture.arch_string.in_(arch_names))
639
640     acl = ACL()
641     acl.name = 'buildd-{0}'.format('+'.join(arch_names))
642     acl.architectures.update(arches)
643     acl.allow_new = True
644     acl.allow_binary = True
645     acl.allow_binary_only = True
646     acl.allow_hijack = True
647     session.add(acl)
648
649     k = Keyring()
650     k.keyring_name = name
651     k.acl = acl
652     k.priority = 10
653     session.add(k)
654
655     session.commit()
656
657 def keyring(command):
658     if command[1].startswith('list-'):
659         show_keyring(command)
660     elif command[1] == 'add-buildd':
661         keyring_add_buildd(command)
662     else:
663         die("E: keyring command unknown")
664
665 dispatch['keyring'] = keyring
666 dispatch['k'] = keyring
667
668 ################################################################################
669
670 def main():
671     """Perform administrative work on the dak database"""
672     global dryrun
673     Cnf = utils.get_conf()
674     arguments = [('h', "help", "Admin::Options::Help"),
675                  ('n', "dry-run", "Admin::Options::Dry-Run")]
676     for i in [ "help", "dry-run" ]:
677         if not Cnf.has_key("Admin::Options::%s" % (i)):
678             Cnf["Admin::Options::%s" % (i)] = ""
679
680     arguments = apt_pkg.parse_commandline(Cnf, arguments, sys.argv)
681
682     options = Cnf.subtree("Admin::Options")
683     if options["Help"] or len(arguments) < 1:
684         usage()
685     if options["Dry-Run"]:
686         dryrun = True
687
688     subcommand = str(arguments[0])
689
690     if subcommand in dispatch.keys():
691         dispatch[subcommand](arguments)
692     else:
693         die("E: Unknown command")
694
695 ################################################################################
696
697 if __name__ == '__main__':
698     main()