archive.py 52 KB

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