]> git.decadent.org.uk Git - dak.git/blobdiff - daklib/dbconn.py
Merge branch 'master' into rm
[dak.git] / daklib / dbconn.py
index 8da26063607d7d501b224162fb1630118b48a5ff..1fa7974f5d0f5e2f2cdd4101c1621722bf86a731 100755 (executable)
 
 ################################################################################
 
+import apt_pkg
 import os
 from os.path import normpath
 import re
 import psycopg2
 import traceback
 import commands
+import signal
 
 try:
     # python >= 2.6
@@ -72,7 +74,7 @@ from sqlalchemy.orm.exc import NoResultFound
 # in the database
 from config import Config
 from textutils import fix_maintainer
-from dak_exceptions import DBUpdateError, NoSourceFieldError
+from dak_exceptions import DBUpdateError, NoSourceFieldError, FileExistsError
 
 # suppress some deprecation warnings in squeeze related to sqlalchemy
 import warnings
@@ -487,6 +489,11 @@ __all__.append('BinContents')
 
 ################################################################################
 
+def subprocess_setup():
+    # Python installs a SIGPIPE handler by default. This is usually not what
+    # non-Python subprocesses expect.
+    signal.signal(signal.SIGPIPE, signal.SIG_DFL)
+
 class DBBinary(ORMObject):
     def __init__(self, package = None, source = None, version = None, \
         maintainer = None, architecture = None, poolfile = None, \
@@ -525,7 +532,8 @@ class DBBinary(ORMObject):
         package does not contain any regular file.
         '''
         fullpath = self.poolfile.fullpath
-        dpkg = Popen(['dpkg-deb', '--fsys-tarfile', fullpath], stdout = PIPE)
+        dpkg = Popen(['dpkg-deb', '--fsys-tarfile', fullpath], stdout = PIPE,
+            preexec_fn = subprocess_setup)
         tar = TarFile.open(fileobj = dpkg.stdout, mode = 'r|')
         for member in tar.getmembers():
             if not member.isdir():
@@ -744,7 +752,7 @@ class BuildQueue(object):
             # Crude hack with open and append, but this whole section is and should be redone.
             if self.notautomatic:
                 release=open("Release", "a")
-                release.write("NotAutomatic: yes")
+                release.write("NotAutomatic: yes\n")
                 release.close()
 
             # Sign if necessary
@@ -883,6 +891,9 @@ class BuildQueue(object):
             else:
                 os.symlink(targetpath, queuepath)
                 qf.fileid = poolfile.file_id
+        except FileExistsError:
+            if not poolfile.identical_to(queuepath):
+                raise
         except OSError:
             return None
 
@@ -941,6 +952,9 @@ class BuildQueue(object):
             # Always copy files from policy queues as they might move around.
             import utils
             utils.copy(source, target)
+        except FileExistsError:
+            if not policyqueuefile.identical_to(target):
+                raise
         except OSError:
             return None
 
@@ -1036,6 +1050,24 @@ class ChangePendingFile(object):
     def __repr__(self):
         return '<ChangePendingFile %s>' % self.change_pending_file_id
 
+    def identical_to(self, filename):
+        """
+        compare size and hash with the given file
+
+        @rtype: bool
+        @return: true if the given file has the same size and hash as this object; false otherwise
+        """
+        st = os.stat(filename)
+        if self.size != st.st_size:
+            return False
+
+        f = open(filename, "r")
+        sha256sum = apt_pkg.sha256sum(f)
+        if sha256sum != self.sha256sum:
+            return False
+
+        return True
+
 __all__.append('ChangePendingFile')
 
 ################################################################################
@@ -1100,6 +1132,19 @@ def get_component(component, session=None):
 
 __all__.append('get_component')
 
+@session_wrapper
+def get_component_names(session=None):
+    """
+    Returns list of strings of component names.
+
+    @rtype: list
+    @return: list of strings of component names
+    """
+
+    return [ x.component_name for x in session.query(Component).all() ]
+
+__all__.append('get_component_names')
+
 ################################################################################
 
 class DBConfig(object):
@@ -1352,6 +1397,17 @@ __all__.append('get_dscfiles')
 
 ################################################################################
 
+class ExternalOverride(ORMObject):
+    def __init__(self, *args, **kwargs):
+        pass
+
+    def __repr__(self):
+        return '<ExternalOverride %s = %s: %s>' % (self.package, self.key, self.value)
+
+__all__.append('ExternalOverride')
+
+################################################################################
+
 class PoolFile(ORMObject):
     def __init__(self, filename = None, location = None, filesize = -1, \
         md5sum = None):
@@ -1374,6 +1430,24 @@ class PoolFile(ORMObject):
     def not_null_constraints(self):
         return ['filename', 'md5sum', 'location']
 
+    def identical_to(self, filename):
+        """
+        compare size and hash with the given file
+
+        @rtype: bool
+        @return: true if the given file has the same size and hash as this object; false otherwise
+        """
+        st = os.stat(filename)
+        if self.filesize != st.st_size:
+            return False
+
+        f = open(filename, "r")
+        sha256sum = apt_pkg.sha256sum(f)
+        if sha256sum != self.sha256sum:
+            return False
+
+        return True
+
 __all__.append('PoolFile')
 
 @session_wrapper
@@ -1716,6 +1790,34 @@ def get_keyring(keyring, session=None):
 
 __all__.append('get_keyring')
 
+@session_wrapper
+def get_active_keyring_paths(session=None):
+    """
+    @rtype: list
+    @return: list of active keyring paths
+    """
+    return [ x.keyring_name for x in session.query(Keyring).filter(Keyring.active == True).order_by(desc(Keyring.priority)).all() ]
+
+__all__.append('get_active_keyring_paths')
+
+@session_wrapper
+def get_primary_keyring_path(session=None):
+    """
+    Get the full path to the highest priority active keyring
+
+    @rtype: str or None
+    @return: path to the active keyring with the highest priority or None if no
+             keyring is configured
+    """
+    keyrings = get_active_keyring_paths()
+
+    if len(keyrings) > 0:
+        return keyrings[0]
+    else:
+        return None
+
+__all__.append('get_primary_keyring_path')
+
 ################################################################################
 
 class KeyringACLMap(object):
@@ -2389,6 +2491,9 @@ class DBSource(ORMObject):
 
     metadata = association_proxy('key', 'value')
 
+    def get_component_name(self):
+        return self.poolfile.location.component.component_name
+
     def scan_contents(self):
         '''
         Returns a set of names for non directories. The path names are
@@ -2446,20 +2551,13 @@ def source_exists(source, source_version, suites = ["any"], session=None):
         q = session.query(DBSource).filter_by(source=source). \
             filter(DBSource.version.in_([source_version, orig_source_version]))
         if suite != "any":
-            # source must exist in suite X, or in some other suite that's
-            # mapped to X, recursively... silent-maps are counted too,
-            # unreleased-maps aren't.
-            maps = cnf.ValueList("SuiteMappings")[:]
-            maps.reverse()
-            maps = [ m.split() for m in maps ]
-            maps = [ (x[1], x[2]) for x in maps
-                            if x[0] == "map" or x[0] == "silent-map" ]
-            s = [suite]
-            for (from_, to) in maps:
-                if from_ in s and to not in s:
-                    s.append(to)
-
-            q = q.filter(DBSource.suites.any(Suite.suite_name.in_(s)))
+            # source must exist in 'suite' or a suite that is enhanced by 'suite'
+            s = get_suite(suite, session)
+            enhances_vcs = session.query(VersionCheck).filter(VersionCheck.suite==s).filter_by(check='Enhances')
+            considered_suites = [ vc.reference for vc in enhances_vcs ]
+            considered_suites.append(s)
+
+            q = q.filter(DBSource.suites.any(Suite.suite_id.in_([s.suite_id for s in considered_suites])))
 
         if q.count() > 0:
             continue
@@ -2581,6 +2679,15 @@ __all__.append('import_metadata_into_db')
 
 ################################################################################
 
+def split_uploaders(uploaders_list):
+    '''
+    Split the Uploaders field into the individual uploaders and yield each of
+    them. Beware: email addresses might contain commas.
+    '''
+    import re
+    for uploader in re.sub(">[ ]*,", ">\t", uploaders_list).split("\t"):
+        yield uploader.strip()
+
 @session_wrapper
 def add_dsc_to_db(u, filename, session=None):
     entry = u.pkg.files[filename]
@@ -2590,7 +2697,11 @@ def add_dsc_to_db(u, filename, session=None):
     source.source = u.pkg.dsc["source"]
     source.version = u.pkg.dsc["version"] # NB: not files[file]["version"], that has no epoch
     source.maintainer_id = get_or_set_maintainer(u.pkg.dsc["maintainer"], session).maintainer_id
-    source.changedby_id = get_or_set_maintainer(u.pkg.changes["changed-by"], session).maintainer_id
+    # If Changed-By isn't available, fall back to maintainer
+    if u.pkg.changes.has_key("changed-by"):
+        source.changedby_id = get_or_set_maintainer(u.pkg.changes["changed-by"], session).maintainer_id
+    else:
+        source.changedby_id = get_or_set_maintainer(u.pkg.dsc["maintainer"], session).maintainer_id
     source.fingerprint_id = get_or_set_fingerprint(u.pkg.changes["fingerprint"], session).fingerprint_id
     source.install_date = datetime.now().date()
 
@@ -2663,10 +2774,11 @@ def add_dsc_to_db(u, filename, session=None):
         session.add(df)
 
     # Add the src_uploaders to the DB
+    session.flush()
+    session.refresh(source)
     source.uploaders = [source.maintainer]
     if u.pkg.dsc.has_key("uploaders"):
-        for up in u.pkg.dsc["uploaders"].replace(">, ", ">\t").split("\t"):
-            up = up.strip()
+        for up in split_uploaders(u.pkg.dsc["uploaders"]):
             source.uploaders.append(get_or_set_maintainer(up, session))
 
     session.flush()
@@ -2861,6 +2973,12 @@ class Suite(ORMObject):
         return session.query(DBSource).filter_by(source = source). \
             with_parent(self)
 
+    def get_overridesuite(self):
+        if self.overridesuite is None:
+            return self
+        else:
+            return object_session(self).query(Suite).filter_by(suite_name=self.overridesuite).one()
+
 __all__.append('Suite')
 
 @session_wrapper
@@ -2890,11 +3008,11 @@ __all__.append('get_suite')
 
 ################################################################################
 
-# TODO: should be removed because the implementation is too trivial
 @session_wrapper
 def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None):
     """
-    Returns list of Architecture objects for given C{suite} name
+    Returns list of Architecture objects for given C{suite} name. The list is
+    empty if suite does not exist.
 
     @type suite: str
     @param suite: Suite name to search for
@@ -2915,48 +3033,15 @@ def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None):
     @return: list of Architecture objects for the given name (may be empty)
     """
 
