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
44 from sqlalchemy import create_engine, Table, MetaData
45 from sqlalchemy.orm import sessionmaker, mapper, relation
46 from sqlalchemy import types as sqltypes
48 # Don't remove this, we re-export the exceptions to scripts which import us
49 from sqlalchemy.exc import *
50 from sqlalchemy.orm.exc import NoResultFound
52 # Only import Config until Queue stuff is changed to store its config
54 from config import Config
55 from singleton import Singleton
56 from textutils import fix_maintainer
58 ################################################################################
60 # Patch in support for the debversion field type so that it works during
63 class DebVersion(sqltypes.Text):
64 def get_col_spec(self):
67 sa_major_version = sqlalchemy.__version__[0:3]
68 if sa_major_version == "0.5":
69 from sqlalchemy.databases import postgres
70 postgres.ischema_names['debversion'] = DebVersion
72 raise Exception("dak isn't ported to SQLA versions != 0.5 yet. See daklib/dbconn.py")
74 ################################################################################
76 __all__ = ['IntegrityError', 'SQLAlchemyError']
78 ################################################################################
80 def session_wrapper(fn):
82 Wrapper around common ".., session=None):" handling. If the wrapped
83 function is called without passing 'session', we create a local one
84 and destroy it when the function ends.
86 Also attaches a commit_or_flush method to the session; if we created a
87 local session, this is a synonym for session.commit(), otherwise it is a
88 synonym for session.flush().
91 def wrapped(*args, **kwargs):
92 private_transaction = False
94 # Find the session object
95 session = kwargs.get('session')
98 if len(args) <= len(getargspec(fn)[0]) - 1:
99 # No session specified as last argument or in kwargs
100 private_transaction = True
101 session = kwargs['session'] = DBConn().session()
103 # Session is last argument in args
107 session = args[-1] = DBConn().session()
108 private_transaction = True
110 if private_transaction:
111 session.commit_or_flush = session.commit
113 session.commit_or_flush = session.flush
116 return fn(*args, **kwargs)
118 if private_transaction:
119 # We created a session; close it.
122 wrapped.__doc__ = fn.__doc__
123 wrapped.func_name = fn.func_name
127 ################################################################################
129 class Architecture(object):
130 def __init__(self, *args, **kwargs):
133 def __eq__(self, val):
134 if isinstance(val, str):
135 return (self.arch_string== val)
136 # This signals to use the normal comparison operator
137 return NotImplemented
139 def __ne__(self, val):
140 if isinstance(val, str):
141 return (self.arch_string != val)
142 # This signals to use the normal comparison operator
143 return NotImplemented
146 return '<Architecture %s>' % self.arch_string
148 __all__.append('Architecture')
151 def get_architecture(architecture, session=None):
153 Returns database id for given C{architecture}.
155 @type architecture: string
156 @param architecture: The name of the architecture
158 @type session: Session
159 @param session: Optional SQLA session object (a temporary one will be
160 generated if not supplied)
163 @return: Architecture object for the given arch (None if not present)
166 q = session.query(Architecture).filter_by(arch_string=architecture)
170 except NoResultFound:
173 __all__.append('get_architecture')
176 def get_architecture_suites(architecture, session=None):
178 Returns list of Suite objects for given C{architecture} name
181 @param source: Architecture name to search for
183 @type session: Session
184 @param session: Optional SQL session object (a temporary one will be
185 generated if not supplied)
188 @return: list of Suite objects for the given name (may be empty)
191 q = session.query(Suite)
192 q = q.join(SuiteArchitecture)
193 q = q.join(Architecture).filter_by(arch_string=architecture).order_by('suite_name')
199 __all__.append('get_architecture_suites')
201 ################################################################################
203 class Archive(object):
204 def __init__(self, *args, **kwargs):
208 return '<Archive %s>' % self.archive_name
210 __all__.append('Archive')
213 def get_archive(archive, session=None):
215 returns database id for given C{archive}.
217 @type archive: string
218 @param archive: the name of the arhive
220 @type session: Session
221 @param session: Optional SQLA session object (a temporary one will be
222 generated if not supplied)
225 @return: Archive object for the given name (None if not present)
228 archive = archive.lower()
230 q = session.query(Archive).filter_by(archive_name=archive)
234 except NoResultFound:
237 __all__.append('get_archive')
239 ################################################################################
241 class BinAssociation(object):
242 def __init__(self, *args, **kwargs):
246 return '<BinAssociation %s (%s, %s)>' % (self.ba_id, self.binary, self.suite)
248 __all__.append('BinAssociation')
250 ################################################################################
252 class BinContents(object):
253 def __init__(self, *args, **kwargs):
257 return '<BinContents (%s, %s)>' % (self.binary, self.filename)
259 __all__.append('BinContents')
261 ################################################################################
263 class DBBinary(object):
264 def __init__(self, *args, **kwargs):
268 return '<DBBinary %s (%s, %s)>' % (self.package, self.version, self.architecture)
270 __all__.append('DBBinary')
273 def get_suites_binary_in(package, session=None):
275 Returns list of Suite objects which given C{package} name is in
278 @param source: DBBinary package name to search for
281 @return: list of Suite objects for the given package
284 return session.query(Suite).join(BinAssociation).join(DBBinary).filter_by(package=package).all()
286 __all__.append('get_suites_binary_in')
289 def get_binary_from_id(id, session=None):
291 Returns DBBinary object for given C{id}
294 @param id: Id of the required binary
296 @type session: Session
297 @param session: Optional SQLA session object (a temporary one will be
298 generated if not supplied)
301 @return: DBBinary object for the given binary (None if not present)
304 q = session.query(DBBinary).filter_by(binary_id=id)
308 except NoResultFound:
311 __all__.append('get_binary_from_id')
314 def get_binaries_from_name(package, version=None, architecture=None, session=None):
316 Returns list of DBBinary objects for given C{package} name
319 @param package: DBBinary package name to search for
321 @type version: str or None
322 @param version: Version to search for (or None)
324 @type package: str, list or None
325 @param package: Architectures to limit to (or None if no limit)
327 @type session: Session
328 @param session: Optional SQL session object (a temporary one will be
329 generated if not supplied)
332 @return: list of DBBinary objects for the given name (may be empty)
335 q = session.query(DBBinary).filter_by(package=package)
337 if version is not None:
338 q = q.filter_by(version=version)
340 if architecture is not None:
341 if not isinstance(architecture, list):
342 architecture = [architecture]
343 q = q.join(Architecture).filter(Architecture.arch_string.in_(architecture))
349 __all__.append('get_binaries_from_name')
352 def get_binaries_from_source_id(source_id, session=None):
354 Returns list of DBBinary objects for given C{source_id}
357 @param source_id: source_id to search for
359 @type session: Session
360 @param session: Optional SQL session object (a temporary one will be
361 generated if not supplied)
364 @return: list of DBBinary objects for the given name (may be empty)
367 return session.query(DBBinary).filter_by(source_id=source_id).all()
369 __all__.append('get_binaries_from_source_id')
372 def get_binary_from_name_suite(package, suitename, session=None):
373 ### For dak examine-package
374 ### XXX: Doesn't use object API yet
376 sql = """SELECT DISTINCT(b.package), b.version, c.name, su.suite_name
377 FROM binaries b, files fi, location l, component c, bin_associations ba, suite su
378 WHERE b.package=:package
380 AND fi.location = l.id
381 AND l.component = c.id
384 AND su.suite_name=:suitename
385 ORDER BY b.version DESC"""
387 return session.execute(sql, {'package': package, 'suitename': suitename})
389 __all__.append('get_binary_from_name_suite')
392 def get_binary_components(package, suitename, arch, session=None):
393 # Check for packages that have moved from one component to another
394 query = """SELECT c.name FROM binaries b, bin_associations ba, suite s, location l, component c, architecture a, files f
395 WHERE b.package=:package AND s.suite_name=:suitename
396 AND (a.arch_string = :arch OR a.arch_string = 'all')
397 AND ba.bin = b.id AND ba.suite = s.id AND b.architecture = a.id
398 AND f.location = l.id
399 AND l.component = c.id
402 vals = {'package': package, 'suitename': suitename, 'arch': arch}
404 return session.execute(query, vals)
406 __all__.append('get_binary_components')
408 ################################################################################
410 class BinaryACL(object):
411 def __init__(self, *args, **kwargs):
415 return '<BinaryACL %s>' % self.binary_acl_id
417 __all__.append('BinaryACL')
419 ################################################################################
421 class BinaryACLMap(object):
422 def __init__(self, *args, **kwargs):
426 return '<BinaryACLMap %s>' % self.binary_acl_map_id
428 __all__.append('BinaryACLMap')
430 ################################################################################
432 class Component(object):
433 def __init__(self, *args, **kwargs):
436 def __eq__(self, val):
437 if isinstance(val, str):
438 return (self.component_name == val)
439 # This signals to use the normal comparison operator
440 return NotImplemented
442 def __ne__(self, val):
443 if isinstance(val, str):
444 return (self.component_name != val)
445 # This signals to use the normal comparison operator
446 return NotImplemented
449 return '<Component %s>' % self.component_name
452 __all__.append('Component')
455 def get_component(component, session=None):
457 Returns database id for given C{component}.
459 @type component: string
460 @param component: The name of the override type
463 @return: the database id for the given component
466 component = component.lower()
468 q = session.query(Component).filter_by(component_name=component)
472 except NoResultFound:
475 __all__.append('get_component')
477 ################################################################################
479 class DBConfig(object):
480 def __init__(self, *args, **kwargs):
484 return '<DBConfig %s>' % self.name
486 __all__.append('DBConfig')
488 ################################################################################
491 def get_or_set_contents_file_id(filename, session=None):
493 Returns database id for given filename.
495 If no matching file is found, a row is inserted.
497 @type filename: string
498 @param filename: The filename
499 @type session: SQLAlchemy
500 @param session: Optional SQL session object (a temporary one will be
501 generated if not supplied). If not passed, a commit will be performed at
502 the end of the function, otherwise the caller is responsible for commiting.
505 @return: the database id for the given component
508 q = session.query(ContentFilename).filter_by(filename=filename)
511 ret = q.one().cafilename_id
512 except NoResultFound:
513 cf = ContentFilename()
514 cf.filename = filename
516 session.commit_or_flush()
517 ret = cf.cafilename_id
521 __all__.append('get_or_set_contents_file_id')
524 def get_contents(suite, overridetype, section=None, session=None):
526 Returns contents for a suite / overridetype combination, limiting
527 to a section if not None.
530 @param suite: Suite object
532 @type overridetype: OverrideType
533 @param overridetype: OverrideType object
535 @type section: Section
536 @param section: Optional section object to limit results to
538 @type session: SQLAlchemy
539 @param session: Optional SQL session object (a temporary one will be
540 generated if not supplied)
543 @return: ResultsProxy object set up to return tuples of (filename, section,
547 # find me all of the contents for a given suite
548 contents_q = """SELECT (p.path||'/'||n.file) AS fn,
552 FROM content_associations c join content_file_paths p ON (c.filepath=p.id)
553 JOIN content_file_names n ON (c.filename=n.id)
554 JOIN binaries b ON (b.id=c.binary_pkg)
555 JOIN override o ON (o.package=b.package)
556 JOIN section s ON (s.id=o.section)
557 WHERE o.suite = :suiteid AND o.type = :overridetypeid
558 AND b.type=:overridetypename"""
560 vals = {'suiteid': suite.suite_id,
561 'overridetypeid': overridetype.overridetype_id,
562 'overridetypename': overridetype.overridetype}
564 if section is not None:
565 contents_q += " AND s.id = :sectionid"
566 vals['sectionid'] = section.section_id
568 contents_q += " ORDER BY fn"
570 return session.execute(contents_q, vals)
572 __all__.append('get_contents')
574 ################################################################################
576 class ContentFilepath(object):
577 def __init__(self, *args, **kwargs):
581 return '<ContentFilepath %s>' % self.filepath
583 __all__.append('ContentFilepath')
586 def get_or_set_contents_path_id(filepath, session=None):
588 Returns database id for given path.
590 If no matching file is found, a row is inserted.
592 @type filename: string
593 @param filename: The filepath
594 @type session: SQLAlchemy
595 @param session: Optional SQL session object (a temporary one will be
596 generated if not supplied). If not passed, a commit will be performed at
597 the end of the function, otherwise the caller is responsible for commiting.
600 @return: the database id for the given path
603 q = session.query(ContentFilepath).filter_by(filepath=filepath)
606 ret = q.one().cafilepath_id
607 except NoResultFound:
608 cf = ContentFilepath()
609 cf.filepath = filepath
611 session.commit_or_flush()
612 ret = cf.cafilepath_id
616 __all__.append('get_or_set_contents_path_id')
618 ################################################################################
620 class ContentAssociation(object):
621 def __init__(self, *args, **kwargs):
625 return '<ContentAssociation %s>' % self.ca_id
627 __all__.append('ContentAssociation')
629 def insert_content_paths(binary_id, fullpaths, session=None):
631 Make sure given path is associated with given binary id
634 @param binary_id: the id of the binary
635 @type fullpaths: list
636 @param fullpaths: the list of paths of the file being associated with the binary
637 @type session: SQLAlchemy session
638 @param session: Optional SQLAlchemy session. If this is passed, the caller
639 is responsible for ensuring a transaction has begun and committing the
640 results or rolling back based on the result code. If not passed, a commit
641 will be performed at the end of the function, otherwise the caller is
642 responsible for commiting.
644 @return: True upon success
649 session = DBConn().session()
655 for fullpath in fullpaths:
656 if fullpath.startswith( './' ):
657 fullpath = fullpath[2:]
659 session.execute( "INSERT INTO bin_contents ( file, binary_id ) VALUES ( :filename, :id )", { 'filename': fullpath, 'id': binary_id} )
667 traceback.print_exc()
669 # Only rollback if we set up the session ourself
676 __all__.append('insert_content_paths')
678 ################################################################################
680 class DSCFile(object):
681 def __init__(self, *args, **kwargs):
685 return '<DSCFile %s>' % self.dscfile_id
687 __all__.append('DSCFile')
690 def get_dscfiles(dscfile_id=None, source_id=None, poolfile_id=None, session=None):
692 Returns a list of DSCFiles which may be empty
694 @type dscfile_id: int (optional)
695 @param dscfile_id: the dscfile_id of the DSCFiles to find
697 @type source_id: int (optional)
698 @param source_id: the source id related to the DSCFiles to find
700 @type poolfile_id: int (optional)
701 @param poolfile_id: the poolfile id related to the DSCFiles to find
704 @return: Possibly empty list of DSCFiles
707 q = session.query(DSCFile)
709 if dscfile_id is not None:
710 q = q.filter_by(dscfile_id=dscfile_id)
712 if source_id is not None:
713 q = q.filter_by(source_id=source_id)
715 if poolfile_id is not None:
716 q = q.filter_by(poolfile_id=poolfile_id)
720 __all__.append('get_dscfiles')
722 ################################################################################
724 class PoolFile(object):
725 def __init__(self, *args, **kwargs):
729 return '<PoolFile %s>' % self.filename
731 __all__.append('PoolFile')
734 def check_poolfile(filename, filesize, md5sum, location_id, session=None):
737 (ValidFileFound [boolean or None], PoolFile object or None)
739 @type filename: string
740 @param filename: the filename of the file to check against the DB
743 @param filesize: the size of the file to check against the DB
746 @param md5sum: the md5sum of the file to check against the DB
748 @type location_id: int
749 @param location_id: the id of the location to look in
752 @return: Tuple of length 2.
753 If more than one file found with that name:
755 If valid pool file found: (True, PoolFile object)
756 If valid pool file not found:
757 (False, None) if no file found
758 (False, PoolFile object) if file found with size/md5sum mismatch
761 q = session.query(PoolFile).filter_by(filename=filename)
762 q = q.join(Location).filter_by(location_id=location_id)
772 if obj.md5sum != md5sum or obj.filesize != filesize:
780 __all__.append('check_poolfile')
783 def get_poolfile_by_id(file_id, session=None):
785 Returns a PoolFile objects or None for the given id
788 @param file_id: the id of the file to look for
790 @rtype: PoolFile or None
791 @return: either the PoolFile object or None
794 q = session.query(PoolFile).filter_by(file_id=file_id)
798 except NoResultFound:
801 __all__.append('get_poolfile_by_id')
805 def get_poolfile_by_name(filename, location_id=None, session=None):
807 Returns an array of PoolFile objects for the given filename and
808 (optionally) location_id
810 @type filename: string
811 @param filename: the filename of the file to check against the DB
813 @type location_id: int
814 @param location_id: the id of the location to look in (optional)
817 @return: array of PoolFile objects
820 q = session.query(PoolFile).filter_by(filename=filename)
822 if location_id is not None:
823 q = q.join(Location).filter_by(location_id=location_id)
827 __all__.append('get_poolfile_by_name')
830 def get_poolfile_like_name(filename, session=None):
832 Returns an array of PoolFile objects which are like the given name
834 @type filename: string
835 @param filename: the filename of the file to check against the DB
838 @return: array of PoolFile objects
841 # TODO: There must be a way of properly using bind parameters with %FOO%
842 q = session.query(PoolFile).filter(PoolFile.filename.like('%%%s%%' % filename))
846 __all__.append('get_poolfile_like_name')
848 ################################################################################
850 class Fingerprint(object):
851 def __init__(self, *args, **kwargs):
855 return '<Fingerprint %s>' % self.fingerprint
857 __all__.append('Fingerprint')
860 def get_fingerprint(fpr, session=None):
862 Returns Fingerprint object for given fpr.
865 @param fpr: The fpr to find / add
867 @type session: SQLAlchemy
868 @param session: Optional SQL session object (a temporary one will be
869 generated if not supplied).
872 @return: the Fingerprint object for the given fpr or None
875 q = session.query(Fingerprint).filter_by(fingerprint=fpr)
879 except NoResultFound:
884 __all__.append('get_fingerprint')
887 def get_or_set_fingerprint(fpr, session=None):
889 Returns Fingerprint object for given fpr.
891 If no matching fpr is found, a row is inserted.
894 @param fpr: The fpr to find / add
896 @type session: SQLAlchemy
897 @param session: Optional SQL session object (a temporary one will be
898 generated if not supplied). If not passed, a commit will be performed at
899 the end of the function, otherwise the caller is responsible for commiting.
900 A flush will be performed either way.
903 @return: the Fingerprint object for the given fpr
906 q = session.query(Fingerprint).filter_by(fingerprint=fpr)
910 except NoResultFound:
911 fingerprint = Fingerprint()
912 fingerprint.fingerprint = fpr
913 session.add(fingerprint)
914 session.commit_or_flush()
919 __all__.append('get_or_set_fingerprint')
921 ################################################################################
923 # Helper routine for Keyring class
924 def get_ldap_name(entry):
926 for k in ["cn", "mn", "sn"]:
928 if ret and ret[0] != "" and ret[0] != "-":
930 return " ".join(name)
932 ################################################################################
934 class Keyring(object):
935 gpg_invocation = "gpg --no-default-keyring --keyring %s" +\
936 " --with-colons --fingerprint --fingerprint"
941 def __init__(self, *args, **kwargs):
945 return '<Keyring %s>' % self.keyring_name
947 def de_escape_gpg_str(self, str):
948 esclist = re.split(r'(\\x..)', str)
949 for x in range(1,len(esclist),2):
950 esclist[x] = "%c" % (int(esclist[x][2:],16))
951 return "".join(esclist)
953 def load_keys(self, keyring):
956 if not self.keyring_id:
957 raise Exception('Must be initialized with database information')
959 k = os.popen(self.gpg_invocation % keyring, "r")
963 for line in k.xreadlines():
964 field = line.split(":")
965 if field[0] == "pub":
967 (name, addr) = email.Utils.parseaddr(field[9])
968 name = re.sub(r"\s*[(].*[)]", "", name)
969 if name == "" or addr == "" or "@" not in addr:
972 name = self.de_escape_gpg_str(name)
973 self.keys[key] = {"email": addr}
975 self.keys[key]["name"] = name
976 self.keys[key]["aliases"] = [name]
977 self.keys[key]["fingerprints"] = []
979 elif key and field[0] == "sub" and len(field) >= 12:
980 signingkey = ("s" in field[11])
981 elif key and field[0] == "uid":
982 (name, addr) = email.Utils.parseaddr(field[9])
983 if name and name not in self.keys[key]["aliases"]:
984 self.keys[key]["aliases"].append(name)
985 elif signingkey and field[0] == "fpr":
986 self.keys[key]["fingerprints"].append(field[9])
987 self.fpr_lookup[field[9]] = key
989 def import_users_from_ldap(self, session):
993 LDAPDn = cnf["Import-LDAP-Fingerprints::LDAPDn"]
994 LDAPServer = cnf["Import-LDAP-Fingerprints::LDAPServer"]
996 l = ldap.open(LDAPServer)
997 l.simple_bind_s("","")
998 Attrs = l.search_s(LDAPDn, ldap.SCOPE_ONELEVEL,
999 "(&(keyfingerprint=*)(gidnumber=%s))" % (cnf["Import-Users-From-Passwd::ValidGID"]),
1000 ["uid", "keyfingerprint", "cn", "mn", "sn"])
1002 ldap_fin_uid_id = {}
1009 uid = entry["uid"][0]
1010 name = get_ldap_name(entry)
1011 fingerprints = entry["keyFingerPrint"]
1013 for f in fingerprints:
1014 key = self.fpr_lookup.get(f, None)
1015 if key not in self.keys:
1017 self.keys[key]["uid"] = uid
1021 keyid = get_or_set_uid(uid, session).uid_id
1022 byuid[keyid] = (uid, name)
1023 byname[uid] = (keyid, name)
1025 return (byname, byuid)
1027 def generate_users_from_keyring(self, format, session):
1031 for x in self.keys.keys():
1032 if self.keys[x]["email"] == "invalid-uid":
1034 self.keys[x]["uid"] = format % "invalid-uid"
1036 uid = format % self.keys[x]["email"]
1037 keyid = get_or_set_uid(uid, session).uid_id
1038 byuid[keyid] = (uid, self.keys[x]["name"])
1039 byname[uid] = (keyid, self.keys[x]["name"])
1040 self.keys[x]["uid"] = uid
1043 uid = format % "invalid-uid"
1044 keyid = get_or_set_uid(uid, session).uid_id
1045 byuid[keyid] = (uid, "ungeneratable user id")
1046 byname[uid] = (keyid, "ungeneratable user id")
1048 return (byname, byuid)
1050 __all__.append('Keyring')
1053 def get_keyring(keyring, session=None):
1055 If C{keyring} does not have an entry in the C{keyrings} table yet, return None
1056 If C{keyring} already has an entry, simply return the existing Keyring
1058 @type keyring: string
1059 @param keyring: the keyring name
1062 @return: the Keyring object for this keyring
1065 q = session.query(Keyring).filter_by(keyring_name=keyring)
1069 except NoResultFound:
1072 __all__.append('get_keyring')
1074 ################################################################################
1076 class KeyringACLMap(object):
1077 def __init__(self, *args, **kwargs):
1081 return '<KeyringACLMap %s>' % self.keyring_acl_map_id
1083 __all__.append('KeyringACLMap')
1085 ################################################################################
1087 class KnownChange(object):
1088 def __init__(self, *args, **kwargs):
1092 return '<KnownChange %s>' % self.changesname
1094 __all__.append('KnownChange')
1097 def get_knownchange(filename, session=None):
1099 returns knownchange object for given C{filename}.
1101 @type archive: string
1102 @param archive: the name of the arhive
1104 @type session: Session
1105 @param session: Optional SQLA session object (a temporary one will be
1106 generated if not supplied)
1109 @return: Archive object for the given name (None if not present)
1112 q = session.query(KnownChange).filter_by(changesname=filename)
1116 except NoResultFound:
1119 __all__.append('get_knownchange')
1121 ################################################################################
1122 class Location(object):
1123 def __init__(self, *args, **kwargs):
1127 return '<Location %s (%s)>' % (self.path, self.location_id)
1129 __all__.append('Location')
1132 def get_location(location, component=None, archive=None, session=None):
1134 Returns Location object for the given combination of location, component
1137 @type location: string
1138 @param location: the path of the location, e.g. I{/srv/ftp.debian.org/ftp/pool/}
1140 @type component: string
1141 @param component: the component name (if None, no restriction applied)
1143 @type archive: string
1144 @param archive_id: the archive name (if None, no restriction applied)
1146 @rtype: Location / None
1147 @return: Either a Location object or None if one can't be found
1150 q = session.query(Location).filter_by(path=location)
1152 if archive is not None:
1153 q = q.join(Archive).filter_by(archive_name=archive)
1155 if component is not None:
1156 q = q.join(Component).filter_by(component_name=component)
1160 except NoResultFound:
1163 __all__.append('get_location')
1165 ################################################################################
1167 class Maintainer(object):
1168 def __init__(self, *args, **kwargs):
1172 return '''<Maintainer '%s' (%s)>''' % (self.name, self.maintainer_id)
1174 def get_split_maintainer(self):
1175 if not hasattr(self, 'name') or self.name is None:
1176 return ('', '', '', '')
1178 return fix_maintainer(self.name.strip())
1180 __all__.append('Maintainer')
1183 def get_or_set_maintainer(name, session=None):
1185 Returns Maintainer object for given maintainer name.
1187 If no matching maintainer name is found, a row is inserted.
1190 @param name: The maintainer name to add
1192 @type session: SQLAlchemy
1193 @param session: Optional SQL session object (a temporary one will be
1194 generated if not supplied). If not passed, a commit will be performed at
1195 the end of the function, otherwise the caller is responsible for commiting.
1196 A flush will be performed either way.
1199 @return: the Maintainer object for the given maintainer
1202 q = session.query(Maintainer).filter_by(name=name)
1205 except NoResultFound:
1206 maintainer = Maintainer()
1207 maintainer.name = name
1208 session.add(maintainer)
1209 session.commit_or_flush()
1214 __all__.append('get_or_set_maintainer')
1217 def get_maintainer(maintainer_id, session=None):
1219 Return the name of the maintainer behind C{maintainer_id} or None if that
1220 maintainer_id is invalid.
1222 @type maintainer_id: int
1223 @param maintainer_id: the id of the maintainer
1226 @return: the Maintainer with this C{maintainer_id}
1229 return session.query(Maintainer).get(maintainer_id)
1231 __all__.append('get_maintainer')
1233 ################################################################################
1235 class NewComment(object):
1236 def __init__(self, *args, **kwargs):
1240 return '''<NewComment for '%s %s' (%s)>''' % (self.package, self.version, self.comment_id)
1242 __all__.append('NewComment')
1245 def has_new_comment(package, version, session=None):
1247 Returns true if the given combination of C{package}, C{version} has a comment.
1249 @type package: string
1250 @param package: name of the package
1252 @type version: string
1253 @param version: package version
1255 @type session: Session
1256 @param session: Optional SQLA session object (a temporary one will be
1257 generated if not supplied)
1263 q = session.query(NewComment)
1264 q = q.filter_by(package=package)
1265 q = q.filter_by(version=version)
1267 return bool(q.count() > 0)
1269 __all__.append('has_new_comment')
1272 def get_new_comments(package=None, version=None, comment_id=None, session=None):
1274 Returns (possibly empty) list of NewComment objects for the given
1277 @type package: string (optional)
1278 @param package: name of the package
1280 @type version: string (optional)
1281 @param version: package version
1283 @type comment_id: int (optional)
1284 @param comment_id: An id of a comment
1286 @type session: Session
1287 @param session: Optional SQLA session object (a temporary one will be
1288 generated if not supplied)
1291 @return: A (possibly empty) list of NewComment objects will be returned
1294 q = session.query(NewComment)
1295 if package is not None: q = q.filter_by(package=package)
1296 if version is not None: q = q.filter_by(version=version)
1297 if comment_id is not None: q = q.filter_by(comment_id=comment_id)
1301 __all__.append('get_new_comments')
1303 ################################################################################
1305 class Override(object):
1306 def __init__(self, *args, **kwargs):
1310 return '<Override %s (%s)>' % (self.package, self.suite_id)
1312 __all__.append('Override')
1315 def get_override(package, suite=None, component=None, overridetype=None, session=None):
1317 Returns Override object for the given parameters
1319 @type package: string
1320 @param package: The name of the package
1322 @type suite: string, list or None
1323 @param suite: The name of the suite (or suites if a list) to limit to. If
1324 None, don't limit. Defaults to None.
1326 @type component: string, list or None
1327 @param component: The name of the component (or components if a list) to
1328 limit to. If None, don't limit. Defaults to None.
1330 @type overridetype: string, list or None
1331 @param overridetype: The name of the overridetype (or overridetypes if a list) to
1332 limit to. If None, don't limit. Defaults to None.
1334 @type session: Session
1335 @param session: Optional SQLA session object (a temporary one will be
1336 generated if not supplied)
1339 @return: A (possibly empty) list of Override objects will be returned
1342 q = session.query(Override)
1343 q = q.filter_by(package=package)
1345 if suite is not None:
1346 if not isinstance(suite, list): suite = [suite]
1347 q = q.join(Suite).filter(Suite.suite_name.in_(suite))
1349 if component is not None:
1350 if not isinstance(component, list): component = [component]
1351 q = q.join(Component).filter(Component.component_name.in_(component))
1353 if overridetype is not None:
1354 if not isinstance(overridetype, list): overridetype = [overridetype]
1355 q = q.join(OverrideType).filter(OverrideType.overridetype.in_(overridetype))
1359 __all__.append('get_override')
1362 ################################################################################
1364 class OverrideType(object):
1365 def __init__(self, *args, **kwargs):
1369 return '<OverrideType %s>' % self.overridetype
1371 __all__.append('OverrideType')
1374 def get_override_type(override_type, session=None):
1376 Returns OverrideType object for given C{override type}.
1378 @type override_type: string
1379 @param override_type: The name of the override type
1381 @type session: Session
1382 @param session: Optional SQLA session object (a temporary one will be
1383 generated if not supplied)
1386 @return: the database id for the given override type
1389 q = session.query(OverrideType).filter_by(overridetype=override_type)
1393 except NoResultFound:
1396 __all__.append('get_override_type')
1398 ################################################################################
1400 class PendingContentAssociation(object):
1401 def __init__(self, *args, **kwargs):
1405 return '<PendingContentAssociation %s>' % self.pca_id
1407 __all__.append('PendingContentAssociation')
1409 def insert_pending_content_paths(package, fullpaths, session=None):
1411 Make sure given paths are temporarily associated with given
1415 @param package: the package to associate with should have been read in from the binary control file
1416 @type fullpaths: list
1417 @param fullpaths: the list of paths of the file being associated with the binary
1418 @type session: SQLAlchemy session
1419 @param session: Optional SQLAlchemy session. If this is passed, the caller
1420 is responsible for ensuring a transaction has begun and committing the
1421 results or rolling back based on the result code. If not passed, a commit
1422 will be performed at the end of the function
1424 @return: True upon success, False if there is a problem
1427 privatetrans = False
1430 session = DBConn().session()
1434 arch = get_architecture(package['Architecture'], session)
1435 arch_id = arch.arch_id
1437 # Remove any already existing recorded files for this package
1438 q = session.query(PendingContentAssociation)
1439 q = q.filter_by(package=package['Package'])
1440 q = q.filter_by(version=package['Version'])
1441 q = q.filter_by(architecture=arch_id)
1446 for fullpath in fullpaths:
1447 (path, file) = os.path.split(fullpath)
1449 if path.startswith( "./" ):
1452 filepath_id = get_or_set_contents_path_id(path, session)
1453 filename_id = get_or_set_contents_file_id(file, session)
1455 pathcache[fullpath] = (filepath_id, filename_id)
1457 for fullpath, dat in pathcache.items():
1458 pca = PendingContentAssociation()
1459 pca.package = package['Package']
1460 pca.version = package['Version']
1461 pca.filepath_id = dat[0]
1462 pca.filename_id = dat[1]
1463 pca.architecture = arch_id
1466 # Only commit if we set up the session ourself
1474 except Exception, e:
1475 traceback.print_exc()
1477 # Only rollback if we set up the session ourself
1484 __all__.append('insert_pending_content_paths')
1486 ################################################################################
1488 class Priority(object):
1489 def __init__(self, *args, **kwargs):
1492 def __eq__(self, val):
1493 if isinstance(val, str):
1494 return (self.priority == val)
1495 # This signals to use the normal comparison operator
1496 return NotImplemented
1498 def __ne__(self, val):
1499 if isinstance(val, str):
1500 return (self.priority != val)
1501 # This signals to use the normal comparison operator
1502 return NotImplemented
1505 return '<Priority %s (%s)>' % (self.priority, self.priority_id)
1507 __all__.append('Priority')
1510 def get_priority(priority, session=None):
1512 Returns Priority object for given C{priority name}.
1514 @type priority: string
1515 @param priority: The name of the priority
1517 @type session: Session
1518 @param session: Optional SQLA session object (a temporary one will be
1519 generated if not supplied)
1522 @return: Priority object for the given priority
1525 q = session.query(Priority).filter_by(priority=priority)
1529 except NoResultFound:
1532 __all__.append('get_priority')
1535 def get_priorities(session=None):
1537 Returns dictionary of priority names -> id mappings
1539 @type session: Session
1540 @param session: Optional SQL session object (a temporary one will be
1541 generated if not supplied)
1544 @return: dictionary of priority names -> id mappings
1548 q = session.query(Priority)
1550 ret[x.priority] = x.priority_id
1554 __all__.append('get_priorities')
1556 ################################################################################
1558 class Queue(object):
1559 def __init__(self, *args, **kwargs):
1563 return '<Queue %s>' % self.queue_name
1565 def autobuild_upload(self, changes, srcpath, session=None):
1567 Update queue_build database table used for incoming autobuild support.
1569 @type changes: Changes
1570 @param changes: changes object for the upload to process
1572 @type srcpath: string
1573 @param srcpath: path for the queue file entries/link destinations
1575 @type session: SQLAlchemy session
1576 @param session: Optional SQLAlchemy session. If this is passed, the
1577 caller is responsible for ensuring a transaction has begun and
1578 committing the results or rolling back based on the result code. If
1579 not passed, a commit will be performed at the end of the function,
1580 otherwise the caller is responsible for commiting.
1582 @rtype: NoneType or string
1583 @return: None if the operation failed, a string describing the error if not
1586 privatetrans = False
1588 session = DBConn().session()
1591 # TODO: Remove by moving queue config into the database
1594 for suitename in changes.changes["distribution"].keys():
1595 # TODO: Move into database as:
1596 # buildqueuedir TEXT DEFAULT NULL (i.e. NULL is no build)
1597 # buildqueuecopy BOOLEAN NOT NULL DEFAULT FALSE (i.e. default is symlink)
1598 # This also gets rid of the SecurityQueueBuild hack below
1599 if suitename not in conf.ValueList("Dinstall::QueueBuildSuites"):
1603 s = get_suite(suitename, session)
1605 return "INTERNAL ERROR: Could not find suite %s" % suitename
1607 # TODO: Get from database as above
1608 dest_dir = conf["Dir::QueueBuild"]
1610 # TODO: Move into database as above
1611 if conf.FindB("Dinstall::SecurityQueueBuild"):
1612 dest_dir = os.path.join(dest_dir, suitename)
1614 for file_entry in changes.files.keys():
1615 src = os.path.join(srcpath, file_entry)
1616 dest = os.path.join(dest_dir, file_entry)
1618 # TODO: Move into database as above
1619 if conf.FindB("Dinstall::SecurityQueueBuild"):
1620 # Copy it since the original won't be readable by www-data
1622 utils.copy(src, dest)
1624 # Create a symlink to it
1625 os.symlink(src, dest)
1628 qb.suite_id = s.suite_id
1629 qb.queue_id = self.queue_id
1635 # If the .orig tarballs are in the pool, create a symlink to
1636 # them (if one doesn't already exist)
1637 for dsc_file in changes.dsc_files.keys():
1638 # Skip all files except orig tarballs
1639 from daklib.regexes import re_is_orig_source
1640 if not re_is_orig_source.match(dsc_file):
1642 # Skip orig files not identified in the pool
1643 if not (changes.orig_files.has_key(dsc_file) and
1644 changes.orig_files[dsc_file].has_key("id")):
1646 orig_file_id = changes.orig_files[dsc_file]["id"]
1647 dest = os.path.join(dest_dir, dsc_file)
1649 # If it doesn't exist, create a symlink
1650 if not os.path.exists(dest):
1651 q = session.execute("SELECT l.path, f.filename FROM location l, files f WHERE f.id = :id and f.location = l.id",
1652 {'id': orig_file_id})
1655 return "[INTERNAL ERROR] Couldn't find id %s in files table." % (orig_file_id)
1657 src = os.path.join(res[0], res[1])
1658 os.symlink(src, dest)
1660 # Add it to the list of packages for later processing by apt-ftparchive
1662 qb.suite_id = s.suite_id
1663 qb.queue_id = self.queue_id
1668 # If it does, update things to ensure it's not removed prematurely
1670 qb = get_queue_build(dest, s.suite_id, session)
1682 __all__.append('Queue')
1685 def get_or_set_queue(queuename, session=None):
1687 Returns Queue object for given C{queue name}, creating it if it does not
1690 @type queuename: string
1691 @param queuename: The name of the queue
1693 @type session: Session
1694 @param session: Optional SQLA session object (a temporary one will be
1695 generated if not supplied)
1698 @return: Queue object for the given queue
1701 q = session.query(Queue).filter_by(queue_name=queuename)
1705 except NoResultFound:
1707 queue.queue_name = queuename
1709 session.commit_or_flush()
1714 __all__.append('get_or_set_queue')
1716 ################################################################################
1718 class QueueBuild(object):
1719 def __init__(self, *args, **kwargs):
1723 return '<QueueBuild %s (%s)>' % (self.filename, self.queue_id)
1725 __all__.append('QueueBuild')
1728 def get_queue_build(filename, suite, session=None):
1730 Returns QueueBuild object for given C{filename} and C{suite}.
1732 @type filename: string
1733 @param filename: The name of the file
1735 @type suiteid: int or str
1736 @param suiteid: Suite name or ID
1738 @type session: Session
1739 @param session: Optional SQLA session object (a temporary one will be
1740 generated if not supplied)
1743 @return: Queue object for the given queue
1746 if isinstance(suite, int):
1747 q = session.query(QueueBuild).filter_by(filename=filename).filter_by(suite_id=suite)
1749 q = session.query(QueueBuild).filter_by(filename=filename)
1750 q = q.join(Suite).filter_by(suite_name=suite)
1754 except NoResultFound:
1757 __all__.append('get_queue_build')
1759 ################################################################################
1761 class Section(object):
1762 def __init__(self, *args, **kwargs):
1765 def __eq__(self, val):
1766 if isinstance(val, str):
1767 return (self.section == val)
1768 # This signals to use the normal comparison operator
1769 return NotImplemented
1771 def __ne__(self, val):
1772 if isinstance(val, str):
1773 return (self.section != val)
1774 # This signals to use the normal comparison operator
1775 return NotImplemented
1778 return '<Section %s>' % self.section
1780 __all__.append('Section')
1783 def get_section(section, session=None):
1785 Returns Section object for given C{section name}.
1787 @type section: string
1788 @param section: The name of the section
1790 @type session: Session
1791 @param session: Optional SQLA session object (a temporary one will be
1792 generated if not supplied)
1795 @return: Section object for the given section name
1798 q = session.query(Section).filter_by(section=section)
1802 except NoResultFound:
1805 __all__.append('get_section')
1808 def get_sections(session=None):
1810 Returns dictionary of section names -> id mappings
1812 @type session: Session
1813 @param session: Optional SQL session object (a temporary one will be
1814 generated if not supplied)
1817 @return: dictionary of section names -> id mappings
1821 q = session.query(Section)
1823 ret[x.section] = x.section_id
1827 __all__.append('get_sections')
1829 ################################################################################
1831 class DBSource(object):
1832 def __init__(self, *args, **kwargs):
1836 return '<DBSource %s (%s)>' % (self.source, self.version)
1838 __all__.append('DBSource')
1841 def source_exists(source, source_version, suites = ["any"], session=None):
1843 Ensure that source exists somewhere in the archive for the binary
1844 upload being processed.
1845 1. exact match => 1.0-3
1846 2. bin-only NMU => 1.0-3+b1 , 1.0-3.1+b1
1848 @type package: string
1849 @param package: package source name
1851 @type source_version: string
1852 @param source_version: expected source version
1855 @param suites: list of suites to check in, default I{any}
1857 @type session: Session
1858 @param session: Optional SQLA session object (a temporary one will be
1859 generated if not supplied)
1862 @return: returns 1 if a source with expected version is found, otherwise 0
1869 for suite in suites:
1870 q = session.query(DBSource).filter_by(source=source)
1872 # source must exist in suite X, or in some other suite that's
1873 # mapped to X, recursively... silent-maps are counted too,
1874 # unreleased-maps aren't.
1875 maps = cnf.ValueList("SuiteMappings")[:]
1877 maps = [ m.split() for m in maps ]
1878 maps = [ (x[1], x[2]) for x in maps
1879 if x[0] == "map" or x[0] == "silent-map" ]
1882 if x[1] in s and x[0] not in s:
1885 q = q.join(SrcAssociation).join(Suite)
1886 q = q.filter(Suite.suite_name.in_(s))
1888 # Reduce the query results to a list of version numbers
1889 ql = [ j.version for j in q.all() ]
1892 if source_version in ql:
1896 from daklib.regexes import re_bin_only_nmu
1897 orig_source_version = re_bin_only_nmu.sub('', source_version)
1898 if orig_source_version in ql:
1901 # No source found so return not ok
1906 __all__.append('source_exists')
1909 def get_suites_source_in(source, session=None):
1911 Returns list of Suite objects which given C{source} name is in
1914 @param source: DBSource package name to search for
1917 @return: list of Suite objects for the given source
1920 return session.query(Suite).join(SrcAssociation).join(DBSource).filter_by(source=source).all()
1922 __all__.append('get_suites_source_in')
1925 def get_sources_from_name(source, version=None, dm_upload_allowed=None, session=None):
1927 Returns list of DBSource objects for given C{source} name and other parameters
1930 @param source: DBSource package name to search for
1932 @type source: str or None
1933 @param source: DBSource version name to search for or None if not applicable
1935 @type dm_upload_allowed: bool
1936 @param dm_upload_allowed: If None, no effect. If True or False, only
1937 return packages with that dm_upload_allowed setting
1939 @type session: Session
1940 @param session: Optional SQL session object (a temporary one will be
1941 generated if not supplied)
1944 @return: list of DBSource objects for the given name (may be empty)
1947 q = session.query(DBSource).filter_by(source=source)
1949 if version is not None:
1950 q = q.filter_by(version=version)
1952 if dm_upload_allowed is not None:
1953 q = q.filter_by(dm_upload_allowed=dm_upload_allowed)
1957 __all__.append('get_sources_from_name')
1960 def get_source_in_suite(source, suite, session=None):
1962 Returns list of DBSource objects for a combination of C{source} and C{suite}.
1964 - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
1965 - B{suite} - a suite name, eg. I{unstable}
1967 @type source: string
1968 @param source: source package name
1971 @param suite: the suite name
1974 @return: the version for I{source} in I{suite}
1978 q = session.query(SrcAssociation)
1979 q = q.join('source').filter_by(source=source)
1980 q = q.join('suite').filter_by(suite_name=suite)
1983 return q.one().source
1984 except NoResultFound:
1987 __all__.append('get_source_in_suite')
1989 ################################################################################
1991 class SourceACL(object):
1992 def __init__(self, *args, **kwargs):
1996 return '<SourceACL %s>' % self.source_acl_id
1998 __all__.append('SourceACL')
2000 ################################################################################
2002 class SrcAssociation(object):
2003 def __init__(self, *args, **kwargs):
2007 return '<SrcAssociation %s (%s, %s)>' % (self.sa_id, self.source, self.suite)
2009 __all__.append('SrcAssociation')
2011 ################################################################################
2013 class SrcFormat(object):
2014 def __init__(self, *args, **kwargs):
2018 return '<SrcFormat %s>' % (self.format_name)
2020 __all__.append('SrcFormat')
2022 ################################################################################
2024 class SrcUploader(object):
2025 def __init__(self, *args, **kwargs):
2029 return '<SrcUploader %s>' % self.uploader_id
2031 __all__.append('SrcUploader')
2033 ################################################################################
2035 SUITE_FIELDS = [ ('SuiteName', 'suite_name'),
2036 ('SuiteID', 'suite_id'),
2037 ('Version', 'version'),
2038 ('Origin', 'origin'),
2040 ('Description', 'description'),
2041 ('Untouchable', 'untouchable'),
2042 ('Announce', 'announce'),
2043 ('Codename', 'codename'),
2044 ('OverrideCodename', 'overridecodename'),
2045 ('ValidTime', 'validtime'),
2046 ('Priority', 'priority'),
2047 ('NotAutomatic', 'notautomatic'),
2048 ('CopyChanges', 'copychanges'),
2049 ('CopyDotDak', 'copydotdak'),
2050 ('CommentsDir', 'commentsdir'),
2051 ('OverrideSuite', 'overridesuite'),
2052 ('ChangelogBase', 'changelogbase')]
2055 class Suite(object):
2056 def __init__(self, *args, **kwargs):
2060 return '<Suite %s>' % self.suite_name
2062 def __eq__(self, val):
2063 if isinstance(val, str):
2064 return (self.suite_name == val)
2065 # This signals to use the normal comparison operator
2066 return NotImplemented
2068 def __ne__(self, val):
2069 if isinstance(val, str):
2070 return (self.suite_name != val)
2071 # This signals to use the normal comparison operator
2072 return NotImplemented
2076 for disp, field in SUITE_FIELDS:
2077 val = getattr(self, field, None)
2079 ret.append("%s: %s" % (disp, val))
2081 return "\n".join(ret)
2083 __all__.append('Suite')
2086 def get_suite_architecture(suite, architecture, session=None):
2088 Returns a SuiteArchitecture object given C{suite} and ${arch} or None if it
2092 @param suite: Suite name to search for
2094 @type architecture: str
2095 @param architecture: Architecture name to search for
2097 @type session: Session
2098 @param session: Optional SQL session object (a temporary one will be
2099 generated if not supplied)
2101 @rtype: SuiteArchitecture
2102 @return: the SuiteArchitecture object or None
2105 q = session.query(SuiteArchitecture)
2106 q = q.join(Architecture).filter_by(arch_string=architecture)
2107 q = q.join(Suite).filter_by(suite_name=suite)
2111 except NoResultFound:
2114 __all__.append('get_suite_architecture')
2117 def get_suite(suite, session=None):
2119 Returns Suite object for given C{suite name}.
2122 @param suite: The name of the suite
2124 @type session: Session
2125 @param session: Optional SQLA session object (a temporary one will be
2126 generated if not supplied)
2129 @return: Suite object for the requested suite name (None if not present)
2132 q = session.query(Suite).filter_by(suite_name=suite)
2136 except NoResultFound:
2139 __all__.append('get_suite')
2141 ################################################################################
2143 class SuiteArchitecture(object):
2144 def __init__(self, *args, **kwargs):
2148 return '<SuiteArchitecture (%s, %s)>' % (self.suite_id, self.arch_id)
2150 __all__.append('SuiteArchitecture')
2153 def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None):
2155 Returns list of Architecture objects for given C{suite} name
2158 @param source: Suite name to search for
2160 @type skipsrc: boolean
2161 @param skipsrc: Whether to skip returning the 'source' architecture entry
2164 @type skipall: boolean
2165 @param skipall: Whether to skip returning the 'all' architecture entry
2168 @type session: Session
2169 @param session: Optional SQL session object (a temporary one will be
2170 generated if not supplied)
2173 @return: list of Architecture objects for the given name (may be empty)
2176 q = session.query(Architecture)
2177 q = q.join(SuiteArchitecture)
2178 q = q.join(Suite).filter_by(suite_name=suite)
2181 q = q.filter(Architecture.arch_string != 'source')
2184 q = q.filter(Architecture.arch_string != 'all')
2186 q = q.order_by('arch_string')
2190 __all__.append('get_suite_architectures')
2192 ################################################################################
2194 class SuiteSrcFormat(object):
2195 def __init__(self, *args, **kwargs):
2199 return '<SuiteSrcFormat (%s, %s)>' % (self.suite_id, self.src_format_id)
2201 __all__.append('SuiteSrcFormat')
2204 def get_suite_src_formats(suite, session=None):
2206 Returns list of allowed SrcFormat for C{suite}.
2209 @param suite: Suite name to search for
2211 @type session: Session
2212 @param session: Optional SQL session object (a temporary one will be
2213 generated if not supplied)
2216 @return: the list of allowed source formats for I{suite}
2219 q = session.query(SrcFormat)
2220 q = q.join(SuiteSrcFormat)
2221 q = q.join(Suite).filter_by(suite_name=suite)
2222 q = q.order_by('format_name')
2226 __all__.append('get_suite_src_formats')
2228 ################################################################################
2231 def __init__(self, *args, **kwargs):
2234 def __eq__(self, val):
2235 if isinstance(val, str):
2236 return (self.uid == val)
2237 # This signals to use the normal comparison operator
2238 return NotImplemented
2240 def __ne__(self, val):
2241 if isinstance(val, str):
2242 return (self.uid != val)
2243 # This signals to use the normal comparison operator
2244 return NotImplemented
2247 return '<Uid %s (%s)>' % (self.uid, self.name)
2249 __all__.append('Uid')
2252 def add_database_user(uidname, session=None):
2254 Adds a database user
2256 @type uidname: string
2257 @param uidname: The uid of the user to add
2259 @type session: SQLAlchemy
2260 @param session: Optional SQL session object (a temporary one will be
2261 generated if not supplied). If not passed, a commit will be performed at
2262 the end of the function, otherwise the caller is responsible for commiting.
2265 @return: the uid object for the given uidname
2268 session.execute("CREATE USER :uid", {'uid': uidname})
2269 session.commit_or_flush()
2271 __all__.append('add_database_user')
2274 def get_or_set_uid(uidname, session=None):
2276 Returns uid object for given uidname.
2278 If no matching uidname is found, a row is inserted.
2280 @type uidname: string
2281 @param uidname: The uid to add
2283 @type session: SQLAlchemy
2284 @param session: Optional SQL session object (a temporary one will be
2285 generated if not supplied). If not passed, a commit will be performed at
2286 the end of the function, otherwise the caller is responsible for commiting.
2289 @return: the uid object for the given uidname
2292 q = session.query(Uid).filter_by(uid=uidname)
2296 except NoResultFound:
2300 session.commit_or_flush()
2305 __all__.append('get_or_set_uid')
2308 def get_uid_from_fingerprint(fpr, session=None):
2309 q = session.query(Uid)
2310 q = q.join(Fingerprint).filter_by(fingerprint=fpr)
2314 except NoResultFound:
2317 __all__.append('get_uid_from_fingerprint')
2319 ################################################################################
2321 class UploadBlock(object):
2322 def __init__(self, *args, **kwargs):
2326 return '<UploadBlock %s (%s)>' % (self.source, self.upload_block_id)
2328 __all__.append('UploadBlock')
2330 ################################################################################
2332 class DBConn(Singleton):
2334 database module init.
2336 def __init__(self, *args, **kwargs):
2337 super(DBConn, self).__init__(*args, **kwargs)
2339 def _startup(self, *args, **kwargs):
2341 if kwargs.has_key('debug'):
2345 def __setuptables(self):
2346 self.tbl_architecture = Table('architecture', self.db_meta, autoload=True)
2347 self.tbl_archive = Table('archive', self.db_meta, autoload=True)
2348 self.tbl_bin_associations = Table('bin_associations', self.db_meta, autoload=True)
2349 self.tbl_binaries = Table('binaries', self.db_meta, autoload=True)
2350 self.tbl_binary_acl = Table('binary_acl', self.db_meta, autoload=True)
2351 self.tbl_binary_acl_map = Table('binary_acl_map', self.db_meta, autoload=True)
2352 self.tbl_component = Table('component', self.db_meta, autoload=True)
2353 self.tbl_config = Table('config', self.db_meta, autoload=True)
2354 self.tbl_content_associations = Table('content_associations', self.db_meta, autoload=True)
2355 self.tbl_content_file_names = Table('content_file_names', self.db_meta, autoload=True)
2356 self.tbl_content_file_paths = Table('content_file_paths', self.db_meta, autoload=True)
2357 self.tbl_dsc_files = Table('dsc_files', self.db_meta, autoload=True)
2358 self.tbl_files = Table('files', self.db_meta, autoload=True)
2359 self.tbl_fingerprint = Table('fingerprint', self.db_meta, autoload=True)
2360 self.tbl_keyrings = Table('keyrings', self.db_meta, autoload=True)
2361 self.tbl_known_changes = Table('known_changes', self.db_meta, autoload=True)
2362 self.tbl_keyring_acl_map = Table('keyring_acl_map', self.db_meta, autoload=True)
2363 self.tbl_location = Table('location', self.db_meta, autoload=True)
2364 self.tbl_maintainer = Table('maintainer', self.db_meta, autoload=True)
2365 self.tbl_new_comments = Table('new_comments', self.db_meta, autoload=True)
2366 self.tbl_override = Table('override', self.db_meta, autoload=True)
2367 self.tbl_override_type = Table('override_type', self.db_meta, autoload=True)
2368 self.tbl_pending_content_associations = Table('pending_content_associations', self.db_meta, autoload=True)
2369 self.tbl_priority = Table('priority', self.db_meta, autoload=True)
2370 self.tbl_queue = Table('queue', self.db_meta, autoload=True)
2371 self.tbl_queue_build = Table('queue_build', self.db_meta, autoload=True)
2372 self.tbl_section = Table('section', self.db_meta, autoload=True)
2373 self.tbl_source = Table('source', self.db_meta, autoload=True)
2374 self.tbl_source_acl = Table('source_acl', self.db_meta, autoload=True)
2375 self.tbl_src_associations = Table('src_associations', self.db_meta, autoload=True)
2376 self.tbl_src_format = Table('src_format', self.db_meta, autoload=True)
2377 self.tbl_src_uploaders = Table('src_uploaders', self.db_meta, autoload=True)
2378 self.tbl_suite = Table('suite', self.db_meta, autoload=True)
2379 self.tbl_suite_architectures = Table('suite_architectures', self.db_meta, autoload=True)
2380 self.tbl_suite_src_formats = Table('suite_src_formats', self.db_meta, autoload=True)
2381 self.tbl_uid = Table('uid', self.db_meta, autoload=True)
2382 self.tbl_upload_blocks = Table('upload_blocks', self.db_meta, autoload=True)
2384 def __setupmappers(self):
2385 mapper(Architecture, self.tbl_architecture,
2386 properties = dict(arch_id = self.tbl_architecture.c.id))
2388 mapper(Archive, self.tbl_archive,
2389 properties = dict(archive_id = self.tbl_archive.c.id,
2390 archive_name = self.tbl_archive.c.name))
2392 mapper(BinAssociation, self.tbl_bin_associations,
2393 properties = dict(ba_id = self.tbl_bin_associations.c.id,
2394 suite_id = self.tbl_bin_associations.c.suite,
2395 suite = relation(Suite),
2396 binary_id = self.tbl_bin_associations.c.bin,
2397 binary = relation(DBBinary)))
2400 mapper(DBBinary, self.tbl_binaries,
2401 properties = dict(binary_id = self.tbl_binaries.c.id,
2402 package = self.tbl_binaries.c.package,
2403 version = self.tbl_binaries.c.version,
2404 maintainer_id = self.tbl_binaries.c.maintainer,
2405 maintainer = relation(Maintainer),
2406 source_id = self.tbl_binaries.c.source,
2407 source = relation(DBSource),
2408 arch_id = self.tbl_binaries.c.architecture,
2409 architecture = relation(Architecture),
2410 poolfile_id = self.tbl_binaries.c.file,
2411 poolfile = relation(PoolFile),
2412 binarytype = self.tbl_binaries.c.type,
2413 fingerprint_id = self.tbl_binaries.c.sig_fpr,
2414 fingerprint = relation(Fingerprint),
2415 install_date = self.tbl_binaries.c.install_date,
2416 binassociations = relation(BinAssociation,
2417 primaryjoin=(self.tbl_binaries.c.id==self.tbl_bin_associations.c.bin))))
2419 mapper(BinaryACL, self.tbl_binary_acl,
2420 properties = dict(binary_acl_id = self.tbl_binary_acl.c.id))
2422 mapper(BinaryACLMap, self.tbl_binary_acl_map,
2423 properties = dict(binary_acl_map_id = self.tbl_binary_acl_map.c.id,
2424 fingerprint = relation(Fingerprint, backref="binary_acl_map"),
2425 architecture = relation(Architecture)))
2427 mapper(Component, self.tbl_component,
2428 properties = dict(component_id = self.tbl_component.c.id,
2429 component_name = self.tbl_component.c.name))
2431 mapper(DBConfig, self.tbl_config,
2432 properties = dict(config_id = self.tbl_config.c.id))
2434 mapper(DSCFile, self.tbl_dsc_files,
2435 properties = dict(dscfile_id = self.tbl_dsc_files.c.id,
2436 source_id = self.tbl_dsc_files.c.source,
2437 source = relation(DBSource),
2438 poolfile_id = self.tbl_dsc_files.c.file,
2439 poolfile = relation(PoolFile)))
2441 mapper(PoolFile, self.tbl_files,
2442 properties = dict(file_id = self.tbl_files.c.id,
2443 filesize = self.tbl_files.c.size,
2444 location_id = self.tbl_files.c.location,
2445 location = relation(Location)))
2447 mapper(Fingerprint, self.tbl_fingerprint,
2448 properties = dict(fingerprint_id = self.tbl_fingerprint.c.id,
2449 uid_id = self.tbl_fingerprint.c.uid,
2450 uid = relation(Uid),
2451 keyring_id = self.tbl_fingerprint.c.keyring,
2452 keyring = relation(Keyring),
2453 source_acl = relation(SourceACL),
2454 binary_acl = relation(BinaryACL)))
2456 mapper(Keyring, self.tbl_keyrings,
2457 properties = dict(keyring_name = self.tbl_keyrings.c.name,
2458 keyring_id = self.tbl_keyrings.c.id))
2460 mapper(KnownChange, self.tbl_known_changes,
2461 properties = dict(known_change_id = self.tbl_known_changes.c.id))
2463 mapper(KeyringACLMap, self.tbl_keyring_acl_map,
2464 properties = dict(keyring_acl_map_id = self.tbl_keyring_acl_map.c.id,
2465 keyring = relation(Keyring, backref="keyring_acl_map"),
2466 architecture = relation(Architecture)))
2468 mapper(Location, self.tbl_location,
2469 properties = dict(location_id = self.tbl_location.c.id,
2470 component_id = self.tbl_location.c.component,
2471 component = relation(Component),
2472 archive_id = self.tbl_location.c.archive,
2473 archive = relation(Archive),
2474 archive_type = self.tbl_location.c.type))
2476 mapper(Maintainer, self.tbl_maintainer,
2477 properties = dict(maintainer_id = self.tbl_maintainer.c.id))
2479 mapper(NewComment, self.tbl_new_comments,
2480 properties = dict(comment_id = self.tbl_new_comments.c.id))
2482 mapper(Override, self.tbl_override,
2483 properties = dict(suite_id = self.tbl_override.c.suite,
2484 suite = relation(Suite),
2485 component_id = self.tbl_override.c.component,
2486 component = relation(Component),
2487 priority_id = self.tbl_override.c.priority,
2488 priority = relation(Priority),
2489 section_id = self.tbl_override.c.section,
2490 section = relation(Section),
2491 overridetype_id = self.tbl_override.c.type,
2492 overridetype = relation(OverrideType)))
2494 mapper(OverrideType, self.tbl_override_type,
2495 properties = dict(overridetype = self.tbl_override_type.c.type,
2496 overridetype_id = self.tbl_override_type.c.id))
2498 mapper(Priority, self.tbl_priority,
2499 properties = dict(priority_id = self.tbl_priority.c.id))
2501 mapper(Queue, self.tbl_queue,
2502 properties = dict(queue_id = self.tbl_queue.c.id))
2504 mapper(QueueBuild, self.tbl_queue_build,
2505 properties = dict(suite_id = self.tbl_queue_build.c.suite,
2506 queue_id = self.tbl_queue_build.c.queue,
2507 queue = relation(Queue, backref='queuebuild')))
2509 mapper(Section, self.tbl_section,
2510 properties = dict(section_id = self.tbl_section.c.id))
2512 mapper(DBSource, self.tbl_source,
2513 properties = dict(source_id = self.tbl_source.c.id,
2514 version = self.tbl_source.c.version,
2515 maintainer_id = self.tbl_source.c.maintainer,
2516 maintainer = relation(Maintainer,
2517 primaryjoin=(self.tbl_source.c.maintainer==self.tbl_maintainer.c.id)),
2518 poolfile_id = self.tbl_source.c.file,
2519 poolfile = relation(PoolFile),
2520 fingerprint_id = self.tbl_source.c.sig_fpr,
2521 fingerprint = relation(Fingerprint),
2522 changedby_id = self.tbl_source.c.changedby,
2523 changedby = relation(Maintainer,
2524 primaryjoin=(self.tbl_source.c.changedby==self.tbl_maintainer.c.id)),
2525 srcfiles = relation(DSCFile,
2526 primaryjoin=(self.tbl_source.c.id==self.tbl_dsc_files.c.source)),
2527 srcassociations = relation(SrcAssociation,
2528 primaryjoin=(self.tbl_source.c.id==self.tbl_src_associations.c.source)),
2529 srcuploaders = relation(SrcUploader)))
2531 mapper(SourceACL, self.tbl_source_acl,
2532 properties = dict(source_acl_id = self.tbl_source_acl.c.id))
2534 mapper(SrcAssociation, self.tbl_src_associations,
2535 properties = dict(sa_id = self.tbl_src_associations.c.id,
2536 suite_id = self.tbl_src_associations.c.suite,
2537 suite = relation(Suite),
2538 source_id = self.tbl_src_associations.c.source,
2539 source = relation(DBSource)))
2541 mapper(SrcFormat, self.tbl_src_format,
2542 properties = dict(src_format_id = self.tbl_src_format.c.id,
2543 format_name = self.tbl_src_format.c.format_name))
2545 mapper(SrcUploader, self.tbl_src_uploaders,
2546 properties = dict(uploader_id = self.tbl_src_uploaders.c.id,
2547 source_id = self.tbl_src_uploaders.c.source,
2548 source = relation(DBSource,
2549 primaryjoin=(self.tbl_src_uploaders.c.source==self.tbl_source.c.id)),
2550 maintainer_id = self.tbl_src_uploaders.c.maintainer,
2551 maintainer = relation(Maintainer,
2552 primaryjoin=(self.tbl_src_uploaders.c.maintainer==self.tbl_maintainer.c.id))))
2554 mapper(Suite, self.tbl_suite,
2555 properties = dict(suite_id = self.tbl_suite.c.id,
2556 policy_queue = relation(Queue)))
2558 mapper(SuiteArchitecture, self.tbl_suite_architectures,
2559 properties = dict(suite_id = self.tbl_suite_architectures.c.suite,
2560 suite = relation(Suite, backref='suitearchitectures'),
2561 arch_id = self.tbl_suite_architectures.c.architecture,
2562 architecture = relation(Architecture)))
2564 mapper(SuiteSrcFormat, self.tbl_suite_src_formats,
2565 properties = dict(suite_id = self.tbl_suite_src_formats.c.suite,
2566 suite = relation(Suite, backref='suitesrcformats'),
2567 src_format_id = self.tbl_suite_src_formats.c.src_format,
2568 src_format = relation(SrcFormat)))
2570 mapper(Uid, self.tbl_uid,
2571 properties = dict(uid_id = self.tbl_uid.c.id,
2572 fingerprint = relation(Fingerprint)))
2574 mapper(UploadBlock, self.tbl_upload_blocks,
2575 properties = dict(upload_block_id = self.tbl_upload_blocks.c.id,
2576 fingerprint = relation(Fingerprint, backref="uploadblocks"),
2577 uid = relation(Uid, backref="uploadblocks")))
2579 ## Connection functions
2580 def __createconn(self):
2581 from config import Config
2585 connstr = "postgres://%s" % cnf["DB::Host"]
2586 if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
2587 connstr += ":%s" % cnf["DB::Port"]
2588 connstr += "/%s" % cnf["DB::Name"]
2591 connstr = "postgres:///%s" % cnf["DB::Name"]
2592 if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
2593 connstr += "?port=%s" % cnf["DB::Port"]
2595 self.db_pg = create_engine(connstr, echo=self.debug)
2596 self.db_meta = MetaData()
2597 self.db_meta.bind = self.db_pg
2598 self.db_smaker = sessionmaker(bind=self.db_pg,
2602 self.__setuptables()
2603 self.__setupmappers()
2606 return self.db_smaker()
2608 __all__.append('DBConn')