policy.py 13 KB

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