archive.py 56 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460
  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 manipulate the archive
  17. This module provides classes to manipulate the archive.
  18. """
  19. from __future__ import print_function
  20. from daklib.dbconn import *
  21. import daklib.checks as checks
  22. from daklib.config import Config
  23. from daklib.externalsignature import check_upload_for_external_signature_request
  24. import daklib.upload as upload
  25. import daklib.utils as utils
  26. from daklib.fstransactions import FilesystemTransaction
  27. from daklib.regexes import re_changelog_versions, re_bin_only_nmu
  28. import daklib.daksubprocess
  29. import os
  30. import shutil
  31. from sqlalchemy.orm.exc import NoResultFound
  32. from sqlalchemy.orm import object_session
  33. import sqlalchemy.exc
  34. import traceback
  35. import six
  36. class ArchiveException(Exception):
  37. pass
  38. class HashMismatchException(ArchiveException):
  39. pass
  40. class ArchiveTransaction(object):
  41. """manipulate the archive in a transaction
  42. """
  43. def __init__(self):
  44. self.fs = FilesystemTransaction()
  45. self.session = DBConn().session()
  46. def get_file(self, hashed_file, source_name, check_hashes=True):
  47. """Look for file C{hashed_file} in database
  48. @type hashed_file: L{daklib.upload.HashedFile}
  49. @param hashed_file: file to look for in the database
  50. @type source_name: str
  51. @param source_name: source package name
  52. @type check_hashes: bool
  53. @param check_hashes: check size and hashes match
  54. @raise KeyError: file was not found in the database
  55. @raise HashMismatchException: hash mismatch
  56. @rtype: L{daklib.dbconn.PoolFile}
  57. @return: database entry for the file
  58. """
  59. poolname = os.path.join(utils.poolify(source_name), hashed_file.filename)
  60. try:
  61. poolfile = self.session.query(PoolFile).filter_by(filename=poolname).one()
  62. if check_hashes and (poolfile.filesize != hashed_file.size
  63. or poolfile.md5sum != hashed_file.md5sum
  64. or poolfile.sha1sum != hashed_file.sha1sum
  65. or poolfile.sha256sum != hashed_file.sha256sum):
  66. raise HashMismatchException('{0}: Does not match file already existing in the pool.'.format(hashed_file.filename))
  67. return poolfile
  68. except NoResultFound:
  69. raise KeyError('{0} not found in database.'.format(poolname))
  70. def _install_file(self, directory, hashed_file, archive, component, source_name):
  71. """Install a file
  72. Will not give an error when the file is already present.
  73. @rtype: L{daklib.dbconn.PoolFile}
  74. @return: database object for the new file
  75. """
  76. session = self.session
  77. poolname = os.path.join(utils.poolify(source_name), hashed_file.filename)
  78. try:
  79. poolfile = self.get_file(hashed_file, source_name)
  80. except KeyError:
  81. poolfile = PoolFile(filename=poolname, filesize=hashed_file.size)
  82. poolfile.md5sum = hashed_file.md5sum
  83. poolfile.sha1sum = hashed_file.sha1sum
  84. poolfile.sha256sum = hashed_file.sha256sum
  85. session.add(poolfile)
  86. session.flush()
  87. try:
  88. session.query(ArchiveFile).filter_by(archive=archive, component=component, file=poolfile).one()
  89. except NoResultFound:
  90. archive_file = ArchiveFile(archive, component, poolfile)
  91. session.add(archive_file)
  92. session.flush()
  93. path = os.path.join(archive.path, 'pool', component.component_name, poolname)
  94. hashed_file_path = os.path.join(directory, hashed_file.input_filename)
  95. self.fs.copy(hashed_file_path, path, link=False, mode=archive.mode)
  96. return poolfile
  97. def install_binary(self, directory, binary, suite, component, allow_tainted=False, fingerprint=None, source_suites=None, extra_source_archives=None):
  98. """Install a binary package
  99. @type directory: str
  100. @param directory: directory the binary package is located in
  101. @type binary: L{daklib.upload.Binary}
  102. @param binary: binary package to install
  103. @type suite: L{daklib.dbconn.Suite}
  104. @param suite: target suite
  105. @type component: L{daklib.dbconn.Component}
  106. @param component: target component
  107. @type allow_tainted: bool
  108. @param allow_tainted: allow to copy additional files from tainted archives
  109. @type fingerprint: L{daklib.dbconn.Fingerprint}
  110. @param fingerprint: optional fingerprint
  111. @type source_suites: SQLAlchemy subquery for C{daklib.dbconn.Suite} or C{True}
  112. @param source_suites: suites to copy the source from if they are not
  113. in C{suite} or C{True} to allow copying from any
  114. suite.
  115. @type extra_source_archives: list of L{daklib.dbconn.Archive}
  116. @param extra_source_archives: extra archives to copy Built-Using sources from
  117. @rtype: L{daklib.dbconn.DBBinary}
  118. @return: databse object for the new package
  119. """
  120. session = self.session
  121. control = binary.control
  122. maintainer = get_or_set_maintainer(control['Maintainer'], session)
  123. architecture = get_architecture(control['Architecture'], session)
  124. (source_name, source_version) = binary.source
  125. source_query = session.query(DBSource).filter_by(source=source_name, version=source_version)
  126. source = source_query.filter(DBSource.suites.contains(suite)).first()
  127. if source is None:
  128. if source_suites is not True:
  129. source_query = source_query.join(DBSource.suites) \
  130. .filter(Suite.suite_id == source_suites.c.id)
  131. source = source_query.first()
  132. if source is None:
  133. raise ArchiveException('{0}: trying to install to {1}, but could not find source ({2} {3})'.
  134. format(binary.hashed_file.filename, suite.suite_name, source_name, source_version))
  135. self.copy_source(source, suite, source.poolfile.component)
  136. db_file = self._install_file(directory, binary.hashed_file, suite.archive, component, source_name)
  137. unique = dict(
  138. package=control['Package'],
  139. version=control['Version'],
  140. architecture=architecture,
  141. )
  142. rest = dict(
  143. source=source,
  144. maintainer=maintainer,
  145. poolfile=db_file,
  146. binarytype=binary.type,
  147. )
  148. # Other attributes that are ignored for purposes of equality with
  149. # an existing source
  150. rest2 = dict(
  151. fingerprint=fingerprint,
  152. )
  153. try:
  154. db_binary = session.query(DBBinary).filter_by(**unique).one()
  155. for key, value in six.iteritems(rest):
  156. if getattr(db_binary, key) != value:
  157. raise ArchiveException('{0}: Does not match binary in database.'.format(binary.hashed_file.filename))
  158. except NoResultFound:
  159. db_binary = DBBinary(**unique)
  160. for key, value in six.iteritems(rest):
  161. setattr(db_binary, key, value)
  162. for key, value in six.iteritems(rest2):
  163. setattr(db_binary, key, value)
  164. session.add(db_binary)
  165. session.flush()
  166. import_metadata_into_db(db_binary, session)
  167. self._add_built_using(db_binary, binary.hashed_file.filename, control, suite, extra_archives=extra_source_archives)
  168. if suite not in db_binary.suites:
  169. db_binary.suites.append(suite)
  170. session.flush()
  171. return db_binary
  172. def _ensure_extra_source_exists(self, filename, source, archive, extra_archives=None):
  173. """ensure source exists in the given archive
  174. This is intended to be used to check that Built-Using sources exist.
  175. @type filename: str
  176. @param filename: filename to use in error messages
  177. @type source: L{daklib.dbconn.DBSource}
  178. @param source: source to look for
  179. @type archive: L{daklib.dbconn.Archive}
  180. @param archive: archive to look in
  181. @type extra_archives: list of L{daklib.dbconn.Archive}
  182. @param extra_archives: list of archives to copy the source package from
  183. if it is not yet present in C{archive}
  184. """
  185. session = self.session
  186. db_file = session.query(ArchiveFile).filter_by(file=source.poolfile, archive=archive).first()
  187. if db_file is not None:
  188. return True
  189. # Try to copy file from one extra archive
  190. if extra_archives is None:
  191. extra_archives = []
  192. db_file = session.query(ArchiveFile).filter_by(file=source.poolfile).filter(ArchiveFile.archive_id.in_([a.archive_id for a in extra_archives])).first()
  193. if db_file is None:
  194. raise ArchiveException('{0}: Built-Using refers to package {1} (= {2}) not in target archive {3}.'.format(filename, source.source, source.version, archive.archive_name))
  195. source_archive = db_file.archive
  196. for dsc_file in source.srcfiles:
  197. af = session.query(ArchiveFile).filter_by(file=dsc_file.poolfile, archive=source_archive, component=db_file.component).one()
  198. # We were given an explicit list of archives so it is okay to copy from tainted archives.
  199. self._copy_file(af.file, archive, db_file.component, allow_tainted=True)
  200. def _add_built_using(self, db_binary, filename, control, suite, extra_archives=None):
  201. """Add Built-Using sources to C{db_binary.extra_sources}
  202. """
  203. session = self.session
  204. for bu_source_name, bu_source_version in daklib.utils.parse_built_using(control):
  205. bu_source = session.query(DBSource).filter_by(source=bu_source_name, version=bu_source_version).first()
  206. if bu_source is None:
  207. raise ArchiveException('{0}: Built-Using refers to non-existing source package {1} (= {2})'.format(filename, bu_source_name, bu_source_version))
  208. self._ensure_extra_source_exists(filename, bu_source, suite.archive, extra_archives=extra_archives)
  209. db_binary.extra_sources.append(bu_source)
  210. def install_source_to_archive(self, directory, source, archive, component, changed_by, allow_tainted=False, fingerprint=None):
  211. session = self.session
  212. control = source.dsc
  213. maintainer = get_or_set_maintainer(control['Maintainer'], session)
  214. source_name = control['Source']
  215. ### Add source package to database
  216. # We need to install the .dsc first as the DBSource object refers to it.
  217. db_file_dsc = self._install_file(directory, source._dsc_file, archive, component, source_name)
  218. unique = dict(
  219. source=source_name,
  220. version=control['Version'],
  221. )
  222. rest = dict(
  223. maintainer=maintainer,
  224. poolfile=db_file_dsc,
  225. dm_upload_allowed=(control.get('DM-Upload-Allowed', 'no') == 'yes'),
  226. )
  227. # Other attributes that are ignored for purposes of equality with
  228. # an existing source
  229. rest2 = dict(
  230. changedby=changed_by,
  231. fingerprint=fingerprint,
  232. )
  233. created = False
  234. try:
  235. db_source = session.query(DBSource).filter_by(**unique).one()
  236. for key, value in six.iteritems(rest):
  237. if getattr(db_source, key) != value:
  238. raise ArchiveException('{0}: Does not match source in database.'.format(source._dsc_file.filename))
  239. except NoResultFound:
  240. created = True
  241. db_source = DBSource(**unique)
  242. for key, value in six.iteritems(rest):
  243. setattr(db_source, key, value)
  244. for key, value in six.iteritems(rest2):
  245. setattr(db_source, key, value)
  246. session.add(db_source)
  247. session.flush()
  248. # Add .dsc file. Other files will be added later.
  249. db_dsc_file = DSCFile()
  250. db_dsc_file.source = db_source
  251. db_dsc_file.poolfile = db_file_dsc
  252. session.add(db_dsc_file)
  253. session.flush()
  254. if not created:
  255. for f in db_source.srcfiles:
  256. self._copy_file(f.poolfile, archive, component, allow_tainted=allow_tainted)
  257. return db_source
  258. ### Now add remaining files and copy them to the archive.
  259. for hashed_file in six.itervalues(source.files):
  260. hashed_file_path = os.path.join(directory, hashed_file.input_filename)
  261. if os.path.exists(hashed_file_path):
  262. db_file = self._install_file(directory, hashed_file, archive, component, source_name)
  263. session.add(db_file)
  264. else:
  265. db_file = self.get_file(hashed_file, source_name)
  266. self._copy_file(db_file, archive, component, allow_tainted=allow_tainted)
  267. db_dsc_file = DSCFile()
  268. db_dsc_file.source = db_source
  269. db_dsc_file.poolfile = db_file
  270. session.add(db_dsc_file)
  271. session.flush()
  272. # Importing is safe as we only arrive here when we did not find the source already installed earlier.
  273. import_metadata_into_db(db_source, session)
  274. # Uploaders are the maintainer and co-maintainers from the Uploaders field
  275. db_source.uploaders.append(maintainer)
  276. if 'Uploaders' in control:
  277. from daklib.textutils import split_uploaders
  278. for u in split_uploaders(control['Uploaders']):
  279. db_source.uploaders.append(get_or_set_maintainer(u, session))
  280. session.flush()
  281. return db_source
  282. def install_source(self, directory, source, suite, component, changed_by, allow_tainted=False, fingerprint=None):
  283. """Install a source package
  284. @type directory: str
  285. @param directory: directory the source package is located in
  286. @type source: L{daklib.upload.Source}
  287. @param source: source package to install
  288. @type suite: L{daklib.dbconn.Suite}
  289. @param suite: target suite
  290. @type component: L{daklib.dbconn.Component}
  291. @param component: target component
  292. @type changed_by: L{daklib.dbconn.Maintainer}
  293. @param changed_by: person who prepared this version of the package
  294. @type allow_tainted: bool
  295. @param allow_tainted: allow to copy additional files from tainted archives
  296. @type fingerprint: L{daklib.dbconn.Fingerprint}
  297. @param fingerprint: optional fingerprint
  298. @rtype: L{daklib.dbconn.DBSource}
  299. @return: database object for the new source
  300. """
  301. db_source = self.install_source_to_archive(directory, source, suite.archive, component, changed_by, allow_tainted, fingerprint)
  302. if suite in db_source.suites:
  303. return db_source
  304. db_source.suites.append(suite)
  305. self.session.flush()
  306. return db_source
  307. def _copy_file(self, db_file, archive, component, allow_tainted=False):
  308. """Copy a file to the given archive and component
  309. @type db_file: L{daklib.dbconn.PoolFile}
  310. @param db_file: file to copy
  311. @type archive: L{daklib.dbconn.Archive}
  312. @param archive: target archive
  313. @type component: L{daklib.dbconn.Archive}
  314. @param component: target component
  315. @type allow_tainted: bool
  316. @param allow_tainted: allow to copy from tainted archives (such as NEW)
  317. """
  318. session = self.session
  319. if session.query(ArchiveFile).filter_by(archive=archive, component=component, file=db_file).first() is None:
  320. query = session.query(ArchiveFile).filter_by(file=db_file)
  321. if not allow_tainted:
  322. query = query.join(Archive).filter(Archive.tainted == False) # noqa:E712
  323. source_af = query.first()
  324. if source_af is None:
  325. raise ArchiveException('cp: Could not find {0} in any archive.'.format(db_file.filename))
  326. target_af = ArchiveFile(archive, component, db_file)
  327. session.add(target_af)
  328. session.flush()
  329. self.fs.copy(source_af.path, target_af.path, link=False, mode=archive.mode)
  330. def copy_binary(self, db_binary, suite, component, allow_tainted=False, extra_archives=None):
  331. """Copy a binary package to the given suite and component
  332. @type db_binary: L{daklib.dbconn.DBBinary}
  333. @param db_binary: binary to copy
  334. @type suite: L{daklib.dbconn.Suite}
  335. @param suite: target suite
  336. @type component: L{daklib.dbconn.Component}
  337. @param component: target component
  338. @type allow_tainted: bool
  339. @param allow_tainted: allow to copy from tainted archives (such as NEW)
  340. @type extra_archives: list of L{daklib.dbconn.Archive}
  341. @param extra_archives: extra archives to copy Built-Using sources from
  342. """
  343. session = self.session
  344. archive = suite.archive
  345. if archive.tainted:
  346. allow_tainted = True
  347. filename = db_binary.poolfile.filename
  348. # make sure source is present in target archive
  349. db_source = db_binary.source
  350. if session.query(ArchiveFile).filter_by(archive=archive, file=db_source.poolfile).first() is None:
  351. raise ArchiveException('{0}: cannot copy to {1}: source is not present in target archive'.format(filename, suite.suite_name))
  352. # make sure built-using packages are present in target archive
  353. for db_source in db_binary.extra_sources:
  354. self._ensure_extra_source_exists(filename, db_source, archive, extra_archives=extra_archives)
  355. # copy binary
  356. db_file = db_binary.poolfile
  357. self._copy_file(db_file, suite.archive, component, allow_tainted=allow_tainted)
  358. if suite not in db_binary.suites:
  359. db_binary.suites.append(suite)
  360. self.session.flush()
  361. def copy_source(self, db_source, suite, component, allow_tainted=False):
  362. """Copy a source package to the given suite and component
  363. @type db_source: L{daklib.dbconn.DBSource}
  364. @param db_source: source to copy
  365. @type suite: L{daklib.dbconn.Suite}
  366. @param suite: target suite
  367. @type component: L{daklib.dbconn.Component}
  368. @param component: target component
  369. @type allow_tainted: bool
  370. @param allow_tainted: allow to copy from tainted archives (such as NEW)
  371. """
  372. archive = suite.archive
  373. if archive.tainted:
  374. allow_tainted = True
  375. for db_dsc_file in db_source.srcfiles:
  376. self._copy_file(db_dsc_file.poolfile, archive, component, allow_tainted=allow_tainted)
  377. if suite not in db_source.suites:
  378. db_source.suites.append(suite)
  379. self.session.flush()
  380. def remove_file(self, db_file, archive, component):
  381. """Remove a file from a given archive and component
  382. @type db_file: L{daklib.dbconn.PoolFile}
  383. @param db_file: file to remove
  384. @type archive: L{daklib.dbconn.Archive}
  385. @param archive: archive to remove the file from
  386. @type component: L{daklib.dbconn.Component}
  387. @param component: component to remove the file from
  388. """
  389. af = self.session.query(ArchiveFile).filter_by(file=db_file, archive=archive, component=component)
  390. self.fs.unlink(af.path)
  391. self.session.delete(af)
  392. def remove_binary(self, binary, suite):
  393. """Remove a binary from a given suite and component
  394. @type binary: L{daklib.dbconn.DBBinary}
  395. @param binary: binary to remove
  396. @type suite: L{daklib.dbconn.Suite}
  397. @param suite: suite to remove the package from
  398. """
  399. binary.suites.remove(suite)
  400. self.session.flush()
  401. def remove_source(self, source, suite):
  402. """Remove a source from a given suite and component
  403. @type source: L{daklib.dbconn.DBSource}
  404. @param source: source to remove
  405. @type suite: L{daklib.dbconn.Suite}
  406. @param suite: suite to remove the package from
  407. @raise ArchiveException: source package is still referenced by other
  408. binaries in the suite
  409. """
  410. session = self.session
  411. query = session.query(DBBinary).filter_by(source=source) \
  412. .filter(DBBinary.suites.contains(suite))
  413. if query.first() is not None:
  414. raise ArchiveException('src:{0} is still used by binaries in suite {1}'.format(source.source, suite.suite_name))
  415. source.suites.remove(suite)
  416. session.flush()
  417. def commit(self):
  418. """commit changes"""
  419. try:
  420. self.session.commit()
  421. self.fs.commit()
  422. finally:
  423. self.session.rollback()
  424. self.fs.rollback()
  425. def rollback(self):
  426. """rollback changes"""
  427. self.session.rollback()
  428. self.fs.rollback()
  429. def flush(self):
  430. self.session.flush()
  431. def __enter__(self):
  432. return self
  433. def __exit__(self, type, value, traceback):
  434. if type is None:
  435. self.commit()
  436. else:
  437. self.rollback()
  438. return None
  439. def source_component_from_package_list(package_list, suite):
  440. """Get component for a source package
  441. This function will look at the Package-List field to determine the
  442. component the source package belongs to. This is the first component
  443. the source package provides binaries for (first with respect to the
  444. ordering of components).
  445. It the source package has no Package-List field, None is returned.
  446. @type package_list: L{daklib.packagelist.PackageList}
  447. @param package_list: package list of the source to get the override for
  448. @type suite: L{daklib.dbconn.Suite}
  449. @param suite: suite to consider for binaries produced
  450. @rtype: L{daklib.dbconn.Component} or C{None}
  451. @return: component for the given source or C{None}
  452. """
  453. if package_list.fallback:
  454. return None
  455. session = object_session(suite)
  456. packages = package_list.packages_for_suite(suite)
  457. components = set(p.component for p in packages)
  458. query = session.query(Component).order_by(Component.ordering) \
  459. .filter(Component.component_name.in_(components))
  460. return query.first()
  461. class ArchiveUpload(object):
  462. """handle an upload
  463. This class can be used in a with-statement::
  464. with ArchiveUpload(...) as upload:
  465. ...
  466. Doing so will automatically run any required cleanup and also rollback the
  467. transaction if it was not committed.
  468. """
  469. def __init__(self, directory, changes, keyrings):
  470. self.transaction = ArchiveTransaction()
  471. """transaction used to handle the upload
  472. @type: L{daklib.archive.ArchiveTransaction}
  473. """
  474. self.session = self.transaction.session
  475. """database session"""
  476. self.original_directory = directory
  477. self.original_changes = changes
  478. self.changes = None
  479. """upload to process
  480. @type: L{daklib.upload.Changes}
  481. """
  482. self.directory = None
  483. """directory with temporary copy of files. set by C{prepare}
  484. @type: str
  485. """
  486. self.keyrings = keyrings
  487. self.fingerprint = self.session.query(Fingerprint).filter_by(fingerprint=changes.primary_fingerprint).one()
  488. """fingerprint of the key used to sign the upload
  489. @type: L{daklib.dbconn.Fingerprint}
  490. """
  491. self.reject_reasons = []
  492. """reasons why the upload cannot by accepted
  493. @type: list of str
  494. """
  495. self.warnings = []
  496. """warnings
  497. @note: Not used yet.
  498. @type: list of str
  499. """
  500. self.final_suites = None
  501. self.new = False
  502. """upload is NEW. set by C{check}
  503. @type: bool
  504. """
  505. self._checked = False
  506. """checks passes. set by C{check}
  507. @type: bool
  508. """
  509. self._new_queue = self.session.query(PolicyQueue).filter_by(queue_name='new').one()
  510. self._new = self._new_queue.suite
  511. def warn(self, message):
  512. """add a warning message
  513. Adds a warning message that can later be seen in C{self.warnings}
  514. @type message: string
  515. @param message: warning message
  516. """
  517. self.warnings.append(message)
  518. def prepare(self):
  519. """prepare upload for further processing
  520. This copies the files involved to a temporary directory. If you use
  521. this method directly, you have to remove the directory given by the
  522. C{directory} attribute later on your own.
  523. Instead of using the method directly, you can also use a with-statement::
  524. with ArchiveUpload(...) as upload:
  525. ...
  526. This will automatically handle any required cleanup.
  527. """
  528. assert self.directory is None
  529. assert self.original_changes.valid_signature
  530. cnf = Config()
  531. session = self.transaction.session
  532. group = cnf.get('Dinstall::UnprivGroup') or None
  533. self.directory = utils.temp_dirname(parent=cnf.get('Dir::TempPath'),
  534. mode=0o2750, group=group)
  535. with FilesystemTransaction() as fs:
  536. src = os.path.join(self.original_directory, self.original_changes.filename)
  537. dst = os.path.join(self.directory, self.original_changes.filename)
  538. fs.copy(src, dst, mode=0o640)
  539. self.changes = upload.Changes(self.directory, self.original_changes.filename, self.keyrings)
  540. files = {}
  541. try:
  542. files = self.changes.files
  543. except upload.InvalidChangesException:
  544. # Do not raise an exception; upload will be rejected later
  545. # due to the missing files
  546. pass
  547. for f in six.itervalues(files):
  548. src = os.path.join(self.original_directory, f.filename)
  549. dst = os.path.join(self.directory, f.filename)
  550. if not os.path.exists(src):
  551. continue
  552. fs.copy(src, dst, mode=0o640)
  553. source = None
  554. try:
  555. source = self.changes.source
  556. except Exception:
  557. # Do not raise an exception here if the .dsc is invalid.
  558. pass
  559. if source is not None:
  560. for f in six.itervalues(source.files):
  561. src = os.path.join(self.original_directory, f.filename)
  562. dst = os.path.join(self.directory, f.filename)
  563. if not os.path.exists(dst):
  564. try:
  565. db_file = self.transaction.get_file(f, source.dsc['Source'], check_hashes=False)
  566. db_archive_file = session.query(ArchiveFile).filter_by(file=db_file).first()
  567. fs.copy(db_archive_file.path, dst, mode=0o640)
  568. except KeyError:
  569. # Ignore if get_file could not find it. Upload will
  570. # probably be rejected later.
  571. pass
  572. def unpacked_source(self):
  573. """Path to unpacked source
  574. Get path to the unpacked source. This method does unpack the source
  575. into a temporary directory under C{self.directory} if it has not
  576. been done so already.
  577. @rtype: str or C{None}
  578. @return: string giving the path to the unpacked source directory
  579. or C{None} if no source was included in the upload.
  580. """
  581. assert self.directory is not None
  582. source = self.changes.source
  583. if source is None:
  584. return None
  585. dsc_path = os.path.join(self.directory, source._dsc_file.filename)
  586. sourcedir = os.path.join(self.directory, 'source')
  587. if not os.path.exists(sourcedir):
  588. with open('/dev/null', 'w') as devnull:
  589. daklib.daksubprocess.check_call(["dpkg-source", "--no-copy", "--no-check", "-x", dsc_path, sourcedir], shell=False, stdout=devnull)
  590. if not os.path.isdir(sourcedir):
  591. raise Exception("{0} is not a directory after extracting source package".format(sourcedir))
  592. return sourcedir
  593. def _map_suite(self, suite_name):
  594. suite_names = set((suite_name, ))
  595. for rule in Config().value_list("SuiteMappings"):
  596. fields = rule.split()
  597. rtype = fields[0]
  598. if rtype == "map" or rtype == "silent-map":
  599. (src, dst) = fields[1:3]
  600. if src in suite_names:
  601. suite_names.remove(src)
  602. suite_names.add(dst)
  603. if rtype != "silent-map":
  604. self.warnings.append('Mapping {0} to {1}.'.format(src, dst))
  605. elif rtype == "copy" or rtype == "silent-copy":
  606. (src, dst) = fields[1:3]
  607. if src in suite_names:
  608. suite_names.add(dst)
  609. if rtype != "silent-copy":
  610. self.warnings.append('Copy {0} to {1}.'.format(src, dst))
  611. elif rtype == "ignore":
  612. ignored = fields[1]
  613. if ignored in suite_names:
  614. suite_names.remove(ignored)
  615. self.warnings.append('Ignoring target suite {0}.'.format(ignored))
  616. elif rtype == "reject":
  617. rejected = fields[1]
  618. if rejected in suite_names:
  619. raise checks.Reject('Uploads to {0} are not accepted.'.format(rejected))
  620. ## XXX: propup-version and map-unreleased not yet implemented
  621. return suite_names
  622. def _mapped_suites(self):
  623. """Get target suites after mappings
  624. @rtype: list of L{daklib.dbconn.Suite}
  625. @return: list giving the mapped target suites of this upload
  626. """
  627. session = self.session
  628. suite_names = set()
  629. for dist in self.changes.distributions:
  630. suite_names.update(self._map_suite(dist))
  631. suites = session.query(Suite).filter(Suite.suite_name.in_(suite_names))
  632. return suites
  633. def _check_new_binary_overrides(self, suite, overridesuite):
  634. new = False
  635. source = self.changes.source
  636. # Check binaries listed in the source package's Package-List field:
  637. if source is not None and not source.package_list.fallback:
  638. packages = source.package_list.packages_for_suite(suite)
  639. binaries = [entry for entry in packages]
  640. for b in binaries:
  641. override = self._binary_override(overridesuite, b)
  642. if override is None:
  643. self.warnings.append('binary:{0} is NEW.'.format(b.name))
  644. new = True
  645. # Check all uploaded packages.
  646. # This is necessary to account for packages without a Package-List
  647. # field, really late binary-only uploads (where an unused override
  648. # was already removed), and for debug packages uploaded to a suite
  649. # without a debug suite (which are then considered as NEW).
  650. binaries = self.changes.binaries
  651. for b in binaries:
  652. if utils.is_in_debug_section(b.control) and suite.debug_suite is not None:
  653. continue
  654. override = self._binary_override(overridesuite, b)
  655. if override is None:
  656. self.warnings.append('binary:{0} is NEW.'.format(b.name))
  657. new = True
  658. return new
  659. def _check_new(self, suite, overridesuite):
  660. """Check if upload is NEW
  661. An upload is NEW if it has binary or source packages that do not have
  662. an override in C{overridesuite} OR if it references files ONLY in a
  663. tainted archive (eg. when it references files in NEW).
  664. Debug packages (*-dbgsym in Section: debug) are not considered as NEW
  665. if C{suite} has a separate debug suite.
  666. @rtype: bool
  667. @return: C{True} if the upload is NEW, C{False} otherwise
  668. """
  669. session = self.session
  670. new = False
  671. # Check for missing overrides
  672. if self._check_new_binary_overrides(suite, overridesuite):
  673. new = True
  674. if self.changes.source is not None:
  675. override = self._source_override(overridesuite, self.changes.source)
  676. if override is None:
  677. self.warnings.append('source:{0} is NEW.'.format(self.changes.source.dsc['Source']))
  678. new = True
  679. # Check if we reference a file only in a tainted archive
  680. files = list(self.changes.files.values())
  681. if self.changes.source is not None:
  682. files.extend(self.changes.source.files.values())
  683. for f in files:
  684. query = session.query(ArchiveFile).join(PoolFile).filter(PoolFile.sha1sum == f.sha1sum)
  685. query_untainted = query.join(Archive).filter(Archive.tainted == False) # noqa:E712
  686. in_archive = (query.first() is not None)
  687. in_untainted_archive = (query_untainted.first() is not None)
  688. if in_archive and not in_untainted_archive:
  689. self.warnings.append('{0} is only available in NEW.'.format(f.filename))
  690. new = True
  691. return new
  692. def _final_suites(self):
  693. session = self.session
  694. mapped_suites = self._mapped_suites()
  695. final_suites = list()
  696. for suite in mapped_suites:
  697. overridesuite = suite
  698. if suite.overridesuite is not None:
  699. overridesuite = session.query(Suite).filter_by(suite_name=suite.overridesuite).one()
  700. if self._check_new(suite, overridesuite):
  701. self.new = True
  702. if suite not in final_suites:
  703. final_suites.append(suite)
  704. return final_suites
  705. def _binary_override(self, suite, binary):
  706. """Get override entry for a binary
  707. @type suite: L{daklib.dbconn.Suite}
  708. @param suite: suite to get override for
  709. @type binary: L{daklib.upload.Binary} or L{daklib.packagelist.PackageListEntry}
  710. @param binary: binary to get override for
  711. @rtype: L{daklib.dbconn.Override} or C{None}
  712. @return: override for the given binary or C{None}
  713. """
  714. if suite.overridesuite is not None:
  715. suite = self.session.query(Suite).filter_by(suite_name=suite.overridesuite).one()
  716. mapped_component = get_mapped_component(binary.component)
  717. if mapped_component is None:
  718. return None
  719. query = self.session.query(Override).filter_by(suite=suite, package=binary.name) \
  720. .join(Component).filter(Component.component_name == mapped_component.component_name) \
  721. .join(OverrideType).filter(OverrideType.overridetype == binary.type)
  722. try:
  723. return query.one()
  724. except NoResultFound:
  725. return None
  726. def _source_override(self, suite, source):
  727. """Get override entry for a source
  728. @type suite: L{daklib.dbconn.Suite}
  729. @param suite: suite to get override for
  730. @type source: L{daklib.upload.Source}
  731. @param source: source to get override for
  732. @rtype: L{daklib.dbconn.Override} or C{None}
  733. @return: override for the given source or C{None}
  734. """
  735. if suite.overridesuite is not None:
  736. suite = self.session.query(Suite).filter_by(suite_name=suite.overridesuite).one()
  737. query = self.session.query(Override).filter_by(suite=suite, package=source.dsc['Source']) \
  738. .join(OverrideType).filter(OverrideType.overridetype == 'dsc')
  739. component = source_component_from_package_list(source.package_list, suite)
  740. if component is not None:
  741. query = query.filter(Override.component == component)
  742. try:
  743. return query.one()
  744. except NoResultFound:
  745. return None
  746. def _binary_component(self, suite, binary, only_overrides=True):
  747. """get component for a binary
  748. By default this will only look at overrides to get the right component;
  749. if C{only_overrides} is C{False} this method will also look at the
  750. Section field.
  751. @type suite: L{daklib.dbconn.Suite}
  752. @type binary: L{daklib.upload.Binary}
  753. @type only_overrides: bool
  754. @param only_overrides: only use overrides to get the right component
  755. @rtype: L{daklib.dbconn.Component} or C{None}
  756. """
  757. override = self._binary_override(suite, binary)
  758. if override is not None:
  759. return override.component
  760. if only_overrides:
  761. return None
  762. return get_mapped_component(binary.component, self.session)
  763. def _source_component(self, suite, source, only_overrides=True):
  764. """get component for a source
  765. By default this will only look at overrides to get the right component;
  766. if C{only_overrides} is C{False} this method will also look at the
  767. Section field.
  768. @type suite: L{daklib.dbconn.Suite}
  769. @type binary: L{daklib.upload.Binary}
  770. @type only_overrides: bool
  771. @param only_overrides: only use overrides to get the right component
  772. @rtype: L{daklib.dbconn.Component} or C{None}
  773. """
  774. override = self._source_override(suite, source)
  775. if override is not None:
  776. return override.component
  777. if only_overrides:
  778. return None
  779. return get_mapped_component(source.component, self.session)
  780. def check(self, force=False):
  781. """run checks against the upload
  782. @type force: bool
  783. @param force: ignore failing forcable checks
  784. @rtype: bool
  785. @return: C{True} if all checks passed, C{False} otherwise
  786. """
  787. # XXX: needs to be better structured.
  788. assert self.changes.valid_signature
  789. try:
  790. # Validate signatures and hashes before we do any real work:
  791. for chk in (
  792. checks.SignatureAndHashesCheck,
  793. checks.WeakSignatureCheck,
  794. checks.SignatureTimestampCheck,
  795. checks.ChangesCheck,
  796. #checks.SuffixCheck,
  797. checks.ExternalHashesCheck,
  798. checks.SourceCheck,
  799. checks.BinaryCheck,
  800. checks.BinaryTimestampCheck,
  801. checks.SingleDistributionCheck,
  802. checks.ArchAllBinNMUCheck,
  803. ):
  804. chk().check(self)
  805. final_suites = self._final_suites()
  806. if len(final_suites) == 0:
  807. self.reject_reasons.append('No target suite found. Please check your target distribution and that you uploaded to the right archive.')
  808. return False
  809. self.final_suites = final_suites
  810. for chk in (
  811. checks.TransitionCheck,
  812. checks.ACLCheck,
  813. checks.NewOverrideCheck,
  814. checks.NoSourceOnlyCheck,
  815. checks.LintianCheck,
  816. ):
  817. chk().check(self)
  818. for chk in (
  819. checks.SuiteCheck,
  820. checks.ACLCheck,
  821. checks.SourceFormatCheck,
  822. checks.SuiteArchitectureCheck,
  823. checks.VersionCheck,
  824. ):
  825. for suite in final_suites:
  826. chk().per_suite_check(self, suite)
  827. if len(self.reject_reasons) != 0:
  828. return False
  829. self._checked = True
  830. return True
  831. except checks.Reject as e:
  832. self.reject_reasons.append(six.text_type(e))
  833. except Exception as e:
  834. self.reject_reasons.append("Processing raised an exception: {0}.\n{1}".format(e, traceback.format_exc()))
  835. return False
  836. def _install_to_suite(self, target_suite, suite, source_component_func, binary_component_func, source_suites=None, extra_source_archives=None, policy_upload=False):
  837. """Install upload to the given suite
  838. @type target_suite: L{daklib.dbconn.Suite}
  839. @param target_suite: target suite (before redirection to policy queue or NEW)
  840. @type suite: L{daklib.dbconn.Suite}
  841. @param suite: suite to install the package into. This is the real suite,
  842. ie. after any redirection to NEW or a policy queue
  843. @param source_component_func: function to get the L{daklib.dbconn.Component}
  844. for a L{daklib.upload.Source} object
  845. @param binary_component_func: function to get the L{daklib.dbconn.Component}
  846. for a L{daklib.upload.Binary} object
  847. @param source_suites: see L{daklib.archive.ArchiveTransaction.install_binary}
  848. @param extra_source_archives: see L{daklib.archive.ArchiveTransaction.install_binary}
  849. @param policy_upload: Boolean indicating upload to policy queue (including NEW)
  850. @return: tuple with two elements. The first is a L{daklib.dbconn.DBSource}
  851. object for the install source or C{None} if no source was
  852. included. The second is a list of L{daklib.dbconn.DBBinary}
  853. objects for the installed binary packages.
  854. """
  855. # XXX: move this function to ArchiveTransaction?
  856. control = self.changes.changes
  857. changed_by = get_or_set_maintainer(control.get('Changed-By', control['Maintainer']), self.session)
  858. if source_suites is None:
  859. source_suites = self.session.query(Suite).join((VersionCheck, VersionCheck.reference_id == Suite.suite_id)).filter(VersionCheck.check == 'Enhances').filter(VersionCheck.suite == suite).subquery()
  860. source = self.changes.source
  861. if source is not None:
  862. component = source_component_func(source)
  863. db_source = self.transaction.install_source(
  864. self.directory,
  865. source,
  866. suite,
  867. component,
  868. changed_by,
  869. fingerprint=self.fingerprint
  870. )
  871. else:
  872. db_source = None
  873. db_binaries = []
  874. for binary in sorted(self.changes.binaries, key=lambda x: x.name):
  875. copy_to_suite = suite
  876. if utils.is_in_debug_section(binary.control) and suite.debug_suite is not None:
  877. copy_to_suite = suite.debug_suite
  878. component = binary_component_func(binary)
  879. db_binary = self.transaction.install_binary(
  880. self.directory,
  881. binary,
  882. copy_to_suite,
  883. component,
  884. fingerprint=self.fingerprint,
  885. source_suites=source_suites,
  886. extra_source_archives=extra_source_archives
  887. )
  888. db_binaries.append(db_binary)
  889. if not policy_upload:
  890. check_upload_for_external_signature_request(self.session, target_suite, copy_to_suite, db_binary)
  891. if suite.copychanges:
  892. src = os.path.join(self.directory, self.changes.filename)
  893. dst = os.path.join(suite.archive.path, 'dists', suite.suite_name, self.changes.filename)
  894. self.transaction.fs.copy(src, dst, mode=suite.archive.mode)
  895. suite.update_last_changed()
  896. return (db_source, db_binaries)
  897. def _install_changes(self):
  898. assert self.changes.valid_signature
  899. control = self.changes.changes
  900. session = self.transaction.session
  901. config = Config()
  902. changelog_id = None
  903. # Only add changelog for sourceful uploads and binNMUs
  904. if self.changes.sourceful or re_bin_only_nmu.search(control['Version']):
  905. query = 'INSERT INTO changelogs_text (changelog) VALUES (:changelog) RETURNING id'
  906. changelog_id = session.execute(query, {'changelog': control['Changes']}).scalar()
  907. assert changelog_id is not None
  908. db_changes = DBChange()
  909. db_changes.changesname = self.changes.filename
  910. db_changes.source = control['Source']
  911. db_changes.binaries = control.get('Binary', None)
  912. db_changes.architecture = control['Architecture']
  913. db_changes.version = control['Version']
  914. db_changes.distribution = control['Distribution']
  915. db_changes.urgency = control['Urgency']
  916. db_changes.maintainer = control['Maintainer']
  917. db_changes.changedby = control.get('Changed-By', control['Maintainer'])
  918. db_changes.date = control['Date']
  919. db_changes.fingerprint = self.fingerprint.fingerprint
  920. db_changes.changelog_id = changelog_id
  921. db_changes.closes = self.changes.closed_bugs
  922. try:
  923. self.transaction.session.add(db_changes)
  924. self.transaction.session.flush()
  925. except sqlalchemy.exc.IntegrityError:
  926. raise ArchiveException('{0} is already known.'.format(self.changes.filename))
  927. return db_changes
  928. def _install_policy(self, policy_queue, target_suite, db_changes, db_source, db_binaries):
  929. u = PolicyQueueUpload()
  930. u.policy_queue = policy_queue
  931. u.target_suite = target_suite
  932. u.changes = db_changes
  933. u.source = db_source
  934. u.binaries = db_binaries
  935. self.transaction.session.add(u)
  936. self.transaction.session.flush()
  937. queue_files = [self.changes.filename]
  938. queue_files.extend(f.filename for f in self.changes.buildinfo_files)
  939. for fn in queue_files:
  940. src = os.path.join(self.changes.directory, fn)
  941. dst = os.path.join(policy_queue.path, fn)
  942. self.transaction.fs.copy(src, dst, mode=policy_queue.change_perms)
  943. return u
  944. def try_autobyhand(self):
  945. """Try AUTOBYHAND
  946. Try to handle byhand packages automatically.
  947. @rtype: list of L{daklib.upload.HashedFile}
  948. @return: list of remaining byhand files
  949. """
  950. assert len(self.reject_reasons) == 0
  951. assert self.changes.valid_signature
  952. assert self.final_suites is not None
  953. assert self._checked
  954. byhand = self.changes.byhand_files
  955. if len(byhand) == 0:
  956. return True
  957. suites = list(self.final_suites)
  958. assert len(suites) == 1, "BYHAND uploads must be to a single suite"
  959. suite = suites[0]
  960. cnf = Config()
  961. control = self.changes.changes
  962. automatic_byhand_packages = cnf.subtree("AutomaticByHandPackages")
  963. remaining = []
  964. for f in byhand:
  965. if '_' in f.filename:
  966. parts = f.filename.split('_', 2)
  967. if len(parts) != 3:
  968. print("W: unexpected byhand filename {0}. No automatic processing.".format(f.filename))
  969. remaining.append(f)
  970. continue
  971. package, version, archext = parts
  972. arch, ext = archext.split('.', 1)
  973. else:
  974. parts = f.filename.split('.')
  975. if len(parts) < 2:
  976. print("W: unexpected byhand filename {0}. No automatic processing.".format(f.filename))
  977. remaining.append(f)
  978. continue
  979. package = parts[0]
  980. version = '0'
  981. arch = 'all'
  982. ext = parts[-1]
  983. try:
  984. rule = automatic_byhand_packages.subtree(package)
  985. except KeyError:
  986. remaining.append(f)
  987. continue
  988. if rule['Source'] != self.changes.source_name \
  989. or rule['Section'] != f.section \
  990. or ('Extension' in rule and rule['Extension'] != ext):
  991. remaining.append(f)
  992. continue
  993. script = rule['Script']
  994. retcode = daklib.daksubprocess.call([script, os.path.join(self.directory, f.filename), control['Version'], arch, os.path.join(self.directory, self.changes.filename), suite.suite_name], shell=False)
  995. if retcode != 0:
  996. print("W: error processing {0}.".format(f.filename))
  997. remaining.append(f)
  998. return len(remaining) == 0
  999. def _install_byhand(self, policy_queue_upload, hashed_file):
  1000. """install byhand file
  1001. @type policy_queue_upload: L{daklib.dbconn.PolicyQueueUpload}
  1002. @type hashed_file: L{daklib.upload.HashedFile}
  1003. """
  1004. fs = self.transaction.fs
  1005. session = self.transaction.session
  1006. policy_queue = policy_queue_upload.policy_queue
  1007. byhand_file = PolicyQueueByhandFile()
  1008. byhand_file.upload = policy_queue_upload
  1009. byhand_file.filename = hashed_file.filename
  1010. session.add(byhand_file)
  1011. session.flush()
  1012. src = os.path.join(self.directory, hashed_file.filename)
  1013. dst = os.path.join(policy_queue.path, hashed_file.filename)
  1014. fs.copy(src, dst, mode=policy_queue.change_perms)
  1015. return byhand_file
  1016. def _do_bts_versiontracking(self):
  1017. cnf = Config()
  1018. fs = self.transaction.fs
  1019. btsdir = cnf.get('Dir::BTSVersionTrack')
  1020. if btsdir is None or btsdir == '':
  1021. return
  1022. base = os.path.join(btsdir, self.changes.filename[:-8])
  1023. # version history
  1024. sourcedir = self.unpacked_source()
  1025. if sourcedir is not None:
  1026. dch_path = os.path.join(sourcedir, 'debian', 'changelog')
  1027. with open(dch_path, 'r') as fh:
  1028. versions = fs.create("{0}.versions".format(base), mode=0o644)
  1029. for line in fh.readlines():
  1030. if re_changelog_versions.match(line):
  1031. versions.write(line)
  1032. versions.close()
  1033. # binary -> source mapping
  1034. if self.changes.binaries:
  1035. debinfo = fs.create("{0}.debinfo".format(base), mode=0o644)
  1036. for binary in self.changes.binaries:
  1037. control = binary.control
  1038. source_package, source_version = binary.source
  1039. line = " ".join([control['Package'], control['Version'], control['Architecture'], source_package, source_version])
  1040. print(line, file=debinfo)
  1041. debinfo.close()
  1042. def _policy_queue(self, suite):
  1043. if suite.policy_queue is not None:
  1044. return suite.policy_queue
  1045. return None
  1046. def install(self):
  1047. """install upload
  1048. Install upload to a suite or policy queue. This method does B{not}
  1049. handle uploads to NEW.
  1050. You need to have called the C{check} method before calling this method.
  1051. """
  1052. assert len(self.reject_reasons) == 0
  1053. assert self.changes.valid_signature
  1054. assert self.final_suites is not None
  1055. assert self._checked
  1056. assert not self.new
  1057. db_changes = self._install_changes()
  1058. for suite in self.final_suites:
  1059. overridesuite = suite
  1060. if suite.overridesuite is not None:
  1061. overridesuite = self.session.query(Suite).filter_by(suite_name=suite.overridesuite).one()
  1062. policy_queue = self._policy_queue(suite)
  1063. policy_upload = False
  1064. redirected_suite = suite
  1065. if policy_queue is not None:
  1066. redirected_suite = policy_queue.suite
  1067. policy_upload = True
  1068. # source can be in the suite we install to or any suite we enhance
  1069. source_suite_ids = set([suite.suite_id, redirected_suite.suite_id])
  1070. for enhanced_suite_id, in self.session.query(VersionCheck.reference_id) \
  1071. .filter(VersionCheck.suite_id.in_(source_suite_ids)) \
  1072. .filter(VersionCheck.check == 'Enhances'):
  1073. source_suite_ids.add(enhanced_suite_id)
  1074. source_suites = self.session.query(Suite).filter(Suite.suite_id.in_(source_suite_ids)).subquery()
  1075. def source_component_func(source):
  1076. return self._source_component(overridesuite, source, only_overrides=False)
  1077. def binary_component_func(binary):
  1078. return self._binary_component(overridesuite, binary, only_overrides=False)
  1079. (db_source, db_binaries) = self._install_to_suite(suite, redirected_suite, source_component_func, binary_component_func, source_suites=source_suites, extra_source_archives=[suite.archive], policy_upload=policy_upload)
  1080. if policy_queue is not None:
  1081. self._install_policy(policy_queue, suite, db_changes, db_source, db_binaries)
  1082. # copy to build queues
  1083. if policy_queue is None or policy_queue.send_to_build_queues:
  1084. for build_queue in suite.copy_queues:
  1085. self._install_to_suite(suite, build_queue.suite, source_component_func, binary_component_func, source_suites=source_suites, extra_source_archives=[suite.archive])
  1086. self._do_bts_versiontracking()
  1087. def install_to_new(self):
  1088. """install upload to NEW
  1089. Install upload to NEW. This method does B{not} handle regular uploads
  1090. to suites or policy queues.
  1091. You need to have called the C{check} method before calling this method.
  1092. """
  1093. # Uploads to NEW are special as we don't have overrides.
  1094. assert len(self.reject_reasons) == 0
  1095. assert self.changes.valid_signature
  1096. assert self.final_suites is not None
  1097. source = self.changes.source
  1098. binaries = self.changes.binaries
  1099. byhand = self.changes.byhand_files
  1100. # we need a suite to guess components
  1101. suites = list(self.final_suites)
  1102. assert len(suites) == 1, "NEW uploads must be to a single suite"
  1103. suite = suites[0]
  1104. # decide which NEW queue to use
  1105. if suite.new_queue is None:
  1106. new_queue = self.transaction.session.query(PolicyQueue).filter_by(queue_name='new').one()
  1107. else:
  1108. new_queue = suite.new_queue
  1109. if len(byhand) > 0:
  1110. # There is only one global BYHAND queue
  1111. new_queue = self.transaction.session.query(PolicyQueue).filter_by(queue_name='byhand').one()
  1112. new_suite = new_queue.suite
  1113. def binary_component_func(binary):
  1114. return self._binary_component(suite, binary, only_overrides=False)
  1115. # guess source component
  1116. # XXX: should be moved into an extra method
  1117. binary_component_names = set()
  1118. for binary in binaries:
  1119. component = binary_component_func(binary)
  1120. binary_component_names.add(component.component_name)
  1121. source_component_name = None
  1122. for c in self.session.query(Component).order_by(Component.component_id):
  1123. guess = c.component_name
  1124. if guess in binary_component_names:
  1125. source_component_name = guess
  1126. break
  1127. if source_component_name is None:
  1128. source_component = self.session.query(Component).order_by(Component.component_id).first()
  1129. else:
  1130. source_component = self.session.query(Component).filter_by(component_name=source_component_name).one()
  1131. def source_component_func(source):
  1132. return source_component
  1133. db_changes = self._install_changes()
  1134. (db_source, db_binaries) = self._install_to_suite(suite, new_suite, source_component_func, binary_component_func, source_suites=True, extra_source_archives=[suite.archive], policy_upload=True)
  1135. policy_upload = self._install_policy(new_queue, suite, db_changes, db_source, db_binaries)
  1136. for f in byhand:
  1137. self._install_byhand(policy_upload, f)
  1138. self._do_bts_versiontracking()
  1139. def commit(self):
  1140. """commit changes"""
  1141. self.transaction.commit()
  1142. def rollback(self):
  1143. """rollback changes"""
  1144. self.transaction.rollback()
  1145. def __enter__(self):
  1146. self.prepare()
  1147. return self
  1148. def __exit__(self, type, value, traceback):
  1149. if self.directory is not None:
  1150. shutil.rmtree(self.directory)
  1151. self.directory = None
  1152. self.changes = None
  1153. self.transaction.rollback()
  1154. return None