policy.py 12 KB

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