-    return get_suite(suite, session).get_architectures(skipsrc, skipall)
+    try:
+        return get_suite(suite, session).get_architectures(skipsrc, skipall)
+    except AttributeError:
+        return []
 
 __all__.append('get_suite_architectures')
 
 ################################################################################
 
-class SuiteSrcFormat(object):
-    def __init__(self, *args, **kwargs):
-        pass
-
-    def __repr__(self):
-        return '<SuiteSrcFormat (%s, %s)>' % (self.suite_id, self.src_format_id)
-
-__all__.append('SuiteSrcFormat')
-
-@session_wrapper
-def get_suite_src_formats(suite, session=None):
-    """
-    Returns list of allowed SrcFormat for C{suite}.
-
-    @type suite: str
-    @param suite: Suite name to search for
-
-    @type session: Session
-    @param session: Optional SQL session object (a temporary one will be
-    generated if not supplied)
-
-    @rtype: list
-    @return: the list of allowed source formats for I{suite}
-    """
-
-    q = session.query(SrcFormat)
-    q = q.join(SuiteSrcFormat)
-    q = q.join(Suite).filter_by(suite_name=suite)
-    q = q.order_by('format_name')
-
-    return q.all()
-
-__all__.append('get_suite_src_formats')
-
-################################################################################
-
 class Uid(ORMObject):
     def __init__(self, uid = None, name = None):
         self.uid = uid
