dbconn.py 79 KB

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