dbconn.py 76 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510
  1. """ DB access class
  2. @contact: Debian FTPMaster <ftpmaster@debian.org>
  3. @copyright: 2000, 2001, 2002, 2003, 2004, 2006 James Troup <james@nocrew.org>
  4. @copyright: 2008-2009 Mark Hymers <mhy@debian.org>
  5. @copyright: 2009, 2010 Joerg Jaspert <joerg@debian.org>
  6. @copyright: 2009 Mike O'Connor <stew@debian.org>
  7. @license: GNU General Public License version 2 or later
  8. """
  9. # This program is free software; you can redistribute it and/or modify
  10. # it under the terms of the GNU General Public License as published by
  11. # the Free Software Foundation; either version 2 of the License, or
  12. # (at your option) any later version.
  13. # This program is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. # GNU General Public License for more details.
  17. # You should have received a copy of the GNU General Public License
  18. # along with this program; if not, write to the Free Software
  19. # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  20. ################################################################################
  21. # < mhy> I need a funny comment
  22. # < sgran> two peanuts were walking down a dark street
  23. # < sgran> one was a-salted
  24. # * mhy looks up the definition of "funny"
  25. ################################################################################
  26. import apt_pkg
  27. from daklib.gpg import GpgException
  28. import functools
  29. import inspect
  30. import os
  31. from os.path import normpath
  32. import re
  33. import subprocess
  34. import warnings
  35. from debian.debfile import Deb822
  36. from tarfile import TarFile
  37. import sqlalchemy
  38. from sqlalchemy import create_engine, Table, desc
  39. from sqlalchemy.orm import sessionmaker, mapper, relation, object_session, \
  40. backref, object_mapper
  41. import sqlalchemy.types
  42. from sqlalchemy.orm.collections import attribute_mapped_collection
  43. from sqlalchemy.ext.associationproxy import association_proxy
  44. # Don't remove this, we re-export the exceptions to scripts which import us
  45. from sqlalchemy.exc import *
  46. from sqlalchemy.orm.exc import NoResultFound
  47. from .aptversion import AptVersion
  48. # Only import Config until Queue stuff is changed to store its config
  49. # in the database
  50. from .config import Config
  51. from .textutils import fix_maintainer
  52. # suppress some deprecation warnings in squeeze related to sqlalchemy
  53. warnings.filterwarnings('ignore',
  54. "Predicate of partial index .* ignored during reflection",
  55. SAWarning)
  56. from .database.base import Base
  57. ################################################################################
  58. # Patch in support for the debversion field type so that it works during
  59. # reflection
  60. class DebVersion(sqlalchemy.types.UserDefinedType):
  61. def get_col_spec(self):
  62. return "DEBVERSION"
  63. def bind_processor(self, dialect):
  64. return None
  65. def result_processor(self, dialect, coltype):
  66. return None
  67. from sqlalchemy.databases import postgresql
  68. postgresql.ischema_names['debversion'] = DebVersion
  69. ################################################################################
  70. __all__ = ['IntegrityError', 'SQLAlchemyError', 'DebVersion']
  71. ################################################################################
  72. def session_wrapper(fn):
  73. """
  74. Wrapper around common ".., session=None):" handling. If the wrapped
  75. function is called without passing 'session', we create a local one
  76. and destroy it when the function ends.
  77. Also attaches a commit_or_flush method to the session; if we created a
  78. local session, this is a synonym for session.commit(), otherwise it is a
  79. synonym for session.flush().
  80. """
  81. def wrapped(*args, **kwargs):
  82. private_transaction = False
  83. # Find the session object
  84. session = kwargs.get('session')
  85. if session is None:
  86. if len(args) < len(inspect.getfullargspec(fn).args):
  87. # No session specified as last argument or in kwargs
  88. private_transaction = True
  89. session = kwargs['session'] = DBConn().session()
  90. else:
  91. # Session is last argument in args
  92. session = args[-1]
  93. if session is None:
  94. args = list(args)
  95. session = args[-1] = DBConn().session()
  96. private_transaction = True
  97. if private_transaction:
  98. session.commit_or_flush = session.commit
  99. else:
  100. session.commit_or_flush = session.flush
  101. try:
  102. return fn(*args, **kwargs)
  103. finally:
  104. if private_transaction:
  105. # We created a session; close it.
  106. session.close()
  107. wrapped.__doc__ = fn.__doc__
  108. wrapped.__name__ = fn.__name__
  109. return wrapped
  110. __all__.append('session_wrapper')
  111. ################################################################################
  112. class ORMObject:
  113. """
  114. ORMObject is a base class for all ORM classes mapped by SQLalchemy. All
  115. derived classes must implement the properties() method.
  116. """
  117. def properties(self):
  118. '''
  119. This method should be implemented by all derived classes and returns a
  120. list of the important properties. The properties 'created' and
  121. 'modified' will be added automatically. A suffix '_count' should be
  122. added to properties that are lists or query objects. The most important
  123. property name should be returned as the first element in the list
  124. because it is used by repr().
  125. '''
  126. return []
  127. def classname(self):
  128. '''
  129. Returns the name of the class.
  130. '''
  131. return type(self).__name__
  132. def __repr__(self):
  133. '''
  134. Returns a short string representation of the object using the first
  135. element from the properties() method.
  136. '''
  137. primary_property = self.properties()[0]
  138. value = getattr(self, primary_property)
  139. return '<%s %s>' % (self.classname(), str(value))
  140. def __str__(self):
  141. '''
  142. Returns a human readable form of the object using the properties()
  143. method.
  144. '''
  145. return '<%s(...)>' % (self.classname())
  146. @classmethod
  147. @session_wrapper
  148. def get(cls, primary_key, session=None):
  149. '''
  150. This is a support function that allows getting an object by its primary
  151. key.
  152. Architecture.get(3[, session])
  153. instead of the more verbose
  154. session.query(Architecture).get(3)
  155. '''
  156. return session.query(cls).get(primary_key)
  157. def session(self):
  158. '''
  159. Returns the current session that is associated with the object. May
  160. return None is object is in detached state.
  161. '''
  162. return object_session(self)
  163. def clone(self, session=None):
  164. """
  165. Clones the current object in a new session and returns the new clone. A
  166. fresh session is created if the optional session parameter is not
  167. provided. The function will fail if a session is provided and has
  168. unflushed changes.
  169. RATIONALE: SQLAlchemy's session is not thread safe. This method clones
  170. an existing object to allow several threads to work with their own
  171. instances of an ORMObject.
  172. WARNING: Only persistent (committed) objects can be cloned. Changes
  173. made to the original object that are not committed yet will get lost.
  174. The session of the new object will always be rolled back to avoid
  175. resource leaks.
  176. """
  177. if self.session() is None:
  178. raise RuntimeError(
  179. 'Method clone() failed for detached object:\n%s' % self)
  180. self.session().flush()
  181. mapper = object_mapper(self)
  182. primary_key = mapper.primary_key_from_instance(self)
  183. object_class = self.__class__
  184. if session is None:
  185. session = DBConn().session()
  186. elif len(session.new) + len(session.dirty) + len(session.deleted) > 0:
  187. raise RuntimeError(
  188. 'Method clone() failed due to unflushed changes in session.')
  189. new_object = session.query(object_class).get(primary_key)
  190. session.rollback()
  191. if new_object is None:
  192. raise RuntimeError(
  193. 'Method clone() failed for non-persistent object:\n%s' % self)
  194. return new_object
  195. __all__.append('ORMObject')
  196. ################################################################################
  197. class ACL(ORMObject):
  198. def __repr__(self):
  199. return "<ACL {0}>".format(self.name)
  200. __all__.append('ACL')
  201. class ACLPerSource(ORMObject):
  202. def __repr__(self):
  203. return "<ACLPerSource acl={0} fingerprint={1} source={2} reason={3}>".format(self.acl.name, self.fingerprint.fingerprint, self.source, self.reason)
  204. __all__.append('ACLPerSource')
  205. ################################################################################
  206. from .database.architecture import Architecture
  207. __all__.append('Architecture')
  208. @session_wrapper
  209. def get_architecture(architecture, session=None):
  210. """
  211. Returns database id for given C{architecture}.
  212. @type architecture: string
  213. @param architecture: The name of the architecture
  214. @type session: Session
  215. @param session: Optional SQLA session object (a temporary one will be
  216. generated if not supplied)
  217. @rtype: Architecture
  218. @return: Architecture object for the given arch (None if not present)
  219. """
  220. q = session.query(Architecture).filter_by(arch_string=architecture)
  221. return q.one_or_none()
  222. __all__.append('get_architecture')
  223. ################################################################################
  224. class Archive:
  225. def __init__(self, *args, **kwargs):
  226. pass
  227. def __repr__(self):
  228. return '<Archive %s>' % self.archive_name
  229. __all__.append('Archive')
  230. @session_wrapper
  231. def get_archive(archive, session=None):
  232. """
  233. returns database id for given C{archive}.
  234. @type archive: string
  235. @param archive: the name of the arhive
  236. @type session: Session
  237. @param session: Optional SQLA session object (a temporary one will be
  238. generated if not supplied)
  239. @rtype: Archive
  240. @return: Archive object for the given name (None if not present)
  241. """
  242. archive = archive.lower()
  243. q = session.query(Archive).filter_by(archive_name=archive)
  244. return q.one_or_none()
  245. __all__.append('get_archive')
  246. ################################################################################
  247. class ArchiveFile:
  248. def __init__(self, archive=None, component=None, file=None):
  249. self.archive = archive
  250. self.component = component
  251. self.file = file
  252. @property
  253. def path(self):
  254. return os.path.join(self.archive.path, 'pool', self.component.component_name, self.file.filename)
  255. __all__.append('ArchiveFile')
  256. ################################################################################
  257. class BinContents(ORMObject):
  258. def __init__(self, file=None, binary=None):
  259. self.file = file
  260. self.binary = binary
  261. def properties(self):
  262. return ['file', 'binary']
  263. __all__.append('BinContents')
  264. ################################################################################
  265. class DBBinary(ORMObject):
  266. def __init__(self, package=None, source=None, version=None,
  267. maintainer=None, architecture=None, poolfile=None,
  268. binarytype='deb', fingerprint=None):
  269. self.package = package
  270. self.source = source
  271. self.version = version
  272. self.maintainer = maintainer
  273. self.architecture = architecture
  274. self.poolfile = poolfile
  275. self.binarytype = binarytype
  276. self.fingerprint = fingerprint
  277. @property
  278. def pkid(self):
  279. return self.binary_id
  280. @property
  281. def name(self):
  282. return self.package
  283. @property
  284. def arch_string(self):
  285. return "%s" % self.architecture
  286. def properties(self):
  287. return ['package', 'version', 'maintainer', 'source', 'architecture',
  288. 'poolfile', 'binarytype', 'fingerprint', 'install_date',
  289. 'suites_count', 'binary_id', 'contents_count', 'extra_sources']
  290. metadata = association_proxy('key', 'value')
  291. def scan_contents(self):
  292. '''
  293. Yields the contents of the package. Only regular files are yielded and
  294. the path names are normalized after converting them from either utf-8
  295. or iso8859-1 encoding. It yields the string ' <EMPTY PACKAGE>' if the
  296. package does not contain any regular file.
  297. '''
  298. fullpath = self.poolfile.fullpath
  299. dpkg_cmd = ('dpkg-deb', '--fsys-tarfile', fullpath)
  300. dpkg = subprocess.Popen(dpkg_cmd, stdout=subprocess.PIPE)
  301. tar = TarFile.open(fileobj=dpkg.stdout, mode='r|')
  302. for member in tar.getmembers():
  303. if not member.isdir():
  304. name = normpath(member.name)
  305. yield name
  306. tar.close()
  307. dpkg.stdout.close()
  308. dpkg.wait()
  309. def read_control(self):
  310. '''
  311. Reads the control information from a binary.
  312. @rtype: text
  313. @return: stanza text of the control section.
  314. '''
  315. from . import utils
  316. fullpath = self.poolfile.fullpath
  317. return utils.deb_extract_control(fullpath)
  318. def read_control_fields(self):
  319. '''
  320. Reads the control information from a binary and return
  321. as a dictionary.
  322. @rtype: dict
  323. @return: fields of the control section as a dictionary.
  324. '''
  325. stanza = self.read_control()
  326. return apt_pkg.TagSection(stanza)
  327. @property
  328. def proxy(self):
  329. session = object_session(self)
  330. query = session.query(BinaryMetadata).filter_by(binary=self)
  331. return MetadataProxy(session, query)
  332. __all__.append('DBBinary')
  333. @session_wrapper
  334. def get_suites_binary_in(package, session=None):
  335. """
  336. Returns list of Suite objects which given C{package} name is in
  337. @type package: str
  338. @param package: DBBinary package name to search for
  339. @rtype: list
  340. @return: list of Suite objects for the given package
  341. """
  342. return session.query(Suite).filter(Suite.binaries.any(DBBinary.package == package)).all()
  343. __all__.append('get_suites_binary_in')
  344. @session_wrapper
  345. def get_component_by_package_suite(package, suite_list, arch_list=None, session=None):
  346. '''
  347. Returns the component name of the newest binary package in suite_list or
  348. None if no package is found. The result can be optionally filtered by a list
  349. of architecture names.
  350. @type package: str
  351. @param package: DBBinary package name to search for
  352. @type suite_list: list of str
  353. @param suite_list: list of suite_name items
  354. @type arch_list: list of str
  355. @param arch_list: optional list of arch_string items that defaults to []
  356. @rtype: str or NoneType
  357. @return: name of component or None
  358. '''
  359. q = session.query(DBBinary).filter_by(package=package). \
  360. join(DBBinary.suites).filter(Suite.suite_name.in_(suite_list))
  361. if arch_list:
  362. q = q.join(DBBinary.architecture). \
  363. filter(Architecture.arch_string.in_(arch_list))
  364. binary = q.order_by(desc(DBBinary.version)).first()
  365. if binary is None:
  366. return None
  367. else:
  368. return binary.poolfile.component.component_name
  369. __all__.append('get_component_by_package_suite')
  370. ################################################################################
  371. class BuildQueue:
  372. def __init__(self, *args, **kwargs):
  373. pass
  374. def __repr__(self):
  375. return '<BuildQueue %s>' % self.queue_name
  376. __all__.append('BuildQueue')
  377. ################################################################################
  378. class Component(ORMObject):
  379. def __init__(self, component_name=None):
  380. self.component_name = component_name
  381. def __eq__(self, val):
  382. if isinstance(val, str):
  383. warnings.warn("comparison with a `str` is deprecated", DeprecationWarning, stacklevel=2)
  384. return (self.component_name == val)
  385. # This signals to use the normal comparison operator
  386. return NotImplemented
  387. def __ne__(self, val):
  388. if isinstance(val, str):
  389. warnings.warn("comparison with a `str` is deprecated", DeprecationWarning, stacklevel=2)
  390. return (self.component_name != val)
  391. # This signals to use the normal comparison operator
  392. return NotImplemented
  393. __hash__ = ORMObject.__hash__
  394. def properties(self):
  395. return ['component_name', 'component_id', 'description',
  396. 'meets_dfsg', 'overrides_count']
  397. __all__.append('Component')
  398. @session_wrapper
  399. def get_component(component, session=None):
  400. """
  401. Returns database id for given C{component}.
  402. @type component: string
  403. @param component: The name of the override type
  404. @rtype: int
  405. @return: the database id for the given component
  406. """
  407. component = component.lower()
  408. q = session.query(Component).filter_by(component_name=component)
  409. return q.one_or_none()
  410. __all__.append('get_component')
  411. def get_mapped_component_name(component_name):
  412. cnf = Config()
  413. for m in cnf.value_list("ComponentMappings"):
  414. (src, dst) = m.split()
  415. if component_name == src:
  416. component_name = dst
  417. return component_name
  418. __all__.append('get_mapped_component_name')
  419. @session_wrapper
  420. def get_mapped_component(component_name, session=None):
  421. """get component after mappings
  422. Evaluate component mappings from ComponentMappings in dak.conf for the
  423. given component name.
  424. @todo: ansgar wants to get rid of this. It's currently only used for
  425. the security archive
  426. @type component_name: str
  427. @param component_name: component name
  428. @param session: database session
  429. @rtype: L{daklib.dbconn.Component} or C{None}
  430. @return: component after applying maps or C{None}
  431. """
  432. component_name = get_mapped_component_name(component_name)
  433. component = session.query(Component).filter_by(component_name=component_name).first()
  434. return component
  435. __all__.append('get_mapped_component')
  436. @session_wrapper
  437. def get_component_names(session=None):
  438. """
  439. Returns list of strings of component names.
  440. @rtype: list
  441. @return: list of strings of component names
  442. """
  443. return [x.component_name for x in session.query(Component).all()]
  444. __all__.append('get_component_names')
  445. ################################################################################
  446. class DBConfig:
  447. def __init__(self, *args, **kwargs):
  448. pass
  449. def __repr__(self):
  450. return '<DBConfig %s>' % self.name
  451. __all__.append('DBConfig')
  452. ################################################################################
  453. class DSCFile:
  454. def __init__(self, *args, **kwargs):
  455. pass
  456. def __repr__(self):
  457. return '<DSCFile %s>' % self.dscfile_id
  458. __all__.append('DSCFile')
  459. @session_wrapper
  460. def get_dscfiles(dscfile_id=None, source_id=None, poolfile_id=None, session=None):
  461. """
  462. Returns a list of DSCFiles which may be empty
  463. @type dscfile_id: int (optional)
  464. @param dscfile_id: the dscfile_id of the DSCFiles to find
  465. @type source_id: int (optional)
  466. @param source_id: the source id related to the DSCFiles to find
  467. @type poolfile_id: int (optional)
  468. @param poolfile_id: the poolfile id related to the DSCFiles to find
  469. @rtype: list
  470. @return: Possibly empty list of DSCFiles
  471. """
  472. q = session.query(DSCFile)
  473. if dscfile_id is not None:
  474. q = q.filter_by(dscfile_id=dscfile_id)
  475. if source_id is not None:
  476. q = q.filter_by(source_id=source_id)
  477. if poolfile_id is not None:
  478. q = q.filter_by(poolfile_id=poolfile_id)
  479. return q.all()
  480. __all__.append('get_dscfiles')
  481. ################################################################################
  482. class ExternalOverride(ORMObject):
  483. def __init__(self, *args, **kwargs):
  484. pass
  485. def __repr__(self):
  486. return '<ExternalOverride %s = %s: %s>' % (self.package, self.key, self.value)
  487. __all__.append('ExternalOverride')
  488. ################################################################################
  489. class PoolFile(ORMObject):
  490. def __init__(self, filename=None, filesize=-1,
  491. md5sum=None):
  492. self.filename = filename
  493. self.filesize = filesize
  494. self.md5sum = md5sum
  495. @property
  496. def fullpath(self):
  497. session = DBConn().session().object_session(self)
  498. af = session.query(ArchiveFile).join(Archive) \
  499. .filter(ArchiveFile.file == self) \
  500. .order_by(Archive.tainted.desc()).first()
  501. return af.path
  502. @property
  503. def component(self):
  504. session = DBConn().session().object_session(self)
  505. component_id = session.query(ArchiveFile.component_id).filter(ArchiveFile.file == self) \
  506. .group_by(ArchiveFile.component_id).one()
  507. return session.query(Component).get(component_id)
  508. @property
  509. def basename(self):
  510. return os.path.basename(self.filename)
  511. def properties(self):
  512. return ['filename', 'file_id', 'filesize', 'md5sum', 'sha1sum',
  513. 'sha256sum', 'source', 'binary', 'last_used']
  514. __all__.append('PoolFile')
  515. ################################################################################
  516. class Fingerprint(ORMObject):
  517. def __init__(self, fingerprint=None):
  518. self.fingerprint = fingerprint
  519. def properties(self):
  520. return ['fingerprint', 'fingerprint_id', 'keyring', 'uid',
  521. 'binary_reject']
  522. __all__.append('Fingerprint')
  523. @session_wrapper
  524. def get_fingerprint(fpr, session=None):
  525. """
  526. Returns Fingerprint object for given fpr.
  527. @type fpr: string
  528. @param fpr: The fpr to find / add
  529. @type session: SQLAlchemy
  530. @param session: Optional SQL session object (a temporary one will be
  531. generated if not supplied).
  532. @rtype: Fingerprint
  533. @return: the Fingerprint object for the given fpr or None
  534. """
  535. q = session.query(Fingerprint).filter_by(fingerprint=fpr)
  536. return q.one_or_none()
  537. __all__.append('get_fingerprint')
  538. @session_wrapper
  539. def get_or_set_fingerprint(fpr, session=None):
  540. """
  541. Returns Fingerprint object for given fpr.
  542. If no matching fpr is found, a row is inserted.
  543. @type fpr: string
  544. @param fpr: The fpr to find / add
  545. @type session: SQLAlchemy
  546. @param session: Optional SQL session object (a temporary one will be
  547. generated if not supplied). If not passed, a commit will be performed at
  548. the end of the function, otherwise the caller is responsible for commiting.
  549. A flush will be performed either way.
  550. @rtype: Fingerprint
  551. @return: the Fingerprint object for the given fpr
  552. """
  553. q = session.query(Fingerprint).filter_by(fingerprint=fpr)
  554. try:
  555. ret = q.one()
  556. except NoResultFound:
  557. fingerprint = Fingerprint()
  558. fingerprint.fingerprint = fpr
  559. session.add(fingerprint)
  560. session.commit_or_flush()
  561. ret = fingerprint
  562. return ret
  563. __all__.append('get_or_set_fingerprint')
  564. ################################################################################
  565. # Helper routine for Keyring class
  566. def get_ldap_name(entry):
  567. name = []
  568. for k in ["cn", "mn", "sn"]:
  569. ret = entry.get(k)
  570. if not ret:
  571. continue
  572. value = ret[0].decode()
  573. if value and value[0] != "-":
  574. name.append(value)
  575. return " ".join(name)
  576. ################################################################################
  577. class Keyring:
  578. keys = {}
  579. fpr_lookup = {}
  580. def __init__(self, *args, **kwargs):
  581. pass
  582. def __repr__(self):
  583. return '<Keyring %s>' % self.keyring_name
  584. def de_escape_gpg_str(self, txt):
  585. esclist = re.split(r'(\\x..)', txt)
  586. for x in range(1, len(esclist), 2):
  587. esclist[x] = "%c" % (int(esclist[x][2:], 16))
  588. return "".join(esclist)
  589. def parse_address(self, uid):
  590. """parses uid and returns a tuple of real name and email address"""
  591. import email.utils
  592. (name, address) = email.utils.parseaddr(uid)
  593. name = re.sub(r"\s*[(].*[)]", "", name)
  594. name = self.de_escape_gpg_str(name)
  595. if name == "":
  596. name = uid
  597. return (name, address)
  598. def load_keys(self, keyring):
  599. if not self.keyring_id:
  600. raise Exception('Must be initialized with database information')
  601. cmd = ["gpg", "--no-default-keyring", "--keyring", keyring,
  602. "--with-colons", "--fingerprint", "--fingerprint"]
  603. p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  604. key = None
  605. need_fingerprint = False
  606. for line_raw in p.stdout:
  607. try:
  608. line = line_raw.decode()
  609. except UnicodeDecodeError:
  610. # Some old UIDs might not use UTF-8 encoding. We assume they
  611. # use latin1.
  612. line = line_raw.decode('latin1')
  613. field = line.split(":")
  614. if field[0] == "pub":
  615. key = field[4]
  616. self.keys[key] = {}
  617. (name, addr) = self.parse_address(field[9])
  618. if "@" in addr:
  619. self.keys[key]["email"] = addr
  620. self.keys[key]["name"] = name
  621. need_fingerprint = True
  622. elif key and field[0] == "uid":
  623. (name, addr) = self.parse_address(field[9])
  624. if "email" not in self.keys[key] and "@" in addr:
  625. self.keys[key]["email"] = addr
  626. self.keys[key]["name"] = name
  627. elif need_fingerprint and field[0] == "fpr":
  628. self.keys[key]["fingerprints"] = [field[9]]
  629. self.fpr_lookup[field[9]] = key
  630. need_fingerprint = False
  631. (out, err) = p.communicate()
  632. r = p.returncode
  633. if r != 0:
  634. raise GpgException("command failed: %s\nstdout: %s\nstderr: %s\n" % (cmd, out, err))
  635. def import_users_from_ldap(self, session):
  636. from .utils import open_ldap_connection
  637. import ldap
  638. l = open_ldap_connection()
  639. cnf = Config()
  640. LDAPDn = cnf["Import-LDAP-Fingerprints::LDAPDn"]
  641. Attrs = l.search_s(LDAPDn, ldap.SCOPE_ONELEVEL,
  642. "(&(keyfingerprint=*)(supplementaryGid=%s))" % (cnf["Import-Users-From-Passwd::ValidGID"]),
  643. ["uid", "keyfingerprint", "cn", "mn", "sn"])
  644. byuid = {}
  645. byname = {}
  646. for i in Attrs:
  647. entry = i[1]
  648. uid = entry["uid"][0].decode()
  649. name = get_ldap_name(entry)
  650. fingerprints = entry["keyFingerPrint"]
  651. keyid = None
  652. for f_raw in fingerprints:
  653. f = f_raw.decode()
  654. key = self.fpr_lookup.get(f, None)
  655. if key not in self.keys:
  656. continue
  657. self.keys[key]["uid"] = uid
  658. if keyid is not None:
  659. continue
  660. keyid = get_or_set_uid(uid, session).uid_id
  661. byuid[keyid] = (uid, name)
  662. byname[uid] = (keyid, name)
  663. return (byname, byuid)
  664. def generate_users_from_keyring(self, format, session):
  665. byuid = {}
  666. byname = {}
  667. any_invalid = False
  668. for x in list(self.keys.keys()):
  669. if "email" not in self.keys[x]:
  670. any_invalid = True
  671. self.keys[x]["uid"] = format % "invalid-uid"
  672. else:
  673. uid = format % self.keys[x]["email"]
  674. keyid = get_or_set_uid(uid, session).uid_id
  675. byuid[keyid] = (uid, self.keys[x]["name"])
  676. byname[uid] = (keyid, self.keys[x]["name"])
  677. self.keys[x]["uid"] = uid
  678. if any_invalid:
  679. uid = format % "invalid-uid"
  680. keyid = get_or_set_uid(uid, session).uid_id
  681. byuid[keyid] = (uid, "ungeneratable user id")
  682. byname[uid] = (keyid, "ungeneratable user id")
  683. return (byname, byuid)
  684. __all__.append('Keyring')
  685. @session_wrapper
  686. def get_keyring(keyring, session=None):
  687. """
  688. If C{keyring} does not have an entry in the C{keyrings} table yet, return None
  689. If C{keyring} already has an entry, simply return the existing Keyring
  690. @type keyring: string
  691. @param keyring: the keyring name
  692. @rtype: Keyring
  693. @return: the Keyring object for this keyring
  694. """
  695. q = session.query(Keyring).filter_by(keyring_name=keyring)
  696. return q.one_or_none()
  697. __all__.append('get_keyring')
  698. @session_wrapper
  699. def get_active_keyring_paths(session=None):
  700. """
  701. @rtype: list
  702. @return: list of active keyring paths
  703. """
  704. return [x.keyring_name for x in session.query(Keyring).filter(Keyring.active == True).order_by(desc(Keyring.priority)).all()] # noqa:E712
  705. __all__.append('get_active_keyring_paths')
  706. ################################################################################
  707. class DBChange:
  708. def __init__(self, *args, **kwargs):
  709. pass
  710. def __repr__(self):
  711. return '<DBChange %s>' % self.changesname
  712. __all__.append('DBChange')
  713. @session_wrapper
  714. def get_dbchange(filename, session=None):
  715. """
  716. returns DBChange object for given C{filename}.
  717. @type filename: string
  718. @param filename: the name of the file
  719. @type session: Session
  720. @param session: Optional SQLA session object (a temporary one will be
  721. generated if not supplied)
  722. @rtype: DBChange
  723. @return: DBChange object for the given filename (C{None} if not present)
  724. """
  725. q = session.query(DBChange).filter_by(changesname=filename)
  726. return q.one_or_none()
  727. __all__.append('get_dbchange')
  728. ################################################################################
  729. class Maintainer(ORMObject):
  730. def __init__(self, name=None):
  731. self.name = name
  732. def properties(self):
  733. return ['name', 'maintainer_id']
  734. def get_split_maintainer(self):
  735. if not hasattr(self, 'name') or self.name is None:
  736. return ('', '', '', '')
  737. return fix_maintainer(self.name.strip())
  738. __all__.append('Maintainer')
  739. @session_wrapper
  740. def get_or_set_maintainer(name, session=None):
  741. """
  742. Returns Maintainer object for given maintainer name.
  743. If no matching maintainer name is found, a row is inserted.
  744. @type name: string
  745. @param name: The maintainer name to add
  746. @type session: SQLAlchemy
  747. @param session: Optional SQL session object (a temporary one will be
  748. generated if not supplied). If not passed, a commit will be performed at
  749. the end of the function, otherwise the caller is responsible for commiting.
  750. A flush will be performed either way.
  751. @rtype: Maintainer
  752. @return: the Maintainer object for the given maintainer
  753. """
  754. q = session.query(Maintainer).filter_by(name=name)
  755. try:
  756. ret = q.one()
  757. except NoResultFound:
  758. maintainer = Maintainer()
  759. maintainer.name = name
  760. session.add(maintainer)
  761. session.commit_or_flush()
  762. ret = maintainer
  763. return ret
  764. __all__.append('get_or_set_maintainer')
  765. @session_wrapper
  766. def get_maintainer(maintainer_id, session=None):
  767. """
  768. Return the name of the maintainer behind C{maintainer_id} or None if that
  769. maintainer_id is invalid.
  770. @type maintainer_id: int
  771. @param maintainer_id: the id of the maintainer
  772. @rtype: Maintainer
  773. @return: the Maintainer with this C{maintainer_id}
  774. """
  775. return session.query(Maintainer).get(maintainer_id)
  776. __all__.append('get_maintainer')
  777. ################################################################################
  778. class NewComment:
  779. def __init__(self, *args, **kwargs):
  780. pass
  781. def __repr__(self):
  782. return '''<NewComment for '%s %s' (%s)>''' % (self.package, self.version, self.comment_id)
  783. __all__.append('NewComment')
  784. @session_wrapper
  785. def has_new_comment(policy_queue, package, version, session=None):
  786. """
  787. Returns true if the given combination of C{package}, C{version} has a comment.
  788. @type package: string
  789. @param package: name of the package
  790. @type version: string
  791. @param version: package version
  792. @type session: Session
  793. @param session: Optional SQLA session object (a temporary one will be
  794. generated if not supplied)
  795. @rtype: boolean
  796. @return: true/false
  797. """
  798. q = session.query(NewComment).filter_by(policy_queue=policy_queue)
  799. q = q.filter_by(package=package)
  800. q = q.filter_by(version=version)
  801. return bool(q.count() > 0)
  802. __all__.append('has_new_comment')
  803. @session_wrapper
  804. def get_new_comments(policy_queue, package=None, version=None, comment_id=None, session=None):
  805. """
  806. Returns (possibly empty) list of NewComment objects for the given
  807. parameters
  808. @type package: string (optional)
  809. @param package: name of the package
  810. @type version: string (optional)
  811. @param version: package version
  812. @type comment_id: int (optional)
  813. @param comment_id: An id of a comment
  814. @type session: Session
  815. @param session: Optional SQLA session object (a temporary one will be
  816. generated if not supplied)
  817. @rtype: list
  818. @return: A (possibly empty) list of NewComment objects will be returned
  819. """
  820. q = session.query(NewComment).filter_by(policy_queue=policy_queue)
  821. if package is not None:
  822. q = q.filter_by(package=package)
  823. if version is not None:
  824. q = q.filter_by(version=version)
  825. if comment_id is not None:
  826. q = q.filter_by(comment_id=comment_id)
  827. return q.all()
  828. __all__.append('get_new_comments')
  829. ################################################################################
  830. class Override(ORMObject):
  831. def __init__(self, package=None, suite=None, component=None, overridetype=None,
  832. section=None, priority=None):
  833. self.package = package
  834. self.suite = suite
  835. self.component = component
  836. self.overridetype = overridetype
  837. self.section = section
  838. self.priority = priority
  839. def properties(self):
  840. return ['package', 'suite', 'component', 'overridetype', 'section',
  841. 'priority']
  842. __all__.append('Override')
  843. @session_wrapper
  844. def get_override(package, suite=None, component=None, overridetype=None, session=None):
  845. """
  846. Returns Override object for the given parameters
  847. @type package: string
  848. @param package: The name of the package
  849. @type suite: string, list or None
  850. @param suite: The name of the suite (or suites if a list) to limit to. If
  851. None, don't limit. Defaults to None.
  852. @type component: string, list or None
  853. @param component: The name of the component (or components if a list) to
  854. limit to. If None, don't limit. Defaults to None.
  855. @type overridetype: string, list or None
  856. @param overridetype: The name of the overridetype (or overridetypes if a list) to
  857. limit to. If None, don't limit. Defaults to None.
  858. @type session: Session
  859. @param session: Optional SQLA session object (a temporary one will be
  860. generated if not supplied)
  861. @rtype: list
  862. @return: A (possibly empty) list of Override objects will be returned
  863. """
  864. q = session.query(Override)
  865. q = q.filter_by(package=package)
  866. if suite is not None:
  867. if not isinstance(suite, list):
  868. suite = [suite]
  869. q = q.join(Suite).filter(Suite.suite_name.in_(suite))
  870. if component is not None:
  871. if not isinstance(component, list):
  872. component = [component]
  873. q = q.join(Component).filter(Component.component_name.in_(component))
  874. if overridetype is not None:
  875. if not isinstance(overridetype, list):
  876. overridetype = [overridetype]
  877. q = q.join(OverrideType).filter(OverrideType.overridetype.in_(overridetype))
  878. return q.all()
  879. __all__.append('get_override')
  880. ################################################################################
  881. class OverrideType(ORMObject):
  882. def __init__(self, overridetype=None):
  883. self.overridetype = overridetype
  884. def properties(self):
  885. return ['overridetype', 'overridetype_id', 'overrides_count']
  886. __all__.append('OverrideType')
  887. @session_wrapper
  888. def get_override_type(override_type, session=None):
  889. """
  890. Returns OverrideType object for given C{override type}.
  891. @type override_type: string
  892. @param override_type: The name of the override type
  893. @type session: Session
  894. @param session: Optional SQLA session object (a temporary one will be
  895. generated if not supplied)
  896. @rtype: int
  897. @return: the database id for the given override type
  898. """
  899. q = session.query(OverrideType).filter_by(overridetype=override_type)
  900. return q.one_or_none()
  901. __all__.append('get_override_type')
  902. ################################################################################
  903. class PolicyQueue:
  904. def __init__(self, *args, **kwargs):
  905. pass
  906. def __repr__(self):
  907. return '<PolicyQueue %s>' % self.queue_name
  908. __all__.append('PolicyQueue')
  909. @session_wrapper
  910. def get_policy_queue(queuename, session=None):
  911. """
  912. Returns PolicyQueue object for given C{queue name}
  913. @type queuename: string
  914. @param queuename: The name of the queue
  915. @type session: Session
  916. @param session: Optional SQLA session object (a temporary one will be
  917. generated if not supplied)
  918. @rtype: PolicyQueue
  919. @return: PolicyQueue object for the given queue
  920. """
  921. q = session.query(PolicyQueue).filter_by(queue_name=queuename)
  922. return q.one_or_none()
  923. __all__.append('get_policy_queue')
  924. ################################################################################
  925. @functools.total_ordering
  926. class PolicyQueueUpload:
  927. def _key(self):
  928. return (
  929. self.changes.source,
  930. AptVersion(self.changes.version),
  931. self.source is None,
  932. self.changes.changesname
  933. )
  934. def __eq__(self, other):
  935. return self._key() == other._key()
  936. def __lt__(self, other):
  937. return self._key() < other._key()
  938. __all__.append('PolicyQueueUpload')
  939. ################################################################################
  940. class PolicyQueueByhandFile:
  941. pass
  942. __all__.append('PolicyQueueByhandFile')
  943. ################################################################################
  944. class Priority(ORMObject):
  945. def __init__(self, priority=None, level=None):
  946. self.priority = priority
  947. self.level = level
  948. def properties(self):
  949. return ['priority', 'priority_id', 'level', 'overrides_count']
  950. def __eq__(self, val):
  951. if isinstance(val, str):
  952. warnings.warn("comparison with a `str` is deprecated", DeprecationWarning, stacklevel=2)
  953. return (self.priority == val)
  954. # This signals to use the normal comparison operator
  955. return NotImplemented
  956. def __ne__(self, val):
  957. if isinstance(val, str):
  958. warnings.warn("comparison with a `str` is deprecated", DeprecationWarning, stacklevel=2)
  959. return (self.priority != val)
  960. # This signals to use the normal comparison operator
  961. return NotImplemented
  962. __hash__ = ORMObject.__hash__
  963. __all__.append('Priority')
  964. @session_wrapper
  965. def get_priority(priority, session=None):
  966. """
  967. Returns Priority object for given C{priority name}.
  968. @type priority: string
  969. @param priority: The name of the priority
  970. @type session: Session
  971. @param session: Optional SQLA session object (a temporary one will be
  972. generated if not supplied)
  973. @rtype: Priority
  974. @return: Priority object for the given priority
  975. """
  976. q = session.query(Priority).filter_by(priority=priority)
  977. return q.one_or_none()
  978. __all__.append('get_priority')
  979. @session_wrapper
  980. def get_priorities(session=None):
  981. """
  982. Returns dictionary of priority names -> id mappings
  983. @type session: Session
  984. @param session: Optional SQL session object (a temporary one will be
  985. generated if not supplied)
  986. @rtype: dictionary
  987. @return: dictionary of priority names -> id mappings
  988. """
  989. ret = {}
  990. q = session.query(Priority)
  991. for x in q.all():
  992. ret[x.priority] = x.priority_id
  993. return ret
  994. __all__.append('get_priorities')
  995. ################################################################################
  996. from .database.section import Section
  997. __all__.append('Section')
  998. @session_wrapper
  999. def get_section(section, session=None):
  1000. """
  1001. Returns Section object for given C{section name}.
  1002. @type section: string
  1003. @param section: The name of the section
  1004. @type session: Session
  1005. @param session: Optional SQLA session object (a temporary one will be
  1006. generated if not supplied)
  1007. @rtype: Section
  1008. @return: Section object for the given section name
  1009. """
  1010. q = session.query(Section).filter_by(section=section)
  1011. return q.one_or_none()
  1012. __all__.append('get_section')
  1013. @session_wrapper
  1014. def get_sections(session=None):
  1015. """
  1016. Returns dictionary of section names -> id mappings
  1017. @type session: Session
  1018. @param session: Optional SQL session object (a temporary one will be
  1019. generated if not supplied)
  1020. @rtype: dictionary
  1021. @return: dictionary of section names -> id mappings
  1022. """
  1023. ret = {}
  1024. q = session.query(Section)
  1025. for x in q.all():
  1026. ret[x.section] = x.section_id
  1027. return ret
  1028. __all__.append('get_sections')
  1029. ################################################################################
  1030. class SignatureHistory(ORMObject):
  1031. @classmethod
  1032. def from_signed_file(cls, signed_file):
  1033. """signature history entry from signed file
  1034. @type signed_file: L{daklib.gpg.SignedFile}
  1035. @param signed_file: signed file
  1036. @rtype: L{SignatureHistory}
  1037. """
  1038. self = cls()
  1039. self.fingerprint = signed_file.primary_fingerprint
  1040. self.signature_timestamp = signed_file.signature_timestamp
  1041. self.contents_sha1 = signed_file.contents_sha1()
  1042. return self
  1043. def query(self, session):
  1044. return session.query(SignatureHistory).filter_by(fingerprint=self.fingerprint, signature_timestamp=self.signature_timestamp, contents_sha1=self.contents_sha1).first()
  1045. __all__.append('SignatureHistory')
  1046. ################################################################################
  1047. class SrcContents(ORMObject):
  1048. def __init__(self, file=None, source=None):
  1049. self.file = file
  1050. self.source = source
  1051. def properties(self):
  1052. return ['file', 'source']
  1053. __all__.append('SrcContents')
  1054. ################################################################################
  1055. class DBSource(ORMObject):
  1056. def __init__(self, source=None, version=None, maintainer=None,
  1057. changedby=None, poolfile=None, install_date=None, fingerprint=None):
  1058. self.source = source
  1059. self.version = version
  1060. self.maintainer = maintainer
  1061. self.changedby = changedby
  1062. self.poolfile = poolfile
  1063. self.install_date = install_date
  1064. self.fingerprint = fingerprint
  1065. @property
  1066. def pkid(self):
  1067. return self.source_id
  1068. @property
  1069. def name(self):
  1070. return self.source
  1071. @property
  1072. def arch_string(self):
  1073. return 'source'
  1074. def properties(self):
  1075. return ['source', 'source_id', 'maintainer', 'changedby',
  1076. 'fingerprint', 'poolfile', 'version', 'suites_count',
  1077. 'install_date', 'binaries_count', 'uploaders_count']
  1078. def read_control_fields(self):
  1079. '''
  1080. Reads the control information from a dsc
  1081. @rtype: tuple
  1082. @return: fields is the dsc information in a dictionary form
  1083. '''
  1084. with open(self.poolfile.fullpath, 'r') as fd:
  1085. fields = Deb822(fd)
  1086. return fields
  1087. metadata = association_proxy('key', 'value')
  1088. def scan_contents(self):
  1089. '''
  1090. Returns a set of names for non directories. The path names are
  1091. normalized after converting them from either utf-8 or iso8859-1
  1092. encoding.
  1093. '''
  1094. fullpath = self.poolfile.fullpath
  1095. from daklib.contents import UnpackedSource
  1096. unpacked = UnpackedSource(fullpath)
  1097. fileset = set()
  1098. for name in unpacked.get_all_filenames():
  1099. fileset.add(name)
  1100. return fileset
  1101. @property
  1102. def proxy(self):
  1103. session = object_session(self)
  1104. query = session.query(SourceMetadata).filter_by(source=self)
  1105. return MetadataProxy(session, query)
  1106. __all__.append('DBSource')
  1107. @session_wrapper
  1108. def get_suites_source_in(source, session=None):
  1109. """
  1110. Returns list of Suite objects which given C{source} name is in
  1111. @type source: str
  1112. @param source: DBSource package name to search for
  1113. @rtype: list
  1114. @return: list of Suite objects for the given source
  1115. """
  1116. return session.query(Suite).filter(Suite.sources.any(source=source)).all()
  1117. __all__.append('get_suites_source_in')
  1118. # FIXME: This function fails badly if it finds more than 1 source package and
  1119. # its implementation is trivial enough to be inlined.
  1120. @session_wrapper
  1121. def get_source_in_suite(source, suite_name, session=None):
  1122. """
  1123. Returns a DBSource object for a combination of C{source} and C{suite_name}.
  1124. - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
  1125. - B{suite_name} - a suite name, eg. I{unstable}
  1126. @type source: string
  1127. @param source: source package name
  1128. @type suite_name: string
  1129. @param suite_name: the suite name
  1130. @rtype: string
  1131. @return: the version for I{source} in I{suite}
  1132. """
  1133. suite = get_suite(suite_name, session)
  1134. if suite is None:
  1135. return None
  1136. return suite.get_sources(source).one_or_none()
  1137. __all__.append('get_source_in_suite')
  1138. @session_wrapper
  1139. def import_metadata_into_db(obj, session=None):
  1140. """
  1141. This routine works on either DBBinary or DBSource objects and imports
  1142. their metadata into the database
  1143. """
  1144. fields = obj.read_control_fields()
  1145. for k in fields.keys():
  1146. try:
  1147. # Try raw ASCII
  1148. val = str(fields[k])
  1149. except UnicodeEncodeError:
  1150. # Fall back to UTF-8
  1151. try:
  1152. val = fields[k].encode('utf-8')
  1153. except UnicodeEncodeError:
  1154. # Finally try iso8859-1
  1155. val = fields[k].encode('iso8859-1')
  1156. # Otherwise we allow the exception to percolate up and we cause
  1157. # a reject as someone is playing silly buggers
  1158. obj.metadata[get_or_set_metadatakey(k, session)] = val
  1159. session.commit_or_flush()
  1160. __all__.append('import_metadata_into_db')
  1161. ################################################################################
  1162. class SrcFormat:
  1163. def __init__(self, *args, **kwargs):
  1164. pass
  1165. def __repr__(self):
  1166. return '<SrcFormat %s>' % (self.format_name)
  1167. __all__.append('SrcFormat')
  1168. ################################################################################
  1169. SUITE_FIELDS = [('SuiteName', 'suite_name'),
  1170. ('SuiteID', 'suite_id'),
  1171. ('Version', 'version'),
  1172. ('Origin', 'origin'),
  1173. ('Label', 'label'),
  1174. ('Description', 'description'),
  1175. ('Untouchable', 'untouchable'),
  1176. ('Announce', 'announce'),
  1177. ('Codename', 'codename'),
  1178. ('OverrideCodename', 'overridecodename'),
  1179. ('ValidTime', 'validtime'),
  1180. ('Priority', 'priority'),
  1181. ('NotAutomatic', 'notautomatic'),
  1182. ('CopyChanges', 'copychanges'),
  1183. ('OverrideSuite', 'overridesuite')]
  1184. # Why the heck don't we have any UNIQUE constraints in table suite?
  1185. # TODO: Add UNIQUE constraints for appropriate columns.
  1186. class Suite(ORMObject):
  1187. def __init__(self, suite_name=None, version=None):
  1188. self.suite_name = suite_name
  1189. self.version = version
  1190. def properties(self):
  1191. return ['suite_name', 'version', 'sources_count', 'binaries_count',
  1192. 'overrides_count']
  1193. def __eq__(self, val):
  1194. if isinstance(val, str):
  1195. warnings.warn("comparison with a `str` is deprecated", DeprecationWarning, stacklevel=2)
  1196. return (self.suite_name == val)
  1197. # This signals to use the normal comparison operator
  1198. return NotImplemented
  1199. def __ne__(self, val):
  1200. if isinstance(val, str):
  1201. warnings.warn("comparison with a `str` is deprecated", DeprecationWarning, stacklevel=2)
  1202. return (self.suite_name != val)
  1203. # This signals to use the normal comparison operator
  1204. return NotImplemented
  1205. __hash__ = ORMObject.__hash__
  1206. def details(self):
  1207. ret = []
  1208. for disp, field in SUITE_FIELDS:
  1209. val = getattr(self, field, None)
  1210. if val is not None:
  1211. ret.append("%s: %s" % (disp, val))
  1212. return "\n".join(ret)
  1213. def get_architectures(self, skipsrc=False, skipall=False):
  1214. """
  1215. Returns list of Architecture objects
  1216. @type skipsrc: boolean
  1217. @param skipsrc: Whether to skip returning the 'source' architecture entry
  1218. (Default False)
  1219. @type skipall: boolean
  1220. @param skipall: Whether to skip returning the 'all' architecture entry
  1221. (Default False)
  1222. @rtype: list
  1223. @return: list of Architecture objects for the given name (may be empty)
  1224. """
  1225. q = object_session(self).query(Architecture).with_parent(self)
  1226. if skipsrc:
  1227. q = q.filter(Architecture.arch_string != 'source')
  1228. if skipall:
  1229. q = q.filter(Architecture.arch_string != 'all')
  1230. return q.order_by(Architecture.arch_string).all()
  1231. def get_sources(self, source):
  1232. """
  1233. Returns a query object representing DBSource that is part of C{suite}.
  1234. - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
  1235. @type source: string
  1236. @param source: source package name
  1237. @rtype: sqlalchemy.orm.query.Query
  1238. @return: a query of DBSource
  1239. """
  1240. session = object_session(self)
  1241. return session.query(DBSource).filter_by(source=source). \
  1242. with_parent(self)
  1243. def get_overridesuite(self):
  1244. if self.overridesuite is None:
  1245. return self
  1246. else:
  1247. return object_session(self).query(Suite).filter_by(suite_name=self.overridesuite).one()
  1248. def update_last_changed(self):
  1249. self.last_changed = sqlalchemy.func.now()
  1250. @property
  1251. def path(self):
  1252. return os.path.join(self.archive.path, 'dists', self.suite_name)
  1253. @property
  1254. def release_suite_output(self):
  1255. if self.release_suite is not None:
  1256. return self.release_suite
  1257. return self.suite_name
  1258. __all__.append('Suite')
  1259. @session_wrapper
  1260. def get_suite(suite, session=None):
  1261. """
  1262. Returns Suite object for given C{suite name}.
  1263. @type suite: string
  1264. @param suite: The name of the suite
  1265. @type session: Session
  1266. @param session: Optional SQLA session object (a temporary one will be
  1267. generated if not supplied)
  1268. @rtype: Suite
  1269. @return: Suite object for the requested suite name (None if not present)
  1270. """
  1271. # Start by looking for the dak internal name
  1272. q = session.query(Suite).filter_by(suite_name=suite)
  1273. try:
  1274. return q.one()
  1275. except NoResultFound:
  1276. pass
  1277. # Now try codename
  1278. q = session.query(Suite).filter_by(codename=suite)
  1279. try:
  1280. return q.one()
  1281. except NoResultFound:
  1282. pass
  1283. # Finally give release_suite a try
  1284. q = session.query(Suite).filter_by(release_suite=suite)
  1285. return q.one_or_none()
  1286. __all__.append('get_suite')
  1287. ################################################################################
  1288. @session_wrapper
  1289. def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None):
  1290. """
  1291. Returns list of Architecture objects for given C{suite} name. The list is
  1292. empty if suite does not exist.
  1293. @type suite: str
  1294. @param suite: Suite name to search for
  1295. @type skipsrc: boolean
  1296. @param skipsrc: Whether to skip returning the 'source' architecture entry
  1297. (Default False)
  1298. @type skipall: boolean
  1299. @param skipall: Whether to skip returning the 'all' architecture entry
  1300. (Default False)
  1301. @type session: Session
  1302. @param session: Optional SQL session object (a temporary one will be
  1303. generated if not supplied)
  1304. @rtype: list
  1305. @return: list of Architecture objects for the given name (may be empty)
  1306. """
  1307. try:
  1308. return get_suite(suite, session).get_architectures(skipsrc, skipall)
  1309. except AttributeError:
  1310. return []
  1311. __all__.append('get_suite_architectures')
  1312. ################################################################################
  1313. class Uid(ORMObject):
  1314. def __init__(self, uid=None, name=None):
  1315. self.uid = uid
  1316. self.name = name
  1317. def __eq__(self, val):
  1318. if isinstance(val, str):
  1319. warnings.warn("comparison with a `str` is deprecated", DeprecationWarning, stacklevel=2)
  1320. return (self.uid == val)
  1321. # This signals to use the normal comparison operator
  1322. return NotImplemented
  1323. def __ne__(self, val):
  1324. if isinstance(val, str):
  1325. warnings.warn("comparison with a `str` is deprecated", DeprecationWarning, stacklevel=2)
  1326. return (self.uid != val)
  1327. # This signals to use the normal comparison operator
  1328. return NotImplemented
  1329. __hash__ = ORMObject.__hash__
  1330. def properties(self):
  1331. return ['uid', 'name', 'fingerprint']
  1332. __all__.append('Uid')
  1333. @session_wrapper
  1334. def get_or_set_uid(uidname, session=None):
  1335. """
  1336. Returns uid object for given uidname.
  1337. If no matching uidname is found, a row is inserted.
  1338. @type uidname: string
  1339. @param uidname: The uid to add
  1340. @type session: SQLAlchemy
  1341. @param session: Optional SQL session object (a temporary one will be
  1342. generated if not supplied). If not passed, a commit will be performed at
  1343. the end of the function, otherwise the caller is responsible for commiting.
  1344. @rtype: Uid
  1345. @return: the uid object for the given uidname
  1346. """
  1347. q = session.query(Uid).filter_by(uid=uidname)
  1348. try:
  1349. ret = q.one()
  1350. except NoResultFound:
  1351. uid = Uid()
  1352. uid.uid = uidname
  1353. session.add(uid)
  1354. session.commit_or_flush()
  1355. ret = uid
  1356. return ret
  1357. __all__.append('get_or_set_uid')
  1358. @session_wrapper
  1359. def get_uid_from_fingerprint(fpr, session=None):
  1360. q = session.query(Uid)
  1361. q = q.join(Fingerprint).filter_by(fingerprint=fpr)
  1362. return q.one_or_none()
  1363. __all__.append('get_uid_from_fingerprint')
  1364. ################################################################################
  1365. class MetadataKey(ORMObject):
  1366. def __init__(self, key=None):
  1367. self.key = key
  1368. def properties(self):
  1369. return ['key']
  1370. __all__.append('MetadataKey')
  1371. @session_wrapper
  1372. def get_or_set_metadatakey(keyname, session=None):
  1373. """
  1374. Returns MetadataKey object for given uidname.
  1375. If no matching keyname is found, a row is inserted.
  1376. @type keyname: string
  1377. @param keyname: The keyname to add
  1378. @type session: SQLAlchemy
  1379. @param session: Optional SQL session object (a temporary one will be
  1380. generated if not supplied). If not passed, a commit will be performed at
  1381. the end of the function, otherwise the caller is responsible for commiting.
  1382. @rtype: MetadataKey
  1383. @return: the metadatakey object for the given keyname
  1384. """
  1385. q = session.query(MetadataKey).filter_by(key=keyname)
  1386. try:
  1387. ret = q.one()
  1388. except NoResultFound:
  1389. ret = MetadataKey(keyname)
  1390. session.add(ret)
  1391. session.commit_or_flush()
  1392. return ret
  1393. __all__.append('get_or_set_metadatakey')
  1394. ################################################################################
  1395. class BinaryMetadata(ORMObject):
  1396. def __init__(self, key=None, value=None, binary=None):
  1397. self.key = key
  1398. self.value = value
  1399. if binary is not None:
  1400. self.binary = binary
  1401. def properties(self):
  1402. return ['binary', 'key', 'value']
  1403. __all__.append('BinaryMetadata')
  1404. ################################################################################
  1405. class SourceMetadata(ORMObject):
  1406. def __init__(self, key=None, value=None, source=None):
  1407. self.key = key
  1408. self.value = value
  1409. if source is not None:
  1410. self.source = source
  1411. def properties(self):
  1412. return ['source', 'key', 'value']
  1413. __all__.append('SourceMetadata')
  1414. ################################################################################
  1415. class MetadataProxy:
  1416. def __init__(self, session, query):
  1417. self.session = session
  1418. self.query = query
  1419. def _get(self, key):
  1420. metadata_key = self.session.query(MetadataKey).filter_by(key=key).first()
  1421. if metadata_key is None:
  1422. return None
  1423. metadata = self.query.filter_by(key=metadata_key).first()
  1424. return metadata
  1425. def __contains__(self, key):
  1426. if self._get(key) is not None:
  1427. return True
  1428. return False
  1429. def __getitem__(self, key):
  1430. metadata = self._get(key)
  1431. if metadata is None:
  1432. raise KeyError
  1433. return metadata.value
  1434. def get(self, key, default=None):
  1435. try:
  1436. return self[key]
  1437. except KeyError:
  1438. return default
  1439. ################################################################################
  1440. class VersionCheck(ORMObject):
  1441. def __init__(self, *args, **kwargs):
  1442. pass
  1443. def properties(self):
  1444. return ['check']
  1445. __all__.append('VersionCheck')
  1446. @session_wrapper
  1447. def get_version_checks(suite_name, check=None, session=None):
  1448. suite = get_suite(suite_name, session)
  1449. if not suite:
  1450. # Make sure that what we return is iterable so that list comprehensions
  1451. # involving this don't cause a traceback
  1452. return []
  1453. q = session.query(VersionCheck).filter_by(suite=suite)
  1454. if check:
  1455. q = q.filter_by(check=check)
  1456. return q.all()
  1457. __all__.append('get_version_checks')
  1458. ################################################################################
  1459. class DBConn:
  1460. """
  1461. database module init.
  1462. """
  1463. __shared_state = {}
  1464. db_meta = None
  1465. tbl_architecture = Architecture.__table__
  1466. tables = (
  1467. 'acl',
  1468. 'acl_architecture_map',
  1469. 'acl_fingerprint_map',
  1470. 'acl_per_source',
  1471. 'archive',
  1472. 'bin_associations',
  1473. 'bin_contents',
  1474. 'binaries',
  1475. 'binaries_metadata',
  1476. 'build_queue',
  1477. 'changelogs_text',
  1478. 'changes',
  1479. 'component',
  1480. 'component_suite',
  1481. 'config',
  1482. 'dsc_files',
  1483. 'external_files',
  1484. 'external_overrides',
  1485. 'external_signature_requests',
  1486. 'extra_src_references',
  1487. 'files',
  1488. 'files_archive_map',
  1489. 'fingerprint',
  1490. 'hashfile',
  1491. 'keyrings',
  1492. 'maintainer',
  1493. 'metadata_keys',
  1494. 'new_comments',
  1495. # TODO: the maintainer column in table override should be removed.
  1496. 'override',
  1497. 'override_type',
  1498. 'policy_queue',
  1499. 'policy_queue_upload',
  1500. 'policy_queue_upload_binaries_map',
  1501. 'policy_queue_byhand_file',
  1502. 'priority',
  1503. 'signature_history',
  1504. 'source',
  1505. 'source_metadata',
  1506. 'src_associations',
  1507. 'src_contents',
  1508. 'src_format',
  1509. 'src_uploaders',
  1510. 'suite',
  1511. 'suite_acl_map',
  1512. 'suite_architectures',
  1513. 'suite_build_queue_copy',
  1514. 'suite_permission',
  1515. 'suite_src_formats',
  1516. 'uid',
  1517. 'version_check',
  1518. )
  1519. views = (
  1520. 'bin_associations_binaries',
  1521. 'changelogs',
  1522. 'newest_source',
  1523. 'newest_src_association',
  1524. 'package_list',
  1525. 'source_suite',
  1526. 'src_associations_src',
  1527. )
  1528. def __init__(self, *args, **kwargs):
  1529. self.__dict__ = self.__shared_state
  1530. if not getattr(self, 'initialised', False):
  1531. self.initialised = True
  1532. self.debug = 'debug' in kwargs
  1533. self.__createconn()
  1534. def __setuptables(self):
  1535. for table_name in self.tables:
  1536. table = Table(table_name, self.db_meta,
  1537. autoload=True, extend_existing=True)
  1538. setattr(self, 'tbl_%s' % table_name, table)
  1539. for view_name in self.views:
  1540. view = Table(view_name, self.db_meta, autoload=True)
  1541. setattr(self, 'view_%s' % view_name, view)
  1542. def __setupmappers(self):
  1543. mapper(ACL, self.tbl_acl,
  1544. properties=dict(
  1545. architectures=relation(Architecture, secondary=self.tbl_acl_architecture_map, collection_class=set),
  1546. fingerprints=relation(Fingerprint, secondary=self.tbl_acl_fingerprint_map, collection_class=set),
  1547. match_keyring=relation(Keyring, primaryjoin=(self.tbl_acl.c.match_keyring_id == self.tbl_keyrings.c.id)),
  1548. per_source=relation(ACLPerSource, collection_class=set),
  1549. ))
  1550. mapper(ACLPerSource, self.tbl_acl_per_source,
  1551. properties=dict(
  1552. acl=relation(ACL),
  1553. fingerprint=relation(Fingerprint, primaryjoin=(self.tbl_acl_per_source.c.fingerprint_id == self.tbl_fingerprint.c.id)),
  1554. created_by=relation(Fingerprint, primaryjoin=(self.tbl_acl_per_source.c.created_by_id == self.tbl_fingerprint.c.id)),
  1555. ))
  1556. mapper(Archive, self.tbl_archive,
  1557. properties=dict(archive_id=self.tbl_archive.c.id,
  1558. archive_name=self.tbl_archive.c.name))
  1559. mapper(ArchiveFile, self.tbl_files_archive_map,
  1560. properties=dict(archive=relation(Archive, backref='files'),
  1561. component=relation(Component),
  1562. file=relation(PoolFile, backref='archives')))
  1563. mapper(BuildQueue, self.tbl_build_queue,
  1564. properties=dict(queue_id=self.tbl_build_queue.c.id,
  1565. suite=relation(Suite, primaryjoin=(self.tbl_build_queue.c.suite_id == self.tbl_suite.c.id))))
  1566. mapper(DBBinary, self.tbl_binaries,
  1567. properties=dict(binary_id=self.tbl_binaries.c.id,
  1568. package=self.tbl_binaries.c.package,
  1569. version=self.tbl_binaries.c.version,
  1570. maintainer_id=self.tbl_binaries.c.maintainer,
  1571. maintainer=relation(Maintainer),
  1572. source_id=self.tbl_binaries.c.source,
  1573. source=relation(DBSource, backref='binaries'),
  1574. arch_id=self.tbl_binaries.c.architecture,
  1575. architecture=relation(Architecture),
  1576. poolfile_id=self.tbl_binaries.c.file,
  1577. poolfile=relation(PoolFile),
  1578. binarytype=self.tbl_binaries.c.type,
  1579. fingerprint_id=self.tbl_binaries.c.sig_fpr,
  1580. fingerprint=relation(Fingerprint),
  1581. install_date=self.tbl_binaries.c.install_date,
  1582. suites=relation(Suite, secondary=self.tbl_bin_associations,
  1583. backref=backref('binaries', lazy='dynamic')),
  1584. extra_sources=relation(DBSource, secondary=self.tbl_extra_src_references,
  1585. backref=backref('extra_binary_references', lazy='dynamic')),
  1586. key=relation(BinaryMetadata, cascade='all',
  1587. collection_class=attribute_mapped_collection('key'))),
  1588. )
  1589. mapper(Component, self.tbl_component,
  1590. properties=dict(component_id=self.tbl_component.c.id,
  1591. component_name=self.tbl_component.c.name),
  1592. )
  1593. mapper(DBConfig, self.tbl_config,
  1594. properties=dict(config_id=self.tbl_config.c.id))
  1595. mapper(DSCFile, self.tbl_dsc_files,
  1596. properties=dict(dscfile_id=self.tbl_dsc_files.c.id,
  1597. source_id=self.tbl_dsc_files.c.source,
  1598. source=relation(DBSource),
  1599. poolfile_id=self.tbl_dsc_files.c.file,
  1600. poolfile=relation(PoolFile)))
  1601. mapper(ExternalOverride, self.tbl_external_overrides,
  1602. properties=dict(
  1603. suite_id=self.tbl_external_overrides.c.suite,
  1604. suite=relation(Suite),
  1605. component_id=self.tbl_external_overrides.c.component,
  1606. component=relation(Component)))
  1607. mapper(PoolFile, self.tbl_files,
  1608. properties=dict(file_id=self.tbl_files.c.id,
  1609. filesize=self.tbl_files.c.size),
  1610. )
  1611. mapper(Fingerprint, self.tbl_fingerprint,
  1612. properties=dict(fingerprint_id=self.tbl_fingerprint.c.id,
  1613. uid_id=self.tbl_fingerprint.c.uid,
  1614. uid=relation(Uid),
  1615. keyring_id=self.tbl_fingerprint.c.keyring,
  1616. keyring=relation(Keyring),
  1617. acl=relation(ACL)),
  1618. )
  1619. mapper(Keyring, self.tbl_keyrings,
  1620. properties=dict(keyring_name=self.tbl_keyrings.c.name,
  1621. keyring_id=self.tbl_keyrings.c.id,
  1622. acl=relation(ACL, primaryjoin=(self.tbl_keyrings.c.acl_id == self.tbl_acl.c.id)))),
  1623. mapper(DBChange, self.tbl_changes,
  1624. properties=dict(change_id=self.tbl_changes.c.id,
  1625. seen=self.tbl_changes.c.seen,
  1626. source=self.tbl_changes.c.source,
  1627. binaries=self.tbl_changes.c.binaries,
  1628. architecture=self.tbl_changes.c.architecture,
  1629. distribution=self.tbl_changes.c.distribution,
  1630. urgency=self.tbl_changes.c.urgency,
  1631. maintainer=self.tbl_changes.c.maintainer,
  1632. changedby=self.tbl_changes.c.changedby,
  1633. date=self.tbl_changes.c.date,
  1634. version=self.tbl_changes.c.version))
  1635. mapper(Maintainer, self.tbl_maintainer,
  1636. properties=dict(maintainer_id=self.tbl_maintainer.c.id,
  1637. maintains_sources=relation(DBSource, backref='maintainer',
  1638. primaryjoin=(self.tbl_maintainer.c.id == self.tbl_source.c.maintainer)),
  1639. changed_sources=relation(DBSource, backref='changedby',
  1640. primaryjoin=(self.tbl_maintainer.c.id == self.tbl_source.c.changedby))),
  1641. )
  1642. mapper(NewComment, self.tbl_new_comments,
  1643. properties=dict(comment_id=self.tbl_new_comments.c.id,
  1644. policy_queue=relation(PolicyQueue)))
  1645. mapper(Override, self.tbl_override,
  1646. properties=dict(suite_id=self.tbl_override.c.suite,
  1647. suite=relation(Suite,
  1648. backref=backref('overrides', lazy='dynamic')),
  1649. package=self.tbl_override.c.package,
  1650. component_id=self.tbl_override.c.component,
  1651. component=relation(Component,
  1652. backref=backref('overrides', lazy='dynamic')),
  1653. priority_id=self.tbl_override.c.priority,
  1654. priority=relation(Priority,
  1655. backref=backref('overrides', lazy='dynamic')),
  1656. section_id=self.tbl_override.c.section,
  1657. section=relation(Section,
  1658. backref=backref('overrides', lazy='dynamic')),
  1659. overridetype_id=self.tbl_override.c.type,
  1660. overridetype=relation(OverrideType,
  1661. backref=backref('overrides', lazy='dynamic'))))
  1662. mapper(OverrideType, self.tbl_override_type,
  1663. properties=dict(overridetype=self.tbl_override_type.c.type,
  1664. overridetype_id=self.tbl_override_type.c.id))
  1665. mapper(PolicyQueue, self.tbl_policy_queue,
  1666. properties=dict(policy_queue_id=self.tbl_policy_queue.c.id,
  1667. suite=relation(Suite, primaryjoin=(self.tbl_policy_queue.c.suite_id == self.tbl_suite.c.id))))
  1668. mapper(PolicyQueueUpload, self.tbl_policy_queue_upload,
  1669. properties=dict(
  1670. changes=relation(DBChange),
  1671. policy_queue=relation(PolicyQueue, backref='uploads'),
  1672. target_suite=relation(Suite),
  1673. source=relation(DBSource),
  1674. binaries=relation(DBBinary, secondary=self.tbl_policy_queue_upload_binaries_map),
  1675. ))
  1676. mapper(PolicyQueueByhandFile, self.tbl_policy_queue_byhand_file,
  1677. properties=dict(
  1678. upload=relation(PolicyQueueUpload, backref='byhand'),
  1679. ))
  1680. mapper(Priority, self.tbl_priority,
  1681. properties=dict(priority_id=self.tbl_priority.c.id))
  1682. mapper(SignatureHistory, self.tbl_signature_history)
  1683. mapper(DBSource, self.tbl_source,
  1684. properties=dict(source_id=self.tbl_source.c.id,
  1685. version=self.tbl_source.c.version,
  1686. maintainer_id=self.tbl_source.c.maintainer,
  1687. poolfile_id=self.tbl_source.c.file,
  1688. poolfile=relation(PoolFile),
  1689. fingerprint_id=self.tbl_source.c.sig_fpr,
  1690. fingerprint=relation(Fingerprint),
  1691. changedby_id=self.tbl_source.c.changedby,
  1692. srcfiles=relation(DSCFile,
  1693. primaryjoin=(self.tbl_source.c.id == self.tbl_dsc_files.c.source)),
  1694. suites=relation(Suite, secondary=self.tbl_src_associations,
  1695. backref=backref('sources', lazy='dynamic')),
  1696. uploaders=relation(Maintainer,
  1697. secondary=self.tbl_src_uploaders),
  1698. key=relation(SourceMetadata, cascade='all',
  1699. collection_class=attribute_mapped_collection('key'))),
  1700. )
  1701. mapper(SrcFormat, self.tbl_src_format,
  1702. properties=dict(src_format_id=self.tbl_src_format.c.id,
  1703. format_name=self.tbl_src_format.c.format_name))
  1704. mapper(Suite, self.tbl_suite,
  1705. properties=dict(suite_id=self.tbl_suite.c.id,
  1706. policy_queue=relation(PolicyQueue, primaryjoin=(self.tbl_suite.c.policy_queue_id == self.tbl_policy_queue.c.id)),
  1707. new_queue=relation(PolicyQueue, primaryjoin=(self.tbl_suite.c.new_queue_id == self.tbl_policy_queue.c.id)),
  1708. debug_suite=relation(Suite, remote_side=[self.tbl_suite.c.id]),
  1709. copy_queues=relation(BuildQueue,
  1710. secondary=self.tbl_suite_build_queue_copy),
  1711. srcformats=relation(SrcFormat, secondary=self.tbl_suite_src_formats,
  1712. backref=backref('suites', lazy='dynamic')),
  1713. archive=relation(Archive, backref='suites'),
  1714. acls=relation(ACL, secondary=self.tbl_suite_acl_map, collection_class=set),
  1715. components=relation(Component, secondary=self.tbl_component_suite,
  1716. order_by=self.tbl_component.c.ordering,
  1717. backref=backref('suites')),
  1718. architectures=relation(Architecture, secondary=self.tbl_suite_architectures,
  1719. backref=backref('suites'))),
  1720. )
  1721. mapper(Uid, self.tbl_uid,
  1722. properties=dict(uid_id=self.tbl_uid.c.id,
  1723. fingerprint=relation(Fingerprint)),
  1724. )
  1725. mapper(BinContents, self.tbl_bin_contents,
  1726. properties=dict(
  1727. binary=relation(DBBinary,
  1728. backref=backref('contents', lazy='dynamic', cascade='all')),
  1729. file=self.tbl_bin_contents.c.file))
  1730. mapper(SrcContents, self.tbl_src_contents,
  1731. properties=dict(
  1732. source=relation(DBSource,
  1733. backref=backref('contents', lazy='dynamic', cascade='all')),
  1734. file=self.tbl_src_contents.c.file))
  1735. mapper(MetadataKey, self.tbl_metadata_keys,
  1736. properties=dict(
  1737. key_id=self.tbl_metadata_keys.c.key_id,
  1738. key=self.tbl_metadata_keys.c.key))
  1739. mapper(BinaryMetadata, self.tbl_binaries_metadata,
  1740. properties=dict(
  1741. binary_id=self.tbl_binaries_metadata.c.bin_id,
  1742. binary=relation(DBBinary),
  1743. key_id=self.tbl_binaries_metadata.c.key_id,
  1744. key=relation(MetadataKey),
  1745. value=self.tbl_binaries_metadata.c.value))
  1746. mapper(SourceMetadata, self.tbl_source_metadata,
  1747. properties=dict(
  1748. source_id=self.tbl_source_metadata.c.src_id,
  1749. source=relation(DBSource),
  1750. key_id=self.tbl_source_metadata.c.key_id,
  1751. key=relation(MetadataKey),
  1752. value=self.tbl_source_metadata.c.value))
  1753. mapper(VersionCheck, self.tbl_version_check,
  1754. properties=dict(
  1755. suite_id=self.tbl_version_check.c.suite,
  1756. suite=relation(Suite, primaryjoin=self.tbl_version_check.c.suite == self.tbl_suite.c.id),
  1757. reference_id=self.tbl_version_check.c.reference,
  1758. reference=relation(Suite, primaryjoin=self.tbl_version_check.c.reference == self.tbl_suite.c.id, lazy='joined')))
  1759. ## Connection functions
  1760. def __createconn(self):
  1761. from .config import Config
  1762. cnf = Config()
  1763. if "DB::Service" in cnf:
  1764. connstr = "postgresql://service=%s" % cnf["DB::Service"]
  1765. elif "DB::Host" in cnf:
  1766. # TCP/IP
  1767. connstr = "postgresql://%s" % cnf["DB::Host"]
  1768. if "DB::Port" in cnf and cnf["DB::Port"] != "-1":
  1769. connstr += ":%s" % cnf["DB::Port"]
  1770. connstr += "/%s" % cnf["DB::Name"]
  1771. else:
  1772. # Unix Socket
  1773. connstr = "postgresql:///%s" % cnf["DB::Name"]
  1774. if "DB::Port" in cnf and cnf["DB::Port"] != "-1":
  1775. connstr += "?port=%s" % cnf["DB::Port"]
  1776. engine_args = {'echo': self.debug}
  1777. if 'DB::PoolSize' in cnf:
  1778. engine_args['pool_size'] = int(cnf['DB::PoolSize'])
  1779. if 'DB::MaxOverflow' in cnf:
  1780. engine_args['max_overflow'] = int(cnf['DB::MaxOverflow'])
  1781. # we don't support non-utf-8 connections
  1782. engine_args['client_encoding'] = 'utf-8'
  1783. # Monkey patch a new dialect in in order to support service= syntax
  1784. import sqlalchemy.dialects.postgresql
  1785. from sqlalchemy.dialects.postgresql.psycopg2 import PGDialect_psycopg2
  1786. class PGDialect_psycopg2_dak(PGDialect_psycopg2):
  1787. def create_connect_args(self, url):
  1788. if str(url).startswith('postgresql://service='):
  1789. # Eww
  1790. servicename = str(url)[21:]
  1791. return (['service=%s' % servicename], {})
  1792. else:
  1793. return PGDialect_psycopg2.create_connect_args(self, url)
  1794. sqlalchemy.dialects.postgresql.base.dialect = PGDialect_psycopg2_dak
  1795. try:
  1796. self.db_pg = create_engine(connstr, **engine_args)
  1797. self.db_smaker = sessionmaker(bind=self.db_pg,
  1798. autoflush=True,
  1799. autocommit=False)
  1800. if self.db_meta is None:
  1801. self.__class__.db_meta = Base.metadata
  1802. self.__class__.db_meta.bind = self.db_pg
  1803. self.__setuptables()
  1804. self.__setupmappers()
  1805. except OperationalError as e:
  1806. from . import utils
  1807. utils.fubar("Cannot connect to database (%s)" % str(e))
  1808. self.pid = os.getpid()
  1809. def session(self, work_mem=0):
  1810. '''
  1811. Returns a new session object. If a work_mem parameter is provided a new
  1812. transaction is started and the work_mem parameter is set for this
  1813. transaction. The work_mem parameter is measured in MB. A default value
  1814. will be used if the parameter is not set.
  1815. '''
  1816. # reinitialize DBConn in new processes
  1817. if self.pid != os.getpid():
  1818. self.__createconn()
  1819. session = self.db_smaker()
  1820. if work_mem > 0:
  1821. session.execute("SET LOCAL work_mem TO '%d MB'" % work_mem)
  1822. return session
  1823. __all__.append('DBConn')