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