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