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 ################################################################################
40 from datetime import datetime
42 from inspect import getargspec
45 from sqlalchemy import create_engine, Table, MetaData
46 from sqlalchemy.orm import sessionmaker, mapper, relation
47 from sqlalchemy import types as sqltypes
49 # Don't remove this, we re-export the exceptions to scripts which import us
50 from sqlalchemy.exc import *
51 from sqlalchemy.orm.exc import NoResultFound
53 # Only import Config until Queue stuff is changed to store its config
55 from config import Config
56 from singleton import Singleton
57 from textutils import fix_maintainer
59 ################################################################################
61 # Patch in support for the debversion field type so that it works during
64 class DebVersion(sqltypes.Text):
65 def get_col_spec(self):
68 sa_major_version = sqlalchemy.__version__[0:3]
69 if sa_major_version == "0.5":
70 from sqlalchemy.databases import postgres
71 postgres.ischema_names['debversion'] = DebVersion
73 raise Exception("dak isn't ported to SQLA versions != 0.5 yet. See daklib/dbconn.py")
75 ################################################################################
77 __all__ = ['IntegrityError', 'SQLAlchemyError']
79 ################################################################################
81 def session_wrapper(fn):
83 Wrapper around common ".., session=None):" handling. If the wrapped
84 function is called without passing 'session', we create a local one
85 and destroy it when the function ends.
87 Also attaches a commit_or_flush method to the session; if we created a
88 local session, this is a synonym for session.commit(), otherwise it is a
89 synonym for session.flush().
92 def wrapped(*args, **kwargs):
93 private_transaction = False
95 # Find the session object
96 session = kwargs.get('session')
99 if len(args) <= len(getargspec(fn)[0]) - 1:
100 # No session specified as last argument or in kwargs
101 private_transaction = True
102 session = kwargs['session'] = DBConn().session()
104 # Session is last argument in args
108 session = args[-1] = DBConn().session()
109 private_transaction = True
111 if private_transaction:
112 session.commit_or_flush = session.commit
114 session.commit_or_flush = session.flush
117 return fn(*args, **kwargs)
119 if private_transaction:
120 # We created a session; close it.
123 wrapped.__doc__ = fn.__doc__
124 wrapped.func_name = fn.func_name
128 __all__.append('session_wrapper')
130 ################################################################################
132 class Architecture(object):
133 def __init__(self, *args, **kwargs):
136 def __eq__(self, val):
137 if isinstance(val, str):
138 return (self.arch_string== val)
139 # This signals to use the normal comparison operator
140 return NotImplemented
142 def __ne__(self, val):
143 if isinstance(val, str):
144 return (self.arch_string != val)
145 # This signals to use the normal comparison operator
146 return NotImplemented
149 return '<Architecture %s>' % self.arch_string
151 __all__.append('Architecture')
154 def get_architecture(architecture, session=None):
156 Returns database id for given C{architecture}.
158 @type architecture: string
159 @param architecture: The name of the architecture
161 @type session: Session
162 @param session: Optional SQLA session object (a temporary one will be
163 generated if not supplied)
166 @return: Architecture object for the given arch (None if not present)
169 q = session.query(Architecture).filter_by(arch_string=architecture)
173 except NoResultFound:
176 __all__.append('get_architecture')
179 def get_architecture_suites(architecture, session=None):
181 Returns list of Suite objects for given C{architecture} name
184 @param source: Architecture name to search for
186 @type session: Session
187 @param session: Optional SQL session object (a temporary one will be
188 generated if not supplied)
191 @return: list of Suite objects for the given name (may be empty)
194 q = session.query(Suite)
195 q = q.join(SuiteArchitecture)
196 q = q.join(Architecture).filter_by(arch_string=architecture).order_by('suite_name')
202 __all__.append('get_architecture_suites')
204 ################################################################################
206 class Archive(object):
207 def __init__(self, *args, **kwargs):
211 return '<Archive %s>' % self.archive_name
213 __all__.append('Archive')
216 def get_archive(archive, session=None):
218 returns database id for given C{archive}.
220 @type archive: string
221 @param archive: the name of the arhive
223 @type session: Session
224 @param session: Optional SQLA session object (a temporary one will be
225 generated if not supplied)
228 @return: Archive object for the given name (None if not present)
231 archive = archive.lower()
233 q = session.query(Archive).filter_by(archive_name=archive)
237 except NoResultFound:
240 __all__.append('get_archive')
242 ################################################################################
244 class BinAssociation(object):
245 def __init__(self, *args, **kwargs):
249 return '<BinAssociation %s (%s, %s)>' % (self.ba_id, self.binary, self.suite)
251 __all__.append('BinAssociation')
253 ################################################################################
255 class BinContents(object):
256 def __init__(self, *args, **kwargs):
260 return '<BinContents (%s, %s)>' % (self.binary, self.filename)
262 __all__.append('BinContents')
264 ################################################################################
266 class DBBinary(object):
267 def __init__(self, *args, **kwargs):
271 return '<DBBinary %s (%s, %s)>' % (self.package, self.version, self.architecture)
273 __all__.append('DBBinary')
276 def get_suites_binary_in(package, session=None):
278 Returns list of Suite objects which given C{package} name is in
281 @param source: DBBinary package name to search for
284 @return: list of Suite objects for the given package
287 return session.query(Suite).join(BinAssociation).join(DBBinary).filter_by(package=package).all()
289 __all__.append('get_suites_binary_in')
292 def get_binary_from_id(binary_id, session=None):
294 Returns DBBinary object for given C{id}
297 @param binary_id: Id of the required binary
299 @type session: Session
300 @param session: Optional SQLA session object (a temporary one will be
301 generated if not supplied)
304 @return: DBBinary object for the given binary (None if not present)
307 q = session.query(DBBinary).filter_by(binary_id=binary_id)
311 except NoResultFound:
314 __all__.append('get_binary_from_id')
317 def get_binaries_from_name(package, version=None, architecture=None, session=None):
319 Returns list of DBBinary objects for given C{package} name
322 @param package: DBBinary package name to search for
324 @type version: str or None
325 @param version: Version to search for (or None)
327 @type package: str, list or None
328 @param package: Architectures to limit to (or None if no limit)
330 @type session: Session
331 @param session: Optional SQL session object (a temporary one will be
332 generated if not supplied)
335 @return: list of DBBinary objects for the given name (may be empty)
338 q = session.query(DBBinary).filter_by(package=package)
340 if version is not None:
341 q = q.filter_by(version=version)
343 if architecture is not None:
344 if not isinstance(architecture, list):
345 architecture = [architecture]
346 q = q.join(Architecture).filter(Architecture.arch_string.in_(architecture))
352 __all__.append('get_binaries_from_name')
355 def get_binaries_from_source_id(source_id, session=None):
357 Returns list of DBBinary objects for given C{source_id}
360 @param source_id: source_id to search for
362 @type session: Session
363 @param session: Optional SQL session object (a temporary one will be
364 generated if not supplied)
367 @return: list of DBBinary objects for the given name (may be empty)
370 return session.query(DBBinary).filter_by(source_id=source_id).all()
372 __all__.append('get_binaries_from_source_id')
375 def get_binary_from_name_suite(package, suitename, session=None):
376 ### For dak examine-package
377 ### XXX: Doesn't use object API yet
379 sql = """SELECT DISTINCT(b.package), b.version, c.name, su.suite_name
380 FROM binaries b, files fi, location l, component c, bin_associations ba, suite su
381 WHERE b.package=:package
383 AND fi.location = l.id
384 AND l.component = c.id
387 AND su.suite_name=:suitename
388 ORDER BY b.version DESC"""
390 return session.execute(sql, {'package': package, 'suitename': suitename})
392 __all__.append('get_binary_from_name_suite')
395 def get_binary_components(package, suitename, arch, session=None):
396 # Check for packages that have moved from one component to another
397 query = """SELECT c.name FROM binaries b, bin_associations ba, suite s, location l, component c, architecture a, files f
398 WHERE b.package=:package AND s.suite_name=:suitename
399 AND (a.arch_string = :arch OR a.arch_string = 'all')
400 AND ba.bin = b.id AND ba.suite = s.id AND b.architecture = a.id
401 AND f.location = l.id
402 AND l.component = c.id
405 vals = {'package': package, 'suitename': suitename, 'arch': arch}
407 return session.execute(query, vals)
409 __all__.append('get_binary_components')
411 ################################################################################
413 class BinaryACL(object):
414 def __init__(self, *args, **kwargs):
418 return '<BinaryACL %s>' % self.binary_acl_id
420 __all__.append('BinaryACL')
422 ################################################################################
424 class BinaryACLMap(object):
425 def __init__(self, *args, **kwargs):
429 return '<BinaryACLMap %s>' % self.binary_acl_map_id
431 __all__.append('BinaryACLMap')
433 ################################################################################
435 class Component(object):
436 def __init__(self, *args, **kwargs):
439 def __eq__(self, val):
440 if isinstance(val, str):
441 return (self.component_name == val)
442 # This signals to use the normal comparison operator
443 return NotImplemented
445 def __ne__(self, val):
446 if isinstance(val, str):
447 return (self.component_name != val)
448 # This signals to use the normal comparison operator
449 return NotImplemented
452 return '<Component %s>' % self.component_name
455 __all__.append('Component')
458 def get_component(component, session=None):
460 Returns database id for given C{component}.
462 @type component: string
463 @param component: The name of the override type
466 @return: the database id for the given component
469 component = component.lower()
471 q = session.query(Component).filter_by(component_name=component)
475 except NoResultFound:
478 __all__.append('get_component')
480 ################################################################################
482 class DBConfig(object):
483 def __init__(self, *args, **kwargs):
487 return '<DBConfig %s>' % self.name
489 __all__.append('DBConfig')
491 ################################################################################
494 def get_or_set_contents_file_id(filename, session=None):
496 Returns database id for given filename.
498 If no matching file is found, a row is inserted.
500 @type filename: string
501 @param filename: The filename
502 @type session: SQLAlchemy
503 @param session: Optional SQL session object (a temporary one will be
504 generated if not supplied). If not passed, a commit will be performed at
505 the end of the function, otherwise the caller is responsible for commiting.
508 @return: the database id for the given component
511 q = session.query(ContentFilename).filter_by(filename=filename)
514 ret = q.one().cafilename_id
515 except NoResultFound:
516 cf = ContentFilename()
517 cf.filename = filename
519 session.commit_or_flush()
520 ret = cf.cafilename_id
524 __all__.append('get_or_set_contents_file_id')
527 def get_contents(suite, overridetype, section=None, session=None):
529 Returns contents for a suite / overridetype combination, limiting
530 to a section if not None.
533 @param suite: Suite object
535 @type overridetype: OverrideType
536 @param overridetype: OverrideType object
538 @type section: Section
539 @param section: Optional section object to limit results to
541 @type session: SQLAlchemy
542 @param session: Optional SQL session object (a temporary one will be
543 generated if not supplied)
546 @return: ResultsProxy object set up to return tuples of (filename, section,
550 # find me all of the contents for a given suite
551 contents_q = """SELECT (p.path||'/'||n.file) AS fn,
555 FROM content_associations c join content_file_paths p ON (c.filepath=p.id)
556 JOIN content_file_names n ON (c.filename=n.id)
557 JOIN binaries b ON (b.id=c.binary_pkg)
558 JOIN override o ON (o.package=b.package)
559 JOIN section s ON (s.id=o.section)
560 WHERE o.suite = :suiteid AND o.type = :overridetypeid
561 AND b.type=:overridetypename"""
563 vals = {'suiteid': suite.suite_id,
564 'overridetypeid': overridetype.overridetype_id,
565 'overridetypename': overridetype.overridetype}
567 if section is not None:
568 contents_q += " AND s.id = :sectionid"
569 vals['sectionid'] = section.section_id
571 contents_q += " ORDER BY fn"
573 return session.execute(contents_q, vals)
575 __all__.append('get_contents')
577 ################################################################################
579 class ContentFilepath(object):
580 def __init__(self, *args, **kwargs):
584 return '<ContentFilepath %s>' % self.filepath
586 __all__.append('ContentFilepath')
589 def get_or_set_contents_path_id(filepath, session=None):
591 Returns database id for given path.
593 If no matching file is found, a row is inserted.
595 @type filename: string
596 @param filename: The filepath
597 @type session: SQLAlchemy
598 @param session: Optional SQL session object (a temporary one will be
599 generated if not supplied). If not passed, a commit will be performed at
600 the end of the function, otherwise the caller is responsible for commiting.
603 @return: the database id for the given path
606 q = session.query(ContentFilepath).filter_by(filepath=filepath)
609 ret = q.one().cafilepath_id
610 except NoResultFound:
611 cf = ContentFilepath()
612 cf.filepath = filepath
614 session.commit_or_flush()
615 ret = cf.cafilepath_id
619 __all__.append('get_or_set_contents_path_id')
621 ################################################################################
623 class ContentAssociation(object):
624 def __init__(self, *args, **kwargs):
628 return '<ContentAssociation %s>' % self.ca_id
630 __all__.append('ContentAssociation')
632 def insert_content_paths(binary_id, fullpaths, session=None):
634 Make sure given path is associated with given binary id
637 @param binary_id: the id of the binary
638 @type fullpaths: list
639 @param fullpaths: the list of paths of the file being associated with the binary
640 @type session: SQLAlchemy session
641 @param session: Optional SQLAlchemy session. If this is passed, the caller
642 is responsible for ensuring a transaction has begun and committing the
643 results or rolling back based on the result code. If not passed, a commit
644 will be performed at the end of the function, otherwise the caller is
645 responsible for commiting.
647 @return: True upon success
652 session = DBConn().session()
658 for fullpath in fullpaths:
659 if fullpath.startswith( './' ):
660 fullpath = fullpath[2:]
662 session.execute( "INSERT INTO bin_contents ( file, binary_id ) VALUES ( :filename, :id )", { 'filename': fullpath, 'id': binary_id} )
670 traceback.print_exc()
672 # Only rollback if we set up the session ourself
679 __all__.append('insert_content_paths')
681 ################################################################################
683 class DSCFile(object):
684 def __init__(self, *args, **kwargs):
688 return '<DSCFile %s>' % self.dscfile_id
690 __all__.append('DSCFile')
693 def get_dscfiles(dscfile_id=None, source_id=None, poolfile_id=None, session=None):
695 Returns a list of DSCFiles which may be empty
697 @type dscfile_id: int (optional)
698 @param dscfile_id: the dscfile_id of the DSCFiles to find
700 @type source_id: int (optional)
701 @param source_id: the source id related to the DSCFiles to find
703 @type poolfile_id: int (optional)
704 @param poolfile_id: the poolfile id related to the DSCFiles to find
707 @return: Possibly empty list of DSCFiles
710 q = session.query(DSCFile)
712 if dscfile_id is not None:
713 q = q.filter_by(dscfile_id=dscfile_id)
715 if source_id is not None:
716 q = q.filter_by(source_id=source_id)
718 if poolfile_id is not None:
719 q = q.filter_by(poolfile_id=poolfile_id)
723 __all__.append('get_dscfiles')
725 ################################################################################
727 class PoolFile(object):
728 def __init__(self, *args, **kwargs):
732 return '<PoolFile %s>' % self.filename
736 return os.path.join(self.location.path, self.filename)
738 __all__.append('PoolFile')
741 def check_poolfile(filename, filesize, md5sum, location_id, session=None):
744 (ValidFileFound [boolean or None], PoolFile object or None)
746 @type filename: string
747 @param filename: the filename of the file to check against the DB
750 @param filesize: the size of the file to check against the DB
753 @param md5sum: the md5sum of the file to check against the DB
755 @type location_id: int
756 @param location_id: the id of the location to look in
759 @return: Tuple of length 2.
760 If more than one file found with that name:
762 If valid pool file found: (True, PoolFile object)
763 If valid pool file not found:
764 (False, None) if no file found
765 (False, PoolFile object) if file found with size/md5sum mismatch
768 q = session.query(PoolFile).filter_by(filename=filename)
769 q = q.join(Location).filter_by(location_id=location_id)
779 if obj.md5sum != md5sum or obj.filesize != int(filesize):
787 __all__.append('check_poolfile')
790 def get_poolfile_by_id(file_id, session=None):
792 Returns a PoolFile objects or None for the given id
795 @param file_id: the id of the file to look for
797 @rtype: PoolFile or None
798 @return: either the PoolFile object or None
801 q = session.query(PoolFile).filter_by(file_id=file_id)
805 except NoResultFound:
808 __all__.append('get_poolfile_by_id')
812 def get_poolfile_by_name(filename, location_id=None, session=None):
814 Returns an array of PoolFile objects for the given filename and
815 (optionally) location_id
817 @type filename: string
818 @param filename: the filename of the file to check against the DB
820 @type location_id: int
821 @param location_id: the id of the location to look in (optional)
824 @return: array of PoolFile objects
827 q = session.query(PoolFile).filter_by(filename=filename)
829 if location_id is not None:
830 q = q.join(Location).filter_by(location_id=location_id)
834 __all__.append('get_poolfile_by_name')
837 def get_poolfile_like_name(filename, session=None):
839 Returns an array of PoolFile objects which are like the given name
841 @type filename: string
842 @param filename: the filename of the file to check against the DB
845 @return: array of PoolFile objects
848 # TODO: There must be a way of properly using bind parameters with %FOO%
849 q = session.query(PoolFile).filter(PoolFile.filename.like('%%%s%%' % filename))
853 __all__.append('get_poolfile_like_name')
856 def add_poolfile(filename, datadict, location_id, session=None):
858 Add a new file to the pool
860 @type filename: string
861 @param filename: filename
864 @param datadict: dict with needed data
866 @type location_id: int
867 @param location_id: database id of the location
870 @return: the PoolFile object created
872 poolfile = PoolFile()
873 poolfile.filename = filename
874 poolfile.filesize = datadict["size"]
875 poolfile.md5sum = datadict["md5sum"]
876 poolfile.sha1sum = datadict["sha1sum"]
877 poolfile.sha256sum = datadict["sha256sum"]
878 poolfile.location_id = location_id
880 session.add(poolfile)
881 # Flush to get a file id (NB: This is not a commit)
886 __all__.append('add_poolfile')
888 ################################################################################
890 class Fingerprint(object):
891 def __init__(self, *args, **kwargs):
895 return '<Fingerprint %s>' % self.fingerprint
897 __all__.append('Fingerprint')
900 def get_fingerprint(fpr, session=None):
902 Returns Fingerprint object for given fpr.
905 @param fpr: The fpr to find / add
907 @type session: SQLAlchemy
908 @param session: Optional SQL session object (a temporary one will be
909 generated if not supplied).
912 @return: the Fingerprint object for the given fpr or None
915 q = session.query(Fingerprint).filter_by(fingerprint=fpr)
919 except NoResultFound:
924 __all__.append('get_fingerprint')
927 def get_or_set_fingerprint(fpr, session=None):
929 Returns Fingerprint object for given fpr.
931 If no matching fpr is found, a row is inserted.
934 @param fpr: The fpr to find / add
936 @type session: SQLAlchemy
937 @param session: Optional SQL session object (a temporary one will be
938 generated if not supplied). If not passed, a commit will be performed at
939 the end of the function, otherwise the caller is responsible for commiting.
940 A flush will be performed either way.
943 @return: the Fingerprint object for the given fpr
946 q = session.query(Fingerprint).filter_by(fingerprint=fpr)
950 except NoResultFound:
951 fingerprint = Fingerprint()
952 fingerprint.fingerprint = fpr
953 session.add(fingerprint)
954 session.commit_or_flush()
959 __all__.append('get_or_set_fingerprint')
961 ################################################################################
963 # Helper routine for Keyring class
964 def get_ldap_name(entry):
966 for k in ["cn", "mn", "sn"]:
968 if ret and ret[0] != "" and ret[0] != "-":
970 return " ".join(name)
972 ################################################################################
974 class Keyring(object):
975 gpg_invocation = "gpg --no-default-keyring --keyring %s" +\
976 " --with-colons --fingerprint --fingerprint"
981 def __init__(self, *args, **kwargs):
985 return '<Keyring %s>' % self.keyring_name
987 def de_escape_gpg_str(self, txt):
988 esclist = re.split(r'(\\x..)', txt)
989 for x in range(1,len(esclist),2):
990 esclist[x] = "%c" % (int(esclist[x][2:],16))
991 return "".join(esclist)
993 def load_keys(self, keyring):
996 if not self.keyring_id:
997 raise Exception('Must be initialized with database information')
999 k = os.popen(self.gpg_invocation % keyring, "r")
1003 for line in k.xreadlines():
1004 field = line.split(":")
1005 if field[0] == "pub":
1007 (name, addr) = email.Utils.parseaddr(field[9])
1008 name = re.sub(r"\s*[(].*[)]", "", name)
1009 if name == "" or addr == "" or "@" not in addr:
1011 addr = "invalid-uid"
1012 name = self.de_escape_gpg_str(name)
1013 self.keys[key] = {"email": addr}
1015 self.keys[key]["name"] = name
1016 self.keys[key]["aliases"] = [name]
1017 self.keys[key]["fingerprints"] = []
1019 elif key and field[0] == "sub" and len(field) >= 12:
1020 signingkey = ("s" in field[11])
1021 elif key and field[0] == "uid":
1022 (name, addr) = email.Utils.parseaddr(field[9])
1023 if name and name not in self.keys[key]["aliases"]:
1024 self.keys[key]["aliases"].append(name)
1025 elif signingkey and field[0] == "fpr":
1026 self.keys[key]["fingerprints"].append(field[9])
1027 self.fpr_lookup[field[9]] = key
1029 def import_users_from_ldap(self, session):
1033 LDAPDn = cnf["Import-LDAP-Fingerprints::LDAPDn"]
1034 LDAPServer = cnf["Import-LDAP-Fingerprints::LDAPServer"]
1036 l = ldap.open(LDAPServer)
1037 l.simple_bind_s("","")
1038 Attrs = l.search_s(LDAPDn, ldap.SCOPE_ONELEVEL,
1039 "(&(keyfingerprint=*)(gidnumber=%s))" % (cnf["Import-Users-From-Passwd::ValidGID"]),
1040 ["uid", "keyfingerprint", "cn", "mn", "sn"])
1042 ldap_fin_uid_id = {}
1049 uid = entry["uid"][0]
1050 name = get_ldap_name(entry)
1051 fingerprints = entry["keyFingerPrint"]
1053 for f in fingerprints:
1054 key = self.fpr_lookup.get(f, None)
1055 if key not in self.keys:
1057 self.keys[key]["uid"] = uid
1061 keyid = get_or_set_uid(uid, session).uid_id
1062 byuid[keyid] = (uid, name)
1063 byname[uid] = (keyid, name)
1065 return (byname, byuid)
1067 def generate_users_from_keyring(self, format, session):
1071 for x in self.keys.keys():
1072 if self.keys[x]["email"] == "invalid-uid":
1074 self.keys[x]["uid"] = format % "invalid-uid"
1076 uid = format % self.keys[x]["email"]
1077 keyid = get_or_set_uid(uid, session).uid_id
1078 byuid[keyid] = (uid, self.keys[x]["name"])
1079 byname[uid] = (keyid, self.keys[x]["name"])
1080 self.keys[x]["uid"] = uid
1083 uid = format % "invalid-uid"
1084 keyid = get_or_set_uid(uid, session).uid_id
1085 byuid[keyid] = (uid, "ungeneratable user id")
1086 byname[uid] = (keyid, "ungeneratable user id")
1088 return (byname, byuid)
1090 __all__.append('Keyring')
1093 def get_keyring(keyring, session=None):
1095 If C{keyring} does not have an entry in the C{keyrings} table yet, return None
1096 If C{keyring} already has an entry, simply return the existing Keyring
1098 @type keyring: string
1099 @param keyring: the keyring name
1102 @return: the Keyring object for this keyring
1105 q = session.query(Keyring).filter_by(keyring_name=keyring)
1109 except NoResultFound:
1112 __all__.append('get_keyring')
1114 ################################################################################
1116 class KeyringACLMap(object):
1117 def __init__(self, *args, **kwargs):
1121 return '<KeyringACLMap %s>' % self.keyring_acl_map_id
1123 __all__.append('KeyringACLMap')
1125 ################################################################################
1127 class KnownChange(object):
1128 def __init__(self, *args, **kwargs):
1132 return '<KnownChange %s>' % self.changesname
1134 __all__.append('KnownChange')
1137 def get_knownchange(filename, session=None):
1139 returns knownchange object for given C{filename}.
1141 @type archive: string
1142 @param archive: the name of the arhive
1144 @type session: Session
1145 @param session: Optional SQLA session object (a temporary one will be
1146 generated if not supplied)
1149 @return: Archive object for the given name (None if not present)
1152 q = session.query(KnownChange).filter_by(changesname=filename)
1156 except NoResultFound:
1159 __all__.append('get_knownchange')
1161 ################################################################################
1163 class KnownChangePendingFile(object):
1164 def __init__(self, *args, **kwargs):
1168 return '<KnownChangePendingFile %s>' % self.known_change_pending_file_id
1170 __all__.append('KnownChangePendingFile')
1172 ################################################################################
1174 class Location(object):
1175 def __init__(self, *args, **kwargs):
1179 return '<Location %s (%s)>' % (self.path, self.location_id)
1181 __all__.append('Location')
1184 def get_location(location, component=None, archive=None, session=None):
1186 Returns Location object for the given combination of location, component
1189 @type location: string
1190 @param location: the path of the location, e.g. I{/srv/ftp.debian.org/ftp/pool/}
1192 @type component: string
1193 @param component: the component name (if None, no restriction applied)
1195 @type archive: string
1196 @param archive_id: the archive name (if None, no restriction applied)
1198 @rtype: Location / None
1199 @return: Either a Location object or None if one can't be found
1202 q = session.query(Location).filter_by(path=location)
1204 if archive is not None:
1205 q = q.join(Archive).filter_by(archive_name=archive)
1207 if component is not None:
1208 q = q.join(Component).filter_by(component_name=component)
1212 except NoResultFound:
1215 __all__.append('get_location')
1217 ################################################################################
1219 class Maintainer(object):
1220 def __init__(self, *args, **kwargs):
1224 return '''<Maintainer '%s' (%s)>''' % (self.name, self.maintainer_id)
1226 def get_split_maintainer(self):
1227 if not hasattr(self, 'name') or self.name is None:
1228 return ('', '', '', '')
1230 return fix_maintainer(self.name.strip())
1232 __all__.append('Maintainer')
1235 def get_or_set_maintainer(name, session=None):
1237 Returns Maintainer object for given maintainer name.
1239 If no matching maintainer name is found, a row is inserted.
1242 @param name: The maintainer name to add
1244 @type session: SQLAlchemy
1245 @param session: Optional SQL session object (a temporary one will be
1246 generated if not supplied). If not passed, a commit will be performed at
1247 the end of the function, otherwise the caller is responsible for commiting.
1248 A flush will be performed either way.
1251 @return: the Maintainer object for the given maintainer
1254 q = session.query(Maintainer).filter_by(name=name)
1257 except NoResultFound:
1258 maintainer = Maintainer()
1259 maintainer.name = name
1260 session.add(maintainer)
1261 session.commit_or_flush()
1266 __all__.append('get_or_set_maintainer')
1269 def get_maintainer(maintainer_id, session=None):
1271 Return the name of the maintainer behind C{maintainer_id} or None if that
1272 maintainer_id is invalid.
1274 @type maintainer_id: int
1275 @param maintainer_id: the id of the maintainer
1278 @return: the Maintainer with this C{maintainer_id}
1281 return session.query(Maintainer).get(maintainer_id)
1283 __all__.append('get_maintainer')
1285 ################################################################################
1287 class NewComment(object):
1288 def __init__(self, *args, **kwargs):
1292 return '''<NewComment for '%s %s' (%s)>''' % (self.package, self.version, self.comment_id)
1294 __all__.append('NewComment')
1297 def has_new_comment(package, version, session=None):
1299 Returns true if the given combination of C{package}, C{version} has a comment.
1301 @type package: string
1302 @param package: name of the package
1304 @type version: string
1305 @param version: package version
1307 @type session: Session
1308 @param session: Optional SQLA session object (a temporary one will be
1309 generated if not supplied)
1315 q = session.query(NewComment)
1316 q = q.filter_by(package=package)
1317 q = q.filter_by(version=version)
1319 return bool(q.count() > 0)
1321 __all__.append('has_new_comment')
1324 def get_new_comments(package=None, version=None, comment_id=None, session=None):
1326 Returns (possibly empty) list of NewComment objects for the given
1329 @type package: string (optional)
1330 @param package: name of the package
1332 @type version: string (optional)
1333 @param version: package version
1335 @type comment_id: int (optional)
1336 @param comment_id: An id of a comment
1338 @type session: Session
1339 @param session: Optional SQLA session object (a temporary one will be
1340 generated if not supplied)
1343 @return: A (possibly empty) list of NewComment objects will be returned
1346 q = session.query(NewComment)
1347 if package is not None: q = q.filter_by(package=package)
1348 if version is not None: q = q.filter_by(version=version)
1349 if comment_id is not None: q = q.filter_by(comment_id=comment_id)
1353 __all__.append('get_new_comments')
1355 ################################################################################
1357 class Override(object):
1358 def __init__(self, *args, **kwargs):
1362 return '<Override %s (%s)>' % (self.package, self.suite_id)
1364 __all__.append('Override')
1367 def get_override(package, suite=None, component=None, overridetype=None, session=None):
1369 Returns Override object for the given parameters
1371 @type package: string
1372 @param package: The name of the package
1374 @type suite: string, list or None
1375 @param suite: The name of the suite (or suites if a list) to limit to. If
1376 None, don't limit. Defaults to None.
1378 @type component: string, list or None
1379 @param component: The name of the component (or components if a list) to
1380 limit to. If None, don't limit. Defaults to None.
1382 @type overridetype: string, list or None
1383 @param overridetype: The name of the overridetype (or overridetypes if a list) to
1384 limit to. If None, don't limit. Defaults to None.
1386 @type session: Session
1387 @param session: Optional SQLA session object (a temporary one will be
1388 generated if not supplied)
1391 @return: A (possibly empty) list of Override objects will be returned
1394 q = session.query(Override)
1395 q = q.filter_by(package=package)
1397 if suite is not None:
1398 if not isinstance(suite, list): suite = [suite]
1399 q = q.join(Suite).filter(Suite.suite_name.in_(suite))
1401 if component is not None:
1402 if not isinstance(component, list): component = [component]
1403 q = q.join(Component).filter(Component.component_name.in_(component))
1405 if overridetype is not None:
1406 if not isinstance(overridetype, list): overridetype = [overridetype]
1407 q = q.join(OverrideType).filter(OverrideType.overridetype.in_(overridetype))
1411 __all__.append('get_override')
1414 ################################################################################
1416 class OverrideType(object):
1417 def __init__(self, *args, **kwargs):
1421 return '<OverrideType %s>' % self.overridetype
1423 __all__.append('OverrideType')
1426 def get_override_type(override_type, session=None):
1428 Returns OverrideType object for given C{override type}.
1430 @type override_type: string
1431 @param override_type: The name of the override type
1433 @type session: Session
1434 @param session: Optional SQLA session object (a temporary one will be
1435 generated if not supplied)
1438 @return: the database id for the given override type
1441 q = session.query(OverrideType).filter_by(overridetype=override_type)
1445 except NoResultFound:
1448 __all__.append('get_override_type')
1450 ################################################################################
1452 class PendingContentAssociation(object):
1453 def __init__(self, *args, **kwargs):
1457 return '<PendingContentAssociation %s>' % self.pca_id
1459 __all__.append('PendingContentAssociation')
1461 def insert_pending_content_paths(package, fullpaths, session=None):
1463 Make sure given paths are temporarily associated with given
1467 @param package: the package to associate with should have been read in from the binary control file
1468 @type fullpaths: list
1469 @param fullpaths: the list of paths of the file being associated with the binary
1470 @type session: SQLAlchemy session
1471 @param session: Optional SQLAlchemy session. If this is passed, the caller
1472 is responsible for ensuring a transaction has begun and committing the
1473 results or rolling back based on the result code. If not passed, a commit
1474 will be performed at the end of the function
1476 @return: True upon success, False if there is a problem
1479 privatetrans = False
1482 session = DBConn().session()
1486 arch = get_architecture(package['Architecture'], session)
1487 arch_id = arch.arch_id
1489 # Remove any already existing recorded files for this package
1490 q = session.query(PendingContentAssociation)
1491 q = q.filter_by(package=package['Package'])
1492 q = q.filter_by(version=package['Version'])
1493 q = q.filter_by(architecture=arch_id)
1498 for fullpath in fullpaths:
1499 (path, filename) = os.path.split(fullpath)
1501 if path.startswith( "./" ):
1504 filepath_id = get_or_set_contents_path_id(path, session)
1505 filename_id = get_or_set_contents_file_id(filename, session)
1507 pathcache[fullpath] = (filepath_id, filename_id)
1509 for fullpath, dat in pathcache.items():
1510 pca = PendingContentAssociation()
1511 pca.package = package['Package']
1512 pca.version = package['Version']
1513 pca.filepath_id = dat[0]
1514 pca.filename_id = dat[1]
1515 pca.architecture = arch_id
1518 # Only commit if we set up the session ourself
1526 except Exception, e:
1527 traceback.print_exc()
1529 # Only rollback if we set up the session ourself
1536 __all__.append('insert_pending_content_paths')
1538 ################################################################################
1540 class Priority(object):
1541 def __init__(self, *args, **kwargs):
1544 def __eq__(self, val):
1545 if isinstance(val, str):
1546 return (self.priority == val)
1547 # This signals to use the normal comparison operator
1548 return NotImplemented
1550 def __ne__(self, val):
1551 if isinstance(val, str):
1552 return (self.priority != val)
1553 # This signals to use the normal comparison operator
1554 return NotImplemented
1557 return '<Priority %s (%s)>' % (self.priority, self.priority_id)
1559 __all__.append('Priority')
1562 def get_priority(priority, session=None):
1564 Returns Priority object for given C{priority name}.
1566 @type priority: string
1567 @param priority: The name of the priority
1569 @type session: Session
1570 @param session: Optional SQLA session object (a temporary one will be
1571 generated if not supplied)
1574 @return: Priority object for the given priority
1577 q = session.query(Priority).filter_by(priority=priority)
1581 except NoResultFound:
1584 __all__.append('get_priority')
1587 def get_priorities(session=None):
1589 Returns dictionary of priority names -> id mappings
1591 @type session: Session
1592 @param session: Optional SQL session object (a temporary one will be
1593 generated if not supplied)
1596 @return: dictionary of priority names -> id mappings
1600 q = session.query(Priority)
1602 ret[x.priority] = x.priority_id
1606 __all__.append('get_priorities')
1608 ################################################################################
1610 class Queue(object):
1611 def __init__(self, *args, **kwargs):
1615 return '<Queue %s>' % self.queue_name
1617 def add_file_from_pool(self, poolfile):
1618 """Copies a file into the pool. Assumes that the PoolFile object is
1619 attached to the same SQLAlchemy session as the Queue object is.
1621 The caller is responsible for committing after calling this function."""
1622 poolfile_basename = poolfile.filename[poolfile.filename.rindex(os.sep)+1:]
1624 # Check if we have a file of this name or this ID already
1625 for f in self.queuefiles:
1626 if f.fileid is not None and f.fileid == poolfile.file_id or \
1627 f.poolfile.filename == poolfile_basename:
1628 # In this case, update the QueueFile entry so we
1629 # don't remove it too early
1630 f.lastused = datetime.now()
1631 DBConn().session().object_session(pf).add(f)
1634 # Prepare QueueFile object
1636 qf.queue_id = self.queue_id
1637 qf.lastused = datetime.now()
1640 targetpath = qf.fullpath
1641 queuepath = os.path.join(self.path, poolfile_basename)
1644 if self.copy_pool_files:
1645 # We need to copy instead of symlink
1647 utils.copy(targetfile, queuepath)
1648 # NULL in the fileid field implies a copy
1651 os.symlink(targetfile, queuepath)
1652 qf.fileid = poolfile.file_id
1656 # Get the same session as the PoolFile is using and add the qf to it
1657 DBConn().session().object_session(poolfile).add(qf)
1662 __all__.append('Queue')
1665 def get_queue(queuename, session=None):
1667 Returns Queue object for given C{queue name}, creating it if it does not
1670 @type queuename: string
1671 @param queuename: The name of the queue
1673 @type session: Session
1674 @param session: Optional SQLA session object (a temporary one will be
1675 generated if not supplied)
1678 @return: Queue object for the given queue
1681 q = session.query(Queue).filter_by(queue_name=queuename)
1685 except NoResultFound:
1688 __all__.append('get_queue')
1690 ################################################################################
1692 class QueueFile(object):
1693 def __init__(self, *args, **kwargs):
1697 return '<QueueFile %s (%s)>' % (self.filename, self.queue_id)
1699 __all__.append('QueueFile')
1701 ################################################################################
1703 class Section(object):
1704 def __init__(self, *args, **kwargs):
1707 def __eq__(self, val):
1708 if isinstance(val, str):
1709 return (self.section == val)
1710 # This signals to use the normal comparison operator
1711 return NotImplemented
1713 def __ne__(self, val):
1714 if isinstance(val, str):
1715 return (self.section != val)
1716 # This signals to use the normal comparison operator
1717 return NotImplemented
1720 return '<Section %s>' % self.section
1722 __all__.append('Section')
1725 def get_section(section, session=None):
1727 Returns Section object for given C{section name}.
1729 @type section: string
1730 @param section: The name of the section
1732 @type session: Session
1733 @param session: Optional SQLA session object (a temporary one will be
1734 generated if not supplied)
1737 @return: Section object for the given section name
1740 q = session.query(Section).filter_by(section=section)
1744 except NoResultFound:
1747 __all__.append('get_section')
1750 def get_sections(session=None):
1752 Returns dictionary of section names -> id mappings
1754 @type session: Session
1755 @param session: Optional SQL session object (a temporary one will be
1756 generated if not supplied)
1759 @return: dictionary of section names -> id mappings
1763 q = session.query(Section)
1765 ret[x.section] = x.section_id
1769 __all__.append('get_sections')
1771 ################################################################################
1773 class DBSource(object):
1774 def __init__(self, *args, **kwargs):
1778 return '<DBSource %s (%s)>' % (self.source, self.version)
1780 __all__.append('DBSource')
1783 def source_exists(source, source_version, suites = ["any"], session=None):
1785 Ensure that source exists somewhere in the archive for the binary
1786 upload being processed.
1787 1. exact match => 1.0-3
1788 2. bin-only NMU => 1.0-3+b1 , 1.0-3.1+b1
1790 @type package: string
1791 @param package: package source name
1793 @type source_version: string
1794 @param source_version: expected source version
1797 @param suites: list of suites to check in, default I{any}
1799 @type session: Session
1800 @param session: Optional SQLA session object (a temporary one will be
1801 generated if not supplied)
1804 @return: returns 1 if a source with expected version is found, otherwise 0
1811 for suite in suites:
1812 q = session.query(DBSource).filter_by(source=source)
1814 # source must exist in suite X, or in some other suite that's
1815 # mapped to X, recursively... silent-maps are counted too,
1816 # unreleased-maps aren't.
1817 maps = cnf.ValueList("SuiteMappings")[:]
1819 maps = [ m.split() for m in maps ]
1820 maps = [ (x[1], x[2]) for x in maps
1821 if x[0] == "map" or x[0] == "silent-map" ]
1824 if x[1] in s and x[0] not in s:
1827 q = q.join(SrcAssociation).join(Suite)
1828 q = q.filter(Suite.suite_name.in_(s))
1830 # Reduce the query results to a list of version numbers
1831 ql = [ j.version for j in q.all() ]
1834 if source_version in ql:
1838 from daklib.regexes import re_bin_only_nmu
1839 orig_source_version = re_bin_only_nmu.sub('', source_version)
1840 if orig_source_version in ql:
1843 # No source found so return not ok
1848 __all__.append('source_exists')
1851 def get_suites_source_in(source, session=None):
1853 Returns list of Suite objects which given C{source} name is in
1856 @param source: DBSource package name to search for
1859 @return: list of Suite objects for the given source
1862 return session.query(Suite).join(SrcAssociation).join(DBSource).filter_by(source=source).all()
1864 __all__.append('get_suites_source_in')
1867 def get_sources_from_name(source, version=None, dm_upload_allowed=None, session=None):
1869 Returns list of DBSource objects for given C{source} name and other parameters
1872 @param source: DBSource package name to search for
1874 @type source: str or None
1875 @param source: DBSource version name to search for or None if not applicable
1877 @type dm_upload_allowed: bool
1878 @param dm_upload_allowed: If None, no effect. If True or False, only
1879 return packages with that dm_upload_allowed setting
1881 @type session: Session
1882 @param session: Optional SQL session object (a temporary one will be
1883 generated if not supplied)
1886 @return: list of DBSource objects for the given name (may be empty)
1889 q = session.query(DBSource).filter_by(source=source)
1891 if version is not None:
1892 q = q.filter_by(version=version)
1894 if dm_upload_allowed is not None:
1895 q = q.filter_by(dm_upload_allowed=dm_upload_allowed)
1899 __all__.append('get_sources_from_name')
1902 def get_source_in_suite(source, suite, session=None):
1904 Returns list of DBSource objects for a combination of C{source} and C{suite}.
1906 - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
1907 - B{suite} - a suite name, eg. I{unstable}
1909 @type source: string
1910 @param source: source package name
1913 @param suite: the suite name
1916 @return: the version for I{source} in I{suite}
1920 q = session.query(SrcAssociation)
1921 q = q.join('source').filter_by(source=source)
1922 q = q.join('suite').filter_by(suite_name=suite)
1925 return q.one().source
1926 except NoResultFound:
1929 __all__.append('get_source_in_suite')
1931 ################################################################################
1934 def add_dsc_to_db(u, filename, session=None):
1935 entry = u.pkg.files[filename]
1938 source.source = u.pkg.dsc["source"]
1939 source.version = u.pkg.dsc["version"] # NB: not files[file]["version"], that has no epoch
1940 source.maintainer_id = get_or_set_maintainer(u.pkg.dsc["maintainer"], session).maintainer_id
1941 source.changedby_id = get_or_set_maintainer(u.pkg.changes["changed-by"], session).maintainer_id
1942 source.fingerprint_id = get_or_set_fingerprint(u.pkg.changes["fingerprint"], session).fingerprint_id
1943 source.install_date = datetime.now().date()
1945 dsc_component = entry["component"]
1946 dsc_location_id = entry["location id"]
1948 source.dm_upload_allowed = (u.pkg.dsc.get("dm-upload-allowed", '') == "yes")
1950 # Set up a new poolfile if necessary
1951 if not entry.has_key("files id") or not entry["files id"]:
1952 filename = entry["pool name"] + filename
1953 poolfile = add_poolfile(filename, entry, dsc_location_id, session)
1955 entry["files id"] = poolfile.file_id
1957 source.poolfile_id = entry["files id"]
1961 for suite_name in u.pkg.changes["distribution"].keys():
1962 sa = SrcAssociation()
1963 sa.source_id = source.source_id
1964 sa.suite_id = get_suite(suite_name).suite_id
1969 # Add the source files to the DB (files and dsc_files)
1971 dscfile.source_id = source.source_id
1972 dscfile.poolfile_id = entry["files id"]
1973 session.add(dscfile)
1975 for dsc_file, dentry in u.pkg.dsc_files.items():
1977 df.source_id = source.source_id
1979 # If the .orig tarball is already in the pool, it's
1980 # files id is stored in dsc_files by check_dsc().
1981 files_id = dentry.get("files id", None)
1983 # Find the entry in the files hash
1984 # TODO: Bail out here properly
1986 for f, e in u.pkg.files.items():
1991 if files_id is None:
1992 filename = dfentry["pool name"] + dsc_file
1994 (found, obj) = check_poolfile(filename, dentry["size"], dentry["md5sum"], dsc_location_id)
1995 # FIXME: needs to check for -1/-2 and or handle exception
1996 if found and obj is not None:
1997 files_id = obj.file_id
1999 # If still not found, add it
2000 if files_id is None:
2001 # HACK: Force sha1sum etc into dentry
2002 dentry["sha1sum"] = dfentry["sha1sum"]
2003 dentry["sha256sum"] = dfentry["sha256sum"]
2004 poolfile = add_poolfile(filename, dentry, dsc_location_id, session)
2005 files_id = poolfile.file_id
2007 df.poolfile_id = files_id
2012 # Add the src_uploaders to the DB
2013 uploader_ids = [source.maintainer_id]
2014 if u.pkg.dsc.has_key("uploaders"):
2015 for up in u.pkg.dsc["uploaders"].split(","):
2017 uploader_ids.append(get_or_set_maintainer(up, session).maintainer_id)
2020 for up in uploader_ids:
2021 if added_ids.has_key(up):
2022 utils.warn("Already saw uploader %s for source %s" % (up, source.source))
2028 su.maintainer_id = up
2029 su.source_id = source.source_id
2034 return dsc_component, dsc_location_id
2036 __all__.append('add_dsc_to_db')
2039 def add_deb_to_db(u, filename, session=None):
2041 Contrary to what you might expect, this routine deals with both
2042 debs and udebs. That info is in 'dbtype', whilst 'type' is
2043 'deb' for both of them
2046 entry = u.pkg.files[filename]
2049 bin.package = entry["package"]
2050 bin.version = entry["version"]
2051 bin.maintainer_id = get_or_set_maintainer(entry["maintainer"], session).maintainer_id
2052 bin.fingerprint_id = get_or_set_fingerprint(u.pkg.changes["fingerprint"], session).fingerprint_id
2053 bin.arch_id = get_architecture(entry["architecture"], session).arch_id
2054 bin.binarytype = entry["dbtype"]
2057 filename = entry["pool name"] + filename
2058 fullpath = os.path.join(cnf["Dir::Pool"], filename)
2059 if not entry.get("location id", None):
2060 entry["location id"] = get_location(cnf["Dir::Pool"], entry["component"], utils.where_am_i(), session).location_id
2062 if not entry.get("files id", None):
2063 poolfile = add_poolfile(filename, entry, entry["location id"], session)
2064 entry["files id"] = poolfile.file_id
2066 bin.poolfile_id = entry["files id"]
2069 bin_sources = get_sources_from_name(entry["source package"], entry["source version"], session=session)
2070 if len(bin_sources) != 1:
2071 raise NoSourceFieldError, "Unable to find a unique source id for %s (%s), %s, file %s, type %s, signed by %s" % \
2072 (bin.package, bin.version, bin.architecture.arch_string,
2073 filename, bin.binarytype, u.pkg.changes["fingerprint"])
2075 bin.source_id = bin_sources[0].source_id
2077 # Add and flush object so it has an ID
2081 # Add BinAssociations
2082 for suite_name in u.pkg.changes["distribution"].keys():
2083 ba = BinAssociation()
2084 ba.binary_id = bin.binary_id
2085 ba.suite_id = get_suite(suite_name).suite_id
2090 # Deal with contents - disabled for now
2091 #contents = copy_temporary_contents(bin.package, bin.version, bin.architecture.arch_string, os.path.basename(filename), None, session)
2093 # print "REJECT\nCould not determine contents of package %s" % bin.package
2094 # session.rollback()
2095 # raise MissingContents, "No contents stored for package %s, and couldn't determine contents of %s" % (bin.package, filename)
2097 __all__.append('add_deb_to_db')
2099 ################################################################################
2101 class SourceACL(object):
2102 def __init__(self, *args, **kwargs):
2106 return '<SourceACL %s>' % self.source_acl_id
2108 __all__.append('SourceACL')
2110 ################################################################################
2112 class SrcAssociation(object):
2113 def __init__(self, *args, **kwargs):
2117 return '<SrcAssociation %s (%s, %s)>' % (self.sa_id, self.source, self.suite)
2119 __all__.append('SrcAssociation')
2121 ################################################################################
2123 class SrcFormat(object):
2124 def __init__(self, *args, **kwargs):
2128 return '<SrcFormat %s>' % (self.format_name)
2130 __all__.append('SrcFormat')
2132 ################################################################################
2134 class SrcUploader(object):
2135 def __init__(self, *args, **kwargs):
2139 return '<SrcUploader %s>' % self.uploader_id
2141 __all__.append('SrcUploader')
2143 ################################################################################
2145 SUITE_FIELDS = [ ('SuiteName', 'suite_name'),
2146 ('SuiteID', 'suite_id'),
2147 ('Version', 'version'),
2148 ('Origin', 'origin'),
2150 ('Description', 'description'),
2151 ('Untouchable', 'untouchable'),
2152 ('Announce', 'announce'),
2153 ('Codename', 'codename'),
2154 ('OverrideCodename', 'overridecodename'),
2155 ('ValidTime', 'validtime'),
2156 ('Priority', 'priority'),
2157 ('NotAutomatic', 'notautomatic'),
2158 ('CopyChanges', 'copychanges'),
2159 ('CopyDotDak', 'copydotdak'),
2160 ('CommentsDir', 'commentsdir'),
2161 ('OverrideSuite', 'overridesuite'),
2162 ('ChangelogBase', 'changelogbase')]
2165 class Suite(object):
2166 def __init__(self, *args, **kwargs):
2170 return '<Suite %s>' % self.suite_name
2172 def __eq__(self, val):
2173 if isinstance(val, str):
2174 return (self.suite_name == val)
2175 # This signals to use the normal comparison operator
2176 return NotImplemented
2178 def __ne__(self, val):
2179 if isinstance(val, str):
2180 return (self.suite_name != val)
2181 # This signals to use the normal comparison operator
2182 return NotImplemented
2186 for disp, field in SUITE_FIELDS:
2187 val = getattr(self, field, None)
2189 ret.append("%s: %s" % (disp, val))
2191 return "\n".join(ret)
2193 __all__.append('Suite')
2196 def get_suite_architecture(suite, architecture, session=None):
2198 Returns a SuiteArchitecture object given C{suite} and ${arch} or None if it
2202 @param suite: Suite name to search for
2204 @type architecture: str
2205 @param architecture: Architecture name to search for
2207 @type session: Session
2208 @param session: Optional SQL session object (a temporary one will be
2209 generated if not supplied)
2211 @rtype: SuiteArchitecture
2212 @return: the SuiteArchitecture object or None
2215 q = session.query(SuiteArchitecture)
2216 q = q.join(Architecture).filter_by(arch_string=architecture)
2217 q = q.join(Suite).filter_by(suite_name=suite)
2221 except NoResultFound:
2224 __all__.append('get_suite_architecture')
2227 def get_suite(suite, session=None):
2229 Returns Suite object for given C{suite name}.
2232 @param suite: The name of the suite
2234 @type session: Session
2235 @param session: Optional SQLA session object (a temporary one will be
2236 generated if not supplied)
2239 @return: Suite object for the requested suite name (None if not present)
2242 q = session.query(Suite).filter_by(suite_name=suite)
2246 except NoResultFound:
2249 __all__.append('get_suite')
2251 ################################################################################
2253 class SuiteArchitecture(object):
2254 def __init__(self, *args, **kwargs):
2258 return '<SuiteArchitecture (%s, %s)>' % (self.suite_id, self.arch_id)
2260 __all__.append('SuiteArchitecture')
2263 def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None):
2265 Returns list of Architecture objects for given C{suite} name
2268 @param source: Suite name to search for
2270 @type skipsrc: boolean
2271 @param skipsrc: Whether to skip returning the 'source' architecture entry
2274 @type skipall: boolean
2275 @param skipall: Whether to skip returning the 'all' architecture entry
2278 @type session: Session
2279 @param session: Optional SQL session object (a temporary one will be
2280 generated if not supplied)
2283 @return: list of Architecture objects for the given name (may be empty)
2286 q = session.query(Architecture)
2287 q = q.join(SuiteArchitecture)
2288 q = q.join(Suite).filter_by(suite_name=suite)
2291 q = q.filter(Architecture.arch_string != 'source')
2294 q = q.filter(Architecture.arch_string != 'all')
2296 q = q.order_by('arch_string')
2300 __all__.append('get_suite_architectures')
2302 ################################################################################
2304 class SuiteSrcFormat(object):
2305 def __init__(self, *args, **kwargs):
2309 return '<SuiteSrcFormat (%s, %s)>' % (self.suite_id, self.src_format_id)
2311 __all__.append('SuiteSrcFormat')
2314 def get_suite_src_formats(suite, session=None):
2316 Returns list of allowed SrcFormat for C{suite}.
2319 @param suite: Suite name to search for
2321 @type session: Session
2322 @param session: Optional SQL session object (a temporary one will be
2323 generated if not supplied)
2326 @return: the list of allowed source formats for I{suite}
2329 q = session.query(SrcFormat)
2330 q = q.join(SuiteSrcFormat)
2331 q = q.join(Suite).filter_by(suite_name=suite)
2332 q = q.order_by('format_name')
2336 __all__.append('get_suite_src_formats')
2338 ################################################################################
2341 def __init__(self, *args, **kwargs):
2344 def __eq__(self, val):
2345 if isinstance(val, str):
2346 return (self.uid == val)
2347 # This signals to use the normal comparison operator
2348 return NotImplemented
2350 def __ne__(self, val):
2351 if isinstance(val, str):
2352 return (self.uid != val)
2353 # This signals to use the normal comparison operator
2354 return NotImplemented
2357 return '<Uid %s (%s)>' % (self.uid, self.name)
2359 __all__.append('Uid')
2362 def add_database_user(uidname, session=None):
2364 Adds a database user
2366 @type uidname: string
2367 @param uidname: The uid of the user to add
2369 @type session: SQLAlchemy
2370 @param session: Optional SQL session object (a temporary one will be
2371 generated if not supplied). If not passed, a commit will be performed at
2372 the end of the function, otherwise the caller is responsible for commiting.
2375 @return: the uid object for the given uidname
2378 session.execute("CREATE USER :uid", {'uid': uidname})
2379 session.commit_or_flush()
2381 __all__.append('add_database_user')
2384 def get_or_set_uid(uidname, session=None):
2386 Returns uid object for given uidname.
2388 If no matching uidname is found, a row is inserted.
2390 @type uidname: string
2391 @param uidname: The uid to add
2393 @type session: SQLAlchemy
2394 @param session: Optional SQL session object (a temporary one will be
2395 generated if not supplied). If not passed, a commit will be performed at
2396 the end of the function, otherwise the caller is responsible for commiting.
2399 @return: the uid object for the given uidname
2402 q = session.query(Uid).filter_by(uid=uidname)
2406 except NoResultFound:
2410 session.commit_or_flush()
2415 __all__.append('get_or_set_uid')
2418 def get_uid_from_fingerprint(fpr, session=None):
2419 q = session.query(Uid)
2420 q = q.join(Fingerprint).filter_by(fingerprint=fpr)
2424 except NoResultFound:
2427 __all__.append('get_uid_from_fingerprint')
2429 ################################################################################
2431 class UploadBlock(object):
2432 def __init__(self, *args, **kwargs):
2436 return '<UploadBlock %s (%s)>' % (self.source, self.upload_block_id)
2438 __all__.append('UploadBlock')
2440 ################################################################################
2442 class DBConn(Singleton):
2444 database module init.
2446 def __init__(self, *args, **kwargs):
2447 super(DBConn, self).__init__(*args, **kwargs)
2449 def _startup(self, *args, **kwargs):
2451 if kwargs.has_key('debug'):
2455 def __setuptables(self):
2456 self.tbl_architecture = Table('architecture', self.db_meta, autoload=True)
2457 self.tbl_archive = Table('archive', self.db_meta, autoload=True)
2458 self.tbl_bin_associations = Table('bin_associations', self.db_meta, autoload=True)
2459 self.tbl_binaries = Table('binaries', self.db_meta, autoload=True)
2460 self.tbl_binary_acl = Table('binary_acl', self.db_meta, autoload=True)
2461 self.tbl_binary_acl_map = Table('binary_acl_map', self.db_meta, autoload=True)
2462 self.tbl_component = Table('component', self.db_meta, autoload=True)
2463 self.tbl_config = Table('config', self.db_meta, autoload=True)
2464 self.tbl_content_associations = Table('content_associations', self.db_meta, autoload=True)
2465 self.tbl_content_file_names = Table('content_file_names', self.db_meta, autoload=True)
2466 self.tbl_content_file_paths = Table('content_file_paths', self.db_meta, autoload=True)
2467 self.tbl_changes_pending_files = Table('changes_pending_files', self.db_meta, autoload=True)
2468 self.tbl_changes_pool_files = Table('changes_pool_files', self.db_meta, autoload=True)
2469 self.tbl_dsc_files = Table('dsc_files', self.db_meta, autoload=True)
2470 self.tbl_files = Table('files', self.db_meta, autoload=True)
2471 self.tbl_fingerprint = Table('fingerprint', self.db_meta, autoload=True)
2472 self.tbl_keyrings = Table('keyrings', self.db_meta, autoload=True)
2473 self.tbl_known_changes = Table('known_changes', self.db_meta, autoload=True)
2474 self.tbl_keyring_acl_map = Table('keyring_acl_map', self.db_meta, autoload=True)
2475 self.tbl_location = Table('location', self.db_meta, autoload=True)
2476 self.tbl_maintainer = Table('maintainer', self.db_meta, autoload=True)
2477 self.tbl_new_comments = Table('new_comments', self.db_meta, autoload=True)
2478 self.tbl_override = Table('override', self.db_meta, autoload=True)
2479 self.tbl_override_type = Table('override_type', self.db_meta, autoload=True)
2480 self.tbl_pending_content_associations = Table('pending_content_associations', self.db_meta, autoload=True)
2481 self.tbl_priority = Table('priority', self.db_meta, autoload=True)
2482 self.tbl_queue = Table('queue', self.db_meta, autoload=True)
2483 self.tbl_queue_files = Table('queue_files', self.db_meta, autoload=True)
2484 self.tbl_section = Table('section', self.db_meta, autoload=True)
2485 self.tbl_source = Table('source', self.db_meta, autoload=True)
2486 self.tbl_source_acl = Table('source_acl', self.db_meta, autoload=True)
2487 self.tbl_src_associations = Table('src_associations', self.db_meta, autoload=True)
2488 self.tbl_src_format = Table('src_format', self.db_meta, autoload=True)
2489 self.tbl_src_uploaders = Table('src_uploaders', self.db_meta, autoload=True)
2490 self.tbl_suite = Table('suite', self.db_meta, autoload=True)
2491 self.tbl_suite_architectures = Table('suite_architectures', self.db_meta, autoload=True)
2492 self.tbl_suite_src_formats = Table('suite_src_formats', self.db_meta, autoload=True)
2493 self.tbl_suite_queue_copy = Table('suite_queue_copy', self.db_meta, autoload=True)
2494 self.tbl_uid = Table('uid', self.db_meta, autoload=True)
2495 self.tbl_upload_blocks = Table('upload_blocks', self.db_meta, autoload=True)
2497 def __setupmappers(self):
2498 mapper(Architecture, self.tbl_architecture,
2499 properties = dict(arch_id = self.tbl_architecture.c.id))
2501 mapper(Archive, self.tbl_archive,
2502 properties = dict(archive_id = self.tbl_archive.c.id,
2503 archive_name = self.tbl_archive.c.name))
2505 mapper(BinAssociation, self.tbl_bin_associations,
2506 properties = dict(ba_id = self.tbl_bin_associations.c.id,
2507 suite_id = self.tbl_bin_associations.c.suite,
2508 suite = relation(Suite),
2509 binary_id = self.tbl_bin_associations.c.bin,
2510 binary = relation(DBBinary)))
2513 mapper(DBBinary, self.tbl_binaries,
2514 properties = dict(binary_id = self.tbl_binaries.c.id,
2515 package = self.tbl_binaries.c.package,
2516 version = self.tbl_binaries.c.version,
2517 maintainer_id = self.tbl_binaries.c.maintainer,
2518 maintainer = relation(Maintainer),
2519 source_id = self.tbl_binaries.c.source,
2520 source = relation(DBSource),
2521 arch_id = self.tbl_binaries.c.architecture,
2522 architecture = relation(Architecture),
2523 poolfile_id = self.tbl_binaries.c.file,
2524 poolfile = relation(PoolFile),
2525 binarytype = self.tbl_binaries.c.type,
2526 fingerprint_id = self.tbl_binaries.c.sig_fpr,
2527 fingerprint = relation(Fingerprint),
2528 install_date = self.tbl_binaries.c.install_date,
2529 binassociations = relation(BinAssociation,
2530 primaryjoin=(self.tbl_binaries.c.id==self.tbl_bin_associations.c.bin))))
2532 mapper(BinaryACL, self.tbl_binary_acl,
2533 properties = dict(binary_acl_id = self.tbl_binary_acl.c.id))
2535 mapper(BinaryACLMap, self.tbl_binary_acl_map,
2536 properties = dict(binary_acl_map_id = self.tbl_binary_acl_map.c.id,
2537 fingerprint = relation(Fingerprint, backref="binary_acl_map"),
2538 architecture = relation(Architecture)))
2540 mapper(Component, self.tbl_component,
2541 properties = dict(component_id = self.tbl_component.c.id,
2542 component_name = self.tbl_component.c.name))
2544 mapper(DBConfig, self.tbl_config,
2545 properties = dict(config_id = self.tbl_config.c.id))
2547 mapper(DSCFile, self.tbl_dsc_files,
2548 properties = dict(dscfile_id = self.tbl_dsc_files.c.id,
2549 source_id = self.tbl_dsc_files.c.source,
2550 source = relation(DBSource),
2551 poolfile_id = self.tbl_dsc_files.c.file,
2552 poolfile = relation(PoolFile)))
2554 mapper(PoolFile, self.tbl_files,
2555 properties = dict(file_id = self.tbl_files.c.id,
2556 filesize = self.tbl_files.c.size,
2557 location_id = self.tbl_files.c.location,
2558 location = relation(Location)))
2560 mapper(Fingerprint, self.tbl_fingerprint,
2561 properties = dict(fingerprint_id = self.tbl_fingerprint.c.id,
2562 uid_id = self.tbl_fingerprint.c.uid,
2563 uid = relation(Uid),
2564 keyring_id = self.tbl_fingerprint.c.keyring,
2565 keyring = relation(Keyring),
2566 source_acl = relation(SourceACL),
2567 binary_acl = relation(BinaryACL)))
2569 mapper(Keyring, self.tbl_keyrings,
2570 properties = dict(keyring_name = self.tbl_keyrings.c.name,
2571 keyring_id = self.tbl_keyrings.c.id))
2573 mapper(KnownChange, self.tbl_known_changes,
2574 properties = dict(known_change_id = self.tbl_known_changes.c.id,
2575 poolfiles = relation(PoolFile,
2576 secondary=self.tbl_changes_pool_files,
2577 backref="changeslinks"),
2578 files = relation(KnownChangePendingFile, backref="changesfile")))
2580 mapper(KnownChangePendingFile, self.tbl_changes_pending_files,
2581 properties = dict(known_change_pending_file_id = self.tbl_changes_pending_files.id))
2583 mapper(KeyringACLMap, self.tbl_keyring_acl_map,
2584 properties = dict(keyring_acl_map_id = self.tbl_keyring_acl_map.c.id,
2585 keyring = relation(Keyring, backref="keyring_acl_map"),
2586 architecture = relation(Architecture)))
2588 mapper(Location, self.tbl_location,
2589 properties = dict(location_id = self.tbl_location.c.id,
2590 component_id = self.tbl_location.c.component,
2591 component = relation(Component),
2592 archive_id = self.tbl_location.c.archive,
2593 archive = relation(Archive),
2594 archive_type = self.tbl_location.c.type))
2596 mapper(Maintainer, self.tbl_maintainer,
2597 properties = dict(maintainer_id = self.tbl_maintainer.c.id))
2599 mapper(NewComment, self.tbl_new_comments,
2600 properties = dict(comment_id = self.tbl_new_comments.c.id))
2602 mapper(Override, self.tbl_override,
2603 properties = dict(suite_id = self.tbl_override.c.suite,
2604 suite = relation(Suite),
2605 component_id = self.tbl_override.c.component,
2606 component = relation(Component),
2607 priority_id = self.tbl_override.c.priority,
2608 priority = relation(Priority),
2609 section_id = self.tbl_override.c.section,
2610 section = relation(Section),
2611 overridetype_id = self.tbl_override.c.type,
2612 overridetype = relation(OverrideType)))
2614 mapper(OverrideType, self.tbl_override_type,
2615 properties = dict(overridetype = self.tbl_override_type.c.type,
2616 overridetype_id = self.tbl_override_type.c.id))
2618 mapper(Priority, self.tbl_priority,
2619 properties = dict(priority_id = self.tbl_priority.c.id))
2621 mapper(Queue, self.tbl_queue,
2622 properties = dict(queue_id = self.tbl_queue.c.id))
2624 mapper(QueueFile, self.tbl_queue_files,
2625 properties = dict(queue = relation(Queue, backref='queuefiles'),
2626 poolfile = relation(PoolFile, backref='queueinstances')))
2628 mapper(Section, self.tbl_section,
2629 properties = dict(section_id = self.tbl_section.c.id))
2631 mapper(DBSource, self.tbl_source,
2632 properties = dict(source_id = self.tbl_source.c.id,
2633 version = self.tbl_source.c.version,
2634 maintainer_id = self.tbl_source.c.maintainer,
2635 maintainer = relation(Maintainer,
2636 primaryjoin=(self.tbl_source.c.maintainer==self.tbl_maintainer.c.id)),
2637 poolfile_id = self.tbl_source.c.file,
2638 poolfile = relation(PoolFile),
2639 fingerprint_id = self.tbl_source.c.sig_fpr,
2640 fingerprint = relation(Fingerprint),
2641 changedby_id = self.tbl_source.c.changedby,
2642 changedby = relation(Maintainer,
2643 primaryjoin=(self.tbl_source.c.changedby==self.tbl_maintainer.c.id)),
2644 srcfiles = relation(DSCFile,
2645 primaryjoin=(self.tbl_source.c.id==self.tbl_dsc_files.c.source)),
2646 srcassociations = relation(SrcAssociation,
2647 primaryjoin=(self.tbl_source.c.id==self.tbl_src_associations.c.source)),
2648 srcuploaders = relation(SrcUploader)))
2650 mapper(SourceACL, self.tbl_source_acl,
2651 properties = dict(source_acl_id = self.tbl_source_acl.c.id))
2653 mapper(SrcAssociation, self.tbl_src_associations,
2654 properties = dict(sa_id = self.tbl_src_associations.c.id,
2655 suite_id = self.tbl_src_associations.c.suite,
2656 suite = relation(Suite),
2657 source_id = self.tbl_src_associations.c.source,
2658 source = relation(DBSource)))
2660 mapper(SrcFormat, self.tbl_src_format,
2661 properties = dict(src_format_id = self.tbl_src_format.c.id,
2662 format_name = self.tbl_src_format.c.format_name))
2664 mapper(SrcUploader, self.tbl_src_uploaders,
2665 properties = dict(uploader_id = self.tbl_src_uploaders.c.id,
2666 source_id = self.tbl_src_uploaders.c.source,
2667 source = relation(DBSource,
2668 primaryjoin=(self.tbl_src_uploaders.c.source==self.tbl_source.c.id)),
2669 maintainer_id = self.tbl_src_uploaders.c.maintainer,
2670 maintainer = relation(Maintainer,
2671 primaryjoin=(self.tbl_src_uploaders.c.maintainer==self.tbl_maintainer.c.id))))
2673 mapper(Suite, self.tbl_suite,
2674 properties = dict(suite_id = self.tbl_suite.c.id,
2675 policy_queue = relation(Queue),
2676 copy_queues = relation(Queue, secondary=self.tbl_suite_queue_copy)))
2678 mapper(SuiteArchitecture, self.tbl_suite_architectures,
2679 properties = dict(suite_id = self.tbl_suite_architectures.c.suite,
2680 suite = relation(Suite, backref='suitearchitectures'),
2681 arch_id = self.tbl_suite_architectures.c.architecture,
2682 architecture = relation(Architecture)))
2684 mapper(SuiteSrcFormat, self.tbl_suite_src_formats,
2685 properties = dict(suite_id = self.tbl_suite_src_formats.c.suite,
2686 suite = relation(Suite, backref='suitesrcformats'),
2687 src_format_id = self.tbl_suite_src_formats.c.src_format,
2688 src_format = relation(SrcFormat)))
2690 mapper(Uid, self.tbl_uid,
2691 properties = dict(uid_id = self.tbl_uid.c.id,
2692 fingerprint = relation(Fingerprint)))
2694 mapper(UploadBlock, self.tbl_upload_blocks,
2695 properties = dict(upload_block_id = self.tbl_upload_blocks.c.id,
2696 fingerprint = relation(Fingerprint, backref="uploadblocks"),
2697 uid = relation(Uid, backref="uploadblocks")))
2699 ## Connection functions
2700 def __createconn(self):
2701 from config import Config
2705 connstr = "postgres://%s" % cnf["DB::Host"]
2706 if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
2707 connstr += ":%s" % cnf["DB::Port"]
2708 connstr += "/%s" % cnf["DB::Name"]
2711 connstr = "postgres:///%s" % cnf["DB::Name"]
2712 if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
2713 connstr += "?port=%s" % cnf["DB::Port"]
2715 self.db_pg = create_engine(connstr, echo=self.debug)
2716 self.db_meta = MetaData()
2717 self.db_meta.bind = self.db_pg
2718 self.db_smaker = sessionmaker(bind=self.db_pg,
2722 self.__setuptables()
2723 self.__setupmappers()
2726 return self.db_smaker()
2728 __all__.append('DBConn')