Source code for dak.admin

#! /usr/bin/env python3

"""Configure dak parameters in the database"""
# Copyright (C) 2009  Mark Hymers <mhy@debian.org>

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

################################################################################

import collections
import json
import sys
from collections.abc import Callable, Sequence
from dataclasses import dataclass
from typing import Any, NoReturn, Optional

import apt_pkg

import daklib.archive
import daklib.gpg

from daklib import utils
from daklib.dbconn import *
from sqlalchemy.orm.exc import NoResultFound

################################################################################

dispatch = {}
dryrun = False

################################################################################


[docs]def warn(msg: str) -> None: print(msg, file=sys.stderr)
[docs]def die(msg: str, exit_code: int = 1) -> NoReturn: print(msg, file=sys.stderr) sys.exit(exit_code)
[docs]def die_arglen(args: Sequence, args_needed: int, msg: str) -> None: if len(args) < args_needed: die(msg)
[docs]def get_suite_or_die( suite_name: str, session=None, error_message="E: Invalid/unknown suite %(suite_name)s", ) -> Suite: suite = get_suite(suite_name.lower(), session=session) if suite is None: die(error_message % {'suite_name': suite_name}) return suite
[docs]def usage(exit_code: int = 0) -> NoReturn: """Perform administrative work on the dak database.""" print("""Usage: dak admin COMMAND Perform administrative work on the dak database. -h, --help show this help and exit. -n, --dry-run don't do anything, just show what would have been done (only applies to add or rm operations). Commands can use a long or abbreviated form: config / c: c db show db config c db-shell show db config in a usable form for psql c NAME show option NAME as set in configuration table keyring / k: k list-all list all keyrings k list-binary list all keyrings with a NULL source acl k list-source list all keyrings with a non NULL source acl k add-buildd NAME ARCH... add buildd keyring with upload permission for the given architectures architecture / a: a list show a list of architectures a rm ARCH remove an architecture (will only work if no longer linked to any suites) a add ARCH DESCRIPTION [SUITELIST] add architecture ARCH with DESCRIPTION. If SUITELIST is given, add to each of the suites at the same time component: component list show a list of components component rm COMPONENT remove a component (will only work if empty) component add NAME DESCRIPTION ORDERING add component NAME with DESCRIPTION. Ordered at ORDERING. suite / s: s list [--print-archive] show a list of suites s show SUITE show config details for a suite s add SUITE VERSION [ label=LABEL ] [ description=DESCRIPTION ] [ origin=ORIGIN ] [ codename=CODENAME ] [ signingkey=SIGNINGKEY ] [ archive=ARCHIVE ] add suite SUITE, version VERSION. label, description, origin, codename and signingkey are optional. s rm SUITE remove a suite (will only work if empty) s add-all-arches SUITE VERSION... as "s add" but adds suite-architecture relationships for all architectures s add-build-queue SUITE BUILD-QUEUE BUILD-QUEUE-CODENAME BUILD-QUEUE-ARCHIVE add a build queue for an existing suite suite-architecture / s-a: s-a list show the architectures for all suites s-a list-suite ARCH show the suites an ARCH is in s-a list-arch SUITE show the architectures in a SUITE s-a add SUITE ARCH add ARCH to suite s-a rm SUITE ARCH remove ARCH from suite (will only work if no packages remain for the arch in the suite) suite-component / s-c: s-c list show the architectures for all suites s-c list-suite COMPONENT show the suites a COMPONENT is in s-c list-component SUITE show the components in a SUITE s-c add SUITE COMPONENT add COMPONENT to suite s-c rm SUITE COMPONENT remove component from suite (will only work if no packages remain for the component in the suite) suite-config / suite-cfg / s-cfg: s-cfg list show the names of the configurations s-cfg list SUITE show the configuration values for SUITE s-cfg list-json SUITE show the configuration values for SUITE in JSON format s-cfg get SUITE NAME ... show the value for NAME in SUITE (format: NAME=VALUE) s-cfg get-value SUITE NAME ... show the value for NAME in SUITE (format: VALUE) s-cfg get-json SUITE NAME ... show the value for NAME in SUITE (format: JSON object) s-cfg set SUITE NAME=VALUE ... set NAME to VALUE in SUITE s-cfg set-json SUITE s-cfg set-json SUITE FILENAME parse FILENAME (if absent or "-", then stdin) as JSON and update all configurations listed to match the value in the JSON. Uses the same format as list-json or get-json outputs. archive: archive list list all archives archive add NAME ROOT DESCRIPTION [primary-mirror=MIRROR] [tainted=1] add archive NAME with path ROOT, primary mirror MIRROR. archive rm NAME remove archive NAME (will only work if there are no files and no suites in the archive) archive rename OLD NEW rename archive OLD to NEW version-check / v-c: v-c list show version checks for all suites v-c list-suite SUITE show version checks for suite SUITE v-c add SUITE CHECK REFERENCE add a version check for suite SUITE v-c rm SUITE CHECK REFERENCE remove a version check where CHECK is one of Enhances, MustBeNewerThan, MustBeOlderThan REFERENCE is another suite name change-component: change-component SUITE COMPONENT source SOURCE... change-component SUITE COMPONENT binary BINARY... Move source or binary packages to a different component by copying associated files and changing the overrides. forget-signature FILE: forget that we saw FILE """) sys.exit(exit_code)
################################################################################
[docs]def __architecture_list(d, args) -> NoReturn: q = d.session().query(Architecture).order_by(Architecture.arch_string) for j in q.all(): # HACK: We should get rid of source from the arch table if j.arch_string == 'source': continue print(j.arch_string) sys.exit(0)
[docs]def __architecture_add(d, args): die_arglen(args, 4, "E: adding an architecture requires a name and a description") print("Adding architecture %s" % args[2]) suites = [str(x) for x in args[4:]] if len(suites) > 0: print("Adding to suites %s" % ", ".join(suites)) if not dryrun: try: s = d.session() a = Architecture() a.arch_string = str(args[2]).lower() a.description = str(args[3]) s.add(a) for sn in suites: su = get_suite(sn, s) if su is not None: a.suites.append(su) else: warn("W: Cannot find suite %s" % su) s.commit() except IntegrityError as e: die("E: Integrity error adding architecture %s (it probably already exists)" % args[2]) except SQLAlchemyError as e: die("E: Error adding architecture %s (%s)" % (args[2], e)) print("Architecture %s added" % (args[2]))
[docs]def __architecture_rm(d, args): die_arglen(args, 3, "E: removing an architecture requires at least a name") print("Removing architecture %s" % args[2]) if not dryrun: try: s = d.session() a = get_architecture(args[2].lower(), s) if a is None: die("E: Cannot find architecture %s" % args[2]) s.delete(a) s.commit() except IntegrityError as e: die("E: Integrity error removing architecture %s (suite-arch entries probably still exist)" % args[2]) except SQLAlchemyError as e: die("E: Error removing architecture %s (%s)" % (args[2], e)) print("Architecture %s removed" % args[2])
[docs]def architecture(args): Cnf = utils.get_conf() d = DBConn() die_arglen(args, 2, "E: architecture needs at least a command") mode = args[1].lower() if mode == 'list': __architecture_list(d, args) elif mode == 'add': __architecture_add(d, args) elif mode == 'rm': __architecture_rm(d, args) else: die("E: architecture command unknown")
dispatch['architecture'] = architecture dispatch['a'] = architecture ################################################################################
[docs]def component_list(): session = DBConn().session() for component in session.query(Component).order_by(Component.component_name): print("{0} ordering={1}".format(component.component_name, component.ordering))
[docs]def component_add(args): (name, description, ordering) = args[0:3] attributes = dict( component_name=name, description=description, ordering=ordering, ) for option in args[3:]: (key, value) = option.split('=') attributes[key] = value session = DBConn().session() component = Component() for key, value in attributes.items(): setattr(component, key, value) session.add(component) session.flush() if dryrun: session.rollback() else: session.commit()
[docs]def component_rm(name: str) -> None: session = DBConn().session() component = get_component(name, session) session.delete(component) session.flush() if dryrun: session.rollback() else: session.commit()
[docs]def component_rename(oldname: str, newname: str) -> None: session = DBConn().session() component = get_component(oldname, session) component.component_name = newname session.flush() if dryrun: session.rollback() else: session.commit()
[docs]def component(command): mode = command[1] if mode == 'list': component_list() elif mode == 'rename': component_rename(command[2], command[3]) elif mode == 'add': component_add(command[2:]) elif mode == 'rm': component_rm(command[2]) else: die("E: component command unknown")
dispatch['component'] = component ################################################################################
[docs]def __suite_list(d, args): s = d.session() for j in s.query(Suite).join(Suite.archive).order_by(Archive.archive_name, Suite.suite_name).all(): if len(args) > 2 and args[2] == "--print-archive": print("{0} {1}".format(j.archive.archive_name, j.suite_name)) else: print("{0}".format(j.suite_name))
[docs]def __suite_show(d, args): if len(args) < 2: die("E: showing an suite entry requires a suite") s = d.session() su = get_suite_or_die(args[2]) print(su.details())
[docs]def __suite_add(d, args, addallarches=False) -> None: die_arglen(args, 4, "E: adding a suite requires at least a name and a version") suite_name = args[2].lower() version = args[3] kvpairs = __suite_config_set_confing_args_as_dict(args[4:]) if len(version) == 0: version = None print("Adding suite %s" % suite_name) if not dryrun: try: s = d.session() suite = Suite() suite.suite_name = suite_name suite.overridecodename = None suite.version = version or None # Most configurations will be handled by # __suite_config_internal_set. However, a few are managed # manually here because __suite_config_internal_set cannot # handle them. Either because they are create-only or # because suite-add handled them different (historically) suite.codename = kvpairs.pop('codename', None) signingkey = kvpairs.pop('signingkey', None) if signingkey is not None: suite.signingkeys = [signingkey.upper()] archive_name = kvpairs.pop('archive', None) if archive_name is not None: suite.archive = get_archive(archive_name, s) else: suite.archive = s.query(Archive).filter(~Archive.archive_name.in_(['build-queues', 'new', 'policy'])).one() suite.srcformats = s.query(SrcFormat).all() __suite_config_internal_set(suite, suite_name, kvpairs, print_config_set=False) s.add(suite) s.flush() except IntegrityError as e: die("E: Integrity error adding suite %s (it probably already exists)" % suite_name) except SQLAlchemyError as e: die("E: Error adding suite %s (%s)" % (suite_name, e)) print("Suite %s added" % (suite_name)) if addallarches: arches = [] q = s.query(Architecture).order_by(Architecture.arch_string) for arch in q.all(): suite.architectures.append(arch) arches.append(arch.arch_string) print("Architectures %s added to %s" % (','.join(arches), suite_name)) s.commit()
[docs]def __suite_rm(d, args): die_arglen(args, 3, "E: removing a suite requires at least a name") name = args[2] print("Removing suite {0}".format(name)) if not dryrun: try: s = d.session() su = get_suite_or_die(name, s) s.delete(su) s.commit() except IntegrityError as e: die("E: Integrity error removing suite {0} (suite-arch entries probably still exist)".format(name)) except SQLAlchemyError as e: die("E: Error removing suite {0} ({1})".format(name, e)) print("Suite {0} removed".format(name))
[docs]def __suite_add_build_queue(d, args): session = d.session() die_arglen(args, 6, "E: Adding a build queue needs four parameters.") suite_name = args[2] build_queue_name = args[3] build_queue_codename = args[4] build_queue_archive_name = args[5] try: suite = session.query(Suite).filter_by(suite_name=suite_name).one() except NoResultFound: die("E: Unknown suite '{0}'".format(suite_name)) try: build_queue_archive = session.query(Archive).filter_by(archive_name=build_queue_archive_name).one() except NoResultFound: die("E: Unknown archive '{0}'".format(build_queue_archive_name)) # Create suite s = Suite() s.suite_name = build_queue_name s.origin = suite.origin s.label = suite.label s.description = "buildd {0} incoming".format(suite_name) s.codename = build_queue_codename s.notautomatic = suite.notautomatic s.overridesuite = suite.overridesuite or suite.suite_name s.butautomaticupgrades = suite.butautomaticupgrades s.signingkeys = suite.signingkeys s.include_long_description = False # Do not accept direct uploads to the build queue s.accept_source_uploads = False s.accept_binary_uploads = False s.archive = build_queue_archive s.architectures.extend(suite.architectures) s.components.extend(suite.components) s.srcformats.extend(suite.srcformats) session.add(s) session.flush() bq = BuildQueue() bq.queue_name = build_queue_codename bq.suite = s session.add(bq) session.flush() suite.copy_queues.append(bq) session.commit()
[docs]def suite(args): Cnf = utils.get_conf() d = DBConn() die_arglen(args, 2, "E: suite needs at least a command") mode = args[1].lower() if mode == 'list': __suite_list(d, args) elif mode == 'show': __suite_show(d, args) elif mode == 'rm': __suite_rm(d, args) elif mode == 'add': __suite_add(d, args, False) elif mode == 'add-all-arches': __suite_add(d, args, True) elif mode == 'add-build-queue': __suite_add_build_queue(d, args) else: die("E: suite command unknown")
dispatch['suite'] = suite dispatch['s'] = suite ################################################################################
[docs]def __suite_architecture_list(d, args): s = d.session() for j in s.query(Suite).order_by(Suite.suite_name): architectures = j.get_architectures(skipsrc=True, skipall=True) print(j.suite_name + ': ' + ', '.join([a.arch_string for a in architectures]))
[docs]def __suite_architecture_listarch(d, args): die_arglen(args, 3, "E: suite-architecture list-arch requires a suite") suite = get_suite_or_die(args[2], d.session()) a = suite.get_architectures(skipsrc=True, skipall=True) for j in a: print(j.arch_string)
[docs]def __suite_architecture_listsuite(d, args): die_arglen(args, 3, "E: suite-architecture list-suite requires an arch") architecture = get_architecture(args[2].lower(), d.session()) if architecture is None: die("E: architecture %s is invalid" % args[2].lower()) for j in architecture.suites: print(j.suite_name)
[docs]def __suite_architecture_add(d, args): if len(args) < 3: die("E: adding a suite-architecture entry requires a suite and arch") s = d.session() suite = get_suite_or_die(args[2], s) for arch_name in args[3:]: arch = get_architecture(arch_name.lower(), s) if arch is None: die("E: Can't find architecture %s" % args[3].lower()) try: suite.architectures.append(arch) s.flush() except IntegrityError as e: die("E: Can't add suite-architecture entry (%s, %s) - probably already exists" % (args[2].lower(), arch_name)) except SQLAlchemyError as e: die("E: Can't add suite-architecture entry (%s, %s) - %s" % (args[2].lower(), arch_name, e)) print("Added suite-architecture entry for %s, %s" % (args[2].lower(), arch_name)) if not dryrun: s.commit() s.close()
[docs]def __suite_architecture_rm(d, args): if len(args) < 3: die("E: removing an suite-architecture entry requires a suite and arch") s = d.session() if not dryrun: try: suite_name = args[2].lower() suite = get_suite_or_die(suite_name, s) arch_string = args[3].lower() architecture = get_architecture(arch_string, s) if architecture not in suite.architectures: die("E: architecture %s not found in suite %s" % (arch_string, suite_name)) suite.architectures.remove(architecture) s.commit() except IntegrityError as e: die("E: Can't remove suite-architecture entry (%s, %s) - it's probably referenced" % (args[2].lower(), args[3].lower())) except SQLAlchemyError as e: die("E: Can't remove suite-architecture entry (%s, %s) - %s" % (args[2].lower(), args[3].lower(), e)) print("Removed suite-architecture entry for %s, %s" % (args[2].lower(), args[3].lower()))
[docs]def suite_architecture(args): Cnf = utils.get_conf() d = DBConn() die_arglen(args, 2, "E: suite-architecture needs at least a command") mode = args[1].lower() if mode == 'list': __suite_architecture_list(d, args) elif mode == 'list-arch': __suite_architecture_listarch(d, args) elif mode == 'list-suite': __suite_architecture_listsuite(d, args) elif mode == 'add': __suite_architecture_add(d, args) elif mode == 'rm': __suite_architecture_rm(d, args) else: die("E: suite-architecture command unknown")
dispatch['suite-architecture'] = suite_architecture dispatch['s-a'] = suite_architecture ################################################################################
[docs]def __suite_component_list(d, args): s = d.session() for j in s.query(Suite).order_by(Suite.suite_name): components = j.components print(j.suite_name + ': ' + ', '.join([c.component_name for c in components]))
[docs]def __suite_component_listcomponent(d, args): die_arglen(args, 3, "E: suite-component list-component requires a suite") suite = get_suite_or_die(args[2], d.session()) for c in suite.components: print(c.component_name)
[docs]def __suite_component_listsuite(d, args): die_arglen(args, 3, "E: suite-component list-suite requires an component") component = get_component(args[2].lower(), d.session()) if component is None: die("E: component %s is invalid" % args[2].lower()) for s in component.suites: print(s.suite_name)
[docs]def __suite_component_add(d, args): if len(args) < 3: die("E: adding a suite-component entry requires a suite and component") s = d.session() suite = get_suite_or_die(args[2], s) for component_name in args[3:]: component = get_component(component_name.lower(), s) if component is None: die("E: Can't find component %s" % args[3].lower()) try: suite.components.append(component) s.flush() except IntegrityError as e: die("E: Can't add suite-component entry (%s, %s) - probably already exists" % (args[2].lower(), component_name)) except SQLAlchemyError as e: die("E: Can't add suite-component entry (%s, %s) - %s" % (args[2].lower(), component_name, e)) print("Added suite-component entry for %s, %s" % (args[2].lower(), component_name)) if not dryrun: s.commit() s.close()
[docs]def __suite_component_rm(d, args): if len(args) < 3: die("E: removing an suite-component entry requires a suite and component") s = d.session() if not dryrun: try: suite_name = args[2].lower() suite = get_suite_or_die(suite_name, s) component_string = args[3].lower() component = get_component(component_string, s) if component not in suite.components: die("E: component %s not found in suite %s" % (component_string, suite_name)) suite.components.remove(component) s.commit() except IntegrityError as e: die("E: Can't remove suite-component entry (%s, %s) - it's probably referenced" % (args[2].lower(), args[3].lower())) except SQLAlchemyError as e: die("E: Can't remove suite-component entry (%s, %s) - %s" % (args[2].lower(), args[3].lower(), e)) print("Removed suite-component entry for %s, %s" % (args[2].lower(), args[3].lower()))
[docs]def suite_component(args): Cnf = utils.get_conf() d = DBConn() die_arglen(args, 2, "E: suite-component needs at least a command") mode = args[1].lower() if mode == 'list': __suite_component_list(d, args) elif mode == 'list-component': __suite_component_listcomponent(d, args) elif mode == 'list-suite': __suite_component_listsuite(d, args) elif mode == 'add': __suite_component_add(d, args) # elif mode == 'rm': # __suite_architecture_rm(d, args) else: die("E: suite-component command unknown")
dispatch['suite-component'] = suite_component dispatch['s-c'] = suite_component ################################################################################ # Sentinel for detecting read-only configurations SUITE_CONFIG_READ_ONLY = object() SUITE_CONFIG_WRITABLE_ONLY_VIA_JSON = object()
[docs]@dataclass class SuiteConfigSerializer: db_name: str serialize: Callable[[Any], str] deserialize: Optional[Callable[[str], Any]]
[docs]def _serialize_suite(x): if x is None: return None return Suite.get(x).suite_name
[docs]def _deserialize_suite(x): if x is None: return None return get_suite_or_die(x).suite_id
[docs]@session_wrapper def _serialize_policy_queue(x, session=None): if x is None: return None try: policy_obj = session.query(PolicyQueue).filter_by(policy_queue_id=x).one() except NoResultFound: return None return policy_obj.queue_name
[docs]def _deserialize_policy_queue(x): if x is None: return None policy_queue = get_policy_queue(x) if policy_queue is None: raise ValueError("There is no policy queue with name %s" % x) return policy_queue.policy_queue_id
[docs]@session_wrapper def _serialize_archive(x, session=None): if x is None: return None try: archive_obj = session.query(Archive).filter_by(archive_id=x).one() except NoResultFound: return None return archive_obj.archive_name
CUSTOM_SUITE_CONFIG_SERIALIZERS = { 'archive': SuiteConfigSerializer(db_name='archive_id', serialize=_serialize_archive, deserialize=None), 'debugsuite': SuiteConfigSerializer(db_name='debugsuite_id', serialize=_serialize_suite, deserialize=_deserialize_suite), 'new_queue': SuiteConfigSerializer(db_name='new_queue_id', serialize=_serialize_policy_queue, deserialize=_deserialize_policy_queue), 'policy_queue': SuiteConfigSerializer(db_name='policy_queue_id', serialize=_serialize_policy_queue, deserialize=_deserialize_policy_queue), } ALLOWED_SUITE_CONFIGS = { 'accept_binary_uploads': utils.parse_boolean_from_user, 'accept_source_uploads': utils.parse_boolean_from_user, 'allowcsset': utils.parse_boolean_from_user, 'announce': SUITE_CONFIG_WRITABLE_ONLY_VIA_JSON, 'archive': SUITE_CONFIG_READ_ONLY, 'butautomaticupgrades': utils.parse_boolean_from_user, 'byhash': utils.parse_boolean_from_user, 'changelog': str, 'changelog_url': str, 'checksums': SUITE_CONFIG_WRITABLE_ONLY_VIA_JSON, 'close_bugs': utils.parse_boolean_from_user, 'codename': SUITE_CONFIG_READ_ONLY, 'debugsuite': str, 'description': str, 'include_long_description': utils.parse_boolean_from_user, 'indices_compression': SUITE_CONFIG_WRITABLE_ONLY_VIA_JSON, 'label': str, 'mail_whitelist': str, 'merged_pdiffs': utils.parse_boolean_from_user, 'new_queue': str, 'notautomatic': utils.parse_boolean_from_user, 'origin': str, 'overridecodename': str, 'overrideorigin': str, 'overrideprocess': utils.parse_boolean_from_user, 'overridesuite': str, 'policy_queue': str, 'priority': int, 'separate_contents_architecture_all': utils.parse_boolean_from_user, # We do not support separate Packages-all, so do not let people set it. 'separate_packages_architecture_all': SUITE_CONFIG_READ_ONLY, 'signingkeys': SUITE_CONFIG_WRITABLE_ONLY_VIA_JSON, 'suite_name': SUITE_CONFIG_READ_ONLY, 'untouchable': utils.parse_boolean_from_user, 'validtime': int, }
[docs]def _get_suite_value(suite, conf_name): serial_config = CUSTOM_SUITE_CONFIG_SERIALIZERS.get(conf_name) if serial_config is None: return getattr(suite, conf_name) db_value = getattr(suite, serial_config.db_name) return serial_config.serialize(db_value)
[docs]def _set_suite_value(suite, conf_name, new_value): serial_config = CUSTOM_SUITE_CONFIG_SERIALIZERS.get(conf_name) db_name = conf_name if serial_config is not None: assert serial_config.deserialize is not None, "Changing %s is not supported!" % conf_name new_value = serial_config.deserialize(new_value) db_name = serial_config.db_name setattr(suite, db_name, new_value)
[docs]def __suite_config_get(d, args, direct_value=False, json_format=False): die_arglen(args, 4, "E: suite-config get needs the name of a configuration") session = d.session() suite_name = args[2] suite = get_suite_or_die(suite_name, session) values = {} for arg in args[3:]: if arg not in ALLOWED_SUITE_CONFIGS: die("Unknown (or unsupported) suite configuration variable") value = _get_suite_value(suite, arg) if json_format: values[arg] = value elif direct_value: print(value) else: print("%s=%s" % (arg, value)) if json_format: print(json.dumps(values, indent=2, sort_keys=True))
[docs]def __suite_config_set(d, args): die_arglen(args, 4, "E: suite-config set needs the name of a configuration") session = d.session() suite_name = args[2] suite = get_suite_or_die(suite_name, session) args_as_kvpairs = __suite_config_set_confing_args_as_dict(args[3:]) __suite_config_internal_set(suite, suite_name, args_as_kvpairs, print_config_set=True ) if dryrun: session.rollback() print() print("This was a dryrun; changes have been rolled back") else: session.commit()
[docs]def __suite_config_set_confing_args_as_dict(args): # Use OrderedDict to preserve order (makes "dak admin suite-config set ..." # less confusing when things are processed in the input order) kvpairs = collections.OrderedDict() for arg in args: if '=' not in arg: die("Missing value for configuration %s: Use key=value format" % arg) conf_name, new_value_str = arg.split('=', 1) kvpairs[conf_name] = new_value_str return kvpairs
[docs]def __suite_config_internal_set(suite, suite_name, kvpairs, print_config_set=True): for kvpair in kvpairs.items(): conf_name, new_value_str = kvpair cli_parser = ALLOWED_SUITE_CONFIGS.get(conf_name) if cli_parser is None: die("Unknown (or unsupported) suite configuration variable") if cli_parser in (SUITE_CONFIG_READ_ONLY, SUITE_CONFIG_WRITABLE_ONLY_VIA_JSON): if cli_parser == SUITE_CONFIG_WRITABLE_ONLY_VIA_JSON: msg = "Cannot parse value for %s" \ ''' - set via echo '{"%s": <...>}' | dak suite-config set-json %s instead''' warn(msg % (conf_name, conf_name, suite_name)) die("Cannot change %s from the command line" % conf_name) try: new_value = cli_parser(new_value_str) except (RuntimeError, ValueError, TypeError) as e: warn("Could not parse new value for %s (given: %s)" % (conf_name, new_value_str)) raise e try: _set_suite_value(suite, conf_name, new_value) except (RuntimeError, ValueError, TypeError) as e: warn("Could not set new value for %s (given: %s)" % (conf_name, new_value)) raise e if print_config_set: print("%s=%s" % (conf_name, _get_suite_value(suite, conf_name)))
[docs]def __suite_config_set_json(d, args): session = d.session() suite_name = args[2] suite = get_suite_or_die(suite_name, session) filename = '-' if len(args) > 3: if len(args) > 4: warn("W: Ignoring extra argument after the json file name") filename = args[3] if filename != '-': with open(filename) as fd: update_config = json.load(fd) else: update_config = json.load(sys.stdin) if update_config is None or not isinstance(update_config, dict): die("E: suite-config set-json expects a dictionary (json object), got %s" % type(update_config)) for conf_name in sorted(update_config): new_value = update_config[conf_name] cli_parser = ALLOWED_SUITE_CONFIGS.get(conf_name) if cli_parser is None: die("Unknown (or unsupported) suite configuration variable: %s" % conf_name) if cli_parser is SUITE_CONFIG_READ_ONLY: die("Cannot change %s via JSON" % conf_name) try: _set_suite_value(suite, conf_name, new_value) except (RuntimeError, ValueError, TypeError) as e: warn("Could not set new value for %s (given: %s)" % (conf_name, new_value)) raise e print("%s=%s" % (conf_name, _get_suite_value(suite, conf_name))) if dryrun: session.rollback() print() print("This was a dryrun; changes have been rolled back") else: session.commit()
[docs]def __suite_config_list(d, args, json_format=False): suite = None session = d.session() if len(args) > 3: warn("W: Ignoring extra argument after the suite name") if len(args) == 3: suite_name = args[2] suite = get_suite_or_die(suite_name, session) else: if json_format: die("E: suite-config list-json requires a suite name!") print("Valid suite-config options manageable by this command:") print() values = {} for arg in sorted(ALLOWED_SUITE_CONFIGS): mode = 'writable' if suite is not None: value = _get_suite_value(suite, arg) if json_format: values[arg] = value else: print("%s=%s" % (arg, value)) else: converter = ALLOWED_SUITE_CONFIGS[arg] if converter is SUITE_CONFIG_READ_ONLY: mode = 'read-only' elif converter is SUITE_CONFIG_WRITABLE_ONLY_VIA_JSON: mode = 'writeable (via set-json only)' print(" * %s (%s)" % (arg, mode)) if json_format: print(json.dumps(values, indent=2, sort_keys=True))
[docs]def suite_config(args): Cnf = utils.get_conf() d = DBConn() die_arglen(args, 2, "E: suite-config needs a command") mode = args[1].lower() if mode in {'get', 'get-value', 'get-json'}: direct_value = False json_format = mode == 'get-json' if mode == 'get-value': direct_value = True if len(args) > 4: die("E: get-value must receive exactly one key to lookup") __suite_config_get(d, args, direct_value=direct_value, json_format=json_format) elif mode == 'set': __suite_config_set(d, args) elif mode == 'set-json': __suite_config_set_json(d, args) elif mode in {'list', 'list-json'}: json_format = mode == 'list-json' __suite_config_list(d, args, json_format=json_format) else: suite = get_suite(mode, d.session()) if suite is not None: warn("Did you get the order of the suite and the subcommand wrong?") warn("Syntax: dak admin %s {get,set,...} <suite>" % args[0]) die("E: suite-config command unknown")
dispatch['suite-config'] = suite_config dispatch['suite-cfg'] = suite_config dispatch['s-cfg'] = suite_config ################################################################################
[docs]def archive_list(): session = DBConn().session() for archive in session.query(Archive).order_by(Archive.archive_name): print("{0} path={1} description={2} tainted={3}".format(archive.archive_name, archive.path, archive.description, archive.tainted))
[docs]def archive_add(args): (name, path, description) = args[0:3] attributes = dict( archive_name=name, path=path, description=description, ) for option in args[3:]: (key, value) = option.split('=') attributes[key] = value session = DBConn().session() archive = Archive() for key, value in attributes.items(): setattr(archive, key, value) session.add(archive) session.flush() if dryrun: session.rollback() else: session.commit()
[docs]def archive_rm(name): session = DBConn().session() archive = get_archive(name, session) session.delete(archive) session.flush() if dryrun: session.rollback() else: session.commit()
[docs]def archive_rename(oldname, newname): session = DBConn().session() archive = get_archive(oldname, session) archive.archive_name = newname session.flush() if dryrun: session.rollback() else: session.commit()
[docs]def archive(command): mode = command[1] if mode == 'list': archive_list() elif mode == 'rename': archive_rename(command[2], command[3]) elif mode == 'add': archive_add(command[2:]) elif mode == 'rm': archive_rm(command[2]) else: die("E: archive command unknown")
dispatch['archive'] = archive ################################################################################
[docs]def __version_check_list(d): session = d.session() for s in session.query(Suite).order_by(Suite.suite_name): __version_check_list_suite(d, s.suite_name)
[docs]def __version_check_list_suite(d, suite_name): vcs = get_version_checks(suite_name) for vc in vcs: print("%s %s %s" % (suite_name, vc.check, vc.reference.suite_name))
[docs]def __version_check_add(d, suite_name, check, reference_name): suite = get_suite_or_die(suite_name, error_message="E: Could not find suite %(suite_name)s") reference = get_suite_or_die(reference_name, error_message="E: Could not find reference suite %(suite_name)s") session = d.session() vc = VersionCheck() vc.suite = suite vc.check = check vc.reference = reference session.add(vc) session.commit()
[docs]def __version_check_rm(d, suite_name, check, reference_name): suite = get_suite_or_die(suite_name, error_message="E: Could not find suite %(suite_name)s") reference = get_suite_or_die(reference_name, error_message="E: Could not find reference suite %(suite_name)s") session = d.session() try: vc = session.query(VersionCheck).filter_by(suite=suite, check=check, reference=reference).one() session.delete(vc) session.commit() except NoResultFound: print("W: version-check not found.")
[docs]def version_check(args): Cnf = utils.get_conf() d = DBConn() die_arglen(args, 2, "E: version-check needs at least a command") mode = args[1].lower() if mode == 'list': __version_check_list(d) elif mode == 'list-suite': if len(args) != 3: die("E: version-check list-suite needs a single parameter") __version_check_list_suite(d, args[2]) elif mode == 'add': if len(args) != 5: die("E: version-check add needs three parameters") __version_check_add(d, args[2], args[3], args[4]) elif mode == 'rm': if len(args) != 5: die("E: version-check rm needs three parameters") __version_check_rm(d, args[2], args[3], args[4]) else: die("E: version-check command unknown")
dispatch['version-check'] = version_check dispatch['v-c'] = version_check ################################################################################
[docs]def show_config(args): cnf = utils.get_conf() die_arglen(args, 2, "E: config needs at least a command") mode = args[1].lower() if mode == 'db': connstr = "" if "DB::Service" in cnf: # Service mode connstr = "postgresql://service=%s" % cnf["DB::Service"] elif "DB::Host" in cnf: # TCP/IP connstr = "postgresql://%s" % cnf["DB::Host"] if "DB::Port" in cnf and cnf["DB::Port"] != "-1": connstr += ":%s" % cnf["DB::Port"] connstr += "/%s" % cnf["DB::Name"] else: # Unix Socket connstr = "postgresql:///%s" % cnf["DB::Name"] if cnf["DB::Port"] and cnf["DB::Port"] != "-1": connstr += "?port=%s" % cnf["DB::Port"] print(connstr) elif mode == 'db-shell': e = [] if "DB::Service" in cnf: e.append('PGSERVICE') print("PGSERVICE=%s" % cnf["DB::Service"]) if "DB::Name" in cnf: e.append('PGDATABASE') print("PGDATABASE=%s" % cnf["DB::Name"]) if "DB::Host" in cnf: print("PGHOST=%s" % cnf["DB::Host"]) e.append('PGHOST') if "DB::Port" in cnf and cnf["DB::Port"] != "-1": print("PGPORT=%s" % cnf["DB::Port"]) e.append('PGPORT') print("export " + " ".join(e)) elif mode == 'get': print(cnf.get(args[2])) else: session = DBConn().session() try: o = session.query(DBConfig).filter_by(name=mode).one() print(o.value) except NoResultFound: print("W: option '%s' not set" % mode)
dispatch['config'] = show_config dispatch['c'] = show_config ################################################################################
[docs]def show_keyring(args): cnf = utils.get_conf() die_arglen(args, 2, "E: keyring needs at least a command") mode = args[1].lower() d = DBConn() q = d.session().query(Keyring).filter(Keyring.active == True) # noqa:E712 if mode == 'list-all': pass elif mode == 'list-binary': q = q.join(Keyring.acl).filter(ACL.allow_source == False) # noqa:E712 elif mode == 'list-source': q = q.join(Keyring.acl).filter(ACL.allow_source == True) # noqa:E712 else: die("E: keyring command unknown") for k in q.all(): print(k.keyring_name)
[docs]def keyring_add_buildd(command): name = command[2] arch_names = command[3:] session = DBConn().session() arches = session.query(Architecture).filter(Architecture.arch_string.in_(arch_names)) acl = ACL() acl.name = 'buildd-{0}'.format('+'.join(arch_names)) acl.architectures.update(arches) acl.allow_new = True acl.allow_binary = True acl.allow_binary_only = True acl.allow_hijack = True session.add(acl) k = Keyring() k.keyring_name = name k.acl = acl k.priority = 10 session.add(k) session.commit()
[docs]def keyring(command): if command[1].startswith('list-'): show_keyring(command) elif command[1] == 'add-buildd': keyring_add_buildd(command) else: die("E: keyring command unknown")
dispatch['keyring'] = keyring dispatch['k'] = keyring ################################################################################
[docs]def change_component_source(transaction, suite, component, source_names): session = transaction.session overrides = session.query(Override).filter(Override.package.in_(source_names)).filter_by(suite=suite).join(OverrideType).filter_by(overridetype='dsc') for override in overrides: print("Changing override for {0}".format(override.package)) override.component = component session.flush() sources = session.query(DBSource).filter(DBSource.source.in_(source_names)).filter(DBSource.suites.contains(suite)) for source in sources: print("Copying {0}={1}".format(source.source, source.version)) transaction.copy_source(source, suite, component)
[docs]def change_component_binary(transaction, suite, component, binary_names): session = transaction.session overrides = session.query(Override).filter(Override.package.in_(binary_names)).filter_by(suite=suite).join(OverrideType).filter(OverrideType.overridetype.in_(['deb', 'udeb'])) for override in overrides: print("Changing override for {0}".format(override.package)) override.component = component session.flush() binaries = session.query(DBBinary).filter(DBBinary.package.in_(binary_names)).filter(DBBinary.suites.contains(suite)) for binary in binaries: print("Copying {0}={1} [{2}]".format(binary.package, binary.version, binary.architecture.arch_string)) transaction.copy_binary(binary, suite, component) pass
[docs]def change_component(args): with daklib.archive.ArchiveTransaction() as transaction: session = transaction.session suite = session.query(Suite).filter_by(suite_name=args[1]).one() component = session.query(Component).filter_by(component_name=args[2]).one() if args[3] == 'source': change_component_source(transaction, suite, component, args[4:]) elif args[3] == 'binary': change_component_binary(transaction, suite, component, args[4:]) else: raise Exception("Can only move source or binary, not {0}".format(args[3])) transaction.commit()
dispatch['change-component'] = change_component ################################################################################
[docs]def forget_signature(args): filename = args[1] with open(filename, 'rb') as fh: data = fh.read() session = DBConn().session() keyrings = [k.keyring_name for k in session.query(Keyring).filter_by(active=True).order_by(Keyring.priority)] signed_file = daklib.gpg.SignedFile(data, keyrings) history = SignatureHistory.from_signed_file(signed_file).query(session) if history is not None: session.delete(history) session.commit() else: print("Signature was not known to dak.") session.rollback()
dispatch['forget-signature'] = forget_signature ################################################################################
[docs]def main(): """Perform administrative work on the dak database""" global dryrun Cnf = utils.get_conf() arguments = [('h', "help", "Admin::Options::Help"), ('n', "dry-run", "Admin::Options::Dry-Run")] for i in ["help", "dry-run"]: key = "Admin::Options::%s" % i if key not in Cnf: Cnf[key] = "" arguments = apt_pkg.parse_commandline(Cnf, arguments, sys.argv) options = Cnf.subtree("Admin::Options") if options["Help"] or len(arguments) < 1: usage() if options["Dry-Run"]: dryrun = True subcommand = arguments[0] if subcommand in dispatch: dispatch[subcommand](arguments) else: die("E: Unknown command")
################################################################################ if __name__ == '__main__': main()