From b740656a6cca0f69bb1a094fc0c8bcb930c56bf2 Mon Sep 17 00:00:00 2001 From: Ansgar Burchardt Date: Sun, 10 Jun 2012 13:10:23 +0200 Subject: [PATCH] Add module to process policy queue uploads. --- daklib/policy.py | 277 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 277 insertions(+) create mode 100644 daklib/policy.py diff --git a/daklib/policy.py b/daklib/policy.py new file mode 100644 index 00000000..c989f1f2 --- /dev/null +++ b/daklib/policy.py @@ -0,0 +1,277 @@ +# Copyright (C) 2012, Ansgar Burchardt +# +# 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., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""module to process policy queue uploads""" + +from .config import Config +from .dbconn import BinaryMetadata, Component, MetadataKey, Override, OverrideType +from .fstransactions import FilesystemTransaction +from .regexes import re_file_changes, re_file_safe + +import errno +import os +import shutil +import tempfile + +class UploadCopy(object): + """export a policy queue upload + + This class can be used in a with-statements: + + with UploadCopy(...) as copy: + ... + + Doing so will provide a temporary copy of the upload in the directory + given by the `directory` attribute. The copy will be removed on leaving + the with-block. + + Args: + upload (daklib.dbconn.PolicyQueueUpload) + """ + def __init__(self, upload): + self.directory = None + self.upload = upload + + def export(self, directory, mode=None, symlink=True): + """export a copy of the upload + + Args: + directory (str) + + Kwargs: + mode (int): permissions to use for the copied files + symlink (bool): use symlinks instead of copying the files (default: True) + """ + with FilesystemTransaction() as fs: + source = self.upload.source + queue = self.upload.policy_queue + + if source is not None: + for dsc_file in source.srcfiles: + f = dsc_file.poolfile + dst = os.path.join(directory, os.path.basename(f.filename)) + fs.copy(f.fullpath, dst, mode=mode, symlink=symlink) + for binary in self.upload.binaries: + f = binary.poolfile + dst = os.path.join(directory, os.path.basename(f.filename)) + fs.copy(f.fullpath, dst, mode=mode, symlink=symlink) + + # copy byhand files + for byhand in self.upload.byhand: + src = os.path.join(queue.path, byhand.filename) + dst = os.path.join(directory, byhand.filename) + fs.copy(src, dst, mode=mode, symlink=symlink) + + # copy .changes + src = os.path.join(queue.path, self.upload.changes.changesname) + dst = os.path.join(directory, self.upload.changes.changesname) + fs.copy(src, dst, mode=mode, symlink=symlink) + + def __enter__(self): + assert self.directory is None + + cnf = Config() + self.directory = tempfile.mkdtemp(dir=cnf.get('Dir::TempPath')) + self.export(self.directory, symlink=True) + return self + + def __exit__(self, *args): + if self.directory is not None: + shutil.rmtree(self.directory) + self.directory = None + return None + +class PolicyQueueUploadHandler(object): + """process uploads to policy queues + + This class allows to accept or reject uploads and to get a list of missing + overrides (for NEW processing). + """ + def __init__(self, upload, session): + """initializer + + Args: + upload (daklib.dbconn.PolicyQueueUpload): upload to process + session: database session + """ + self.upload = upload + self.session = session + + @property + def _overridesuite(self): + overridesuite = self.upload.target_suite + if overridesuite.overridesuite is not None: + overridesuite = self.session.query(Suite).filter_by(suite_name=overridesuite.overridesuite).one() + return overridesuite + + def _source_override(self, component_name): + package = self.upload.source.source + suite = self._overridesuite + query = self.session.query(Override).filter_by(package=package, suite=suite) \ + .join(OverrideType).filter(OverrideType.overridetype == 'dsc') \ + .join(Component).filter(Component.component_name == component_name) + return query.first() + + def _binary_override(self, binary, component_name): + package = binary.package + suite = self._overridesuite + overridetype = binary.binarytype + query = self.session.query(Override).filter_by(package=package, suite=suite) \ + .join(OverrideType).filter(OverrideType.overridetype == overridetype) \ + .join(Component).filter(Component.component_name == component_name) + return query.first() + + def _binary_metadata(self, binary, key): + metadata_key = self.session.query(MetadataKey).filter_by(key=key).first() + if metadata_key is None: + return None + metadata = self.session.query(BinaryMetadata).filter_by(binary=binary, key=metadata_key).first() + if metadata is None: + return None + return metadata.value + + @property + def _changes_prefix(self): + changesname = self.upload.changes.changesname + assert changesname.endswith('.changes') + assert re_file_changes.match(changesname) + return changesname[0:-8] + + def accept(self): + """mark upload as accepted""" + assert len(self.missing_overrides()) == 0 + + fn1 = 'ACCEPT.{0}'.format(self._changes_prefix) + fn = os.path.join(self.upload.policy_queue.path, 'COMMENTS', fn1) + try: + fh = os.open(fn, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o644) + os.write(fh, 'OK\n') + os.close(fh) + except OSError as e: + if e.errno == errno.EEXIST: + pass + else: + raise + + def reject(self, reason): + """mark upload as rejected + + Args: + reason (str): reason for the rejection + """ + fn1 = 'REJECT.{0}'.format(self._changes_prefix) + assert re_file_safe.match(fn1) + + fn = os.path.join(self.upload.policy_queue.path, 'COMMENTS', fn1) + try: + fh = os.open(fn, os.O_CREAT | os.O_EXCL | os.O_WRONLY) + os.write(fh, 'NOTOK\n') + os.write(fh, reason) + os.close(fh) + except OSError as e: + if e.errno == errno.EEXIST: + pass + else: + raise + + def get_action(self): + """get current action + + Returns: + string giving the current action, one of 'ACCEPT', 'ACCEPTED', 'REJECT' + """ + changes_prefix = self._changes_prefix + + for action in ('ACCEPT', 'ACCEPTED', 'REJECT'): + fn1 = '{0}.{1}'.format(action, changes_prefix) + fn = os.path.join(self.upload.policy_queue.path, 'COMMENTS', fn1) + if os.path.exists(fn): + return action + + return None + + def missing_overrides(self, hints=None): + """get missing override entries for the upload + + Kwargs: + hints (list of dict): suggested hints for new overrides in the same + format as the return value + + Returns: + list of dicts with the following keys: + package: package name + priority: default priority (from upload) + section: default section (from upload) + component: default component (from upload) + type: type of required override ('dsc', 'deb' or 'udeb') + All values are strings. + """ + # TODO: use Package-List field + missing = [] + components = set() + + if hints is None: + hints = [] + hints_map = dict([ ((o['type'], o['package']), o) for o in hints ]) + + for binary in self.upload.binaries: + priority = self._binary_metadata(binary, 'Priority') + section = self._binary_metadata(binary, 'Section') + component = 'main' + if section.find('/') != -1: + component = section.split('/', 1)[0] + override = self._binary_override(binary, component) + if override is None: + hint = hints_map.get((binary.binarytype, binary.package)) + if hint is not None: + missing.append(hint) + component = hint['component'] + else: + missing.append(dict( + package = binary.package, + priority = priority, + section = section, + component = component, + type = binary.binarytype, + )) + components.add(component) + + source_component = '(unknown)' + for component in ('main', 'contrib', 'non-free'): + if component in components: + source_component = component + break + + source = self.upload.source + if source is not None: + override = self._source_override(source_component) + if override is None: + hint = hints_map.get(('dsc', source.source)) + if hint is not None: + missing.append(hint) + else: + section = 'misc' + if component != 'main': + section = "{0}/{1}".format(component, section) + missing.append(dict( + package = source.source, + priority = 'extra', + section = section, + component = source_component, + type = 'dsc', + )) + + return missing -- 2.39.2