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, 2010 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, timedelta
41 from errno import ENOENT
42 from tempfile import mkstemp, mkdtemp
44 from inspect import getargspec
47 from sqlalchemy import create_engine, Table, MetaData
48 from sqlalchemy.orm import sessionmaker, mapper, relation
49 from sqlalchemy import types as sqltypes
51 # Don't remove this, we re-export the exceptions to scripts which import us
52 from sqlalchemy.exc import *
53 from sqlalchemy.orm.exc import NoResultFound
55 from config import Config
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):
65 Support the debversion type
68 def get_col_spec(self):
71 sa_major_version = sqlalchemy.__version__[0:3]
72 if sa_major_version == "0.5":
73 from sqlalchemy.databases import postgres
74 postgres.ischema_names['debversion'] = DebVersion
76 raise Exception("dak isn't ported to SQLA versions != 0.5 yet. See daklib/dbconn.py")
78 ################################################################################
80 __all__ = ['IntegrityError', 'SQLAlchemyError']
82 ################################################################################
84 def session_wrapper(fn):
86 Wrapper around common ".., session=None):" handling. If the wrapped
87 function is called without passing 'session', we create a local one
88 and destroy it when the function ends.
90 Also attaches a commit_or_flush method to the session; if we created a
91 local session, this is a synonym for session.commit(), otherwise it is a
92 synonym for session.flush().
95 def wrapped(*args, **kwargs):
96 private_transaction = False
98 # Find the session object
99 session = kwargs.get('session')
102 if len(args) <= len(getargspec(fn)[0]) - 1:
103 # No session specified as last argument or in kwargs
104 private_transaction = True
105 session = kwargs['session'] = DBConn().session()
107 # Session is last argument in args
111 session = args[-1] = DBConn().session()
112 private_transaction = True
114 if private_transaction:
115 session.commit_or_flush = session.commit
117 session.commit_or_flush = session.flush
120 return fn(*args, **kwargs)
122 if private_transaction:
123 # We created a session; close it.
126 wrapped.__doc__ = fn.__doc__
127 wrapped.func_name = fn.func_name
131 __all__.append('session_wrapper')
133 ################################################################################
135 class Architecture(object):
136 def __init__(self, *args, **kwargs):
139 def __eq__(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
145 def __ne__(self, val):
146 if isinstance(val, str):
147 return (self.arch_string != val)
148 # This signals to use the normal comparison operator
149 return NotImplemented
152 return '<Architecture %s>' % self.arch_string
154 __all__.append('Architecture')
157 def get_architecture(architecture, session=None):
159 Returns database id for given C{architecture}.
161 @type architecture: string
162 @param architecture: The name of the architecture
164 @type session: Session
165 @param session: Optional SQLA session object (a temporary one will be
166 generated if not supplied)
169 @return: Architecture object for the given arch (None if not present)
172 q = session.query(Architecture).filter_by(arch_string=architecture)
176 except NoResultFound:
179 __all__.append('get_architecture')
182 def get_architecture_suites(architecture, session=None):
184 Returns list of Suite objects for given C{architecture} name
186 @type architecture: str
187 @param architecture: Architecture name to search for
189 @type session: Session
190 @param session: Optional SQL session object (a temporary one will be
191 generated if not supplied)
194 @return: list of Suite objects for the given name (may be empty)
197 q = session.query(Suite)
198 q = q.join(SuiteArchitecture)
199 q = q.join(Architecture).filter_by(arch_string=architecture).order_by('suite_name')
205 __all__.append('get_architecture_suites')
207 ################################################################################
209 class Archive(object):
210 def __init__(self, *args, **kwargs):
214 return '<Archive %s>' % self.archive_name
216 __all__.append('Archive')
219 def get_archive(archive, session=None):
221 returns database id for given C{archive}.
223 @type archive: string
224 @param archive: the name of the arhive
226 @type session: Session
227 @param session: Optional SQLA session object (a temporary one will be
228 generated if not supplied)
231 @return: Archive object for the given name (None if not present)
234 archive = archive.lower()
236 q = session.query(Archive).filter_by(archive_name=archive)
240 except NoResultFound:
243 __all__.append('get_archive')
245 ################################################################################
247 class BinAssociation(object):
248 def __init__(self, *args, **kwargs):
252 return '<BinAssociation %s (%s, %s)>' % (self.ba_id, self.binary, self.suite)
254 __all__.append('BinAssociation')
256 ################################################################################
258 class BinContents(object):
259 def __init__(self, *args, **kwargs):
263 return '<BinContents (%s, %s)>' % (self.binary, self.filename)
265 __all__.append('BinContents')
267 ################################################################################
269 class DBBinary(object):
270 def __init__(self, *args, **kwargs):
274 return '<DBBinary %s (%s, %s)>' % (self.package, self.version, self.architecture)
276 __all__.append('DBBinary')
279 def get_suites_binary_in(package, session=None):
281 Returns list of Suite objects which given C{package} name is in
284 @param package: DBBinary package name to search for
287 @return: list of Suite objects for the given package
290 return session.query(Suite).join(BinAssociation).join(DBBinary).filter_by(package=package).all()
292 __all__.append('get_suites_binary_in')
295 def get_binary_from_id(binary_id, session=None):
297 Returns DBBinary object for given C{id}
300 @param binary_id: Id of the required binary
302 @type session: Session
303 @param session: Optional SQLA session object (a temporary one will be
304 generated if not supplied)
307 @return: DBBinary object for the given binary (None if not present)
310 q = session.query(DBBinary).filter_by(binary_id=binary_id)
314 except NoResultFound:
317 __all__.append('get_binary_from_id')
320 def get_binaries_from_name(package, version=None, architecture=None, session=None):
322 Returns list of DBBinary objects for given C{package} name
325 @param package: DBBinary package name to search for
327 @type version: str or None
328 @param version: Version to search for (or None)
330 @type architecture: str, list or None
331 @param architecture: Architectures to limit to (or None if no limit)
333 @type session: Session
334 @param session: Optional SQL session object (a temporary one will be
335 generated if not supplied)
338 @return: list of DBBinary objects for the given name (may be empty)
341 q = session.query(DBBinary).filter_by(package=package)
343 if version is not None:
344 q = q.filter_by(version=version)
346 if architecture is not None:
347 if not isinstance(architecture, list):
348 architecture = [architecture]
349 q = q.join(Architecture).filter(Architecture.arch_string.in_(architecture))
355 __all__.append('get_binaries_from_name')
358 def get_binaries_from_source_id(source_id, session=None):
360 Returns list of DBBinary objects for given C{source_id}
363 @param source_id: source_id to search for
365 @type session: Session
366 @param session: Optional SQL session object (a temporary one will be
367 generated if not supplied)
370 @return: list of DBBinary objects for the given name (may be empty)
373 return session.query(DBBinary).filter_by(source_id=source_id).all()
375 __all__.append('get_binaries_from_source_id')
378 def get_binary_from_name_suite(package, suitename, session=None):
379 ### For dak examine-package
380 ### XXX: Doesn't use object API yet
382 sql = """SELECT DISTINCT(b.package), b.version, c.name, su.suite_name
383 FROM binaries b, files fi, location l, component c, bin_associations ba, suite su
384 WHERE b.package='%(package)s'
386 AND fi.location = l.id
387 AND l.component = c.id
390 AND su.suite_name %(suitename)s
391 ORDER BY b.version DESC"""
393 return session.execute(sql % {'package': package, 'suitename': suitename})
395 __all__.append('get_binary_from_name_suite')
398 def get_binary_components(package, suitename, arch, session=None):
399 # Check for packages that have moved from one component to another
400 query = """SELECT c.name FROM binaries b, bin_associations ba, suite s, location l, component c, architecture a, files f
401 WHERE b.package=:package AND s.suite_name=:suitename
402 AND (a.arch_string = :arch OR a.arch_string = 'all')
403 AND ba.bin = b.id AND ba.suite = s.id AND b.architecture = a.id
404 AND f.location = l.id
405 AND l.component = c.id
408 vals = {'package': package, 'suitename': suitename, 'arch': arch}
410 return session.execute(query, vals)
412 __all__.append('get_binary_components')
414 ################################################################################
416 class BinaryACL(object):
417 def __init__(self, *args, **kwargs):
421 return '<BinaryACL %s>' % self.binary_acl_id
423 __all__.append('BinaryACL')
425 ################################################################################
427 class BinaryACLMap(object):
428 def __init__(self, *args, **kwargs):
432 return '<BinaryACLMap %s>' % self.binary_acl_map_id
434 __all__.append('BinaryACLMap')
436 ################################################################################
441 ArchiveDir "%(archivepath)s";
442 OverrideDir "/srv/ftp.debian.org/scripts/override/";
443 CacheDir "/srv/ftp.debian.org/database/";
448 Packages::Compress ". bzip2 gzip";
449 Sources::Compress ". bzip2 gzip";
454 bindirectory "incoming"
459 BinOverride "override.sid.all3";
460 BinCacheDB "packages-accepted.db";
462 FileList "%(filelist)s";
465 Packages::Extensions ".deb .udeb";
468 bindirectory "incoming/"
471 BinOverride "override.sid.all3";
472 SrcOverride "override.sid.all3.src";
473 FileList "%(filelist)s";
477 class BuildQueue(object):
478 def __init__(self, *args, **kwargs):
482 return '<BuildQueue %s>' % self.queue_name
484 def write_metadata(self, starttime, force=False):
485 # Do we write out metafiles?
486 if not (force or self.generate_metadata):
489 session = DBConn().session().object_session(self)
491 fl_fd = fl_name = ac_fd = ac_name = None
493 arches = " ".join([ a.arch_string for a in session.query(Architecture).all() if a.arch_string != 'source' ])
494 startdir = os.getcwd()
497 # Grab files we want to include
498 newer = session.query(BuildQueueFile).filter_by(build_queue_id = self.queue_id).filter(BuildQueueFile.lastused + timedelta(seconds=self.stay_of_execution) > starttime).all()
499 # Write file list with newer files
500 (fl_fd, fl_name) = mkstemp()
502 os.write(fl_fd, '%s\n' % n.fullpath)
505 # Write minimal apt.conf
506 # TODO: Remove hardcoding from template
507 (ac_fd, ac_name) = mkstemp()
508 os.write(ac_fd, MINIMAL_APT_CONF % {'archivepath': self.path,
509 'filelist': fl_name})
512 # Run apt-ftparchive generate
513 os.chdir(os.path.dirname(ac_name))
514 os.system('apt-ftparchive -qq -o APT::FTPArchive::Contents=off generate %s' % os.path.basename(ac_name))
516 # Run apt-ftparchive release
517 # TODO: Eww - fix this
518 bname = os.path.basename(self.path)
522 # We have to remove the Release file otherwise it'll be included in the
525 os.unlink(os.path.join(bname, 'Release'))
529 os.system("""apt-ftparchive -qq -o APT::FTPArchive::Release::Origin="%s" -o APT::FTPArchive::Release::Label="%s" -o APT::FTPArchive::Release::Description="%s" -o APT::FTPArchive::Release::Architectures="%s" release %s > Release""" % (self.origin, self.label, self.releasedescription, arches, bname))
534 keyring = "--secret-keyring \"%s\"" % cnf["Dinstall::SigningKeyring"]
535 if cnf.has_key("Dinstall::SigningPubKeyring"):
536 keyring += " --keyring \"%s\"" % cnf["Dinstall::SigningPubKeyring"]
538 os.system("gpg %s --no-options --batch --no-tty --armour --default-key %s --detach-sign -o Release.gpg Release""" % (keyring, self.signingkey))
540 # Move the files if we got this far
541 os.rename('Release', os.path.join(bname, 'Release'))
543 os.rename('Release.gpg', os.path.join(bname, 'Release.gpg'))
545 # Clean up any left behind files
572 def clean_and_update(self, starttime, Logger, dryrun=False):
573 """WARNING: This routine commits for you"""
574 session = DBConn().session().object_session(self)
576 if self.generate_metadata and not dryrun:
577 self.write_metadata(starttime)
579 # Grab files older than our execution time
580 older = session.query(BuildQueueFile).filter_by(build_queue_id = self.queue_id).filter(BuildQueueFile.lastused + timedelta(seconds=self.stay_of_execution) <= starttime).all()
586 Logger.log(["I: Would have removed %s from the queue" % o.fullpath])
588 Logger.log(["I: Removing %s from the queue" % o.fullpath])
589 os.unlink(o.fullpath)
592 # If it wasn't there, don't worry
593 if e.errno == ENOENT:
596 # TODO: Replace with proper logging call
597 Logger.log(["E: Could not remove %s" % o.fullpath])
604 for f in os.listdir(self.path):
605 if f.startswith('Packages') or f.startswith('Source') or f.startswith('Release'):
609 r = session.query(BuildQueueFile).filter_by(build_queue_id = self.queue_id).filter_by(filename = f).one()
610 except NoResultFound:
611 fp = os.path.join(self.path, f)
613 Logger.log(["I: Would remove unused link %s" % fp])
615 Logger.log(["I: Removing unused link %s" % fp])
619 Logger.log(["E: Failed to unlink unreferenced file %s" % r.fullpath])
621 def add_file_from_pool(self, poolfile):
622 """Copies a file into the pool. Assumes that the PoolFile object is
623 attached to the same SQLAlchemy session as the Queue object is.
625 The caller is responsible for committing after calling this function."""
626 poolfile_basename = poolfile.filename[poolfile.filename.rindex(os.sep)+1:]
628 # Check if we have a file of this name or this ID already
629 for f in self.queuefiles:
630 if f.fileid is not None and f.fileid == poolfile.file_id or \
631 f.poolfile.filename == poolfile_basename:
632 # In this case, update the BuildQueueFile entry so we
633 # don't remove it too early
634 f.lastused = datetime.now()
635 DBConn().session().object_session(poolfile).add(f)
638 # Prepare BuildQueueFile object
639 qf = BuildQueueFile()
640 qf.build_queue_id = self.queue_id
641 qf.lastused = datetime.now()
642 qf.filename = poolfile_basename
644 targetpath = poolfile.fullpath
645 queuepath = os.path.join(self.path, poolfile_basename)
649 # We need to copy instead of symlink
651 utils.copy(targetpath, queuepath)
652 # NULL in the fileid field implies a copy
655 os.symlink(targetpath, queuepath)
656 qf.fileid = poolfile.file_id
660 # Get the same session as the PoolFile is using and add the qf to it
661 DBConn().session().object_session(poolfile).add(qf)
666 __all__.append('BuildQueue')
669 def get_build_queue(queuename, session=None):
671 Returns BuildQueue object for given C{queue name}, creating it if it does not
674 @type queuename: string
675 @param queuename: The name of the queue
677 @type session: Session
678 @param session: Optional SQLA session object (a temporary one will be
679 generated if not supplied)
682 @return: BuildQueue object for the given queue
685 q = session.query(BuildQueue).filter_by(queue_name=queuename)
689 except NoResultFound:
692 __all__.append('get_build_queue')
694 ################################################################################
696 class BuildQueueFile(object):
697 def __init__(self, *args, **kwargs):
701 return '<BuildQueueFile %s (%s)>' % (self.filename, self.build_queue_id)
705 return os.path.join(self.buildqueue.path, self.filename)
708 __all__.append('BuildQueueFile')
710 ################################################################################
712 class ChangePendingBinary(object):
713 def __init__(self, *args, **kwargs):
717 return '<ChangePendingBinary %s>' % self.change_pending_binary_id
719 __all__.append('ChangePendingBinary')
721 ################################################################################
723 class ChangePendingFile(object):
724 def __init__(self, *args, **kwargs):
728 return '<ChangePendingFile %s>' % self.change_pending_file_id
730 __all__.append('ChangePendingFile')
732 ################################################################################
734 class ChangePendingSource(object):
735 def __init__(self, *args, **kwargs):
739 return '<ChangePendingSource %s>' % self.change_pending_source_id
741 __all__.append('ChangePendingSource')
743 ################################################################################
745 class Component(object):
746 def __init__(self, *args, **kwargs):
749 def __eq__(self, val):
750 if isinstance(val, str):
751 return (self.component_name == val)
752 # This signals to use the normal comparison operator
753 return NotImplemented
755 def __ne__(self, val):
756 if isinstance(val, str):
757 return (self.component_name != val)
758 # This signals to use the normal comparison operator
759 return NotImplemented
762 return '<Component %s>' % self.component_name
765 __all__.append('Component')
768 def get_component(component, session=None):
770 Returns database id for given C{component}.
772 @type component: string
773 @param component: The name of the override type
776 @return: the database id for the given component
779 component = component.lower()
781 q = session.query(Component).filter_by(component_name=component)
785 except NoResultFound:
788 __all__.append('get_component')
790 ################################################################################
792 class DBConfig(object):
793 def __init__(self, *args, **kwargs):
797 return '<DBConfig %s>' % self.name
799 __all__.append('DBConfig')
801 ################################################################################
804 def get_or_set_contents_file_id(filename, session=None):
806 Returns database id for given filename.
808 If no matching file is found, a row is inserted.
810 @type filename: string
811 @param filename: The filename
812 @type session: SQLAlchemy
813 @param session: Optional SQL session object (a temporary one will be
814 generated if not supplied). If not passed, a commit will be performed at
815 the end of the function, otherwise the caller is responsible for commiting.
818 @return: the database id for the given component
821 q = session.query(ContentFilename).filter_by(filename=filename)
824 ret = q.one().cafilename_id
825 except NoResultFound:
826 cf = ContentFilename()
827 cf.filename = filename
829 session.commit_or_flush()
830 ret = cf.cafilename_id
834 __all__.append('get_or_set_contents_file_id')
837 def get_contents(suite, overridetype, section=None, session=None):
839 Returns contents for a suite / overridetype combination, limiting
840 to a section if not None.
843 @param suite: Suite object
845 @type overridetype: OverrideType
846 @param overridetype: OverrideType object
848 @type section: Section
849 @param section: Optional section object to limit results to
851 @type session: SQLAlchemy
852 @param session: Optional SQL session object (a temporary one will be
853 generated if not supplied)
856 @return: ResultsProxy object set up to return tuples of (filename, section,
860 # find me all of the contents for a given suite
861 contents_q = """SELECT (p.path||'/'||n.file) AS fn,
865 FROM content_associations c join content_file_paths p ON (c.filepath=p.id)
866 JOIN content_file_names n ON (c.filename=n.id)
867 JOIN binaries b ON (b.id=c.binary_pkg)
868 JOIN override o ON (o.package=b.package)
869 JOIN section s ON (s.id=o.section)
870 WHERE o.suite = :suiteid AND o.type = :overridetypeid
871 AND b.type=:overridetypename"""
873 vals = {'suiteid': suite.suite_id,
874 'overridetypeid': overridetype.overridetype_id,
875 'overridetypename': overridetype.overridetype}
877 if section is not None:
878 contents_q += " AND s.id = :sectionid"
879 vals['sectionid'] = section.section_id
881 contents_q += " ORDER BY fn"
883 return session.execute(contents_q, vals)
885 __all__.append('get_contents')
887 ################################################################################
889 class ContentFilepath(object):
890 def __init__(self, *args, **kwargs):
894 return '<ContentFilepath %s>' % self.filepath
896 __all__.append('ContentFilepath')
899 def get_or_set_contents_path_id(filepath, session=None):
901 Returns database id for given path.
903 If no matching file is found, a row is inserted.
905 @type filepath: string
906 @param filepath: The filepath
908 @type session: SQLAlchemy
909 @param session: Optional SQL session object (a temporary one will be
910 generated if not supplied). If not passed, a commit will be performed at
911 the end of the function, otherwise the caller is responsible for commiting.
914 @return: the database id for the given path
917 q = session.query(ContentFilepath).filter_by(filepath=filepath)
920 ret = q.one().cafilepath_id
921 except NoResultFound:
922 cf = ContentFilepath()
923 cf.filepath = filepath
925 session.commit_or_flush()
926 ret = cf.cafilepath_id
930 __all__.append('get_or_set_contents_path_id')
932 ################################################################################
934 class ContentAssociation(object):
935 def __init__(self, *args, **kwargs):
939 return '<ContentAssociation %s>' % self.ca_id
941 __all__.append('ContentAssociation')
943 def insert_content_paths(binary_id, fullpaths, session=None):
945 Make sure given path is associated with given binary id
948 @param binary_id: the id of the binary
949 @type fullpaths: list
950 @param fullpaths: the list of paths of the file being associated with the binary
951 @type session: SQLAlchemy session
952 @param session: Optional SQLAlchemy session. If this is passed, the caller
953 is responsible for ensuring a transaction has begun and committing the
954 results or rolling back based on the result code. If not passed, a commit
955 will be performed at the end of the function, otherwise the caller is
956 responsible for commiting.
958 @return: True upon success
963 session = DBConn().session()
969 for fullpath in fullpaths:
970 if fullpath.startswith( './' ):
971 fullpath = fullpath[2:]
973 session.execute( "INSERT INTO bin_contents ( file, binary_id ) VALUES ( :filename, :id )", { 'filename': fullpath, 'id': binary_id} )
981 traceback.print_exc()
983 # Only rollback if we set up the session ourself
990 __all__.append('insert_content_paths')
992 ################################################################################
994 class DSCFile(object):
995 def __init__(self, *args, **kwargs):
999 return '<DSCFile %s>' % self.dscfile_id
1001 __all__.append('DSCFile')
1004 def get_dscfiles(dscfile_id=None, source_id=None, poolfile_id=None, session=None):
1006 Returns a list of DSCFiles which may be empty
1008 @type dscfile_id: int (optional)
1009 @param dscfile_id: the dscfile_id of the DSCFiles to find
1011 @type source_id: int (optional)
1012 @param source_id: the source id related to the DSCFiles to find
1014 @type poolfile_id: int (optional)
1015 @param poolfile_id: the poolfile id related to the DSCFiles to find
1018 @return: Possibly empty list of DSCFiles
1021 q = session.query(DSCFile)
1023 if dscfile_id is not None:
1024 q = q.filter_by(dscfile_id=dscfile_id)
1026 if source_id is not None:
1027 q = q.filter_by(source_id=source_id)
1029 if poolfile_id is not None:
1030 q = q.filter_by(poolfile_id=poolfile_id)
1034 __all__.append('get_dscfiles')
1036 ################################################################################
1038 class PoolFile(object):
1039 def __init__(self, *args, **kwargs):
1043 return '<PoolFile %s>' % self.filename
1047 return os.path.join(self.location.path, self.filename)
1049 __all__.append('PoolFile')
1052 def check_poolfile(filename, filesize, md5sum, location_id, session=None):
1055 (ValidFileFound [boolean or None], PoolFile object or None)
1057 @type filename: string
1058 @param filename: the filename of the file to check against the DB
1061 @param filesize: the size of the file to check against the DB
1063 @type md5sum: string
1064 @param md5sum: the md5sum of the file to check against the DB
1066 @type location_id: int
1067 @param location_id: the id of the location to look in
1070 @return: Tuple of length 2.
1071 - If more than one file found with that name: (C{None}, C{None})
1072 - If valid pool file found: (C{True}, C{PoolFile object})
1073 - If valid pool file not found:
1074 - (C{False}, C{None}) if no file found
1075 - (C{False}, C{PoolFile object}) if file found with size/md5sum mismatch
1078 q = session.query(PoolFile).filter_by(filename=filename)
1079 q = q.join(Location).filter_by(location_id=location_id)
1089 if obj.md5sum != md5sum or obj.filesize != int(filesize):
1097 __all__.append('check_poolfile')
1100 def get_poolfile_by_id(file_id, session=None):
1102 Returns a PoolFile objects or None for the given id
1105 @param file_id: the id of the file to look for
1107 @rtype: PoolFile or None
1108 @return: either the PoolFile object or None
1111 q = session.query(PoolFile).filter_by(file_id=file_id)
1115 except NoResultFound:
1118 __all__.append('get_poolfile_by_id')
1122 def get_poolfile_by_name(filename, location_id=None, session=None):
1124 Returns an array of PoolFile objects for the given filename and
1125 (optionally) location_id
1127 @type filename: string
1128 @param filename: the filename of the file to check against the DB
1130 @type location_id: int
1131 @param location_id: the id of the location to look in (optional)
1134 @return: array of PoolFile objects
1137 q = session.query(PoolFile).filter_by(filename=filename)
1139 if location_id is not None:
1140 q = q.join(Location).filter_by(location_id=location_id)
1144 __all__.append('get_poolfile_by_name')
1147 def get_poolfile_like_name(filename, session=None):
1149 Returns an array of PoolFile objects which are like the given name
1151 @type filename: string
1152 @param filename: the filename of the file to check against the DB
1155 @return: array of PoolFile objects
1158 # TODO: There must be a way of properly using bind parameters with %FOO%
1159 q = session.query(PoolFile).filter(PoolFile.filename.like('%%/%s' % filename))
1163 __all__.append('get_poolfile_like_name')
1166 def add_poolfile(filename, datadict, location_id, session=None):
1168 Add a new file to the pool
1170 @type filename: string
1171 @param filename: filename
1173 @type datadict: dict
1174 @param datadict: dict with needed data
1176 @type location_id: int
1177 @param location_id: database id of the location
1180 @return: the PoolFile object created
1182 poolfile = PoolFile()
1183 poolfile.filename = filename
1184 poolfile.filesize = datadict["size"]
1185 poolfile.md5sum = datadict["md5sum"]
1186 poolfile.sha1sum = datadict["sha1sum"]
1187 poolfile.sha256sum = datadict["sha256sum"]
1188 poolfile.location_id = location_id
1190 session.add(poolfile)
1191 # Flush to get a file id (NB: This is not a commit)
1196 __all__.append('add_poolfile')
1198 ################################################################################
1200 class Fingerprint(object):
1201 def __init__(self, *args, **kwargs):
1205 return '<Fingerprint %s>' % self.fingerprint
1207 __all__.append('Fingerprint')
1210 def get_fingerprint(fpr, session=None):
1212 Returns Fingerprint object for given fpr.
1215 @param fpr: The fpr to find / add
1217 @type session: SQLAlchemy
1218 @param session: Optional SQL session object (a temporary one will be
1219 generated if not supplied).
1222 @return: the Fingerprint object for the given fpr or None
1225 q = session.query(Fingerprint).filter_by(fingerprint=fpr)
1229 except NoResultFound:
1234 __all__.append('get_fingerprint')
1237 def get_or_set_fingerprint(fpr, session=None):
1239 Returns Fingerprint object for given fpr.
1241 If no matching fpr is found, a row is inserted.
1244 @param fpr: The fpr to find / add
1246 @type session: SQLAlchemy
1247 @param session: Optional SQL session object (a temporary one will be
1248 generated if not supplied). If not passed, a commit will be performed at
1249 the end of the function, otherwise the caller is responsible for commiting.
1250 A flush will be performed either way.
1253 @return: the Fingerprint object for the given fpr
1256 q = session.query(Fingerprint).filter_by(fingerprint=fpr)
1260 except NoResultFound:
1261 fingerprint = Fingerprint()
1262 fingerprint.fingerprint = fpr
1263 session.add(fingerprint)
1264 session.commit_or_flush()
1269 __all__.append('get_or_set_fingerprint')
1271 ################################################################################
1273 # Helper routine for Keyring class
1274 def get_ldap_name(entry):
1276 for k in ["cn", "mn", "sn"]:
1278 if ret and ret[0] != "" and ret[0] != "-":
1280 return " ".join(name)
1282 ################################################################################
1284 class Keyring(object):
1285 gpg_invocation = "gpg --no-default-keyring --keyring %s" +\
1286 " --with-colons --fingerprint --fingerprint"
1291 def __init__(self, *args, **kwargs):
1295 return '<Keyring %s>' % self.keyring_name
1297 def de_escape_gpg_str(self, txt):
1298 esclist = re.split(r'(\\x..)', txt)
1299 for x in range(1,len(esclist),2):
1300 esclist[x] = "%c" % (int(esclist[x][2:],16))
1301 return "".join(esclist)
1303 def load_keys(self, keyring):
1306 if not self.keyring_id:
1307 raise Exception('Must be initialized with database information')
1309 k = os.popen(self.gpg_invocation % keyring, "r")
1313 for line in k.xreadlines():
1314 field = line.split(":")
1315 if field[0] == "pub":
1317 (name, addr) = email.Utils.parseaddr(field[9])
1318 name = re.sub(r"\s*[(].*[)]", "", name)
1319 if name == "" or addr == "" or "@" not in addr:
1321 addr = "invalid-uid"
1322 name = self.de_escape_gpg_str(name)
1323 self.keys[key] = {"email": addr}
1325 self.keys[key]["name"] = name
1326 self.keys[key]["aliases"] = [name]
1327 self.keys[key]["fingerprints"] = []
1329 elif key and field[0] == "sub" and len(field) >= 12:
1330 signingkey = ("s" in field[11])
1331 elif key and field[0] == "uid":
1332 (name, addr) = email.Utils.parseaddr(field[9])
1333 if name and name not in self.keys[key]["aliases"]:
1334 self.keys[key]["aliases"].append(name)
1335 elif signingkey and field[0] == "fpr":
1336 self.keys[key]["fingerprints"].append(field[9])
1337 self.fpr_lookup[field[9]] = key
1339 def import_users_from_ldap(self, session):
1343 LDAPDn = cnf["Import-LDAP-Fingerprints::LDAPDn"]
1344 LDAPServer = cnf["Import-LDAP-Fingerprints::LDAPServer"]
1346 l = ldap.open(LDAPServer)
1347 l.simple_bind_s("","")
1348 Attrs = l.search_s(LDAPDn, ldap.SCOPE_ONELEVEL,
1349 "(&(keyfingerprint=*)(gidnumber=%s))" % (cnf["Import-Users-From-Passwd::ValidGID"]),
1350 ["uid", "keyfingerprint", "cn", "mn", "sn"])
1352 ldap_fin_uid_id = {}
1359 uid = entry["uid"][0]
1360 name = get_ldap_name(entry)
1361 fingerprints = entry["keyFingerPrint"]
1363 for f in fingerprints:
1364 key = self.fpr_lookup.get(f, None)
1365 if key not in self.keys:
1367 self.keys[key]["uid"] = uid
1371 keyid = get_or_set_uid(uid, session).uid_id
1372 byuid[keyid] = (uid, name)
1373 byname[uid] = (keyid, name)
1375 return (byname, byuid)
1377 def generate_users_from_keyring(self, format, session):
1381 for x in self.keys.keys():
1382 if self.keys[x]["email"] == "invalid-uid":
1384 self.keys[x]["uid"] = format % "invalid-uid"
1386 uid = format % self.keys[x]["email"]
1387 keyid = get_or_set_uid(uid, session).uid_id
1388 byuid[keyid] = (uid, self.keys[x]["name"])
1389 byname[uid] = (keyid, self.keys[x]["name"])
1390 self.keys[x]["uid"] = uid
1393 uid = format % "invalid-uid"
1394 keyid = get_or_set_uid(uid, session).uid_id
1395 byuid[keyid] = (uid, "ungeneratable user id")
1396 byname[uid] = (keyid, "ungeneratable user id")
1398 return (byname, byuid)
1400 __all__.append('Keyring')
1403 def get_keyring(keyring, session=None):
1405 If C{keyring} does not have an entry in the C{keyrings} table yet, return None
1406 If C{keyring} already has an entry, simply return the existing Keyring
1408 @type keyring: string
1409 @param keyring: the keyring name
1412 @return: the Keyring object for this keyring
1415 q = session.query(Keyring).filter_by(keyring_name=keyring)
1419 except NoResultFound:
1422 __all__.append('get_keyring')
1424 ################################################################################
1426 class KeyringACLMap(object):
1427 def __init__(self, *args, **kwargs):
1431 return '<KeyringACLMap %s>' % self.keyring_acl_map_id
1433 __all__.append('KeyringACLMap')
1435 ################################################################################
1437 class DBChange(object):
1438 def __init__(self, *args, **kwargs):
1442 return '<DBChange %s>' % self.changesname
1444 def clean_from_queue(self):
1445 session = DBConn().session().object_session(self)
1447 # Remove changes_pool_files entries
1450 # Remove changes_pending_files references
1453 # Clear out of queue
1454 self.in_queue = None
1455 self.approved_for_id = None
1457 __all__.append('DBChange')
1460 def get_dbchange(filename, session=None):
1462 returns DBChange object for given C{filename}.
1464 @type filename: string
1465 @param filename: the name of the file
1467 @type session: Session
1468 @param session: Optional SQLA session object (a temporary one will be
1469 generated if not supplied)
1472 @return: DBChange object for the given filename (C{None} if not present)
1475 q = session.query(DBChange).filter_by(changesname=filename)
1479 except NoResultFound:
1482 __all__.append('get_dbchange')
1484 ################################################################################
1486 class Location(object):
1487 def __init__(self, *args, **kwargs):
1491 return '<Location %s (%s)>' % (self.path, self.location_id)
1493 __all__.append('Location')
1496 def get_location(location, component=None, archive=None, session=None):
1498 Returns Location object for the given combination of location, component
1501 @type location: string
1502 @param location: the path of the location, e.g. I{/srv/ftp.debian.org/ftp/pool/}
1504 @type component: string
1505 @param component: the component name (if None, no restriction applied)
1507 @type archive: string
1508 @param archive: the archive name (if None, no restriction applied)
1510 @rtype: Location / None
1511 @return: Either a Location object or None if one can't be found
1514 q = session.query(Location).filter_by(path=location)
1516 if archive is not None:
1517 q = q.join(Archive).filter_by(archive_name=archive)
1519 if component is not None:
1520 q = q.join(Component).filter_by(component_name=component)
1524 except NoResultFound:
1527 __all__.append('get_location')
1529 ################################################################################
1531 class Maintainer(object):
1532 def __init__(self, *args, **kwargs):
1536 return '''<Maintainer '%s' (%s)>''' % (self.name, self.maintainer_id)
1538 def get_split_maintainer(self):
1539 if not hasattr(self, 'name') or self.name is None:
1540 return ('', '', '', '')
1542 return fix_maintainer(self.name.strip())
1544 __all__.append('Maintainer')
1547 def get_or_set_maintainer(name, session=None):
1549 Returns Maintainer object for given maintainer name.
1551 If no matching maintainer name is found, a row is inserted.
1554 @param name: The maintainer name to add
1556 @type session: SQLAlchemy
1557 @param session: Optional SQL session object (a temporary one will be
1558 generated if not supplied). If not passed, a commit will be performed at
1559 the end of the function, otherwise the caller is responsible for commiting.
1560 A flush will be performed either way.
1563 @return: the Maintainer object for the given maintainer
1566 q = session.query(Maintainer).filter_by(name=name)
1569 except NoResultFound:
1570 maintainer = Maintainer()
1571 maintainer.name = name
1572 session.add(maintainer)
1573 session.commit_or_flush()
1578 __all__.append('get_or_set_maintainer')
1581 def get_maintainer(maintainer_id, session=None):
1583 Return the name of the maintainer behind C{maintainer_id} or None if that
1584 maintainer_id is invalid.
1586 @type maintainer_id: int
1587 @param maintainer_id: the id of the maintainer
1590 @return: the Maintainer with this C{maintainer_id}
1593 return session.query(Maintainer).get(maintainer_id)
1595 __all__.append('get_maintainer')
1597 ################################################################################
1599 class NewComment(object):
1600 def __init__(self, *args, **kwargs):
1604 return '''<NewComment for '%s %s' (%s)>''' % (self.package, self.version, self.comment_id)
1606 __all__.append('NewComment')
1609 def has_new_comment(package, version, session=None):
1611 Returns true if the given combination of C{package}, C{version} has a comment.
1613 @type package: string
1614 @param package: name of the package
1616 @type version: string
1617 @param version: package version
1619 @type session: Session
1620 @param session: Optional SQLA session object (a temporary one will be
1621 generated if not supplied)
1627 q = session.query(NewComment)
1628 q = q.filter_by(package=package)
1629 q = q.filter_by(version=version)
1631 return bool(q.count() > 0)
1633 __all__.append('has_new_comment')
1636 def get_new_comments(package=None, version=None, comment_id=None, session=None):
1638 Returns (possibly empty) list of NewComment objects for the given
1641 @type package: string (optional)
1642 @param package: name of the package
1644 @type version: string (optional)
1645 @param version: package version
1647 @type comment_id: int (optional)
1648 @param comment_id: An id of a comment
1650 @type session: Session
1651 @param session: Optional SQLA session object (a temporary one will be
1652 generated if not supplied)
1655 @return: A (possibly empty) list of NewComment objects will be returned
1658 q = session.query(NewComment)
1659 if package is not None: q = q.filter_by(package=package)
1660 if version is not None: q = q.filter_by(version=version)
1661 if comment_id is not None: q = q.filter_by(comment_id=comment_id)
1665 __all__.append('get_new_comments')
1667 ################################################################################
1669 class Override(object):
1670 def __init__(self, *args, **kwargs):
1674 return '<Override %s (%s)>' % (self.package, self.suite_id)
1676 __all__.append('Override')
1679 def get_override(package, suite=None, component=None, overridetype=None, session=None):
1681 Returns Override object for the given parameters
1683 @type package: string
1684 @param package: The name of the package
1686 @type suite: string, list or None
1687 @param suite: The name of the suite (or suites if a list) to limit to. If
1688 None, don't limit. Defaults to None.
1690 @type component: string, list or None
1691 @param component: The name of the component (or components if a list) to
1692 limit to. If None, don't limit. Defaults to None.
1694 @type overridetype: string, list or None
1695 @param overridetype: The name of the overridetype (or overridetypes if a list) to
1696 limit to. If None, don't limit. Defaults to None.
1698 @type session: Session
1699 @param session: Optional SQLA session object (a temporary one will be
1700 generated if not supplied)
1703 @return: A (possibly empty) list of Override objects will be returned
1706 q = session.query(Override)
1707 q = q.filter_by(package=package)
1709 if suite is not None:
1710 if not isinstance(suite, list): suite = [suite]
1711 q = q.join(Suite).filter(Suite.suite_name.in_(suite))
1713 if component is not None:
1714 if not isinstance(component, list): component = [component]
1715 q = q.join(Component).filter(Component.component_name.in_(component))
1717 if overridetype is not None:
1718 if not isinstance(overridetype, list): overridetype = [overridetype]
1719 q = q.join(OverrideType).filter(OverrideType.overridetype.in_(overridetype))
1723 __all__.append('get_override')
1726 ################################################################################
1728 class OverrideType(object):
1729 def __init__(self, *args, **kwargs):
1733 return '<OverrideType %s>' % self.overridetype
1735 __all__.append('OverrideType')
1738 def get_override_type(override_type, session=None):
1740 Returns OverrideType object for given C{override type}.
1742 @type override_type: string
1743 @param override_type: The name of the override type
1745 @type session: Session
1746 @param session: Optional SQLA session object (a temporary one will be
1747 generated if not supplied)
1750 @return: the database id for the given override type
1753 q = session.query(OverrideType).filter_by(overridetype=override_type)
1757 except NoResultFound:
1760 __all__.append('get_override_type')
1762 ################################################################################
1764 class PendingContentAssociation(object):
1765 def __init__(self, *args, **kwargs):
1769 return '<PendingContentAssociation %s>' % self.pca_id
1771 __all__.append('PendingContentAssociation')
1773 def insert_pending_content_paths(package, fullpaths, session=None):
1775 Make sure given paths are temporarily associated with given
1779 @param package: the package to associate with should have been read in from the binary control file
1780 @type fullpaths: list
1781 @param fullpaths: the list of paths of the file being associated with the binary
1782 @type session: SQLAlchemy session
1783 @param session: Optional SQLAlchemy session. If this is passed, the caller
1784 is responsible for ensuring a transaction has begun and committing the
1785 results or rolling back based on the result code. If not passed, a commit
1786 will be performed at the end of the function
1788 @return: True upon success, False if there is a problem
1791 privatetrans = False
1794 session = DBConn().session()
1798 arch = get_architecture(package['Architecture'], session)
1799 arch_id = arch.arch_id
1801 # Remove any already existing recorded files for this package
1802 q = session.query(PendingContentAssociation)
1803 q = q.filter_by(package=package['Package'])
1804 q = q.filter_by(version=package['Version'])
1805 q = q.filter_by(architecture=arch_id)
1810 for fullpath in fullpaths:
1811 (path, filename) = os.path.split(fullpath)
1813 if path.startswith( "./" ):
1816 filepath_id = get_or_set_contents_path_id(path, session)
1817 filename_id = get_or_set_contents_file_id(filename, session)
1819 pathcache[fullpath] = (filepath_id, filename_id)
1821 for fullpath, dat in pathcache.items():
1822 pca = PendingContentAssociation()
1823 pca.package = package['Package']
1824 pca.version = package['Version']
1825 pca.filepath_id = dat[0]
1826 pca.filename_id = dat[1]
1827 pca.architecture = arch_id
1830 # Only commit if we set up the session ourself
1838 except Exception, e:
1839 traceback.print_exc()
1841 # Only rollback if we set up the session ourself
1848 __all__.append('insert_pending_content_paths')
1850 ################################################################################
1852 class PolicyQueue(object):
1853 def __init__(self, *args, **kwargs):
1857 return '<PolicyQueue %s>' % self.queue_name
1859 __all__.append('PolicyQueue')
1862 def get_policy_queue(queuename, session=None):
1864 Returns PolicyQueue object for given C{queue name}
1866 @type queuename: string
1867 @param queuename: The name of the queue
1869 @type session: Session
1870 @param session: Optional SQLA session object (a temporary one will be
1871 generated if not supplied)
1874 @return: PolicyQueue object for the given queue
1877 q = session.query(PolicyQueue).filter_by(queue_name=queuename)
1881 except NoResultFound:
1884 __all__.append('get_policy_queue')
1886 ################################################################################
1888 class Priority(object):
1889 def __init__(self, *args, **kwargs):
1892 def __eq__(self, val):
1893 if isinstance(val, str):
1894 return (self.priority == val)
1895 # This signals to use the normal comparison operator
1896 return NotImplemented
1898 def __ne__(self, val):
1899 if isinstance(val, str):
1900 return (self.priority != val)
1901 # This signals to use the normal comparison operator
1902 return NotImplemented
1905 return '<Priority %s (%s)>' % (self.priority, self.priority_id)
1907 __all__.append('Priority')
1910 def get_priority(priority, session=None):
1912 Returns Priority object for given C{priority name}.
1914 @type priority: string
1915 @param priority: The name of the priority
1917 @type session: Session
1918 @param session: Optional SQLA session object (a temporary one will be
1919 generated if not supplied)
1922 @return: Priority object for the given priority
1925 q = session.query(Priority).filter_by(priority=priority)
1929 except NoResultFound:
1932 __all__.append('get_priority')
1935 def get_priorities(session=None):
1937 Returns dictionary of priority names -> id mappings
1939 @type session: Session
1940 @param session: Optional SQL session object (a temporary one will be
1941 generated if not supplied)
1944 @return: dictionary of priority names -> id mappings
1948 q = session.query(Priority)
1950 ret[x.priority] = x.priority_id
1954 __all__.append('get_priorities')
1956 ################################################################################
1958 class Section(object):
1959 def __init__(self, *args, **kwargs):
1962 def __eq__(self, val):
1963 if isinstance(val, str):
1964 return (self.section == val)
1965 # This signals to use the normal comparison operator
1966 return NotImplemented
1968 def __ne__(self, val):
1969 if isinstance(val, str):
1970 return (self.section != val)
1971 # This signals to use the normal comparison operator
1972 return NotImplemented
1975 return '<Section %s>' % self.section
1977 __all__.append('Section')
1980 def get_section(section, session=None):
1982 Returns Section object for given C{section name}.
1984 @type section: string
1985 @param section: The name of the section
1987 @type session: Session
1988 @param session: Optional SQLA session object (a temporary one will be
1989 generated if not supplied)
1992 @return: Section object for the given section name
1995 q = session.query(Section).filter_by(section=section)
1999 except NoResultFound:
2002 __all__.append('get_section')
2005 def get_sections(session=None):
2007 Returns dictionary of section names -> id mappings
2009 @type session: Session
2010 @param session: Optional SQL session object (a temporary one will be
2011 generated if not supplied)
2014 @return: dictionary of section names -> id mappings
2018 q = session.query(Section)
2020 ret[x.section] = x.section_id
2024 __all__.append('get_sections')
2026 ################################################################################
2028 class DBSource(object):
2029 def __init__(self, *args, **kwargs):
2033 return '<DBSource %s (%s)>' % (self.source, self.version)
2035 __all__.append('DBSource')
2038 def source_exists(source, source_version, suites = ["any"], session=None):
2040 Ensure that source exists somewhere in the archive for the binary
2041 upload being processed.
2042 1. exact match => 1.0-3
2043 2. bin-only NMU => 1.0-3+b1 , 1.0-3.1+b1
2045 @type source: string
2046 @param source: source name
2048 @type source_version: string
2049 @param source_version: expected source version
2052 @param suites: list of suites to check in, default I{any}
2054 @type session: Session
2055 @param session: Optional SQLA session object (a temporary one will be
2056 generated if not supplied)
2059 @return: returns 1 if a source with expected version is found, otherwise 0
2066 for suite in suites:
2067 q = session.query(DBSource).filter_by(source=source)
2069 # source must exist in suite X, or in some other suite that's
2070 # mapped to X, recursively... silent-maps are counted too,
2071 # unreleased-maps aren't.
2072 maps = cnf.ValueList("SuiteMappings")[:]
2074 maps = [ m.split() for m in maps ]
2075 maps = [ (x[1], x[2]) for x in maps
2076 if x[0] == "map" or x[0] == "silent-map" ]
2079 if x[1] in s and x[0] not in s:
2082 q = q.join(SrcAssociation).join(Suite)
2083 q = q.filter(Suite.suite_name.in_(s))
2085 # Reduce the query results to a list of version numbers
2086 ql = [ j.version for j in q.all() ]
2089 if source_version in ql:
2093 from daklib.regexes import re_bin_only_nmu
2094 orig_source_version = re_bin_only_nmu.sub('', source_version)
2095 if orig_source_version in ql:
2098 # No source found so return not ok
2103 __all__.append('source_exists')
2106 def get_suites_source_in(source, session=None):
2108 Returns list of Suite objects which given C{source} name is in
2111 @param source: DBSource package name to search for
2114 @return: list of Suite objects for the given source
2117 return session.query(Suite).join(SrcAssociation).join(DBSource).filter_by(source=source).all()
2119 __all__.append('get_suites_source_in')
2122 def get_sources_from_name(source, version=None, dm_upload_allowed=None, session=None):
2124 Returns list of DBSource objects for given C{source} name and other parameters
2127 @param source: DBSource package name to search for
2129 @type version: str or None
2130 @param version: DBSource version name to search for or None if not applicable
2132 @type dm_upload_allowed: bool
2133 @param dm_upload_allowed: If None, no effect. If True or False, only
2134 return packages with that dm_upload_allowed setting
2136 @type session: Session
2137 @param session: Optional SQL session object (a temporary one will be
2138 generated if not supplied)
2141 @return: list of DBSource objects for the given name (may be empty)
2144 q = session.query(DBSource).filter_by(source=source)
2146 if version is not None:
2147 q = q.filter_by(version=version)
2149 if dm_upload_allowed is not None:
2150 q = q.filter_by(dm_upload_allowed=dm_upload_allowed)
2154 __all__.append('get_sources_from_name')
2157 def get_source_in_suite(source, suite, session=None):
2159 Returns list of DBSource objects for a combination of C{source} and C{suite}.
2161 - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
2162 - B{suite} - a suite name, eg. I{unstable}
2164 @type source: string
2165 @param source: source package name
2168 @param suite: the suite name
2171 @return: the version for I{source} in I{suite}
2175 q = session.query(SrcAssociation)
2176 q = q.join('source').filter_by(source=source)
2177 q = q.join('suite').filter_by(suite_name=suite)
2180 return q.one().source
2181 except NoResultFound:
2184 __all__.append('get_source_in_suite')
2186 ################################################################################
2189 def add_dsc_to_db(u, filename, session=None):
2190 entry = u.pkg.files[filename]
2194 source.source = u.pkg.dsc["source"]
2195 source.version = u.pkg.dsc["version"] # NB: not files[file]["version"], that has no epoch
2196 source.maintainer_id = get_or_set_maintainer(u.pkg.dsc["maintainer"], session).maintainer_id
2197 source.changedby_id = get_or_set_maintainer(u.pkg.changes["changed-by"], session).maintainer_id
2198 source.fingerprint_id = get_or_set_fingerprint(u.pkg.changes["fingerprint"], session).fingerprint_id
2199 source.install_date = datetime.now().date()
2201 dsc_component = entry["component"]
2202 dsc_location_id = entry["location id"]
2204 source.dm_upload_allowed = (u.pkg.dsc.get("dm-upload-allowed", '') == "yes")
2206 # Set up a new poolfile if necessary
2207 if not entry.has_key("files id") or not entry["files id"]:
2208 filename = entry["pool name"] + filename
2209 poolfile = add_poolfile(filename, entry, dsc_location_id, session)
2211 pfs.append(poolfile)
2212 entry["files id"] = poolfile.file_id
2214 source.poolfile_id = entry["files id"]
2218 for suite_name in u.pkg.changes["distribution"].keys():
2219 sa = SrcAssociation()
2220 sa.source_id = source.source_id
2221 sa.suite_id = get_suite(suite_name).suite_id
2226 # Add the source files to the DB (files and dsc_files)
2228 dscfile.source_id = source.source_id
2229 dscfile.poolfile_id = entry["files id"]
2230 session.add(dscfile)
2232 for dsc_file, dentry in u.pkg.dsc_files.items():
2234 df.source_id = source.source_id
2236 # If the .orig tarball is already in the pool, it's
2237 # files id is stored in dsc_files by check_dsc().
2238 files_id = dentry.get("files id", None)
2240 # Find the entry in the files hash
2241 # TODO: Bail out here properly
2243 for f, e in u.pkg.files.items():
2248 if files_id is None:
2249 filename = dfentry["pool name"] + dsc_file
2251 (found, obj) = check_poolfile(filename, dentry["size"], dentry["md5sum"], dsc_location_id)
2252 # FIXME: needs to check for -1/-2 and or handle exception
2253 if found and obj is not None:
2254 files_id = obj.file_id
2257 # If still not found, add it
2258 if files_id is None:
2259 # HACK: Force sha1sum etc into dentry
2260 dentry["sha1sum"] = dfentry["sha1sum"]
2261 dentry["sha256sum"] = dfentry["sha256sum"]
2262 poolfile = add_poolfile(filename, dentry, dsc_location_id, session)
2263 pfs.append(poolfile)
2264 files_id = poolfile.file_id
2266 poolfile = get_poolfile_by_id(files_id, session)
2267 if poolfile is None:
2268 utils.fubar("INTERNAL ERROR. Found no poolfile with id %d" % files_id)
2269 pfs.append(poolfile)
2271 df.poolfile_id = files_id
2276 # Add the src_uploaders to the DB
2277 uploader_ids = [source.maintainer_id]
2278 if u.pkg.dsc.has_key("uploaders"):
2279 for up in u.pkg.dsc["uploaders"].split(","):
2281 uploader_ids.append(get_or_set_maintainer(up, session).maintainer_id)
2284 for up_id in uploader_ids:
2285 if added_ids.has_key(up_id):
2287 utils.warn("Already saw uploader %s for source %s" % (up_id, source.source))
2293 su.maintainer_id = up_id
2294 su.source_id = source.source_id
2299 return source, dsc_component, dsc_location_id, pfs
2301 __all__.append('add_dsc_to_db')
2304 def add_deb_to_db(u, filename, session=None):
2306 Contrary to what you might expect, this routine deals with both
2307 debs and udebs. That info is in 'dbtype', whilst 'type' is
2308 'deb' for both of them
2311 entry = u.pkg.files[filename]
2314 bin.package = entry["package"]
2315 bin.version = entry["version"]
2316 bin.maintainer_id = get_or_set_maintainer(entry["maintainer"], session).maintainer_id
2317 bin.fingerprint_id = get_or_set_fingerprint(u.pkg.changes["fingerprint"], session).fingerprint_id
2318 bin.arch_id = get_architecture(entry["architecture"], session).arch_id
2319 bin.binarytype = entry["dbtype"]
2322 filename = entry["pool name"] + filename
2323 fullpath = os.path.join(cnf["Dir::Pool"], filename)
2324 if not entry.get("location id", None):
2325 entry["location id"] = get_location(cnf["Dir::Pool"], entry["component"], session=session).location_id
2327 if entry.get("files id", None):
2328 poolfile = get_poolfile_by_id(bin.poolfile_id)
2329 bin.poolfile_id = entry["files id"]
2331 poolfile = add_poolfile(filename, entry, entry["location id"], session)
2332 bin.poolfile_id = entry["files id"] = poolfile.file_id
2335 bin_sources = get_sources_from_name(entry["source package"], entry["source version"], session=session)
2336 if len(bin_sources) != 1:
2337 raise NoSourceFieldError, "Unable to find a unique source id for %s (%s), %s, file %s, type %s, signed by %s" % \
2338 (bin.package, bin.version, bin.architecture.arch_string,
2339 filename, bin.binarytype, u.pkg.changes["fingerprint"])
2341 bin.source_id = bin_sources[0].source_id
2343 # Add and flush object so it has an ID
2347 # Add BinAssociations
2348 for suite_name in u.pkg.changes["distribution"].keys():
2349 ba = BinAssociation()
2350 ba.binary_id = bin.binary_id
2351 ba.suite_id = get_suite(suite_name).suite_id
2356 # Deal with contents - disabled for now
2357 #contents = copy_temporary_contents(bin.package, bin.version, bin.architecture.arch_string, os.path.basename(filename), None, session)
2359 # print "REJECT\nCould not determine contents of package %s" % bin.package
2360 # session.rollback()
2361 # raise MissingContents, "No contents stored for package %s, and couldn't determine contents of %s" % (bin.package, filename)
2365 __all__.append('add_deb_to_db')
2367 ################################################################################
2369 class SourceACL(object):
2370 def __init__(self, *args, **kwargs):
2374 return '<SourceACL %s>' % self.source_acl_id
2376 __all__.append('SourceACL')
2378 ################################################################################
2380 class SrcAssociation(object):
2381 def __init__(self, *args, **kwargs):
2385 return '<SrcAssociation %s (%s, %s)>' % (self.sa_id, self.source, self.suite)
2387 __all__.append('SrcAssociation')
2389 ################################################################################
2391 class SrcFormat(object):
2392 def __init__(self, *args, **kwargs):
2396 return '<SrcFormat %s>' % (self.format_name)
2398 __all__.append('SrcFormat')
2400 ################################################################################
2402 class SrcUploader(object):
2403 def __init__(self, *args, **kwargs):
2407 return '<SrcUploader %s>' % self.uploader_id
2409 __all__.append('SrcUploader')
2411 ################################################################################
2413 SUITE_FIELDS = [ ('SuiteName', 'suite_name'),
2414 ('SuiteID', 'suite_id'),
2415 ('Version', 'version'),
2416 ('Origin', 'origin'),
2418 ('Description', 'description'),
2419 ('Untouchable', 'untouchable'),
2420 ('Announce', 'announce'),
2421 ('Codename', 'codename'),
2422 ('OverrideCodename', 'overridecodename'),
2423 ('ValidTime', 'validtime'),
2424 ('Priority', 'priority'),
2425 ('NotAutomatic', 'notautomatic'),
2426 ('CopyChanges', 'copychanges'),
2427 ('CopyDotDak', 'copydotdak'),
2428 ('CommentsDir', 'commentsdir'),
2429 ('OverrideSuite', 'overridesuite'),
2430 ('ChangelogBase', 'changelogbase')]
2433 class Suite(object):
2434 def __init__(self, *args, **kwargs):
2438 return '<Suite %s>' % self.suite_name
2440 def __eq__(self, val):
2441 if isinstance(val, str):
2442 return (self.suite_name == val)
2443 # This signals to use the normal comparison operator
2444 return NotImplemented
2446 def __ne__(self, val):
2447 if isinstance(val, str):
2448 return (self.suite_name != val)
2449 # This signals to use the normal comparison operator
2450 return NotImplemented
2454 for disp, field in SUITE_FIELDS:
2455 val = getattr(self, field, None)
2457 ret.append("%s: %s" % (disp, val))
2459 return "\n".join(ret)
2461 __all__.append('Suite')
2464 def get_suite_architecture(suite, architecture, session=None):
2466 Returns a SuiteArchitecture object given C{suite} and ${arch} or None if it
2470 @param suite: Suite name to search for
2472 @type architecture: str
2473 @param architecture: Architecture name to search for
2475 @type session: Session
2476 @param session: Optional SQL session object (a temporary one will be
2477 generated if not supplied)
2479 @rtype: SuiteArchitecture
2480 @return: the SuiteArchitecture object or None
2483 q = session.query(SuiteArchitecture)
2484 q = q.join(Architecture).filter_by(arch_string=architecture)
2485 q = q.join(Suite).filter_by(suite_name=suite)
2489 except NoResultFound:
2492 __all__.append('get_suite_architecture')
2495 def get_suite(suite, session=None):
2497 Returns Suite object for given C{suite name}.
2500 @param suite: The name of the suite
2502 @type session: Session
2503 @param session: Optional SQLA session object (a temporary one will be
2504 generated if not supplied)
2507 @return: Suite object for the requested suite name (None if not present)
2510 q = session.query(Suite).filter_by(suite_name=suite)
2514 except NoResultFound:
2517 __all__.append('get_suite')
2519 ################################################################################
2521 class SuiteArchitecture(object):
2522 def __init__(self, *args, **kwargs):
2526 return '<SuiteArchitecture (%s, %s)>' % (self.suite_id, self.arch_id)
2528 __all__.append('SuiteArchitecture')
2531 def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None):
2533 Returns list of Architecture objects for given C{suite} name
2536 @param suite: Suite name to search for
2538 @type skipsrc: boolean
2539 @param skipsrc: Whether to skip returning the 'source' architecture entry
2542 @type skipall: boolean
2543 @param skipall: Whether to skip returning the 'all' architecture entry
2546 @type session: Session
2547 @param session: Optional SQL session object (a temporary one will be
2548 generated if not supplied)
2551 @return: list of Architecture objects for the given name (may be empty)
2554 q = session.query(Architecture)
2555 q = q.join(SuiteArchitecture)
2556 q = q.join(Suite).filter_by(suite_name=suite)
2559 q = q.filter(Architecture.arch_string != 'source')
2562 q = q.filter(Architecture.arch_string != 'all')
2564 q = q.order_by('arch_string')
2568 __all__.append('get_suite_architectures')
2570 ################################################################################
2572 class SuiteSrcFormat(object):
2573 def __init__(self, *args, **kwargs):
2577 return '<SuiteSrcFormat (%s, %s)>' % (self.suite_id, self.src_format_id)
2579 __all__.append('SuiteSrcFormat')
2582 def get_suite_src_formats(suite, session=None):
2584 Returns list of allowed SrcFormat for C{suite}.
2587 @param suite: Suite name to search for
2589 @type session: Session
2590 @param session: Optional SQL session object (a temporary one will be
2591 generated if not supplied)
2594 @return: the list of allowed source formats for I{suite}
2597 q = session.query(SrcFormat)
2598 q = q.join(SuiteSrcFormat)
2599 q = q.join(Suite).filter_by(suite_name=suite)
2600 q = q.order_by('format_name')
2604 __all__.append('get_suite_src_formats')
2606 ################################################################################
2609 def __init__(self, *args, **kwargs):
2612 def __eq__(self, val):
2613 if isinstance(val, str):
2614 return (self.uid == val)
2615 # This signals to use the normal comparison operator
2616 return NotImplemented
2618 def __ne__(self, val):
2619 if isinstance(val, str):
2620 return (self.uid != val)
2621 # This signals to use the normal comparison operator
2622 return NotImplemented
2625 return '<Uid %s (%s)>' % (self.uid, self.name)
2627 __all__.append('Uid')
2630 def add_database_user(uidname, session=None):
2632 Adds a database user
2634 @type uidname: string
2635 @param uidname: The uid of the user to add
2637 @type session: SQLAlchemy
2638 @param session: Optional SQL session object (a temporary one will be
2639 generated if not supplied). If not passed, a commit will be performed at
2640 the end of the function, otherwise the caller is responsible for commiting.
2643 @return: the uid object for the given uidname
2646 session.execute("CREATE USER :uid", {'uid': uidname})
2647 session.commit_or_flush()
2649 __all__.append('add_database_user')
2652 def get_or_set_uid(uidname, session=None):
2654 Returns uid object for given uidname.
2656 If no matching uidname is found, a row is inserted.
2658 @type uidname: string
2659 @param uidname: The uid to add
2661 @type session: SQLAlchemy
2662 @param session: Optional SQL session object (a temporary one will be
2663 generated if not supplied). If not passed, a commit will be performed at
2664 the end of the function, otherwise the caller is responsible for commiting.
2667 @return: the uid object for the given uidname
2670 q = session.query(Uid).filter_by(uid=uidname)
2674 except NoResultFound:
2678 session.commit_or_flush()
2683 __all__.append('get_or_set_uid')
2686 def get_uid_from_fingerprint(fpr, session=None):
2687 q = session.query(Uid)
2688 q = q.join(Fingerprint).filter_by(fingerprint=fpr)
2692 except NoResultFound:
2695 __all__.append('get_uid_from_fingerprint')
2697 ################################################################################
2699 class UploadBlock(object):
2700 def __init__(self, *args, **kwargs):
2704 return '<UploadBlock %s (%s)>' % (self.source, self.upload_block_id)
2706 __all__.append('UploadBlock')
2708 ################################################################################
2710 class DBConn(object):
2712 database module init.
2716 def __init__(self, *args, **kwargs):
2717 self.__dict__ = self.__shared_state
2719 if not getattr(self, 'initialised', False):
2720 self.initialised = True
2721 self.debug = kwargs.has_key('debug')
2724 def __setuptables(self):
2733 'build_queue_files',
2736 'content_associations',
2737 'content_file_names',
2738 'content_file_paths',
2739 'changes_pending_binaries',
2740 'changes_pending_files',
2741 'changes_pending_files_map',
2742 'changes_pending_source',
2743 'changes_pending_source_files',
2744 'changes_pool_files',
2756 'pending_content_associations',
2766 'suite_architectures',
2767 'suite_src_formats',
2768 'suite_build_queue_copy',
2773 for table_name in tables:
2774 table = Table(table_name, self.db_meta, autoload=True)
2775 setattr(self, 'tbl_%s' % table_name, table)
2777 def __setupmappers(self):
2778 mapper(Architecture, self.tbl_architecture,
2779 properties = dict(arch_id = self.tbl_architecture.c.id))
2781 mapper(Archive, self.tbl_archive,
2782 properties = dict(archive_id = self.tbl_archive.c.id,
2783 archive_name = self.tbl_archive.c.name))
2785 mapper(BinAssociation, self.tbl_bin_associations,
2786 properties = dict(ba_id = self.tbl_bin_associations.c.id,
2787 suite_id = self.tbl_bin_associations.c.suite,
2788 suite = relation(Suite),
2789 binary_id = self.tbl_bin_associations.c.bin,
2790 binary = relation(DBBinary)))
2792 mapper(BuildQueue, self.tbl_build_queue,
2793 properties = dict(queue_id = self.tbl_build_queue.c.id))
2795 mapper(BuildQueueFile, self.tbl_build_queue_files,
2796 properties = dict(buildqueue = relation(BuildQueue, backref='queuefiles'),
2797 poolfile = relation(PoolFile, backref='buildqueueinstances')))
2799 mapper(DBBinary, self.tbl_binaries,
2800 properties = dict(binary_id = self.tbl_binaries.c.id,
2801 package = self.tbl_binaries.c.package,
2802 version = self.tbl_binaries.c.version,
2803 maintainer_id = self.tbl_binaries.c.maintainer,
2804 maintainer = relation(Maintainer),
2805 source_id = self.tbl_binaries.c.source,
2806 source = relation(DBSource),
2807 arch_id = self.tbl_binaries.c.architecture,
2808 architecture = relation(Architecture),
2809 poolfile_id = self.tbl_binaries.c.file,
2810 poolfile = relation(PoolFile),
2811 binarytype = self.tbl_binaries.c.type,
2812 fingerprint_id = self.tbl_binaries.c.sig_fpr,
2813 fingerprint = relation(Fingerprint),
2814 install_date = self.tbl_binaries.c.install_date,
2815 binassociations = relation(BinAssociation,
2816 primaryjoin=(self.tbl_binaries.c.id==self.tbl_bin_associations.c.bin))))
2818 mapper(BinaryACL, self.tbl_binary_acl,
2819 properties = dict(binary_acl_id = self.tbl_binary_acl.c.id))
2821 mapper(BinaryACLMap, self.tbl_binary_acl_map,
2822 properties = dict(binary_acl_map_id = self.tbl_binary_acl_map.c.id,
2823 fingerprint = relation(Fingerprint, backref="binary_acl_map"),
2824 architecture = relation(Architecture)))
2826 mapper(Component, self.tbl_component,
2827 properties = dict(component_id = self.tbl_component.c.id,
2828 component_name = self.tbl_component.c.name))
2830 mapper(DBConfig, self.tbl_config,
2831 properties = dict(config_id = self.tbl_config.c.id))
2833 mapper(DSCFile, self.tbl_dsc_files,
2834 properties = dict(dscfile_id = self.tbl_dsc_files.c.id,
2835 source_id = self.tbl_dsc_files.c.source,
2836 source = relation(DBSource),
2837 poolfile_id = self.tbl_dsc_files.c.file,
2838 poolfile = relation(PoolFile)))
2840 mapper(PoolFile, self.tbl_files,
2841 properties = dict(file_id = self.tbl_files.c.id,
2842 filesize = self.tbl_files.c.size,
2843 location_id = self.tbl_files.c.location,
2844 location = relation(Location)))
2846 mapper(Fingerprint, self.tbl_fingerprint,
2847 properties = dict(fingerprint_id = self.tbl_fingerprint.c.id,
2848 uid_id = self.tbl_fingerprint.c.uid,
2849 uid = relation(Uid),
2850 keyring_id = self.tbl_fingerprint.c.keyring,
2851 keyring = relation(Keyring),
2852 source_acl = relation(SourceACL),
2853 binary_acl = relation(BinaryACL)))
2855 mapper(Keyring, self.tbl_keyrings,
2856 properties = dict(keyring_name = self.tbl_keyrings.c.name,
2857 keyring_id = self.tbl_keyrings.c.id))
2859 mapper(DBChange, self.tbl_changes,
2860 properties = dict(change_id = self.tbl_changes.c.id,
2861 poolfiles = relation(PoolFile,
2862 secondary=self.tbl_changes_pool_files,
2863 backref="changeslinks"),
2864 seen = self.tbl_changes.c.seen,
2865 source = self.tbl_changes.c.source,
2866 binaries = self.tbl_changes.c.binaries,
2867 architecture = self.tbl_changes.c.architecture,
2868 distribution = self.tbl_changes.c.distribution,
2869 urgency = self.tbl_changes.c.urgency,
2870 maintainer = self.tbl_changes.c.maintainer,
2871 changedby = self.tbl_changes.c.changedby,
2872 date = self.tbl_changes.c.date,
2873 version = self.tbl_changes.c.version,
2874 files = relation(ChangePendingFile,
2875 secondary=self.tbl_changes_pending_files_map,
2876 backref="changesfile"),
2877 in_queue_id = self.tbl_changes.c.in_queue,
2878 in_queue = relation(PolicyQueue,
2879 primaryjoin=(self.tbl_changes.c.in_queue==self.tbl_policy_queue.c.id)),
2880 approved_for_id = self.tbl_changes.c.approved_for))
2882 mapper(ChangePendingBinary, self.tbl_changes_pending_binaries,
2883 properties = dict(change_pending_binary_id = self.tbl_changes_pending_binaries.c.id))
2885 mapper(ChangePendingFile, self.tbl_changes_pending_files,
2886 properties = dict(change_pending_file_id = self.tbl_changes_pending_files.c.id,
2887 filename = self.tbl_changes_pending_files.c.filename,
2888 size = self.tbl_changes_pending_files.c.size,
2889 md5sum = self.tbl_changes_pending_files.c.md5sum,
2890 sha1sum = self.tbl_changes_pending_files.c.sha1sum,
2891 sha256sum = self.tbl_changes_pending_files.c.sha256sum))
2893 mapper(ChangePendingSource, self.tbl_changes_pending_source,
2894 properties = dict(change_pending_source_id = self.tbl_changes_pending_source.c.id,
2895 change = relation(DBChange),
2896 maintainer = relation(Maintainer,
2897 primaryjoin=(self.tbl_changes_pending_source.c.maintainer_id==self.tbl_maintainer.c.id)),
2898 changedby = relation(Maintainer,
2899 primaryjoin=(self.tbl_changes_pending_source.c.changedby_id==self.tbl_maintainer.c.id)),
2900 fingerprint = relation(Fingerprint),
2901 source_files = relation(ChangePendingFile,
2902 secondary=self.tbl_changes_pending_source_files,
2903 backref="pending_sources")))
2904 mapper(KeyringACLMap, self.tbl_keyring_acl_map,
2905 properties = dict(keyring_acl_map_id = self.tbl_keyring_acl_map.c.id,
2906 keyring = relation(Keyring, backref="keyring_acl_map"),
2907 architecture = relation(Architecture)))
2909 mapper(Location, self.tbl_location,
2910 properties = dict(location_id = self.tbl_location.c.id,
2911 component_id = self.tbl_location.c.component,
2912 component = relation(Component),
2913 archive_id = self.tbl_location.c.archive,
2914 archive = relation(Archive),
2915 archive_type = self.tbl_location.c.type))
2917 mapper(Maintainer, self.tbl_maintainer,
2918 properties = dict(maintainer_id = self.tbl_maintainer.c.id))
2920 mapper(NewComment, self.tbl_new_comments,
2921 properties = dict(comment_id = self.tbl_new_comments.c.id))
2923 mapper(Override, self.tbl_override,
2924 properties = dict(suite_id = self.tbl_override.c.suite,
2925 suite = relation(Suite),
2926 component_id = self.tbl_override.c.component,
2927 component = relation(Component),
2928 priority_id = self.tbl_override.c.priority,
2929 priority = relation(Priority),
2930 section_id = self.tbl_override.c.section,
2931 section = relation(Section),
2932 overridetype_id = self.tbl_override.c.type,
2933 overridetype = relation(OverrideType)))
2935 mapper(OverrideType, self.tbl_override_type,
2936 properties = dict(overridetype = self.tbl_override_type.c.type,
2937 overridetype_id = self.tbl_override_type.c.id))
2939 mapper(PolicyQueue, self.tbl_policy_queue,
2940 properties = dict(policy_queue_id = self.tbl_policy_queue.c.id))
2942 mapper(Priority, self.tbl_priority,
2943 properties = dict(priority_id = self.tbl_priority.c.id))
2945 mapper(Section, self.tbl_section,
2946 properties = dict(section_id = self.tbl_section.c.id))
2948 mapper(DBSource, self.tbl_source,
2949 properties = dict(source_id = self.tbl_source.c.id,
2950 version = self.tbl_source.c.version,
2951 maintainer_id = self.tbl_source.c.maintainer,
2952 maintainer = relation(Maintainer,
2953 primaryjoin=(self.tbl_source.c.maintainer==self.tbl_maintainer.c.id)),
2954 poolfile_id = self.tbl_source.c.file,
2955 poolfile = relation(PoolFile),
2956 fingerprint_id = self.tbl_source.c.sig_fpr,
2957 fingerprint = relation(Fingerprint),
2958 changedby_id = self.tbl_source.c.changedby,
2959 changedby = relation(Maintainer,
2960 primaryjoin=(self.tbl_source.c.changedby==self.tbl_maintainer.c.id)),
2961 srcfiles = relation(DSCFile,
2962 primaryjoin=(self.tbl_source.c.id==self.tbl_dsc_files.c.source)),
2963 srcassociations = relation(SrcAssociation,
2964 primaryjoin=(self.tbl_source.c.id==self.tbl_src_associations.c.source)),
2965 srcuploaders = relation(SrcUploader)))
2967 mapper(SourceACL, self.tbl_source_acl,
2968 properties = dict(source_acl_id = self.tbl_source_acl.c.id))
2970 mapper(SrcAssociation, self.tbl_src_associations,
2971 properties = dict(sa_id = self.tbl_src_associations.c.id,
2972 suite_id = self.tbl_src_associations.c.suite,
2973 suite = relation(Suite),
2974 source_id = self.tbl_src_associations.c.source,
2975 source = relation(DBSource)))
2977 mapper(SrcFormat, self.tbl_src_format,
2978 properties = dict(src_format_id = self.tbl_src_format.c.id,
2979 format_name = self.tbl_src_format.c.format_name))
2981 mapper(SrcUploader, self.tbl_src_uploaders,
2982 properties = dict(uploader_id = self.tbl_src_uploaders.c.id,
2983 source_id = self.tbl_src_uploaders.c.source,
2984 source = relation(DBSource,
2985 primaryjoin=(self.tbl_src_uploaders.c.source==self.tbl_source.c.id)),
2986 maintainer_id = self.tbl_src_uploaders.c.maintainer,
2987 maintainer = relation(Maintainer,
2988 primaryjoin=(self.tbl_src_uploaders.c.maintainer==self.tbl_maintainer.c.id))))
2990 mapper(Suite, self.tbl_suite,
2991 properties = dict(suite_id = self.tbl_suite.c.id,
2992 policy_queue = relation(PolicyQueue),
2993 copy_queues = relation(BuildQueue, secondary=self.tbl_suite_build_queue_copy)))
2995 mapper(SuiteArchitecture, self.tbl_suite_architectures,
2996 properties = dict(suite_id = self.tbl_suite_architectures.c.suite,
2997 suite = relation(Suite, backref='suitearchitectures'),
2998 arch_id = self.tbl_suite_architectures.c.architecture,
2999 architecture = relation(Architecture)))
3001 mapper(SuiteSrcFormat, self.tbl_suite_src_formats,
3002 properties = dict(suite_id = self.tbl_suite_src_formats.c.suite,
3003 suite = relation(Suite, backref='suitesrcformats'),
3004 src_format_id = self.tbl_suite_src_formats.c.src_format,
3005 src_format = relation(SrcFormat)))
3007 mapper(Uid, self.tbl_uid,
3008 properties = dict(uid_id = self.tbl_uid.c.id,
3009 fingerprint = relation(Fingerprint)))
3011 mapper(UploadBlock, self.tbl_upload_blocks,
3012 properties = dict(upload_block_id = self.tbl_upload_blocks.c.id,
3013 fingerprint = relation(Fingerprint, backref="uploadblocks"),
3014 uid = relation(Uid, backref="uploadblocks")))
3016 ## Connection functions
3017 def __createconn(self):
3018 from config import Config
3022 connstr = "postgres://%s" % cnf["DB::Host"]
3023 if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
3024 connstr += ":%s" % cnf["DB::Port"]
3025 connstr += "/%s" % cnf["DB::Name"]
3028 connstr = "postgres:///%s" % cnf["DB::Name"]
3029 if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
3030 connstr += "?port=%s" % cnf["DB::Port"]
3032 self.db_pg = create_engine(connstr, echo=self.debug)
3033 self.db_meta = MetaData()
3034 self.db_meta.bind = self.db_pg
3035 self.db_smaker = sessionmaker(bind=self.db_pg,
3039 self.__setuptables()
3040 self.__setupmappers()
3043 return self.db_smaker()
3045 __all__.append('DBConn')