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
21 from .fstransactions import FilesystemTransaction
22 from .regexes import re_file_changes, re_file_safe
29 class UploadCopy(object):
30 """export a policy queue upload
32 This class can be used in a with-statements:
34 with UploadCopy(...) as copy:
37 Doing so will provide a temporary copy of the upload in the directory
38 given by the `directory` attribute. The copy will be removed on leaving
42 upload (daklib.dbconn.PolicyQueueUpload)
44 def __init__(self, upload):
48 def export(self, directory, mode=None, symlink=True):
49 """export a copy of the upload
55 mode (int): permissions to use for the copied files
56 symlink (bool): use symlinks instead of copying the files (default: True)
58 with FilesystemTransaction() as fs:
59 source = self.upload.source
60 queue = self.upload.policy_queue
62 if source is not None:
63 for dsc_file in source.srcfiles:
65 dst = os.path.join(directory, os.path.basename(f.filename))
66 fs.copy(f.fullpath, dst, mode=mode, symlink=symlink)
67 for binary in self.upload.binaries:
69 dst = os.path.join(directory, os.path.basename(f.filename))
70 fs.copy(f.fullpath, dst, mode=mode, symlink=symlink)
73 for byhand in self.upload.byhand:
74 src = os.path.join(queue.path, byhand.filename)
75 dst = os.path.join(directory, byhand.filename)
76 fs.copy(src, dst, mode=mode, symlink=symlink)
79 src = os.path.join(queue.path, self.upload.changes.changesname)
80 dst = os.path.join(directory, self.upload.changes.changesname)
81 fs.copy(src, dst, mode=mode, symlink=symlink)
84 assert self.directory is None
87 self.directory = tempfile.mkdtemp(dir=cnf.get('Dir::TempPath'))
88 self.export(self.directory, symlink=True)
91 def __exit__(self, *args):
92 if self.directory is not None:
93 shutil.rmtree(self.directory)
97 class PolicyQueueUploadHandler(object):
98 """process uploads to policy queues
100 This class allows to accept or reject uploads and to get a list of missing
101 overrides (for NEW processing).
103 def __init__(self, upload, session):
107 upload (daklib.dbconn.PolicyQueueUpload): upload to process
108 session: database session
111 self.session = session
114 def _overridesuite(self):
115 overridesuite = self.upload.target_suite
116 if overridesuite.overridesuite is not None:
117 overridesuite = self.session.query(Suite).filter_by(suite_name=overridesuite.overridesuite).one()
120 def _source_override(self, component_name):
121 package = self.upload.source.source
122 suite = self._overridesuite
123 query = self.session.query(Override).filter_by(package=package, suite=suite) \
124 .join(OverrideType).filter(OverrideType.overridetype == 'dsc') \
125 .join(Component).filter(Component.component_name == component_name)
128 def _binary_override(self, binary, component_name):
129 package = binary.package
130 suite = self._overridesuite
131 overridetype = binary.binarytype
132 query = self.session.query(Override).filter_by(package=package, suite=suite) \
133 .join(OverrideType).filter(OverrideType.overridetype == overridetype) \
134 .join(Component).filter(Component.component_name == component_name)
137 def _binary_metadata(self, binary, key):
138 metadata_key = self.session.query(MetadataKey).filter_by(key=key).first()
139 if metadata_key is None:
141 metadata = self.session.query(BinaryMetadata).filter_by(binary=binary, key=metadata_key).first()
144 return metadata.value
147 def _changes_prefix(self):
148 changesname = self.upload.changes.changesname
149 assert changesname.endswith('.changes')
150 assert re_file_changes.match(changesname)
151 return changesname[0:-8]
154 """mark upload as accepted"""
155 assert len(self.missing_overrides()) == 0
157 fn1 = 'ACCEPT.{0}'.format(self._changes_prefix)
158 fn = os.path.join(self.upload.policy_queue.path, 'COMMENTS', fn1)
160 fh = os.open(fn, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o644)
164 if e.errno == errno.EEXIST:
169 def reject(self, reason):
170 """mark upload as rejected
173 reason (str): reason for the rejection
175 fn1 = 'REJECT.{0}'.format(self._changes_prefix)
176 assert re_file_safe.match(fn1)
178 fn = os.path.join(self.upload.policy_queue.path, 'COMMENTS', fn1)
180 fh = os.open(fn, os.O_CREAT | os.O_EXCL | os.O_WRONLY)
181 os.write(fh, 'NOTOK\n')
185 if e.errno == errno.EEXIST:
190 def get_action(self):
191 """get current action
194 string giving the current action, one of 'ACCEPT', 'ACCEPTED', 'REJECT'
196 changes_prefix = self._changes_prefix
198 for action in ('ACCEPT', 'ACCEPTED', 'REJECT'):
199 fn1 = '{0}.{1}'.format(action, changes_prefix)
200 fn = os.path.join(self.upload.policy_queue.path, 'COMMENTS', fn1)
201 if os.path.exists(fn):
206 def missing_overrides(self, hints=None):
207 """get missing override entries for the upload
210 hints (list of dict): suggested hints for new overrides in the same
211 format as the return value
214 list of dicts with the following keys:
215 package: package name
216 priority: default priority (from upload)
217 section: default section (from upload)
218 component: default component (from upload)
219 type: type of required override ('dsc', 'deb' or 'udeb')
220 All values are strings.
222 # TODO: use Package-List field
228 hints_map = dict([ ((o['type'], o['package']), o) for o in hints ])
230 for binary in self.upload.binaries:
231 priority = self._binary_metadata(binary, 'Priority')
232 section = self._binary_metadata(binary, 'Section')
234 if section.find('/') != -1:
235 component = section.split('/', 1)[0]
236 override = self._binary_override(binary, component)
238 hint = hints_map.get((binary.binarytype, binary.package))
241 component = hint['component']
244 package = binary.package,
247 component = component,
248 type = binary.binarytype,
250 components.add(component)
252 source_component = '(unknown)'
253 for component in ('main', 'contrib', 'non-free'):
254 if component in components:
255 source_component = component
258 source = self.upload.source
259 if source is not None:
260 override = self._source_override(source_component)
262 hint = hints_map.get(('dsc', source.source))
267 if component != 'main':
268 section = "{0}/{1}".format(component, section)
270 package = source.source,
273 component = source_component,