3 """Configure dak parameters in the database"""
4 # Copyright (C) 2009 Mark Hymers <mhy@debian.org>
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.
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.
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
20 ################################################################################
28 from daklib import utils
29 from daklib.dbconn import *
30 from sqlalchemy.orm.exc import NoResultFound
32 ################################################################################
37 ################################################################################
39 print >> sys.stderr, msg
41 def die(msg, exit_code=1):
42 print >> sys.stderr, msg
45 def die_arglen(args, args_needed, msg):
46 if len(args) < args_needed:
49 def usage(exit_code=0):
50 """Perform administrative work on the dak database."""
52 print """Usage: dak admin COMMAND
53 Perform administrative work on the dak database.
55 -h, --help show this help and exit.
56 -n, --dry-run don't do anything, just show what would have been done
57 (only applies to add or rm operations).
59 Commands can use a long or abbreviated form:
63 c db-shell show db config in a usable form for psql
64 c NAME show option NAME as set in configuration table
67 k list-all list all keyrings
68 k list-binary list all keyrings with a NULL source acl
69 k list-source list all keyrings with a non NULL source acl
70 k add-buildd NAME ARCH... add buildd keyring with upload permission
71 for the given architectures
74 a list show a list of architectures
75 a rm ARCH remove an architecture (will only work if
76 no longer linked to any suites)
77 a add ARCH DESCRIPTION [SUITELIST]
78 add architecture ARCH with DESCRIPTION.
79 If SUITELIST is given, add to each of the
80 suites at the same time
83 component list show a list of components
84 component rm COMPONENT remove a component (will only work if
86 component add NAME DESCRIPTION ORDERING
87 add component NAME with DESCRIPTION.
91 s list [--print-archive]
93 s show SUITE show config details for a suite
94 s add SUITE VERSION [ label=LABEL ] [ description=DESCRIPTION ]
95 [ origin=ORIGIN ] [ codename=CODENAME ]
96 [ signingkey=SIGNINGKEY ] [ archive=ARCHIVE ]
97 add suite SUITE, version VERSION.
98 label, description, origin, codename
99 and signingkey are optional.
101 s add-all-arches SUITE VERSION... as "s add" but adds suite-architecture
102 relationships for all architectures
104 suite-architecture / s-a:
105 s-a list show the architectures for all suites
106 s-a list-suite ARCH show the suites an ARCH is in
107 s-a list-arch SUITE show the architectures in a SUITE
108 s-a add SUITE ARCH add ARCH to suite
109 s-a rm SUITE ARCH remove ARCH from suite (will only work if
110 no packages remain for the arch in the suite)
112 suite-component / s-c:
113 s-c list show the architectures for all suites
114 s-c list-suite COMPONENT
115 show the suites a COMPONENT is in
116 s-c list-component SUITE
117 show the components in a SUITE
118 s-c add SUITE COMPONENT
119 add COMPONENT to suite
120 s-c rm SUITE COMPONENT remove component from suite (will only work if
121 no packages remain for the component in the suite)
124 archive list list all archives
125 archive add NAME ROOT DESCRIPTION [primary-mirror=MIRROR] [tainted=1]
126 add archive NAME with path ROOT,
127 primary mirror MIRROR.
128 archive rm NAME remove archive NAME (will only work if there are
129 no files and no suites in the archive)
130 archive rename OLD NEW rename archive OLD to NEW
133 v-c list show version checks for all suites
134 v-c list-suite SUITE show version checks for suite SUITE
135 v-c add SUITE CHECK REFERENCE add a version check for suite SUITE
136 v-c rm SUITE CHECK REFERENCE remove a version check
138 CHECK is one of Enhances, MustBeNewerThan, MustBeOlderThan
139 REFERENCE is another suite name
142 change-component SUITE COMPONENT source SOURCE...
143 change-component SUITE COMPONENT binary BINARY...
144 Move source or binary packages to a different component by copying
145 associated files and changing the overrides.
149 ################################################################################
151 def __architecture_list(d, args):
152 q = d.session().query(Architecture).order_by(Architecture.arch_string)
154 # HACK: We should get rid of source from the arch table
155 if j.arch_string == 'source': continue
159 def __architecture_add(d, args):
160 die_arglen(args, 4, "E: adding an architecture requires a name and a description")
161 print "Adding architecture %s" % args[2]
162 suites = [str(x) for x in args[4:]]
164 print "Adding to suites %s" % ", ".join(suites)
169 a.arch_string = str(args[2]).lower()
170 a.description = str(args[3])
173 su = get_suite(sn, s)
177 warn("W: Cannot find suite %s" % su)
179 except IntegrityError as e:
180 die("E: Integrity error adding architecture %s (it probably already exists)" % args[2])
181 except SQLAlchemyError as e:
182 die("E: Error adding architecture %s (%s)" % (args[2], e))
183 print "Architecture %s added" % (args[2])
185 def __architecture_rm(d, args):
186 die_arglen(args, 3, "E: removing an architecture requires at least a name")
187 print "Removing architecture %s" % args[2]
191 a = get_architecture(args[2].lower(), s)
193 die("E: Cannot find architecture %s" % args[2])
196 except IntegrityError as e:
197 die("E: Integrity error removing architecture %s (suite-arch entries probably still exist)" % args[2])
198 except SQLAlchemyError as e:
199 die("E: Error removing architecture %s (%s)" % (args[2], e))
200 print "Architecture %s removed" % args[2]
202 def architecture(command):
203 args = [str(x) for x in command]
204 Cnf = utils.get_conf()
207 die_arglen(args, 2, "E: architecture needs at least a command")
209 mode = args[1].lower()
211 __architecture_list(d, args)
213 __architecture_add(d, args)
215 __architecture_rm(d, args)
217 die("E: architecture command unknown")
219 dispatch['architecture'] = architecture
220 dispatch['a'] = architecture
222 ################################################################################
224 def component_list():
225 session = DBConn().session()
226 for component in session.query(Component).order_by(Component.component_name):
227 print "{0} ordering={1}".format(component.component_name, component.ordering)
229 def component_add(args):
230 (name, description, ordering) = args[0:3]
234 description=description,
238 for option in args[3:]:
239 (key, value) = option.split('=')
240 attributes[key] = value
242 session = DBConn().session()
244 component = Component()
245 for key, value in attributes.iteritems():
246 setattr(component, key, value)
248 session.add(component)
256 def component_rm(name):
257 session = DBConn().session()
258 component = get_component(name, session)
259 session.delete(component)
267 def component_rename(oldname, newname):
268 session = DBConn().session()
269 component = get_component(oldname, session)
270 component.component_name = newname
278 def component(command):
282 elif mode == 'rename':
283 component_rename(command[2], command[3])
285 component_add(command[2:])
287 component_rm(command[2])
289 die("E: component command unknown")
291 dispatch['component'] = component
293 ################################################################################
295 def __suite_list(d, args):
297 for j in s.query(Suite).join(Suite.archive).order_by(Archive.archive_name, Suite.suite_name).all():
298 if len(args) > 2 and args[2] == "--print-archive":
299 print "{0} {1}".format(j.archive.archive_name, j.suite_name)
301 print "{0}".format(j.suite_name)
303 def __suite_show(d, args):
305 die("E: showing an suite entry requires a suite")
308 su = get_suite(args[2].lower())
310 die("E: can't find suite entry for %s" % (args[2].lower()))
314 def __suite_add(d, args, addallarches=False):
315 die_arglen(args, 4, "E: adding a suite requires at least a name and a version")
316 suite_name = args[2].lower()
320 def get_field(field):
322 if varval.startswith(field + '='):
323 return varval.split('=')[1]
326 print "Adding suite %s" % suite_name
331 suite.suite_name = suite_name
332 suite.overridecodename = suite_name
333 suite.version = version
334 suite.label = get_field('label')
335 suite.description = get_field('description')
336 suite.origin = get_field('origin')
337 suite.codename = get_field('codename')
338 signingkey = get_field('signingkey')
339 if signingkey is not None:
340 suite.signingkeys = [signingkey.upper()]
341 archive_name = get_field('archive')
342 if archive_name is not None:
343 suite.archive = get_archive(archive_name, s)
345 suite.archive = s.query(Archive).filter(~Archive.archive_name.in_(['build-queues', 'new', 'policy'])).one()
346 suite.srcformats = s.query(SrcFormat).all()
349 except IntegrityError as e:
350 die("E: Integrity error adding suite %s (it probably already exists)" % suite_name)
351 except SQLAlchemyError as e:
352 die("E: Error adding suite %s (%s)" % (suite_name, e))
353 print "Suite %s added" % (suite_name)
357 q = s.query(Architecture).order_by(Architecture.arch_string)
359 suite.architectures.append(arch)
360 arches.append(arch.arch_string)
362 print "Architectures %s added to %s" % (','.join(arches), suite_name)
366 def __suite_rm(d, args):
367 die_arglen(args, 3, "E: removing a suite requires at least a name")
369 print "Removing suite {0}".format(name)
373 su = get_suite(name.lower())
375 die("E: Cannot find suite {0}".format(name))
378 except IntegrityError as e:
379 die("E: Integrity error removing suite {0} (suite-arch entries probably still exist)".format(name))
380 except SQLAlchemyError as e:
381 die("E: Error removing suite {0} ({1})".format(name, e))
382 print "Suite {0} removed".format(name)
385 args = [str(x) for x in command]
386 Cnf = utils.get_conf()
389 die_arglen(args, 2, "E: suite needs at least a command")
391 mode = args[1].lower()
394 __suite_list(d, args)
396 __suite_show(d, args)
400 __suite_add(d, args, False)
401 elif mode == 'add-all-arches':
402 __suite_add(d, args, True)
404 die("E: suite command unknown")
406 dispatch['suite'] = suite
407 dispatch['s'] = suite
409 ################################################################################
411 def __suite_architecture_list(d, args):
413 for j in s.query(Suite).order_by(Suite.suite_name):
414 architectures = j.get_architectures(skipsrc = True, skipall = True)
415 print j.suite_name + ': ' + \
416 ', '.join([a.arch_string for a in architectures])
418 def __suite_architecture_listarch(d, args):
419 die_arglen(args, 3, "E: suite-architecture list-arch requires a suite")
420 suite = get_suite(args[2].lower(), d.session())
422 die('E: suite %s is invalid' % args[2].lower())
423 a = suite.get_architectures(skipsrc = True, skipall = True)
428 def __suite_architecture_listsuite(d, args):
429 die_arglen(args, 3, "E: suite-architecture list-suite requires an arch")
430 architecture = get_architecture(args[2].lower(), d.session())
431 if architecture is None:
432 die("E: architecture %s is invalid" % args[2].lower())
433 for j in architecture.suites:
437 def __suite_architecture_add(d, args):
439 die("E: adding a suite-architecture entry requires a suite and arch")
443 suite = get_suite(args[2].lower(), s)
444 if suite is None: die("E: Can't find suite %s" % args[2].lower())
446 for arch_name in args[3:]:
447 arch = get_architecture(arch_name.lower(), s)
448 if arch is None: die("E: Can't find architecture %s" % args[3].lower())
451 suite.architectures.append(arch)
453 except IntegrityError as e:
454 die("E: Can't add suite-architecture entry (%s, %s) - probably already exists" % (args[2].lower(), arch_name))
455 except SQLAlchemyError as e:
456 die("E: Can't add suite-architecture entry (%s, %s) - %s" % (args[2].lower(), arch_name, e))
458 print "Added suite-architecture entry for %s, %s" % (args[2].lower(), arch_name)
465 def __suite_architecture_rm(d, args):
467 die("E: removing an suite-architecture entry requires a suite and arch")
472 suite_name = args[2].lower()
473 suite = get_suite(suite_name, s)
475 die('E: no such suite %s' % suite_name)
476 arch_string = args[3].lower()
477 architecture = get_architecture(arch_string, s)
478 if architecture not in suite.architectures:
479 die("E: architecture %s not found in suite %s" % (arch_string, suite_name))
480 suite.architectures.remove(architecture)
482 except IntegrityError as e:
483 die("E: Can't remove suite-architecture entry (%s, %s) - it's probably referenced" % (args[2].lower(), args[3].lower()))
484 except SQLAlchemyError as e:
485 die("E: Can't remove suite-architecture entry (%s, %s) - %s" % (args[2].lower(), args[3].lower(), e))
487 print "Removed suite-architecture entry for %s, %s" % (args[2].lower(), args[3].lower())
490 def suite_architecture(command):
491 args = [str(x) for x in command]
492 Cnf = utils.get_conf()
495 die_arglen(args, 2, "E: suite-architecture needs at least a command")
497 mode = args[1].lower()
500 __suite_architecture_list(d, args)
501 elif mode == 'list-arch':
502 __suite_architecture_listarch(d, args)
503 elif mode == 'list-suite':
504 __suite_architecture_listsuite(d, args)
506 __suite_architecture_add(d, args)
508 __suite_architecture_rm(d, args)
510 die("E: suite-architecture command unknown")
512 dispatch['suite-architecture'] = suite_architecture
513 dispatch['s-a'] = suite_architecture
515 ################################################################################
517 def __suite_component_list(d, args):
519 for j in s.query(Suite).order_by(Suite.suite_name):
520 components = j.components
521 print j.suite_name + ': ' + \
522 ', '.join([c.component_name for c in components])
525 def __suite_component_listcomponent(d, args):
526 die_arglen(args, 3, "E: suite-component list-component requires a suite")
527 suite = get_suite(args[2].lower(), d.session())
529 die('E: suite %s is invalid' % args[2].lower())
530 for c in suite.components:
531 print c.component_name
534 def __suite_component_listsuite(d, args):
535 die_arglen(args, 3, "E: suite-component list-suite requires an component")
536 component = get_component(args[2].lower(), d.session())
537 if component is None:
538 die("E: component %s is invalid" % args[2].lower())
539 for s in component.suites:
543 def __suite_component_add(d, args):
545 die("E: adding a suite-component entry requires a suite and component")
549 suite = get_suite(args[2].lower(), s)
550 if suite is None: die("E: Can't find suite %s" % args[2].lower())
552 for component_name in args[3:]:
553 component = get_component(component_name.lower(), s)
554 if component is None: die("E: Can't find component %s" % args[3].lower())
557 suite.components.append(component)
559 except IntegrityError as e:
560 die("E: Can't add suite-component entry (%s, %s) - probably already exists" % (args[2].lower(), component_name))
561 except SQLAlchemyError as e:
562 die("E: Can't add suite-component entry (%s, %s) - %s" % (args[2].lower(), component_name, e))
564 print "Added suite-component entry for %s, %s" % (args[2].lower(), component_name)
570 def __suite_component_rm(d, args):
572 die("E: removing an suite-component entry requires a suite and component")
577 suite_name = args[2].lower()
578 suite = get_suite(suite_name, s)
580 die('E: no such suite %s' % suite_name)
581 component_string = args[3].lower()
582 component = get_component(arch_string, s)
583 if component not in suite.components:
584 die("E: component %s not found in suite %s" % (component_string, suite_name))
585 suite.components.remove(component)
587 except IntegrityError as e:
588 die("E: Can't remove suite-component entry (%s, %s) - it's probably referenced" % (args[2].lower(), args[3].lower()))
589 except SQLAlchemyError as e:
590 die("E: Can't remove suite-component entry (%s, %s) - %s" % (args[2].lower(), args[3].lower(), e))
592 print "Removed suite-component entry for %s, %s" % (args[2].lower(), args[3].lower())
595 def suite_component(command):
596 args = [str(x) for x in command]
597 Cnf = utils.get_conf()
600 die_arglen(args, 2, "E: suite-component needs at least a command")
602 mode = args[1].lower()
605 __suite_component_list(d, args)
606 elif mode == 'list-component':
607 __suite_component_listcomponent(d, args)
608 elif mode == 'list-suite':
609 __suite_component_listsuite(d, args)
611 __suite_component_add(d, args)
613 # __suite_architecture_rm(d, args)
615 die("E: suite-component command unknown")
617 dispatch['suite-component'] = suite_component
618 dispatch['s-c'] = suite_component
620 ################################################################################
623 session = DBConn().session()
624 for archive in session.query(Archive).order_by(Archive.archive_name):
625 print "{0} path={1} description={2} tainted={3}".format(archive.archive_name, archive.path, archive.description, archive.tainted)
627 def archive_add(args):
628 (name, path, description) = args[0:3]
633 description=description,
636 for option in args[3:]:
637 (key, value) = option.split('=')
638 attributes[key] = value
640 session = DBConn().session()
643 for key, value in attributes.iteritems():
644 setattr(archive, key, value)
654 def archive_rm(name):
655 session = DBConn().session()
656 archive = get_archive(name, session)
657 session.delete(archive)
665 def archive_rename(oldname, newname):
666 session = DBConn().session()
667 archive = get_archive(oldname, session)
668 archive.archive_name = newname
676 def archive(command):
680 elif mode == 'rename':
681 archive_rename(command[2], command[3])
683 archive_add(command[2:])
685 archive_rm(command[2])
687 die("E: archive command unknown")
689 dispatch['archive'] = archive
691 ################################################################################
693 def __version_check_list(d):
694 session = d.session()
695 for s in session.query(Suite).order_by(Suite.suite_name):
696 __version_check_list_suite(d, s.suite_name)
698 def __version_check_list_suite(d, suite_name):
699 vcs = get_version_checks(suite_name)
701 print "%s %s %s" % (suite_name, vc.check, vc.reference.suite_name)
703 def __version_check_add(d, suite_name, check, reference_name):
704 suite = get_suite(suite_name)
706 die("E: Could not find suite %s." % (suite_name))
707 reference = get_suite(reference_name)
709 die("E: Could not find reference suite %s." % (reference_name))
711 session = d.session()
715 vc.reference = reference
719 def __version_check_rm(d, suite_name, check, reference_name):
720 suite = get_suite(suite_name)
722 die("E: Could not find suite %s." % (suite_name))
723 reference = get_suite(reference_name)
725 die("E: Could not find reference suite %s." % (reference_name))
727 session = d.session()
729 vc = session.query(VersionCheck).filter_by(suite=suite, check=check, reference=reference).one()
732 except NoResultFound:
733 print "W: version-check not found."
735 def version_check(command):
736 args = [str(x) for x in command]
737 Cnf = utils.get_conf()
740 die_arglen(args, 2, "E: version-check needs at least a command")
741 mode = args[1].lower()
744 __version_check_list(d)
745 elif mode == 'list-suite':
747 die("E: version-check list-suite needs a single parameter")
748 __version_check_list_suite(d, args[2])
751 die("E: version-check add needs three parameters")
752 __version_check_add(d, args[2], args[3], args[4])
755 die("E: version-check rm needs three parameters")
756 __version_check_rm(d, args[2], args[3], args[4])
758 die("E: version-check command unknown")
760 dispatch['version-check'] = version_check
761 dispatch['v-c'] = version_check
763 ################################################################################
765 def show_config(command):
766 args = [str(x) for x in command]
767 cnf = utils.get_conf()
769 die_arglen(args, 2, "E: config needs at least a command")
771 mode = args[1].lower()
775 if cnf.has_key("DB::Service"):
777 connstr = "postgresql://service=%s" % cnf["DB::Service"]
778 elif cnf.has_key("DB::Host"):
780 connstr = "postgres://%s" % cnf["DB::Host"]
781 if cnf.has_key("DB::Port") and cnf["DB::Port"] != "-1":
782 connstr += ":%s" % cnf["DB::Port"]
783 connstr += "/%s" % cnf["DB::Name"]
786 connstr = "postgres:///%s" % cnf["DB::Name"]
787 if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
788 connstr += "?port=%s" % cnf["DB::Port"]
790 elif mode == 'db-shell':
792 if cnf.has_key("DB::Service"):
793 e.append('PGSERVICE')
794 print "PGSERVICE=%s" % cnf["DB::Service"]
795 if cnf.has_key("DB::Name"):
796 e.append('PGDATABASE')
797 print "PGDATABASE=%s" % cnf["DB::Name"]
798 if cnf.has_key("DB::Host"):
799 print "PGHOST=%s" % cnf["DB::Host"]
801 if cnf.has_key("DB::Port") and cnf["DB::Port"] != "-1":
802 print "PGPORT=%s" % cnf["DB::Port"]
804 print "export " + " ".join(e)
806 print cnf.get(args[2])
808 session = DBConn().session()
810 o = session.query(DBConfig).filter_by(name = mode).one()
812 except NoResultFound:
813 print "W: option '%s' not set" % mode
815 dispatch['config'] = show_config
816 dispatch['c'] = show_config
818 ################################################################################
820 def show_keyring(command):
821 args = [str(x) for x in command]
822 cnf = utils.get_conf()
824 die_arglen(args, 2, "E: keyring needs at least a command")
826 mode = args[1].lower()
830 q = d.session().query(Keyring).filter(Keyring.active == True)
832 if mode == 'list-all':
834 elif mode == 'list-binary':
835 q = q.join(Keyring.acl).filter(ACL.allow_source == False)
836 elif mode == 'list-source':
837 q = q.join(Keyring.acl).filter(ACL.allow_source == True)
839 die("E: keyring command unknown")
844 def keyring_add_buildd(command):
846 arch_names = command[3:]
848 session = DBConn().session()
849 arches = session.query(Architecture).filter(Architecture.arch_string.in_(arch_names))
852 acl.name = 'buildd-{0}'.format('+'.join(arch_names))
853 acl.architectures.update(arches)
855 acl.allow_binary = True
856 acl.allow_binary_only = True
857 acl.allow_hijack = True
861 k.keyring_name = name
868 def keyring(command):
869 if command[1].startswith('list-'):
870 show_keyring(command)
871 elif command[1] == 'add-buildd':
872 keyring_add_buildd(command)
874 die("E: keyring command unknown")
876 dispatch['keyring'] = keyring
877 dispatch['k'] = keyring
879 ################################################################################
881 def change_component_source(transaction, suite, component, source_names):
882 session = transaction.session
884 overrides = session.query(Override).filter(Override.package.in_(source_names)).filter_by(suite=suite).join(OverrideType).filter_by(overridetype='dsc')
885 for override in overrides:
886 print "Changing override for {0}".format(override.package)
887 override.component = component
890 sources = session.query(DBSource).filter(DBSource.source.in_(source_names)).filter(DBSource.suites.contains(suite))
891 for source in sources:
892 print "Copying {0}={1}".format(source.source, source.version)
893 transaction.copy_source(source, suite, component)
895 def change_component_binary(transaction, suite, component, binary_names):
896 session = transaction.session
898 overrides = session.query(Override).filter(Override.package.in_(binary_names)).filter_by(suite=suite).join(OverrideType).filter(OverrideType.overridetype.in_(['deb', 'udeb']))
899 for override in overrides:
900 print "Changing override for {0}".format(override.package)
901 override.component = component
904 binaries = session.query(DBBinary).filter(DBBinary.package.in_(binary_names)).filter(DBBinary.suites.contains(suite))
905 for binary in binaries:
906 print "Copying {0}={1} [{2}]".format(binary.package, binary.version, binary.architecture.arch_string)
907 transaction.copy_binary(binary, suite, component)
910 def change_component(args):
911 with daklib.archive.ArchiveTransaction() as transaction:
912 session = transaction.session
914 suite = session.query(Suite).filter_by(suite_name=args[1]).one()
915 component = session.query(Component).filter_by(component_name=args[2]).one()
917 if args[3] == 'source':
918 change_component_source(transaction, suite, component, args[4:])
919 elif args[3] == 'binary':
920 change_component_binary(transaction, suite, component, args[4:])
922 raise Exception("Can only move source or binary, not {0}".format(args[3]))
926 dispatch['change-component'] = change_component
928 ################################################################################
931 """Perform administrative work on the dak database"""
933 Cnf = utils.get_conf()
934 arguments = [('h', "help", "Admin::Options::Help"),
935 ('n', "dry-run", "Admin::Options::Dry-Run")]
936 for i in [ "help", "dry-run" ]:
937 if not Cnf.has_key("Admin::Options::%s" % (i)):
938 Cnf["Admin::Options::%s" % (i)] = ""
940 arguments = apt_pkg.parse_commandline(Cnf, arguments, sys.argv)
942 options = Cnf.subtree("Admin::Options")
943 if options["Help"] or len(arguments) < 1:
945 if options["Dry-Run"]:
948 subcommand = str(arguments[0])
950 if subcommand in dispatch.keys():
951 dispatch[subcommand](arguments)
953 die("E: Unknown command")
955 ################################################################################
957 if __name__ == '__main__':