1 # Copyright (C) 2012, Ansgar Burchardt <ansgar@debian.org>
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 2 of the License, or
6 # (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License along
14 # with this program; if not, write to the Free Software Foundation, Inc.,
15 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 """module to process policy queue uploads"""
19 from .config import Config
20 from .dbconn import BinaryMetadata, Component, MetadataKey, Override, OverrideType, get_mapped_component
21 from .fstransactions import FilesystemTransaction
22 from .regexes import re_file_changes, re_file_safe
23 import daklib.utils as utils
30 class UploadCopy(object):
31 """export a policy queue upload
33 This class can be used in a with-statement::
35 with UploadCopy(...) as copy:
38 Doing so will provide a temporary copy of the upload in the directory
39 given by the C{directory} attribute. The copy will be removed on leaving
42 def __init__(self, upload):
45 @type upload: L{daklib.dbconn.PolicyQueueUpload}
46 @param upload: upload to handle
52 def export(self, directory, mode=None, symlink=True, ignore_existing=False):
53 """export a copy of the upload
56 @param directory: directory to export to
59 @param mode: permissions to use for the copied files
62 @param symlink: use symlinks instead of copying the files
64 @type ignore_existing: bool
65 @param ignore_existing: ignore already existing files
67 with FilesystemTransaction() as fs:
68 source = self.upload.source
69 queue = self.upload.policy_queue
71 if source is not None:
72 for dsc_file in source.srcfiles:
74 dst = os.path.join(directory, os.path.basename(f.filename))
75 if not os.path.exists(dst) or not ignore_existing:
76 fs.copy(f.fullpath, dst, mode=mode, symlink=symlink)
78 for binary in self.upload.binaries:
80 dst = os.path.join(directory, os.path.basename(f.filename))
81 if not os.path.exists(dst) or not ignore_existing:
82 fs.copy(f.fullpath, dst, mode=mode, symlink=symlink)
85 for byhand in self.upload.byhand:
86 src = os.path.join(queue.path, byhand.filename)
87 dst = os.path.join(directory, byhand.filename)
88 if not os.path.exists(dst) or not ignore_existing:
89 fs.copy(src, dst, mode=mode, symlink=symlink)
92 src = os.path.join(queue.path, self.upload.changes.changesname)
93 dst = os.path.join(directory, self.upload.changes.changesname)
94 if not os.path.exists(dst) or not ignore_existing:
95 fs.copy(src, dst, mode=mode, symlink=symlink)
98 assert self.directory is None
101 self.directory = tempfile.mkdtemp(dir=cnf.get('Dir::TempPath'))
102 self.export(self.directory, symlink=True)
105 def __exit__(self, *args):
106 if self.directory is not None:
107 shutil.rmtree(self.directory)
108 self.directory = None
111 class PolicyQueueUploadHandler(object):
112 """process uploads to policy queues
114 This class allows to accept or reject uploads and to get a list of missing
115 overrides (for NEW processing).
117 def __init__(self, upload, session):
120 @type upload: L{daklib.dbconn.PolicyQueueUpload}
121 @param upload: upload to process
123 @param session: database session
126 self.session = session
129 def _overridesuite(self):
130 overridesuite = self.upload.target_suite
131 if overridesuite.overridesuite is not None:
132 overridesuite = self.session.query(Suite).filter_by(suite_name=overridesuite.overridesuite).one()
135 def _source_override(self, component_name):
136 package = self.upload.source.source
137 suite = self._overridesuite
138 component = get_mapped_component(component_name, self.session)
139 query = self.session.query(Override).filter_by(package=package, suite=suite) \
140 .join(OverrideType).filter(OverrideType.overridetype == 'dsc') \
141 .filter(Override.component == component)
144 def _binary_override(self, binary, component_name):
145 package = binary.package
146 suite = self._overridesuite
147 overridetype = binary.binarytype
148 component = get_mapped_component(component_name, self.session)
149 query = self.session.query(Override).filter_by(package=package, suite=suite) \
150 .join(OverrideType).filter(OverrideType.overridetype == overridetype) \
151 .filter(Override.component == component)
154 def _binary_metadata(self, binary, key):
155 metadata_key = self.session.query(MetadataKey).filter_by(key=key).first()
156 if metadata_key is None:
158 metadata = self.session.query(BinaryMetadata).filter_by(binary=binary, key=metadata_key).first()
161 return metadata.value
164 def _changes_prefix(self):
165 changesname = self.upload.changes.changesname
166 assert changesname.endswith('.changes')
167 assert re_file_changes.match(changesname)
168 return changesname[0:-8]
171 """mark upload as accepted"""
172 assert len(self.missing_overrides()) == 0
174 fn1 = 'ACCEPT.{0}'.format(self._changes_prefix)
175 fn = os.path.join(self.upload.policy_queue.path, 'COMMENTS', fn1)
177 fh = os.open(fn, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o644)
181 if e.errno == errno.EEXIST:
186 def reject(self, reason):
187 """mark upload as rejected
190 @param reason: reason for the rejection
194 fn1 = 'REJECT.{0}'.format(self._changes_prefix)
195 assert re_file_safe.match(fn1)
197 fn = os.path.join(self.upload.policy_queue.path, 'COMMENTS', fn1)
199 fh = os.open(fn, os.O_CREAT | os.O_EXCL | os.O_WRONLY)
200 os.write(fh, 'NOTOK\n')
201 os.write(fh, 'From: {0} <{1}>\n\n'.format(utils.whoami(), cnf['Dinstall::MyAdminAddress']))
205 if e.errno == errno.EEXIST:
210 def get_action(self):
211 """get current action
214 @return: string giving the current action, one of 'ACCEPT', 'ACCEPTED', 'REJECT'
216 changes_prefix = self._changes_prefix
218 for action in ('ACCEPT', 'ACCEPTED', 'REJECT'):
219 fn1 = '{0}.{1}'.format(action, changes_prefix)
220 fn = os.path.join(self.upload.policy_queue.path, 'COMMENTS', fn1)
221 if os.path.exists(fn):
226 def missing_overrides(self, hints=None):
227 """get missing override entries for the upload
229 @type hints: list of dict
230 @param hints: suggested hints for new overrides in the same format as
233 @return: list of dicts with the following keys:
235 - package: package name
236 - priority: default priority (from upload)
237 - section: default section (from upload)
238 - component: default component (from upload)
239 - type: type of required override ('dsc', 'deb' or 'udeb')
241 All values are strings.
243 # TODO: use Package-List field
249 hints_map = dict([ ((o['type'], o['package']), o) for o in hints ])
251 for binary in self.upload.binaries:
252 priority = self._binary_metadata(binary, 'Priority')
253 section = self._binary_metadata(binary, 'Section')
255 if section.find('/') != -1:
256 component = section.split('/', 1)[0]
257 override = self._binary_override(binary, component)
259 hint = hints_map.get((binary.binarytype, binary.package))
262 component = hint['component']
265 package = binary.package,
268 component = component,
269 type = binary.binarytype,
271 components.add(component)
273 source_component = '(unknown)'
274 for component in ('main', 'contrib', 'non-free'):
275 if component in components:
276 source_component = component
279 source = self.upload.source
280 if source is not None:
281 override = self._source_override(source_component)
283 hint = hints_map.get(('dsc', source.source))
288 if component != 'main':
289 section = "{0}/{1}".format(component, section)
291 package = source.source,
294 component = source_component,