dbconn.py 81 KB

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