@@ -3136,7 +3221,9 @@ __all__.append('VersionCheck')
 def get_version_checks(suite_name, check = None, session = None):
     suite = get_suite(suite_name, session)
     if not suite:
-        return None
+        # Make sure that what we return is iterable so that list comprehensions
+        # involving this don't cause a traceback
+        return []
     q = session.query(VersionCheck).filter_by(suite=suite)
     if check:
         q = q.filter_by(check=check)
@@ -3184,6 +3271,7 @@ class DBConn(object):
             'changes_pending_source_files',
             'changes_pool_files',
             'dsc_files',
+            'external_overrides',
             'extra_src_references',
             'files',
             'fingerprint',
@@ -3318,6 +3406,13 @@ class DBConn(object):
                                  poolfile_id = self.tbl_dsc_files.c.file,
                                  poolfile = relation(PoolFile)))
 
+        mapper(ExternalOverride, self.tbl_external_overrides,
+                properties = dict(
+                    suite_id = self.tbl_external_overrides.c.suite,
+                    suite = relation(Suite),
+                    component_id = self.tbl_external_overrides.c.component,
+                    component = relation(Component)))
+
         mapper(PoolFile, self.tbl_files,
                properties = dict(file_id = self.tbl_files.c.id,
                                  filesize = self.tbl_files.c.size,
@@ -3479,15 +3574,11 @@ class DBConn(object):
                properties = dict(suite_id = self.tbl_suite.c.id,
                                  policy_queue = relation(PolicyQueue),
                                  copy_queues = relation(BuildQueue,
-                                     secondary=self.tbl_suite_build_queue_copy)),
+                                     secondary=self.tbl_suite_build_queue_copy),
+                                 srcformats = relation(SrcFormat, secondary=self.tbl_suite_src_formats,
+                                     backref=backref('suites', lazy='dynamic'))),
                 extension = validator)
 
