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