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