-        mapper(SuiteSrcFormat, self.tbl_suite_src_formats,
-               properties = dict(suite_id = self.tbl_suite_src_formats.c.suite,
-                                 suite = relation(Suite, backref='suitesrcformats'),
-                                 src_format_id = self.tbl_suite_src_formats.c.src_format,
-                                 src_format = relation(SrcFormat)))
-
         mapper(Uid, self.tbl_uid,
                properties = dict(uid_id = self.tbl_uid.c.id,
                                  fingerprint = relation(Fingerprint)),
@@ -3579,15 +3670,21 @@ class DBConn(object):
 
         sqlalchemy.dialects.postgresql.base.dialect = PGDialect_psycopg2_dak
 
-        self.db_pg   = create_engine(connstr, **engine_args)
-        self.db_meta = MetaData()
-        self.db_meta.bind = self.db_pg
-        self.db_smaker = sessionmaker(bind=self.db_pg,
-                                      autoflush=True,
-                                      autocommit=False)
+        try:
+            self.db_pg   = create_engine(connstr, **engine_args)
+            self.db_meta = MetaData()
+            self.db_meta.bind = self.db_pg
+            self.db_smaker = sessionmaker(bind=self.db_pg,
+                                          autoflush=True,
+                                          autocommit=False)
+
+            self.__setuptables()
+            self.__setupmappers()
+
+        except OperationalError, e:
+            import utils
+            utils.fubar("Cannot connect to database (%s)" % str(e))
 
-        self.__setuptables()
-        self.__setupmappers()
         self.pid = os.getpid()
 
     def session(self, work_mem = 0):