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-statement::
34 with UploadCopy(...) as copy:
37 Doing so will provide a temporary copy of the upload in the directory
38 given by the C{directory} attribute. The copy will be removed on leaving
41 def __init__(self, upload):
44 @type upload: L{daklib.dbconn.PolicyQueueUpload}
45 @param upload: upload to handle
51 def export(self, directory, mode=None, symlink=True):
52 """export a copy of the upload
55 @param directory: directory to export to
58 @param mode: permissions to use for the copied files
61 @param symlink: use symlinks instead of copying the files
63 with FilesystemTransaction() as fs:
64 source = self.upload.source
65 queue = self.upload.policy_queue
67 if source is not None:
68 for dsc_file in source.srcfiles:
70 dst = os.path.join(directory, os.path.basename(f.filename))
71 fs.copy(f.fullpath, dst, mode=mode, symlink=symlink)
72 for binary in self.upload.binaries:
74 dst = os.path.join(directory, os.path.basename(f.filename))
75 fs.copy(f.fullpath, dst, mode=mode, symlink=symlink)
78 for byhand in self.upload.byhand:
79 src = os.path.join(queue.path, byhand.filename)
80 dst = os.path.join(directory, byhand.filename)
81 fs.copy(src, dst, mode=mode, symlink=symlink)
84 src = os.path.join(queue.path, self.upload.changes.changesname)
85 dst = os.path.join(directory, self.upload.changes.changesname)
86 fs.copy(src, dst, mode=mode, symlink=symlink)
89 assert self.directory is None
92 self.directory = tempfile.mkdtemp(dir=cnf.get('Dir::TempPath'))
93 self.export(self.directory, symlink=True)
96 def __exit__(self, *args):
97 if self.directory is not None:
98 shutil.rmtree(self.directory)
102 class PolicyQueueUploadHandler(object):
103 """process uploads to policy queues
105 This class allows to accept or reject uploads and to get a list of missing
106 overrides (for NEW processing).
108 def __init__(self, upload, session):
111 @type upload: L{daklib.dbconn.PolicyQueueUpload}
112 @param upload: upload to process
114 @param session: database session
117 self.session = session
120 def _overridesuite(self):
121 overridesuite = self.upload.target_suite
122 if overridesuite.overridesuite is not None:
123 overridesuite = self.session.query(Suite).filter_by(suite_name=overridesuite.overridesuite).one()
126 def _source_override(self, component_name):
127 package = self.upload.source.source
128 suite = self._overridesuite
129 query = self.session.query(Override).filter_by(package=package, suite=suite) \
130 .join(OverrideType).filter(OverrideType.overridetype == 'dsc') \
131 .join(Component).filter(Component.component_name == component_name)
134 def _binary_override(self, binary, component_name):
135 package = binary.package
136 suite = self._overridesuite
137 overridetype = binary.binarytype
138 query = self.session.query(Override).filter_by(package=package, suite=suite) \
139 .join(OverrideType).filter(OverrideType.overridetype == overridetype) \
140 .join(Component).filter(Component.component_name == component_name)
143 def _binary_metadata(self, binary, key):
144 metadata_key = self.session.query(MetadataKey).filter_by(key=key).first()
145 if metadata_key is None:
147 metadata = self.session.query(BinaryMetadata).filter_by(binary=binary, key=metadata_key).first()
150 return metadata.value
153 def _changes_prefix(self):
154 changesname = self.upload.changes.changesname
155 assert changesname.endswith('.changes')
156 assert re_file_changes.match(changesname)
157 return changesname[0:-8]
160 """mark upload as accepted"""
161 assert len(self.missing_overrides()) == 0
163 fn1 = 'ACCEPT.{0}'.format(self._changes_prefix)
164 fn = os.path.join(self.upload.policy_queue.path, 'COMMENTS', fn1)
166 fh = os.open(fn, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o644)
170 if e.errno == errno.EEXIST:
175 def reject(self, reason):
176 """mark upload as rejected
179 @param reason: reason for the rejection
181 fn1 = 'REJECT.{0}'.format(self._changes_prefix)
182 assert re_file_safe.match(fn1)
184 fn = os.path.join(self.upload.policy_queue.path, 'COMMENTS', fn1)
186 fh = os.open(fn, os.O_CREAT | os.O_EXCL | os.O_WRONLY)
187 os.write(fh, 'NOTOK\n')
191 if e.errno == errno.EEXIST:
196 def get_action(self):
197 """get current action
200 @return: string giving the current action, one of 'ACCEPT', 'ACCEPTED', 'REJECT'
202 changes_prefix = self._changes_prefix
204 for action in ('ACCEPT', 'ACCEPTED', 'REJECT'):
205 fn1 = '{0}.{1}'.format(action, changes_prefix)
206 fn = os.path.join(self.upload.policy_queue.path, 'COMMENTS', fn1)
207 if os.path.exists(fn):
212 def missing_overrides(self, hints=None):
213 """get missing override entries for the upload
215 @type hints: list of dict
216 @param hints: suggested hints for new overrides in the same format as
219 @return: list of dicts with the following keys:
221 - package: package name
222 - priority: default priority (from upload)
223 - section: default section (from upload)
224 - component: default component (from upload)
225 - type: type of required override ('dsc', 'deb' or 'udeb')
227 All values are strings.
229 # TODO: use Package-List field
235 hints_map = dict([ ((o['type'], o['package']), o) for o in hints ])
237 for binary in self.upload.binaries:
238 priority = self._binary_metadata(binary, 'Priority')
239 section = self._binary_metadata(binary, 'Section')
241 if section.find('/') != -1:
242 component = section.split('/', 1)[0]
243 override = self._binary_override(binary, component)
245 hint = hints_map.get((binary.binarytype, binary.package))
248 component = hint['component']
251 package = binary.package,
254 component = component,
255 type = binary.binarytype,
257 components.add(component)
259 source_component = '(unknown)'
260 for component in ('main', 'contrib', 'non-free'):
261 if component in components:
262 source_component = component
265 source = self.upload.source
266 if source is not None:
267 override = self._source_override(source_component)
269 hint = hints_map.get(('dsc', source.source))
274 if component != 'main':
275 section = "{0}/{1}".format(component, section)
277 package = source.source,
280 component = source_component,