5 @contact: Debian FTPMaster <ftpmaster@debian.org>
6 @copyright: 2000, 2001, 2002, 2003, 2004, 2006 James Troup <james@nocrew.org>
7 @copyright: 2008-2009 Mark Hymers <mhy@debian.org>
8 @copyright: 2009 Joerg Jaspert <joerg@debian.org>
9 @copyright: 2009 Mike O'Connor <stew@debian.org>
10 @license: GNU General Public License version 2 or later
13 # This program is free software; you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation; either version 2 of the License, or
16 # (at your option) any later version.
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
27 ################################################################################
29 # < mhy> I need a funny comment
30 # < sgran> two peanuts were walking down a dark street
31 # < sgran> one was a-salted
32 # * mhy looks up the definition of "funny"
34 ################################################################################
41 from inspect import getargspec
43 from sqlalchemy import create_engine, Table, MetaData
44 from sqlalchemy.orm import sessionmaker, mapper, relation
46 # Don't remove this, we re-export the exceptions to scripts which import us
47 from sqlalchemy.exc import *
48 from sqlalchemy.orm.exc import NoResultFound
50 # Only import Config until Queue stuff is changed to store its config
52 from config import Config
53 from singleton import Singleton
54 from textutils import fix_maintainer
56 ################################################################################
58 __all__ = ['IntegrityError', 'SQLAlchemyError']
60 ################################################################################
62 def session_wrapper(fn):
64 Wrapper around common ".., session=None):" handling. If the wrapped
65 function is called without passing 'session', we create a local one
66 and destroy it when the function ends.
68 Also attaches a commit_or_flush method to the session; if we created a
69 local session, this is a synonym for session.commit(), otherwise it is a
70 synonym for session.flush().
73 def wrapped(*args, **kwargs):
74 private_transaction = False
76 # Find the session object
77 session = kwargs.get('session')
80 if len(args) <= len(getargspec(fn)[0]) - 1:
81 # No session specified as last argument or in kwargs
82 private_transaction = True
83 session = kwargs['session'] = DBConn().session()
85 # Session is last argument in args
89 session = args[-1] = DBConn().session()
90 private_transaction = True
92 if private_transaction:
93 session.commit_or_flush = session.commit
95 session.commit_or_flush = session.flush
98 return fn(*args, **kwargs)
100 if private_transaction:
101 # We created a session; close it.
104 wrapped.__doc__ = fn.__doc__
105 wrapped.func_name = fn.func_name
109 ################################################################################
111 class Architecture(object):
112 def __init__(self, *args, **kwargs):
115 def __eq__(self, val):
116 if isinstance(val, str):
117 return (self.arch_string== val)
118 # This signals to use the normal comparison operator
119 return NotImplemented
121 def __ne__(self, val):
122 if isinstance(val, str):
123 return (self.arch_string != val)
124 # This signals to use the normal comparison operator
125 return NotImplemented
128 return '<Architecture %s>' % self.arch_string
130 __all__.append('Architecture')
133 def get_architecture(architecture, session=None):
135 Returns database id for given C{architecture}.
137 @type architecture: string
138 @param architecture: The name of the architecture
140 @type session: Session
141 @param session: Optional SQLA session object (a temporary one will be
142 generated if not supplied)
145 @return: Architecture object for the given arch (None if not present)
148 q = session.query(Architecture).filter_by(arch_string=architecture)
152 except NoResultFound:
155 __all__.append('get_architecture')
158 def get_architecture_suites(architecture, session=None):
160 Returns list of Suite objects for given C{architecture} name
163 @param source: Architecture name to search for
165 @type session: Session
166 @param session: Optional SQL session object (a temporary one will be
167 generated if not supplied)
170 @return: list of Suite objects for the given name (may be empty)
173 q = session.query(Suite)
174 q = q.join(SuiteArchitecture)
175 q = q.join(Architecture).filter_by(arch_string=architecture).order_by('suite_name')
181 __all__.append('get_architecture_suites')
183 ################################################################################
185 class Archive(object):
186 def __init__(self, *args, **kwargs):
190 return '<Archive %s>' % self.archive_name
192 __all__.append('Archive')
195 def get_archive(archive, session=None):
197 returns database id for given C{archive}.
199 @type archive: string
200 @param archive: the name of the arhive
202 @type session: Session
203 @param session: Optional SQLA session object (a temporary one will be
204 generated if not supplied)
207 @return: Archive object for the given name (None if not present)
210 archive = archive.lower()
212 q = session.query(Archive).filter_by(archive_name=archive)
216 except NoResultFound:
219 __all__.append('get_archive')
221 ################################################################################
223 class BinAssociation(object):
224 def __init__(self, *args, **kwargs):
228 return '<BinAssociation %s (%s, %s)>' % (self.ba_id, self.binary, self.suite)
230 __all__.append('BinAssociation')
232 ################################################################################
234 class BinContents(object):
235 def __init__(self, *args, **kwargs):
239 return '<BinContents (%s, %s)>' % (self.binary, self.filename)
241 __all__.append('BinContents')
243 ################################################################################
245 class DBBinary(object):
246 def __init__(self, *args, **kwargs):
250 return '<DBBinary %s (%s, %s)>' % (self.package, self.version, self.architecture)
252 __all__.append('DBBinary')
255 def get_suites_binary_in(package, session=None):
257 Returns list of Suite objects which given C{package} name is in
260 @param source: DBBinary package name to search for
263 @return: list of Suite objects for the given package
266 return session.query(Suite).join(BinAssociation).join(DBBinary).filter_by(package=package).all()
268 __all__.append('get_suites_binary_in')
271 def get_binary_from_id(id, session=None):
273 Returns DBBinary object for given C{id}
276 @param id: Id of the required binary
278 @type session: Session
279 @param session: Optional SQLA session object (a temporary one will be
280 generated if not supplied)
283 @return: DBBinary object for the given binary (None if not present)
286 q = session.query(DBBinary).filter_by(binary_id=id)
290 except NoResultFound:
293 __all__.append('get_binary_from_id')
296 def get_binaries_from_name(package, version=None, architecture=None, session=None):
298 Returns list of DBBinary objects for given C{package} name
301 @param package: DBBinary package name to search for
303 @type version: str or None
304 @param version: Version to search for (or None)
306 @type package: str, list or None
307 @param package: Architectures to limit to (or None if no limit)
309 @type session: Session
310 @param session: Optional SQL session object (a temporary one will be
311 generated if not supplied)
314 @return: list of DBBinary objects for the given name (may be empty)
317 q = session.query(DBBinary).filter_by(package=package)
319 if version is not None:
320 q = q.filter_by(version=version)
322 if architecture is not None:
323 if not isinstance(architecture, list):
324 architecture = [architecture]
325 q = q.join(Architecture).filter(Architecture.arch_string.in_(architecture))
331 __all__.append('get_binaries_from_name')
334 def get_binaries_from_source_id(source_id, session=None):
336 Returns list of DBBinary objects for given C{source_id}
339 @param source_id: source_id to search for
341 @type session: Session
342 @param session: Optional SQL session object (a temporary one will be
343 generated if not supplied)
346 @return: list of DBBinary objects for the given name (may be empty)
349 return session.query(DBBinary).filter_by(source_id=source_id).all()
351 __all__.append('get_binaries_from_source_id')
354 def get_binary_from_name_suite(package, suitename, session=None):
355 ### For dak examine-package
356 ### XXX: Doesn't use object API yet
358 sql = """SELECT DISTINCT(b.package), b.version, c.name, su.suite_name
359 FROM binaries b, files fi, location l, component c, bin_associations ba, suite su
360 WHERE b.package=:package
362 AND fi.location = l.id
363 AND l.component = c.id
366 AND su.suite_name=:suitename
367 ORDER BY b.version DESC"""
369 return session.execute(sql, {'package': package, 'suitename': suitename})
371 __all__.append('get_binary_from_name_suite')
374 def get_binary_components(package, suitename, arch, session=None):
375 # Check for packages that have moved from one component to another
376 query = """SELECT c.name FROM binaries b, bin_associations ba, suite s, location l, component c, architecture a, files f
377 WHERE b.package=:package AND s.suite_name=:suitename
378 AND (a.arch_string = :arch OR a.arch_string = 'all')
379 AND ba.bin = b.id AND ba.suite = s.id AND b.architecture = a.id
380 AND f.location = l.id
381 AND l.component = c.id
384 vals = {'package': package, 'suitename': suitename, 'arch': arch}
386 return session.execute(query, vals)
388 __all__.append('get_binary_components')
390 ################################################################################
392 class BinaryACL(object):
393 def __init__(self, *args, **kwargs):
397 return '<BinaryACL %s>' % self.binary_acl_id
399 __all__.append('BinaryACL')
401 ################################################################################
403 class BinaryACLMap(object):
404 def __init__(self, *args, **kwargs):
408 return '<BinaryACLMap %s>' % self.binary_acl_map_id
410 __all__.append('BinaryACLMap')
412 ################################################################################
414 class Component(object):
415 def __init__(self, *args, **kwargs):
418 def __eq__(self, val):
419 if isinstance(val, str):
420 return (self.component_name == val)
421 # This signals to use the normal comparison operator
422 return NotImplemented
424 def __ne__(self, val):
425 if isinstance(val, str):
426 return (self.component_name != val)
427 # This signals to use the normal comparison operator
428 return NotImplemented
431 return '<Component %s>' % self.component_name
434 __all__.append('Component')
437 def get_component(component, session=None):
439 Returns database id for given C{component}.
441 @type component: string
442 @param component: The name of the override type
445 @return: the database id for the given component
448 component = component.lower()
450 q = session.query(Component).filter_by(component_name=component)
454 except NoResultFound:
457 __all__.append('get_component')
459 ################################################################################
461 class DBConfig(object):
462 def __init__(self, *args, **kwargs):
466 return '<DBConfig %s>' % self.name
468 __all__.append('DBConfig')
470 ################################################################################
473 def get_or_set_contents_file_id(filename, session=None):
475 Returns database id for given filename.
477 If no matching file is found, a row is inserted.
479 @type filename: string
480 @param filename: The filename
481 @type session: SQLAlchemy
482 @param session: Optional SQL session object (a temporary one will be
483 generated if not supplied). If not passed, a commit will be performed at
484 the end of the function, otherwise the caller is responsible for commiting.
487 @return: the database id for the given component
490 q = session.query(ContentFilename).filter_by(filename=filename)
493 ret = q.one().cafilename_id
494 except NoResultFound:
495 cf = ContentFilename()
496 cf.filename = filename
498 session.commit_or_flush()
499 ret = cf.cafilename_id
503 __all__.append('get_or_set_contents_file_id')
506 def get_contents(suite, overridetype, section=None, session=None):
508 Returns contents for a suite / overridetype combination, limiting
509 to a section if not None.
512 @param suite: Suite object
514 @type overridetype: OverrideType
515 @param overridetype: OverrideType object
517 @type section: Section
518 @param section: Optional section object to limit results to
520 @type session: SQLAlchemy
521 @param session: Optional SQL session object (a temporary one will be
522 generated if not supplied)
525 @return: ResultsProxy object set up to return tuples of (filename, section,
529 # find me all of the contents for a given suite
530 contents_q = """SELECT (p.path||'/'||n.file) AS fn,
534 FROM content_associations c join content_file_paths p ON (c.filepath=p.id)
535 JOIN content_file_names n ON (c.filename=n.id)
536 JOIN binaries b ON (b.id=c.binary_pkg)
537 JOIN override o ON (o.package=b.package)
538 JOIN section s ON (s.id=o.section)
539 WHERE o.suite = :suiteid AND o.type = :overridetypeid
540 AND b.type=:overridetypename"""
542 vals = {'suiteid': suite.suite_id,
543 'overridetypeid': overridetype.overridetype_id,
544 'overridetypename': overridetype.overridetype}
546 if section is not None:
547 contents_q += " AND s.id = :sectionid"
548 vals['sectionid'] = section.section_id
550 contents_q += " ORDER BY fn"
552 return session.execute(contents_q, vals)
554 __all__.append('get_contents')
556 ################################################################################
558 class ContentFilepath(object):
559 def __init__(self, *args, **kwargs):
563 return '<ContentFilepath %s>' % self.filepath
565 __all__.append('ContentFilepath')
568 def get_or_set_contents_path_id(filepath, session=None):
570 Returns database id for given path.
572 If no matching file is found, a row is inserted.
574 @type filename: string
575 @param filename: The filepath
576 @type session: SQLAlchemy
577 @param session: Optional SQL session object (a temporary one will be
578 generated if not supplied). If not passed, a commit will be performed at
579 the end of the function, otherwise the caller is responsible for commiting.
582 @return: the database id for the given path
585 q = session.query(ContentFilepath).filter_by(filepath=filepath)
588 ret = q.one().cafilepath_id
589 except NoResultFound:
590 cf = ContentFilepath()
591 cf.filepath = filepath
593 session.commit_or_flush()
594 ret = cf.cafilepath_id
598 __all__.append('get_or_set_contents_path_id')
600 ################################################################################
602 class ContentAssociation(object):
603 def __init__(self, *args, **kwargs):
607 return '<ContentAssociation %s>' % self.ca_id
609 __all__.append('ContentAssociation')
611 def insert_content_paths(binary_id, fullpaths, session=None):
613 Make sure given path is associated with given binary id
616 @param binary_id: the id of the binary
617 @type fullpaths: list
618 @param fullpaths: the list of paths of the file being associated with the binary
619 @type session: SQLAlchemy session
620 @param session: Optional SQLAlchemy session. If this is passed, the caller
621 is responsible for ensuring a transaction has begun and committing the
622 results or rolling back based on the result code. If not passed, a commit
623 will be performed at the end of the function, otherwise the caller is
624 responsible for commiting.
626 @return: True upon success
631 session = DBConn().session()
637 for fullpath in fullpaths:
638 if fullpath.startswith( './' ):
639 fullpath = fullpath[2:]
641 session.execute( "INSERT INTO bin_contents ( file, binary_id ) VALUES ( :filename, :id )", { 'filename': fullpath, 'id': binary_id} )
649 traceback.print_exc()
651 # Only rollback if we set up the session ourself
658 __all__.append('insert_content_paths')
660 ################################################################################
662 class DSCFile(object):
663 def __init__(self, *args, **kwargs):
667 return '<DSCFile %s>' % self.dscfile_id
669 __all__.append('DSCFile')
672 def get_dscfiles(dscfile_id=None, source_id=None, poolfile_id=None, session=None):
674 Returns a list of DSCFiles which may be empty
676 @type dscfile_id: int (optional)
677 @param dscfile_id: the dscfile_id of the DSCFiles to find
679 @type source_id: int (optional)
680 @param source_id: the source id related to the DSCFiles to find
682 @type poolfile_id: int (optional)
683 @param poolfile_id: the poolfile id related to the DSCFiles to find
686 @return: Possibly empty list of DSCFiles
689 q = session.query(DSCFile)
691 if dscfile_id is not None:
692 q = q.filter_by(dscfile_id=dscfile_id)
694 if source_id is not None:
695 q = q.filter_by(source_id=source_id)
697 if poolfile_id is not None:
698 q = q.filter_by(poolfile_id=poolfile_id)
702 __all__.append('get_dscfiles')
704 ################################################################################
706 class PoolFile(object):
707 def __init__(self, *args, **kwargs):
711 return '<PoolFile %s>' % self.filename
713 __all__.append('PoolFile')
716 def check_poolfile(filename, filesize, md5sum, location_id, session=None):
719 (ValidFileFound [boolean or None], PoolFile object or None)
721 @type filename: string
722 @param filename: the filename of the file to check against the DB
725 @param filesize: the size of the file to check against the DB
728 @param md5sum: the md5sum of the file to check against the DB
730 @type location_id: int
731 @param location_id: the id of the location to look in
734 @return: Tuple of length 2.
735 If more than one file found with that name:
737 If valid pool file found: (True, PoolFile object)
738 If valid pool file not found:
739 (False, None) if no file found
740 (False, PoolFile object) if file found with size/md5sum mismatch
743 q = session.query(PoolFile).filter_by(filename=filename)
744 q = q.join(Location).filter_by(location_id=location_id)
754 if obj.md5sum != md5sum or obj.filesize != filesize:
762 __all__.append('check_poolfile')
765 def get_poolfile_by_id(file_id, session=None):
767 Returns a PoolFile objects or None for the given id
770 @param file_id: the id of the file to look for
772 @rtype: PoolFile or None
773 @return: either the PoolFile object or None
776 q = session.query(PoolFile).filter_by(file_id=file_id)
780 except NoResultFound:
783 __all__.append('get_poolfile_by_id')
787 def get_poolfile_by_name(filename, location_id=None, session=None):
789 Returns an array of PoolFile objects for the given filename and
790 (optionally) location_id
792 @type filename: string
793 @param filename: the filename of the file to check against the DB
795 @type location_id: int
796 @param location_id: the id of the location to look in (optional)
799 @return: array of PoolFile objects
802 q = session.query(PoolFile).filter_by(filename=filename)
804 if location_id is not None:
805 q = q.join(Location).filter_by(location_id=location_id)
809 __all__.append('get_poolfile_by_name')
812 def get_poolfile_like_name(filename, session=None):
814 Returns an array of PoolFile objects which are like the given name
816 @type filename: string
817 @param filename: the filename of the file to check against the DB
820 @return: array of PoolFile objects
823 # TODO: There must be a way of properly using bind parameters with %FOO%
824 q = session.query(PoolFile).filter(PoolFile.filename.like('%%%s%%' % filename))
828 __all__.append('get_poolfile_like_name')
830 ################################################################################
832 class Fingerprint(object):
833 def __init__(self, *args, **kwargs):
837 return '<Fingerprint %s>' % self.fingerprint
839 __all__.append('Fingerprint')
842 def get_fingerprint(fpr, session=None):
844 Returns Fingerprint object for given fpr.
847 @param fpr: The fpr to find / add
849 @type session: SQLAlchemy
850 @param session: Optional SQL session object (a temporary one will be
851 generated if not supplied).
854 @return: the Fingerprint object for the given fpr or None
857 q = session.query(Fingerprint).filter_by(fingerprint=fpr)
861 except NoResultFound:
866 __all__.append('get_fingerprint')
869 def get_or_set_fingerprint(fpr, session=None):
871 Returns Fingerprint object for given fpr.
873 If no matching fpr is found, a row is inserted.
876 @param fpr: The fpr to find / add
878 @type session: SQLAlchemy
879 @param session: Optional SQL session object (a temporary one will be
880 generated if not supplied). If not passed, a commit will be performed at
881 the end of the function, otherwise the caller is responsible for commiting.
882 A flush will be performed either way.
885 @return: the Fingerprint object for the given fpr
888 q = session.query(Fingerprint).filter_by(fingerprint=fpr)
892 except NoResultFound:
893 fingerprint = Fingerprint()
894 fingerprint.fingerprint = fpr
895 session.add(fingerprint)
896 session.commit_or_flush()
901 __all__.append('get_or_set_fingerprint')
903 ################################################################################
905 # Helper routine for Keyring class
906 def get_ldap_name(entry):
908 for k in ["cn", "mn", "sn"]:
910 if ret and ret[0] != "" and ret[0] != "-":
912 return " ".join(name)
914 ################################################################################
916 class Keyring(object):
917 gpg_invocation = "gpg --no-default-keyring --keyring %s" +\
918 " --with-colons --fingerprint --fingerprint"
923 def __init__(self, *args, **kwargs):
927 return '<Keyring %s>' % self.keyring_name
929 def de_escape_gpg_str(self, str):
930 esclist = re.split(r'(\\x..)', str)
931 for x in range(1,len(esclist),2):
932 esclist[x] = "%c" % (int(esclist[x][2:],16))
933 return "".join(esclist)
935 def load_keys(self, keyring):
938 if not self.keyring_id:
939 raise Exception('Must be initialized with database information')
941 k = os.popen(self.gpg_invocation % keyring, "r")
945 for line in k.xreadlines():
946 field = line.split(":")
947 if field[0] == "pub":
949 (name, addr) = email.Utils.parseaddr(field[9])
950 name = re.sub(r"\s*[(].*[)]", "", name)
951 if name == "" or addr == "" or "@" not in addr:
954 name = self.de_escape_gpg_str(name)
955 self.keys[key] = {"email": addr}
957 self.keys[key]["name"] = name
958 self.keys[key]["aliases"] = [name]
959 self.keys[key]["fingerprints"] = []
961 elif key and field[0] == "sub" and len(field) >= 12:
962 signingkey = ("s" in field[11])
963 elif key and field[0] == "uid":
964 (name, addr) = email.Utils.parseaddr(field[9])
965 if name and name not in self.keys[key]["aliases"]:
966 self.keys[key]["aliases"].append(name)
967 elif signingkey and field[0] == "fpr":
968 self.keys[key]["fingerprints"].append(field[9])
969 self.fpr_lookup[field[9]] = key
971 def import_users_from_ldap(self, session):
975 LDAPDn = cnf["Import-LDAP-Fingerprints::LDAPDn"]
976 LDAPServer = cnf["Import-LDAP-Fingerprints::LDAPServer"]
978 l = ldap.open(LDAPServer)
979 l.simple_bind_s("","")
980 Attrs = l.search_s(LDAPDn, ldap.SCOPE_ONELEVEL,
981 "(&(keyfingerprint=*)(gidnumber=%s))" % (cnf["Import-Users-From-Passwd::ValidGID"]),
982 ["uid", "keyfingerprint", "cn", "mn", "sn"])
991 uid = entry["uid"][0]
992 name = get_ldap_name(entry)
993 fingerprints = entry["keyFingerPrint"]
995 for f in fingerprints:
996 key = self.fpr_lookup.get(f, None)
997 if key not in self.keys:
999 self.keys[key]["uid"] = uid
1003 keyid = get_or_set_uid(uid, session).uid_id
1004 byuid[keyid] = (uid, name)
1005 byname[uid] = (keyid, name)
1007 return (byname, byuid)
1009 def generate_users_from_keyring(self, format, session):
1013 for x in self.keys.keys():
1014 if self.keys[x]["email"] == "invalid-uid":
1016 self.keys[x]["uid"] = format % "invalid-uid"
1018 uid = format % self.keys[x]["email"]
1019 keyid = get_or_set_uid(uid, session).uid_id
1020 byuid[keyid] = (uid, self.keys[x]["name"])
1021 byname[uid] = (keyid, self.keys[x]["name"])
1022 self.keys[x]["uid"] = uid
1025 uid = format % "invalid-uid"
1026 keyid = get_or_set_uid(uid, session).uid_id
1027 byuid[keyid] = (uid, "ungeneratable user id")
1028 byname[uid] = (keyid, "ungeneratable user id")
1030 return (byname, byuid)
1032 __all__.append('Keyring')
1035 def get_keyring(keyring, session=None):
1037 If C{keyring} does not have an entry in the C{keyrings} table yet, return None
1038 If C{keyring} already has an entry, simply return the existing Keyring
1040 @type keyring: string
1041 @param keyring: the keyring name
1044 @return: the Keyring object for this keyring
1047 q = session.query(Keyring).filter_by(keyring_name=keyring)
1051 except NoResultFound:
1054 __all__.append('get_keyring')
1056 ################################################################################
1058 class KeyringACLMap(object):
1059 def __init__(self, *args, **kwargs):
1063 return '<KeyringACLMap %s>' % self.keyring_acl_map_id
1065 __all__.append('KeyringACLMap')
1067 ################################################################################
1069 class Location(object):
1070 def __init__(self, *args, **kwargs):
1074 return '<Location %s (%s)>' % (self.path, self.location_id)
1076 __all__.append('Location')
1079 def get_location(location, component=None, archive=None, session=None):
1081 Returns Location object for the given combination of location, component
1084 @type location: string
1085 @param location: the path of the location, e.g. I{/srv/ftp.debian.org/ftp/pool/}
1087 @type component: string
1088 @param component: the component name (if None, no restriction applied)
1090 @type archive: string
1091 @param archive_id: the archive name (if None, no restriction applied)
1093 @rtype: Location / None
1094 @return: Either a Location object or None if one can't be found
1097 q = session.query(Location).filter_by(path=location)
1099 if archive is not None:
1100 q = q.join(Archive).filter_by(archive_name=archive)
1102 if component is not None:
1103 q = q.join(Component).filter_by(component_name=component)
1107 except NoResultFound:
1110 __all__.append('get_location')
1112 ################################################################################
1114 class Maintainer(object):
1115 def __init__(self, *args, **kwargs):
1119 return '''<Maintainer '%s' (%s)>''' % (self.name, self.maintainer_id)
1121 def get_split_maintainer(self):
1122 if not hasattr(self, 'name') or self.name is None:
1123 return ('', '', '', '')
1125 return fix_maintainer(self.name.strip())
1127 __all__.append('Maintainer')
1130 def get_or_set_maintainer(name, session=None):
1132 Returns Maintainer object for given maintainer name.
1134 If no matching maintainer name is found, a row is inserted.
1137 @param name: The maintainer name to add
1139 @type session: SQLAlchemy
1140 @param session: Optional SQL session object (a temporary one will be
1141 generated if not supplied). If not passed, a commit will be performed at
1142 the end of the function, otherwise the caller is responsible for commiting.
1143 A flush will be performed either way.
1146 @return: the Maintainer object for the given maintainer
1149 q = session.query(Maintainer).filter_by(name=name)
1152 except NoResultFound:
1153 maintainer = Maintainer()
1154 maintainer.name = name
1155 session.add(maintainer)
1156 session.commit_or_flush()
1161 __all__.append('get_or_set_maintainer')
1164 def get_maintainer(maintainer_id, session=None):
1166 Return the name of the maintainer behind C{maintainer_id} or None if that
1167 maintainer_id is invalid.
1169 @type maintainer_id: int
1170 @param maintainer_id: the id of the maintainer
1173 @return: the Maintainer with this C{maintainer_id}
1176 return session.query(Maintainer).get(maintainer_id)
1178 __all__.append('get_maintainer')
1180 ################################################################################
1182 class NewComment(object):
1183 def __init__(self, *args, **kwargs):
1187 return '''<NewComment for '%s %s' (%s)>''' % (self.package, self.version, self.comment_id)
1189 __all__.append('NewComment')
1192 def has_new_comment(package, version, session=None):
1194 Returns true if the given combination of C{package}, C{version} has a comment.
1196 @type package: string
1197 @param package: name of the package
1199 @type version: string
1200 @param version: package version
1202 @type session: Session
1203 @param session: Optional SQLA session object (a temporary one will be
1204 generated if not supplied)
1210 q = session.query(NewComment)
1211 q = q.filter_by(package=package)
1212 q = q.filter_by(version=version)
1214 return bool(q.count() > 0)
1216 __all__.append('has_new_comment')
1219 def get_new_comments(package=None, version=None, comment_id=None, session=None):
1221 Returns (possibly empty) list of NewComment objects for the given
1224 @type package: string (optional)
1225 @param package: name of the package
1227 @type version: string (optional)
1228 @param version: package version
1230 @type comment_id: int (optional)
1231 @param comment_id: An id of a comment
1233 @type session: Session
1234 @param session: Optional SQLA session object (a temporary one will be
1235 generated if not supplied)
1238 @return: A (possibly empty) list of NewComment objects will be returned
1241 q = session.query(NewComment)
1242 if package is not None: q = q.filter_by(package=package)
1243 if version is not None: q = q.filter_by(version=version)
1244 if comment_id is not None: q = q.filter_by(comment_id=comment_id)
1248 __all__.append('get_new_comments')
1250 ################################################################################
1252 class Override(object):
1253 def __init__(self, *args, **kwargs):
1257 return '<Override %s (%s)>' % (self.package, self.suite_id)
1259 __all__.append('Override')
1262 def get_override(package, suite=None, component=None, overridetype=None, session=None):
1264 Returns Override object for the given parameters
1266 @type package: string
1267 @param package: The name of the package
1269 @type suite: string, list or None
1270 @param suite: The name of the suite (or suites if a list) to limit to. If
1271 None, don't limit. Defaults to None.
1273 @type component: string, list or None
1274 @param component: The name of the component (or components if a list) to
1275 limit to. If None, don't limit. Defaults to None.
1277 @type overridetype: string, list or None
1278 @param overridetype: The name of the overridetype (or overridetypes if a list) to
1279 limit to. If None, don't limit. Defaults to None.
1281 @type session: Session
1282 @param session: Optional SQLA session object (a temporary one will be
1283 generated if not supplied)
1286 @return: A (possibly empty) list of Override objects will be returned
1289 q = session.query(Override)
1290 q = q.filter_by(package=package)
1292 if suite is not None:
1293 if not isinstance(suite, list): suite = [suite]
1294 q = q.join(Suite).filter(Suite.suite_name.in_(suite))
1296 if component is not None:
1297 if not isinstance(component, list): component = [component]
1298 q = q.join(Component).filter(Component.component_name.in_(component))
1300 if overridetype is not None:
1301 if not isinstance(overridetype, list): overridetype = [overridetype]
1302 q = q.join(OverrideType).filter(OverrideType.overridetype.in_(overridetype))
1306 __all__.append('get_override')
1309 ################################################################################
1311 class OverrideType(object):
1312 def __init__(self, *args, **kwargs):
1316 return '<OverrideType %s>' % self.overridetype
1318 __all__.append('OverrideType')
1321 def get_override_type(override_type, session=None):
1323 Returns OverrideType object for given C{override type}.
1325 @type override_type: string
1326 @param override_type: The name of the override type
1328 @type session: Session
1329 @param session: Optional SQLA session object (a temporary one will be
1330 generated if not supplied)
1333 @return: the database id for the given override type
1336 q = session.query(OverrideType).filter_by(overridetype=override_type)
1340 except NoResultFound:
1343 __all__.append('get_override_type')
1345 ################################################################################
1347 class PendingContentAssociation(object):
1348 def __init__(self, *args, **kwargs):
1352 return '<PendingContentAssociation %s>' % self.pca_id
1354 __all__.append('PendingContentAssociation')
1356 def insert_pending_content_paths(package, fullpaths, session=None):
1358 Make sure given paths are temporarily associated with given
1362 @param package: the package to associate with should have been read in from the binary control file
1363 @type fullpaths: list
1364 @param fullpaths: the list of paths of the file being associated with the binary
1365 @type session: SQLAlchemy session
1366 @param session: Optional SQLAlchemy session. If this is passed, the caller
1367 is responsible for ensuring a transaction has begun and committing the
1368 results or rolling back based on the result code. If not passed, a commit
1369 will be performed at the end of the function
1371 @return: True upon success, False if there is a problem
1374 privatetrans = False
1377 session = DBConn().session()
1381 arch = get_architecture(package['Architecture'], session)
1382 arch_id = arch.arch_id
1384 # Remove any already existing recorded files for this package
1385 q = session.query(PendingContentAssociation)
1386 q = q.filter_by(package=package['Package'])
1387 q = q.filter_by(version=package['Version'])
1388 q = q.filter_by(architecture=arch_id)
1393 for fullpath in fullpaths:
1394 (path, file) = os.path.split(fullpath)
1396 if path.startswith( "./" ):
1399 filepath_id = get_or_set_contents_path_id(path, session)
1400 filename_id = get_or_set_contents_file_id(file, session)
1402 pathcache[fullpath] = (filepath_id, filename_id)
1404 for fullpath, dat in pathcache.items():
1405 pca = PendingContentAssociation()
1406 pca.package = package['Package']
1407 pca.version = package['Version']
1408 pca.filepath_id = dat[0]
1409 pca.filename_id = dat[1]
1410 pca.architecture = arch_id
1413 # Only commit if we set up the session ourself
1421 except Exception, e:
1422 traceback.print_exc()
1424 # Only rollback if we set up the session ourself
1431 __all__.append('insert_pending_content_paths')
1433 ################################################################################
1435 class Priority(object):
1436 def __init__(self, *args, **kwargs):
1439 def __eq__(self, val):
1440 if isinstance(val, str):
1441 return (self.priority == val)
1442 # This signals to use the normal comparison operator
1443 return NotImplemented
1445 def __ne__(self, val):
1446 if isinstance(val, str):
1447 return (self.priority != val)
1448 # This signals to use the normal comparison operator
1449 return NotImplemented
1452 return '<Priority %s (%s)>' % (self.priority, self.priority_id)
1454 __all__.append('Priority')
1457 def get_priority(priority, session=None):
1459 Returns Priority object for given C{priority name}.
1461 @type priority: string
1462 @param priority: The name of the priority
1464 @type session: Session
1465 @param session: Optional SQLA session object (a temporary one will be
1466 generated if not supplied)
1469 @return: Priority object for the given priority
1472 q = session.query(Priority).filter_by(priority=priority)
1476 except NoResultFound:
1479 __all__.append('get_priority')
1482 def get_priorities(session=None):
1484 Returns dictionary of priority names -> id mappings
1486 @type session: Session
1487 @param session: Optional SQL session object (a temporary one will be
1488 generated if not supplied)
1491 @return: dictionary of priority names -> id mappings
1495 q = session.query(Priority)
1497 ret[x.priority] = x.priority_id
1501 __all__.append('get_priorities')
1503 ################################################################################
1505 class Queue(object):
1506 def __init__(self, *args, **kwargs):
1510 return '<Queue %s>' % self.queue_name
1512 def autobuild_upload(self, changes, srcpath, session=None):
1514 Update queue_build database table used for incoming autobuild support.
1516 @type changes: Changes
1517 @param changes: changes object for the upload to process
1519 @type srcpath: string
1520 @param srcpath: path for the queue file entries/link destinations
1522 @type session: SQLAlchemy session
1523 @param session: Optional SQLAlchemy session. If this is passed, the
1524 caller is responsible for ensuring a transaction has begun and
1525 committing the results or rolling back based on the result code. If
1526 not passed, a commit will be performed at the end of the function,
1527 otherwise the caller is responsible for commiting.
1529 @rtype: NoneType or string
1530 @return: None if the operation failed, a string describing the error if not
1533 privatetrans = False
1535 session = DBConn().session()
1538 # TODO: Remove by moving queue config into the database
1541 for suitename in changes.changes["distribution"].keys():
1542 # TODO: Move into database as:
1543 # buildqueuedir TEXT DEFAULT NULL (i.e. NULL is no build)
1544 # buildqueuecopy BOOLEAN NOT NULL DEFAULT FALSE (i.e. default is symlink)
1545 # This also gets rid of the SecurityQueueBuild hack below
1546 if suitename not in conf.ValueList("Dinstall::QueueBuildSuites"):
1550 s = get_suite(suitename, session)
1552 return "INTERNAL ERROR: Could not find suite %s" % suitename
1554 # TODO: Get from database as above
1555 dest_dir = conf["Dir::QueueBuild"]
1557 # TODO: Move into database as above
1558 if conf.FindB("Dinstall::SecurityQueueBuild"):
1559 dest_dir = os.path.join(dest_dir, suitename)
1561 for file_entry in changes.files.keys():
1562 src = os.path.join(srcpath, file_entry)
1563 dest = os.path.join(dest_dir, file_entry)
1565 # TODO: Move into database as above
1566 if conf.FindB("Dinstall::SecurityQueueBuild"):
1567 # Copy it since the original won't be readable by www-data
1569 utils.copy(src, dest)
1571 # Create a symlink to it
1572 os.symlink(src, dest)
1575 qb.suite_id = s.suite_id
1576 qb.queue_id = self.queue_id
1582 # If the .orig tarballs are in the pool, create a symlink to
1583 # them (if one doesn't already exist)
1584 for dsc_file in changes.dsc_files.keys():
1585 # Skip all files except orig tarballs
1586 from daklib.regexes import re_is_orig_source
1587 if not re_is_orig_source.match(dsc_file):
1589 # Skip orig files not identified in the pool
1590 if not (changes.orig_files.has_key(dsc_file) and
1591 changes.orig_files[dsc_file].has_key("id")):
1593 orig_file_id = changes.orig_files[dsc_file]["id"]
1594 dest = os.path.join(dest_dir, dsc_file)
1596 # If it doesn't exist, create a symlink
1597 if not os.path.exists(dest):
1598 q = session.execute("SELECT l.path, f.filename FROM location l, files f WHERE f.id = :id and f.location = l.id",
1599 {'id': orig_file_id})
1602 return "[INTERNAL ERROR] Couldn't find id %s in files table." % (orig_file_id)
1604 src = os.path.join(res[0], res[1])
1605 os.symlink(src, dest)
1607 # Add it to the list of packages for later processing by apt-ftparchive
1609 qb.suite_id = s.suite_id
1610 qb.queue_id = self.queue_id
1615 # If it does, update things to ensure it's not removed prematurely
1617 qb = get_queue_build(dest, s.suite_id, session)
1629 __all__.append('Queue')
1632 def get_or_set_queue(queuename, session=None):
1634 Returns Queue object for given C{queue name}, creating it if it does not
1637 @type queuename: string
1638 @param queuename: The name of the queue
1640 @type session: Session
1641 @param session: Optional SQLA session object (a temporary one will be
1642 generated if not supplied)
1645 @return: Queue object for the given queue
1648 q = session.query(Queue).filter_by(queue_name=queuename)
1652 except NoResultFound:
1654 queue.queue_name = queuename
1656 session.commit_or_flush()
1661 __all__.append('get_or_set_queue')
1663 ################################################################################
1665 class QueueBuild(object):
1666 def __init__(self, *args, **kwargs):
1670 return '<QueueBuild %s (%s)>' % (self.filename, self.queue_id)
1672 __all__.append('QueueBuild')
1675 def get_queue_build(filename, suite, session=None):
1677 Returns QueueBuild object for given C{filename} and C{suite}.
1679 @type filename: string
1680 @param filename: The name of the file
1682 @type suiteid: int or str
1683 @param suiteid: Suite name or ID
1685 @type session: Session
1686 @param session: Optional SQLA session object (a temporary one will be
1687 generated if not supplied)
1690 @return: Queue object for the given queue
1693 if isinstance(suite, int):
1694 q = session.query(QueueBuild).filter_by(filename=filename).filter_by(suite_id=suite)
1696 q = session.query(QueueBuild).filter_by(filename=filename)
1697 q = q.join(Suite).filter_by(suite_name=suite)
1701 except NoResultFound:
1704 __all__.append('get_queue_build')
1706 ################################################################################
1708 class Section(object):
1709 def __init__(self, *args, **kwargs):
1712 def __eq__(self, val):
1713 if isinstance(val, str):
1714 return (self.section == val)
1715 # This signals to use the normal comparison operator
1716 return NotImplemented
1718 def __ne__(self, val):
1719 if isinstance(val, str):
1720 return (self.section != val)
1721 # This signals to use the normal comparison operator
1722 return NotImplemented
1725 return '<Section %s>' % self.section
1727 __all__.append('Section')
1730 def get_section(section, session=None):
1732 Returns Section object for given C{section name}.
1734 @type section: string
1735 @param section: The name of the section
1737 @type session: Session
1738 @param session: Optional SQLA session object (a temporary one will be
1739 generated if not supplied)
1742 @return: Section object for the given section name
1745 q = session.query(Section).filter_by(section=section)
1749 except NoResultFound:
1752 __all__.append('get_section')
1755 def get_sections(session=None):
1757 Returns dictionary of section names -> id mappings
1759 @type session: Session
1760 @param session: Optional SQL session object (a temporary one will be
1761 generated if not supplied)
1764 @return: dictionary of section names -> id mappings
1768 q = session.query(Section)
1770 ret[x.section] = x.section_id
1774 __all__.append('get_sections')
1776 ################################################################################
1778 class DBSource(object):
1779 def __init__(self, *args, **kwargs):
1783 return '<DBSource %s (%s)>' % (self.source, self.version)
1785 __all__.append('DBSource')
1788 def source_exists(source, source_version, suites = ["any"], session=None):
1790 Ensure that source exists somewhere in the archive for the binary
1791 upload being processed.
1792 1. exact match => 1.0-3
1793 2. bin-only NMU => 1.0-3+b1 , 1.0-3.1+b1
1795 @type package: string
1796 @param package: package source name
1798 @type source_version: string
1799 @param source_version: expected source version
1802 @param suites: list of suites to check in, default I{any}
1804 @type session: Session
1805 @param session: Optional SQLA session object (a temporary one will be
1806 generated if not supplied)
1809 @return: returns 1 if a source with expected version is found, otherwise 0
1816 for suite in suites:
1817 q = session.query(DBSource).filter_by(source=source)
1819 # source must exist in suite X, or in some other suite that's
1820 # mapped to X, recursively... silent-maps are counted too,
1821 # unreleased-maps aren't.
1822 maps = cnf.ValueList("SuiteMappings")[:]
1824 maps = [ m.split() for m in maps ]
1825 maps = [ (x[1], x[2]) for x in maps
1826 if x[0] == "map" or x[0] == "silent-map" ]
1829 if x[1] in s and x[0] not in s:
1832 q = q.join(SrcAssociation).join(Suite)
1833 q = q.filter(Suite.suite_name.in_(s))
1835 # Reduce the query results to a list of version numbers
1836 ql = [ j.version for j in q.all() ]
1839 if source_version in ql:
1843 from daklib.regexes import re_bin_only_nmu
1844 orig_source_version = re_bin_only_nmu.sub('', source_version)
1845 if orig_source_version in ql:
1848 # No source found so return not ok
1853 __all__.append('source_exists')
1856 def get_suites_source_in(source, session=None):
1858 Returns list of Suite objects which given C{source} name is in
1861 @param source: DBSource package name to search for
1864 @return: list of Suite objects for the given source
1867 return session.query(Suite).join(SrcAssociation).join(DBSource).filter_by(source=source).all()
1869 __all__.append('get_suites_source_in')
1872 def get_sources_from_name(source, version=None, dm_upload_allowed=None, session=None):
1874 Returns list of DBSource objects for given C{source} name and other parameters
1877 @param source: DBSource package name to search for
1879 @type source: str or None
1880 @param source: DBSource version name to search for or None if not applicable
1882 @type dm_upload_allowed: bool
1883 @param dm_upload_allowed: If None, no effect. If True or False, only
1884 return packages with that dm_upload_allowed setting
1886 @type session: Session
1887 @param session: Optional SQL session object (a temporary one will be
1888 generated if not supplied)
1891 @return: list of DBSource objects for the given name (may be empty)
1894 q = session.query(DBSource).filter_by(source=source)
1896 if version is not None:
1897 q = q.filter_by(version=version)
1899 if dm_upload_allowed is not None:
1900 q = q.filter_by(dm_upload_allowed=dm_upload_allowed)
1904 __all__.append('get_sources_from_name')
1907 def get_source_in_suite(source, suite, session=None):
1909 Returns list of DBSource objects for a combination of C{source} and C{suite}.
1911 - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
1912 - B{suite} - a suite name, eg. I{unstable}
1914 @type source: string
1915 @param source: source package name
1918 @param suite: the suite name
1921 @return: the version for I{source} in I{suite}
1925 q = session.query(SrcAssociation)
1926 q = q.join('source').filter_by(source=source)
1927 q = q.join('suite').filter_by(suite_name=suite)
1930 return q.one().source
1931 except NoResultFound:
1934 __all__.append('get_source_in_suite')
1936 ################################################################################
1938 class SourceACL(object):
1939 def __init__(self, *args, **kwargs):
1943 return '<SourceACL %s>' % self.source_acl_id
1945 __all__.append('SourceACL')
1947 ################################################################################
1949 class SrcAssociation(object):
1950 def __init__(self, *args, **kwargs):
1954 return '<SrcAssociation %s (%s, %s)>' % (self.sa_id, self.source, self.suite)
1956 __all__.append('SrcAssociation')
1958 ################################################################################
1960 class SrcFormat(object):
1961 def __init__(self, *args, **kwargs):
1965 return '<SrcFormat %s>' % (self.format_name)
1967 __all__.append('SrcFormat')
1969 ################################################################################
1971 class SrcUploader(object):
1972 def __init__(self, *args, **kwargs):
1976 return '<SrcUploader %s>' % self.uploader_id
1978 __all__.append('SrcUploader')
1980 ################################################################################
1982 SUITE_FIELDS = [ ('SuiteName', 'suite_name'),
1983 ('SuiteID', 'suite_id'),
1984 ('Version', 'version'),
1985 ('Origin', 'origin'),
1987 ('Description', 'description'),
1988 ('Untouchable', 'untouchable'),
1989 ('Announce', 'announce'),
1990 ('Codename', 'codename'),
1991 ('OverrideCodename', 'overridecodename'),
1992 ('ValidTime', 'validtime'),
1993 ('Priority', 'priority'),
1994 ('NotAutomatic', 'notautomatic'),
1995 ('CopyChanges', 'copychanges'),
1996 ('CopyDotDak', 'copydotdak'),
1997 ('CommentsDir', 'commentsdir'),
1998 ('OverrideSuite', 'overridesuite'),
1999 ('ChangelogBase', 'changelogbase')]
2002 class Suite(object):
2003 def __init__(self, *args, **kwargs):
2007 return '<Suite %s>' % self.suite_name
2009 def __eq__(self, val):
2010 if isinstance(val, str):
2011 return (self.suite_name == val)
2012 # This signals to use the normal comparison operator
2013 return NotImplemented
2015 def __ne__(self, val):
2016 if isinstance(val, str):
2017 return (self.suite_name != val)
2018 # This signals to use the normal comparison operator
2019 return NotImplemented
2023 for disp, field in SUITE_FIELDS:
2024 val = getattr(self, field, None)
2026 ret.append("%s: %s" % (disp, val))
2028 return "\n".join(ret)
2030 __all__.append('Suite')
2033 def get_suite_architecture(suite, architecture, session=None):
2035 Returns a SuiteArchitecture object given C{suite} and ${arch} or None if it
2039 @param suite: Suite name to search for
2041 @type architecture: str
2042 @param architecture: Architecture name to search for
2044 @type session: Session
2045 @param session: Optional SQL session object (a temporary one will be
2046 generated if not supplied)
2048 @rtype: SuiteArchitecture
2049 @return: the SuiteArchitecture object or None
2052 q = session.query(SuiteArchitecture)
2053 q = q.join(Architecture).filter_by(arch_string=architecture)
2054 q = q.join(Suite).filter_by(suite_name=suite)
2058 except NoResultFound:
2061 __all__.append('get_suite_architecture')
2064 def get_suite(suite, session=None):
2066 Returns Suite object for given C{suite name}.
2069 @param suite: The name of the suite
2071 @type session: Session
2072 @param session: Optional SQLA session object (a temporary one will be
2073 generated if not supplied)
2076 @return: Suite object for the requested suite name (None if not present)
2079 q = session.query(Suite).filter_by(suite_name=suite)
2083 except NoResultFound:
2086 __all__.append('get_suite')
2088 ################################################################################
2090 class SuiteArchitecture(object):
2091 def __init__(self, *args, **kwargs):
2095 return '<SuiteArchitecture (%s, %s)>' % (self.suite_id, self.arch_id)
2097 __all__.append('SuiteArchitecture')
2100 def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None):
2102 Returns list of Architecture objects for given C{suite} name
2105 @param source: Suite name to search for
2107 @type skipsrc: boolean
2108 @param skipsrc: Whether to skip returning the 'source' architecture entry
2111 @type skipall: boolean
2112 @param skipall: Whether to skip returning the 'all' architecture entry
2115 @type session: Session
2116 @param session: Optional SQL session object (a temporary one will be
2117 generated if not supplied)
2120 @return: list of Architecture objects for the given name (may be empty)
2123 q = session.query(Architecture)
2124 q = q.join(SuiteArchitecture)
2125 q = q.join(Suite).filter_by(suite_name=suite)
2128 q = q.filter(Architecture.arch_string != 'source')
2131 q = q.filter(Architecture.arch_string != 'all')
2133 q = q.order_by('arch_string')
2137 __all__.append('get_suite_architectures')
2139 ################################################################################
2141 class SuiteSrcFormat(object):
2142 def __init__(self, *args, **kwargs):
2146 return '<SuiteSrcFormat (%s, %s)>' % (self.suite_id, self.src_format_id)
2148 __all__.append('SuiteSrcFormat')
2151 def get_suite_src_formats(suite, session=None):
2153 Returns list of allowed SrcFormat for C{suite}.
2156 @param suite: Suite name to search for
2158 @type session: Session
2159 @param session: Optional SQL session object (a temporary one will be
2160 generated if not supplied)
2163 @return: the list of allowed source formats for I{suite}
2166 q = session.query(SrcFormat)
2167 q = q.join(SuiteSrcFormat)
2168 q = q.join(Suite).filter_by(suite_name=suite)
2169 q = q.order_by('format_name')
2173 __all__.append('get_suite_src_formats')
2175 ################################################################################
2178 def __init__(self, *args, **kwargs):
2181 def __eq__(self, val):
2182 if isinstance(val, str):
2183 return (self.uid == val)
2184 # This signals to use the normal comparison operator
2185 return NotImplemented
2187 def __ne__(self, val):
2188 if isinstance(val, str):
2189 return (self.uid != val)
2190 # This signals to use the normal comparison operator
2191 return NotImplemented
2194 return '<Uid %s (%s)>' % (self.uid, self.name)
2196 __all__.append('Uid')
2199 def add_database_user(uidname, session=None):
2201 Adds a database user
2203 @type uidname: string
2204 @param uidname: The uid of the user to add
2206 @type session: SQLAlchemy
2207 @param session: Optional SQL session object (a temporary one will be
2208 generated if not supplied). If not passed, a commit will be performed at
2209 the end of the function, otherwise the caller is responsible for commiting.
2212 @return: the uid object for the given uidname
2215 session.execute("CREATE USER :uid", {'uid': uidname})
2216 session.commit_or_flush()
2218 __all__.append('add_database_user')
2221 def get_or_set_uid(uidname, session=None):
2223 Returns uid object for given uidname.
2225 If no matching uidname is found, a row is inserted.
2227 @type uidname: string
2228 @param uidname: The uid to add
2230 @type session: SQLAlchemy
2231 @param session: Optional SQL session object (a temporary one will be
2232 generated if not supplied). If not passed, a commit will be performed at
2233 the end of the function, otherwise the caller is responsible for commiting.
2236 @return: the uid object for the given uidname
2239 q = session.query(Uid).filter_by(uid=uidname)
2243 except NoResultFound:
2247 session.commit_or_flush()
2252 __all__.append('get_or_set_uid')
2255 def get_uid_from_fingerprint(fpr, session=None):
2256 q = session.query(Uid)
2257 q = q.join(Fingerprint).filter_by(fingerprint=fpr)
2261 except NoResultFound:
2264 __all__.append('get_uid_from_fingerprint')
2266 ################################################################################
2268 class UploadBlock(object):
2269 def __init__(self, *args, **kwargs):
2273 return '<UploadBlock %s (%s)>' % (self.source, self.upload_block_id)
2275 __all__.append('UploadBlock')
2277 ################################################################################
2279 class DBConn(Singleton):
2281 database module init.
2283 def __init__(self, *args, **kwargs):
2284 super(DBConn, self).__init__(*args, **kwargs)
2286 def _startup(self, *args, **kwargs):
2288 if kwargs.has_key('debug'):
2292 def __setuptables(self):
2293 self.tbl_architecture = Table('architecture', self.db_meta, autoload=True)
2294 self.tbl_archive = Table('archive', self.db_meta, autoload=True)
2295 self.tbl_bin_associations = Table('bin_associations', self.db_meta, autoload=True)
2296 self.tbl_binaries = Table('binaries', self.db_meta, autoload=True)
2297 self.tbl_binary_acl = Table('binary_acl', self.db_meta, autoload=True)
2298 self.tbl_binary_acl_map = Table('binary_acl_map', self.db_meta, autoload=True)
2299 self.tbl_component = Table('component', self.db_meta, autoload=True)
2300 self.tbl_config = Table('config', self.db_meta, autoload=True)
2301 self.tbl_content_associations = Table('content_associations', self.db_meta, autoload=True)
2302 self.tbl_content_file_names = Table('content_file_names', self.db_meta, autoload=True)
2303 self.tbl_content_file_paths = Table('content_file_paths', self.db_meta, autoload=True)
2304 self.tbl_dsc_files = Table('dsc_files', self.db_meta, autoload=True)
2305 self.tbl_files = Table('files', self.db_meta, autoload=True)
2306 self.tbl_fingerprint = Table('fingerprint', self.db_meta, autoload=True)
2307 self.tbl_keyrings = Table('keyrings', self.db_meta, autoload=True)
2308 self.tbl_keyring_acl_map = Table('keyring_acl_map', self.db_meta, autoload=True)
2309 self.tbl_location = Table('location', self.db_meta, autoload=True)
2310 self.tbl_maintainer = Table('maintainer', self.db_meta, autoload=True)
2311 self.tbl_new_comments = Table('new_comments', self.db_meta, autoload=True)
2312 self.tbl_override = Table('override', self.db_meta, autoload=True)
2313 self.tbl_override_type = Table('override_type', self.db_meta, autoload=True)
2314 self.tbl_pending_content_associations = Table('pending_content_associations', self.db_meta, autoload=True)
2315 self.tbl_priority = Table('priority', self.db_meta, autoload=True)
2316 self.tbl_queue = Table('queue', self.db_meta, autoload=True)
2317 self.tbl_queue_build = Table('queue_build', self.db_meta, autoload=True)
2318 self.tbl_section = Table('section', self.db_meta, autoload=True)
2319 self.tbl_source = Table('source', self.db_meta, autoload=True)
2320 self.tbl_source_acl = Table('source_acl', self.db_meta, autoload=True)
2321 self.tbl_src_associations = Table('src_associations', self.db_meta, autoload=True)
2322 self.tbl_src_format = Table('src_format', self.db_meta, autoload=True)
2323 self.tbl_src_uploaders = Table('src_uploaders', self.db_meta, autoload=True)
2324 self.tbl_suite = Table('suite', self.db_meta, autoload=True)
2325 self.tbl_suite_architectures = Table('suite_architectures', self.db_meta, autoload=True)
2326 self.tbl_suite_src_formats = Table('suite_src_formats', self.db_meta, autoload=True)
2327 self.tbl_uid = Table('uid', self.db_meta, autoload=True)
2328 self.tbl_upload_blocks = Table('upload_blocks', self.db_meta, autoload=True)
2330 def __setupmappers(self):
2331 mapper(Architecture, self.tbl_architecture,
2332 properties = dict(arch_id = self.tbl_architecture.c.id))
2334 mapper(Archive, self.tbl_archive,
2335 properties = dict(archive_id = self.tbl_archive.c.id,
2336 archive_name = self.tbl_archive.c.name))
2338 mapper(BinAssociation, self.tbl_bin_associations,
2339 properties = dict(ba_id = self.tbl_bin_associations.c.id,
2340 suite_id = self.tbl_bin_associations.c.suite,
2341 suite = relation(Suite),
2342 binary_id = self.tbl_bin_associations.c.bin,
2343 binary = relation(DBBinary)))
2346 mapper(DBBinary, self.tbl_binaries,
2347 properties = dict(binary_id = self.tbl_binaries.c.id,
2348 package = self.tbl_binaries.c.package,
2349 version = self.tbl_binaries.c.version,
2350 maintainer_id = self.tbl_binaries.c.maintainer,
2351 maintainer = relation(Maintainer),
2352 source_id = self.tbl_binaries.c.source,
2353 source = relation(DBSource),
2354 arch_id = self.tbl_binaries.c.architecture,
2355 architecture = relation(Architecture),
2356 poolfile_id = self.tbl_binaries.c.file,
2357 poolfile = relation(PoolFile),
2358 binarytype = self.tbl_binaries.c.type,
2359 fingerprint_id = self.tbl_binaries.c.sig_fpr,
2360 fingerprint = relation(Fingerprint),
2361 install_date = self.tbl_binaries.c.install_date,
2362 binassociations = relation(BinAssociation,
2363 primaryjoin=(self.tbl_binaries.c.id==self.tbl_bin_associations.c.bin))))
2365 mapper(BinaryACL, self.tbl_binary_acl,
2366 properties = dict(binary_acl_id = self.tbl_binary_acl.c.id))
2368 mapper(BinaryACLMap, self.tbl_binary_acl_map,
2369 properties = dict(binary_acl_map_id = self.tbl_binary_acl_map.c.id,
2370 fingerprint = relation(Fingerprint, backref="binary_acl_map"),
2371 architecture = relation(Architecture)))
2373 mapper(Component, self.tbl_component,
2374 properties = dict(component_id = self.tbl_component.c.id,
2375 component_name = self.tbl_component.c.name))
2377 mapper(DBConfig, self.tbl_config,
2378 properties = dict(config_id = self.tbl_config.c.id))
2380 mapper(DSCFile, self.tbl_dsc_files,
2381 properties = dict(dscfile_id = self.tbl_dsc_files.c.id,
2382 source_id = self.tbl_dsc_files.c.source,
2383 source = relation(DBSource),
2384 poolfile_id = self.tbl_dsc_files.c.file,
2385 poolfile = relation(PoolFile)))
2387 mapper(PoolFile, self.tbl_files,
2388 properties = dict(file_id = self.tbl_files.c.id,
2389 filesize = self.tbl_files.c.size,
2390 location_id = self.tbl_files.c.location,
2391 location = relation(Location)))
2393 mapper(Fingerprint, self.tbl_fingerprint,
2394 properties = dict(fingerprint_id = self.tbl_fingerprint.c.id,
2395 uid_id = self.tbl_fingerprint.c.uid,
2396 uid = relation(Uid),
2397 keyring_id = self.tbl_fingerprint.c.keyring,
2398 keyring = relation(Keyring),
2399 source_acl = relation(SourceACL),
2400 binary_acl = relation(BinaryACL)))
2402 mapper(Keyring, self.tbl_keyrings,
2403 properties = dict(keyring_name = self.tbl_keyrings.c.name,
2404 keyring_id = self.tbl_keyrings.c.id))
2406 mapper(KeyringACLMap, self.tbl_keyring_acl_map,
2407 properties = dict(keyring_acl_map_id = self.tbl_keyring_acl_map.c.id,
2408 keyring = relation(Keyring, backref="keyring_acl_map"),
2409 architecture = relation(Architecture)))
2411 mapper(Location, self.tbl_location,
2412 properties = dict(location_id = self.tbl_location.c.id,
2413 component_id = self.tbl_location.c.component,
2414 component = relation(Component),
2415 archive_id = self.tbl_location.c.archive,
2416 archive = relation(Archive),
2417 archive_type = self.tbl_location.c.type))
2419 mapper(Maintainer, self.tbl_maintainer,
2420 properties = dict(maintainer_id = self.tbl_maintainer.c.id))
2422 mapper(NewComment, self.tbl_new_comments,
2423 properties = dict(comment_id = self.tbl_new_comments.c.id))
2425 mapper(Override, self.tbl_override,
2426 properties = dict(suite_id = self.tbl_override.c.suite,
2427 suite = relation(Suite),
2428 component_id = self.tbl_override.c.component,
2429 component = relation(Component),
2430 priority_id = self.tbl_override.c.priority,
2431 priority = relation(Priority),
2432 section_id = self.tbl_override.c.section,
2433 section = relation(Section),
2434 overridetype_id = self.tbl_override.c.type,
2435 overridetype = relation(OverrideType)))
2437 mapper(OverrideType, self.tbl_override_type,
2438 properties = dict(overridetype = self.tbl_override_type.c.type,
2439 overridetype_id = self.tbl_override_type.c.id))
2441 mapper(Priority, self.tbl_priority,
2442 properties = dict(priority_id = self.tbl_priority.c.id))
2444 mapper(Queue, self.tbl_queue,
2445 properties = dict(queue_id = self.tbl_queue.c.id))
2447 mapper(QueueBuild, self.tbl_queue_build,
2448 properties = dict(suite_id = self.tbl_queue_build.c.suite,
2449 queue_id = self.tbl_queue_build.c.queue,
2450 queue = relation(Queue, backref='queuebuild')))
2452 mapper(Section, self.tbl_section,
2453 properties = dict(section_id = self.tbl_section.c.id))
2455 mapper(DBSource, self.tbl_source,
2456 properties = dict(source_id = self.tbl_source.c.id,
2457 version = self.tbl_source.c.version,
2458 maintainer_id = self.tbl_source.c.maintainer,
2459 maintainer = relation(Maintainer,
2460 primaryjoin=(self.tbl_source.c.maintainer==self.tbl_maintainer.c.id)),
2461 poolfile_id = self.tbl_source.c.file,
2462 poolfile = relation(PoolFile),
2463 fingerprint_id = self.tbl_source.c.sig_fpr,
2464 fingerprint = relation(Fingerprint),
2465 changedby_id = self.tbl_source.c.changedby,
2466 changedby = relation(Maintainer,
2467 primaryjoin=(self.tbl_source.c.changedby==self.tbl_maintainer.c.id)),
2468 srcfiles = relation(DSCFile,
2469 primaryjoin=(self.tbl_source.c.id==self.tbl_dsc_files.c.source)),
2470 srcassociations = relation(SrcAssociation,
2471 primaryjoin=(self.tbl_source.c.id==self.tbl_src_associations.c.source)),
2472 srcuploaders = relation(SrcUploader)))
2474 mapper(SourceACL, self.tbl_source_acl,
2475 properties = dict(source_acl_id = self.tbl_source_acl.c.id))
2477 mapper(SrcAssociation, self.tbl_src_associations,
2478 properties = dict(sa_id = self.tbl_src_associations.c.id,
2479 suite_id = self.tbl_src_associations.c.suite,
2480 suite = relation(Suite),
2481 source_id = self.tbl_src_associations.c.source,
2482 source = relation(DBSource)))
2484 mapper(SrcFormat, self.tbl_src_format,
2485 properties = dict(src_format_id = self.tbl_src_format.c.id,
2486 format_name = self.tbl_src_format.c.format_name))
2488 mapper(SrcUploader, self.tbl_src_uploaders,
2489 properties = dict(uploader_id = self.tbl_src_uploaders.c.id,
2490 source_id = self.tbl_src_uploaders.c.source,
2491 source = relation(DBSource,
2492 primaryjoin=(self.tbl_src_uploaders.c.source==self.tbl_source.c.id)),
2493 maintainer_id = self.tbl_src_uploaders.c.maintainer,
2494 maintainer = relation(Maintainer,
2495 primaryjoin=(self.tbl_src_uploaders.c.maintainer==self.tbl_maintainer.c.id))))
2497 mapper(Suite, self.tbl_suite,
2498 properties = dict(suite_id = self.tbl_suite.c.id))
2500 mapper(SuiteArchitecture, self.tbl_suite_architectures,
2501 properties = dict(suite_id = self.tbl_suite_architectures.c.suite,
2502 suite = relation(Suite, backref='suitearchitectures'),
2503 arch_id = self.tbl_suite_architectures.c.architecture,
2504 architecture = relation(Architecture)))
2506 mapper(SuiteSrcFormat, self.tbl_suite_src_formats,
2507 properties = dict(suite_id = self.tbl_suite_src_formats.c.suite,
2508 suite = relation(Suite, backref='suitesrcformats'),
2509 src_format_id = self.tbl_suite_src_formats.c.src_format,
2510 src_format = relation(SrcFormat)))
2512 mapper(Uid, self.tbl_uid,
2513 properties = dict(uid_id = self.tbl_uid.c.id,
2514 fingerprint = relation(Fingerprint)))
2516 mapper(UploadBlock, self.tbl_upload_blocks,
2517 properties = dict(upload_block_id = self.tbl_upload_blocks.c.id,
2518 fingerprint = relation(Fingerprint, backref="uploadblocks"),
2519 uid = relation(Uid, backref="uploadblocks")))
2521 ## Connection functions
2522 def __createconn(self):
2523 from config import Config
2527 connstr = "postgres://%s" % cnf["DB::Host"]
2528 if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
2529 connstr += ":%s" % cnf["DB::Port"]
2530 connstr += "/%s" % cnf["DB::Name"]
2533 connstr = "postgres:///%s" % cnf["DB::Name"]
2534 if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
2535 connstr += "?port=%s" % cnf["DB::Port"]
2537 self.db_pg = create_engine(connstr, echo=self.debug)
2538 self.db_meta = MetaData()
2539 self.db_meta.bind = self.db_pg
2540 self.db_smaker = sessionmaker(bind=self.db_pg,
2544 self.__setuptables()
2545 self.__setupmappers()
2548 return self.db_smaker()
2550 __all__.append('DBConn')