Package daklib :: Module policy
[hide private]
[frames] | no frames]

Source Code for Module daklib.policy

  1  # Copyright (C) 2012, Ansgar Burchardt <ansgar@debian.org> 
  2  # 
  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. 
  7  # 
  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. 
 12  # 
 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. 
 16   
 17  """module to process policy queue uploads""" 
 18   
 19  from .config import Config 
 20  from .dbconn import Component, Override, OverrideType, Priority, Section, Suite, get_mapped_component, get_mapped_component_name 
 21  from .fstransactions import FilesystemTransaction 
 22  from .regexes import re_file_changes, re_file_safe 
 23  from .packagelist import PackageList 
 24  import daklib.utils as utils 
 25   
 26  import errno 
 27  import os 
 28  import shutil 
29 30 31 -class UploadCopy:
32 """export a policy queue upload 33 34 This class can be used in a with-statement:: 35 36 with UploadCopy(...) as copy: 37 ... 38 39 Doing so will provide a temporary copy of the upload in the directory 40 given by the C{directory} attribute. The copy will be removed on leaving 41 the with-block. 42 """ 43
44 - def __init__(self, upload, group=None):
45 """initializer 46 47 @type upload: L{daklib.dbconn.PolicyQueueUpload} 48 @param upload: upload to handle 49 """ 50 51 self.directory = None 52 self.upload = upload 53 self.group = group
54
55 - def export(self, directory, mode=None, symlink=True, ignore_existing=False):
56 """export a copy of the upload 57 58 @type directory: str 59 @param directory: directory to export to 60 61 @type mode: int 62 @param mode: permissions to use for the copied files 63 64 @type symlink: bool 65 @param symlink: use symlinks instead of copying the files 66 67 @type ignore_existing: bool 68 @param ignore_existing: ignore already existing files 69 """ 70 with FilesystemTransaction() as fs: 71 source = self.upload.source 72 queue = self.upload.policy_queue 73 74 if source is not None: 75 for dsc_file in source.srcfiles: 76 f = dsc_file.poolfile 77 dst = os.path.join(directory, os.path.basename(f.filename)) 78 if not os.path.exists(dst) or not ignore_existing: 79 fs.copy(f.fullpath, dst, mode=mode, symlink=symlink) 80 81 for binary in self.upload.binaries: 82 f = binary.poolfile 83 dst = os.path.join(directory, os.path.basename(f.filename)) 84 if not os.path.exists(dst) or not ignore_existing: 85 fs.copy(f.fullpath, dst, mode=mode, symlink=symlink) 86 87 # copy byhand files 88 for byhand in self.upload.byhand: 89 src = os.path.join(queue.path, byhand.filename) 90 dst = os.path.join(directory, byhand.filename) 91 if os.path.exists(src) and (not os.path.exists(dst) or not ignore_existing): 92 fs.copy(src, dst, mode=mode, symlink=symlink) 93 94 # copy .changes 95 src = os.path.join(queue.path, self.upload.changes.changesname) 96 dst = os.path.join(directory, self.upload.changes.changesname) 97 if not os.path.exists(dst) or not ignore_existing: 98 fs.copy(src, dst, mode=mode, symlink=symlink)
99
100 - def __enter__(self):
101 assert self.directory is None 102 103 mode = 0o0700 104 symlink = True 105 if self.group is not None: 106 mode = 0o2750 107 symlink = False 108 109 cnf = Config() 110 self.directory = utils.temp_dirname(parent=cnf.get('Dir::TempPath'), 111 mode=mode, 112 group=self.group) 113 self.export(self.directory, symlink=symlink) 114 return self
115
116 - def __exit__(self, *args):
117 if self.directory is not None: 118 shutil.rmtree(self.directory) 119 self.directory = None 120 return None
121
122 123 -class PolicyQueueUploadHandler:
124 """process uploads to policy queues 125 126 This class allows to accept or reject uploads and to get a list of missing 127 overrides (for NEW processing). 128 """ 129
130 - def __init__(self, upload, session):
131 """initializer 132 133 @type upload: L{daklib.dbconn.PolicyQueueUpload} 134 @param upload: upload to process 135 136 @param session: database session 137 """ 138 self.upload = upload 139 self.session = session
140 141 @property
142 - def _overridesuite(self):
143 overridesuite = self.upload.target_suite 144 if overridesuite.overridesuite is not None: 145 overridesuite = self.session.query(Suite).filter_by(suite_name=overridesuite.overridesuite).one() 146 return overridesuite
147
148 - def _source_override(self, component_name):
149 package = self.upload.source.source 150 suite = self._overridesuite 151 component = get_mapped_component(component_name, self.session) 152 query = self.session.query(Override).filter_by(package=package, suite=suite) \ 153 .join(OverrideType).filter(OverrideType.overridetype == 'dsc') \ 154 .filter(Override.component == component) 155 return query.first()
156
157 - def _binary_override(self, name, binarytype, component_name):
158 suite = self._overridesuite 159 component = get_mapped_component(component_name, self.session) 160 query = self.session.query(Override).filter_by(package=name, suite=suite) \ 161 .join(OverrideType).filter(OverrideType.overridetype == binarytype) \ 162 .filter(Override.component == component) 163 return query.first()
164 165 @property
166 - def _changes_prefix(self):
167 changesname = self.upload.changes.changesname 168 assert changesname.endswith('.changes') 169 assert re_file_changes.match(changesname) 170 return changesname[0:-8]
171
172 - def accept(self):
173 """mark upload as accepted""" 174 assert len(self.missing_overrides()) == 0 175 176 fn1 = 'ACCEPT.{0}'.format(self._changes_prefix) 177 fn = os.path.join(self.upload.policy_queue.path, 'COMMENTS', fn1) 178 try: 179 fh = os.open(fn, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o644) 180 with os.fdopen(fh, 'wt') as f: 181 f.write('OK\n') 182 except OSError as e: 183 if e.errno == errno.EEXIST: 184 pass 185 else: 186 raise
187
188 - def reject(self, reason):
189 """mark upload as rejected 190 191 @type reason: str 192 @param reason: reason for the rejection 193 """ 194 cnf = Config() 195 196 fn1 = 'REJECT.{0}'.format(self._changes_prefix) 197 assert re_file_safe.match(fn1) 198 199 fn = os.path.join(self.upload.policy_queue.path, 'COMMENTS', fn1) 200 try: 201 fh = os.open(fn, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o644) 202 with os.fdopen(fh, 'wt') as f: 203 f.write('NOTOK\n') 204 f.write('From: {0} <{1}>\n\n'.format(utils.whoami(), cnf['Dinstall::MyAdminAddress'])) 205 f.write(reason) 206 except OSError as e: 207 if e.errno == errno.EEXIST: 208 pass 209 else: 210 raise
211
212 - def get_action(self):
213 """get current action 214 215 @rtype: str 216 @return: string giving the current action, one of 'ACCEPT', 'ACCEPTED', 'REJECT' 217 """ 218 changes_prefix = self._changes_prefix 219 220 for action in ('ACCEPT', 'ACCEPTED', 'REJECT'): 221 fn1 = '{0}.{1}'.format(action, changes_prefix) 222 fn = os.path.join(self.upload.policy_queue.path, 'COMMENTS', fn1) 223 if os.path.exists(fn): 224 return action 225 226 return None
227
228 - def missing_overrides(self, hints=None):
229 """get missing override entries for the upload 230 231 @type hints: list of dict 232 @param hints: suggested hints for new overrides in the same format as 233 the return value 234 235 @return: list of dicts with the following keys: 236 237 - package: package name 238 - priority: default priority (from upload) 239 - section: default section (from upload) 240 - component: default component (from upload) 241 - type: type of required override ('dsc', 'deb' or 'udeb') 242 243 All values are strings. 244 """ 245 # TODO: use Package-List field 246 missing = [] 247 components = set() 248 249 source = self.upload.source 250 251 if hints is None: 252 hints = [] 253 hints_map = dict([((o['type'], o['package']), o) for o in hints]) 254 255 def check_override(name, type, priority, section, included): 256 component = 'main' 257 if section.find('/') != -1: 258 component = section.split('/', 1)[0] 259 override = self._binary_override(name, type, component) 260 if override is None and not any(o['package'] == name and o['type'] == type for o in missing): 261 hint = hints_map.get((type, name)) 262 if hint is not None: 263 missing.append(hint) 264 component = hint['component'] 265 else: 266 missing.append( 267 dict( 268 package=name, 269 priority=priority, 270 section=section, 271 component=component, 272 type=type, 273 included=included 274 )) 275 components.add(component)
276 277 for binary in self.upload.binaries: 278 binary_proxy = binary.proxy 279 priority = binary_proxy['Priority'] 280 section = binary_proxy['Section'] 281 check_override(binary.package, binary.binarytype, priority, section, included=True) 282 283 if source is not None: 284 source_proxy = source.proxy 285 package_list = PackageList(source_proxy) 286 if not package_list.fallback: 287 packages = package_list.packages_for_suite(self.upload.target_suite) 288 for p in packages: 289 check_override(p.name, p.type, p.priority, p.section, included=False) 290 291 # see daklib.archive.source_component_from_package_list 292 # which we cannot use here as we might not have a Package-List 293 # field for old packages 294 mapped_components = [get_mapped_component_name(c) for c in components] 295 query = self.session.query(Component).order_by(Component.ordering) \ 296 .filter(Component.component_name.in_(mapped_components)) 297 source_component = query.first().component_name 298 299 override = self._source_override(source_component) 300 if override is None: 301 hint = hints_map.get(('dsc', source.source)) 302 if hint is not None: 303 missing.append(hint) 304 else: 305 section = 'misc' 306 if source_component != 'main': 307 section = "{0}/{1}".format(source_component, section) 308 missing.append( 309 dict( 310 package=source.source, 311 priority='optional', 312 section=section, 313 component=source_component, 314 type='dsc', 315 included=True, 316 )) 317 318 return missing
319
320 - def add_overrides(self, new_overrides, suite):
321 if suite.overridesuite is not None: 322 suite = self.session.query(Suite).filter_by(suite_name=suite.overridesuite).one() 323 324 for override in new_overrides: 325 package = override['package'] 326 priority = self.session.query(Priority).filter_by(priority=override['priority']).first() 327 section = self.session.query(Section).filter_by(section=override['section']).first() 328 component = get_mapped_component(override['component'], self.session) 329 overridetype = self.session.query(OverrideType).filter_by(overridetype=override['type']).one() 330 331 if priority is None: 332 raise Exception('Invalid priority {0} for package {1}'.format(priority, package)) 333 if section is None: 334 raise Exception('Invalid section {0} for package {1}'.format(section, package)) 335 if component is None: 336 raise Exception('Invalid component {0} for package {1}'.format(component, package)) 337 338 o = Override(package=package, suite=suite, component=component, priority=priority, section=section, overridetype=overridetype) 339 self.session.add(o) 340 341 self.session.commit()
342