dbconn.py 87 KB

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