dbconn.py 77.3 KB
Newer Older
1
#!/usr/bin/python
M
Mark Hymers 已提交
2

3 4 5 6 7
""" DB access class

@contact: Debian FTPMaster <ftpmaster@debian.org>
@copyright: 2000, 2001, 2002, 2003, 2004, 2006  James Troup <james@nocrew.org>
@copyright: 2008-2009  Mark Hymers <mhy@debian.org>
J
Joerg Jaspert 已提交
8
@copyright: 2009, 2010  Joerg Jaspert <joerg@debian.org>
9
@copyright: 2009  Mike O'Connor <stew@debian.org>
10 11
@license: GNU General Public License version 2 or later
"""
M
Mark Hymers 已提交
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

################################################################################

# < mhy> I need a funny comment
# < sgran> two peanuts were walking down a dark street
# < sgran> one was a-salted
#  * mhy looks up the definition of "funny"

################################################################################

B
Bastian Blank 已提交
36 37
from __future__ import absolute_import, print_function

38
import apt_pkg
39
import daklib.daksubprocess
40
import functools
41
import os
42
from os.path import normpath
M
Mark Hymers 已提交
43
import re
44
import subprocess
T
Torsten Werner 已提交
45

46
from debian.debfile import Deb822
47
from tarfile import TarFile
M
Mark Hymers 已提交
48

49 50
from inspect import getargspec

51
import sqlalchemy
A
Ansgar Burchardt 已提交
52
from sqlalchemy import create_engine, Table, desc
53
from sqlalchemy.orm import sessionmaker, mapper, relation, object_session, \
A
Ansgar Burchardt 已提交
54
    backref, object_mapper
55
import sqlalchemy.types
56 57
from sqlalchemy.orm.collections import attribute_mapped_collection
from sqlalchemy.ext.associationproxy import association_proxy
M
Mark Hymers 已提交
58

M
Mark Hymers 已提交
59 60
# Don't remove this, we re-export the exceptions to scripts which import us
from sqlalchemy.exc import *
61
from sqlalchemy.orm.exc import NoResultFound
M
Mark Hymers 已提交
62

63
from .aptversion import AptVersion
64 65
# Only import Config until Queue stuff is changed to store its config
# in the database
66 67
from .config import Config
from .textutils import fix_maintainer
M
Mark Hymers 已提交
68

69 70
# suppress some deprecation warnings in squeeze related to sqlalchemy
import warnings
71 72
warnings.filterwarnings('ignore',
    "The SQLAlchemy PostgreSQL dialect has been renamed from 'postgres' to 'postgresql'.*",
73
    SADeprecationWarning)
74 75
warnings.filterwarnings('ignore',
    "Predicate of partial index .* ignored during reflection",
76
    SAWarning)
77

B
Bastian Blank 已提交
78 79
from .database.base import Base

80

M
Mark Hymers 已提交
81 82
################################################################################

83 84 85
# Patch in support for the debversion field type so that it works during
# reflection

86
class DebVersion(sqlalchemy.types.UserDefinedType):
87 88 89
    def get_col_spec(self):
        return "DEBVERSION"

90 91 92
    def bind_processor(self, dialect):
        return None

93
    def result_processor(self, dialect, coltype):
94 95
        return None

96 97
from sqlalchemy.databases import postgres
postgres.ischema_names['debversion'] = DebVersion
98 99 100

################################################################################

101
__all__ = ['IntegrityError', 'SQLAlchemyError', 'DebVersion']
102 103 104

################################################################################

105

106
def session_wrapper(fn):
C
Chris Lamb 已提交
107 108 109 110
    """
    Wrapper around common ".., session=None):" handling. If the wrapped
    function is called without passing 'session', we create a local one
    and destroy it when the function ends.
111 112 113 114

    Also attaches a commit_or_flush method to the session; if we created a
    local session, this is a synonym for session.commit(), otherwise it is a
    synonym for session.flush().
C
Chris Lamb 已提交
115 116
    """

117 118 119
    def wrapped(*args, **kwargs):
        private_transaction = False

120
        # Find the session object
C
Chris Lamb 已提交
121 122 123
        session = kwargs.get('session')

        if session is None:
124 125 126 127 128 129 130
            if len(args) <= len(getargspec(fn)[0]) - 1:
                # No session specified as last argument or in kwargs
                private_transaction = True
                session = kwargs['session'] = DBConn().session()
            else:
                # Session is last argument in args
                session = args[-1]
131
                if session is None:
M
fixup  
Mark Hymers 已提交
132
                    args = list(args)
133 134
                    session = args[-1] = DBConn().session()
                    private_transaction = True
135 136 137 138 139

        if private_transaction:
            session.commit_or_flush = session.commit
        else:
            session.commit_or_flush = session.flush
140 141 142 143 144 145

        try:
            return fn(*args, **kwargs)
        finally:
            if private_transaction:
                # We created a session; close it.
146
                session.close()
147

148
    wrapped.__doc__ = fn.__doc__
149
    wrapped.__name__ = fn.__name__
150

151 152
    return wrapped

F
Frank Lichtenheld 已提交
153 154
__all__.append('session_wrapper')

155 156
################################################################################

157

158 159 160
class ORMObject(object):
    """
    ORMObject is a base class for all ORM classes mapped by SQLalchemy. All
T
Torsten Werner 已提交
161
    derived classes must implement the properties() method.
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
    """

    def properties(self):
        '''
        This method should be implemented by all derived classes and returns a
        list of the important properties. The properties 'created' and
        'modified' will be added automatically. A suffix '_count' should be
        added to properties that are lists or query objects. The most important
        property name should be returned as the first element in the list
        because it is used by repr().
        '''
        return []

    def classname(self):
        '''
        Returns the name of the class.
        '''
        return type(self).__name__

    def __repr__(self):
        '''
        Returns a short string representation of the object using the first
        element from the properties() method.
        '''
        primary_property = self.properties()[0]
        value = getattr(self, primary_property)
        return '<%s %s>' % (self.classname(), str(value))

    def __str__(self):
        '''
        Returns a human readable form of the object using the properties()
        method.
        '''
195
        return '<%s(...)>' % (self.classname())
196

197 198
    @classmethod
    @session_wrapper
199
    def get(cls, primary_key,  session=None):
200 201 202 203 204 205 206 207 208 209 210 211
        '''
        This is a support function that allows getting an object by its primary
        key.

        Architecture.get(3[, session])

        instead of the more verbose

        session.query(Architecture).get(3)
        '''
        return session.query(cls).get(primary_key)

212
    def session(self, replace=False):
213 214 215 216 217 218 219
        '''
        Returns the current session that is associated with the object. May
        return None is object is in detached state.
        '''

        return object_session(self)

220
    def clone(self, session=None):
T
Tollef Fog Heen 已提交
221
        """
222 223
        Clones the current object in a new session and returns the new clone. A
        fresh session is created if the optional session parameter is not
224 225
        provided. The function will fail if a session is provided and has
        unflushed changes.
226

227 228 229
        RATIONALE: SQLAlchemy's session is not thread safe. This method clones
        an existing object to allow several threads to work with their own
        instances of an ORMObject.
230

231 232 233
        WARNING: Only persistent (committed) objects can be cloned. Changes
        made to the original object that are not committed yet will get lost.
        The session of the new object will always be rolled back to avoid
T
Tollef Fog Heen 已提交
234 235
        resource leaks.
        """
236 237

        if self.session() is None:
238
            raise RuntimeError(
239
                'Method clone() failed for detached object:\n%s' % self)
240 241 242 243
        self.session().flush()
        mapper = object_mapper(self)
        primary_key = mapper.primary_key_from_instance(self)
        object_class = self.__class__
244 245 246
        if session is None:
            session = DBConn().session()
        elif len(session.new) + len(session.dirty) + len(session.deleted) > 0:
247
            raise RuntimeError(
248
                'Method clone() failed due to unflushed changes in session.')
249
        new_object = session.query(object_class).get(primary_key)
250
        session.rollback()
251
        if new_object is None:
252
            raise RuntimeError(
253 254 255
                'Method clone() failed for non-persistent object:\n%s' % self)
        return new_object

256 257 258 259
__all__.append('ORMObject')

################################################################################

260

261 262 263 264 265 266
class ACL(ORMObject):
    def __repr__(self):
        return "<ACL {0}>".format(self.name)

__all__.append('ACL')

267

268 269 270 271 272 273 274 275
class ACLPerSource(ORMObject):
    def __repr__(self):
        return "<ACLPerSource acl={0} fingerprint={1} source={2} reason={3}>".format(self.acl.name, self.fingerprint.fingerprint, self.source, self.reason)

__all__.append('ACLPerSource')

################################################################################

276

277
from .database.architecture import Architecture
M
Mark Hymers 已提交
278

279 280
__all__.append('Architecture')

281

282
@session_wrapper
283 284 285 286 287 288 289 290 291 292 293 294 295 296
def get_architecture(architecture, session=None):
    """
    Returns database id for given C{architecture}.

    @type architecture: string
    @param architecture: The name of the architecture

    @type session: Session
    @param session: Optional SQLA session object (a temporary one will be
    generated if not supplied)

    @rtype: Architecture
    @return: Architecture object for the given arch (None if not present)
    """
297

298
    q = session.query(Architecture).filter_by(arch_string=architecture)
299

300 301 302 303
    try:
        return q.one()
    except NoResultFound:
        return None
304

305 306
__all__.append('get_architecture')

M
Mark Hymers 已提交
307 308
################################################################################

309

M
Mark Hymers 已提交
310
class Archive(object):
M
Mark Hymers 已提交
311 312
    def __init__(self, *args, **kwargs):
        pass
M
Mark Hymers 已提交
313 314

    def __repr__(self):
C
Chris Lamb 已提交
315
        return '<Archive %s>' % self.archive_name
M
Mark Hymers 已提交
316

317 318
__all__.append('Archive')

319

320
@session_wrapper
321 322
def get_archive(archive, session=None):
    """
F
Frank Lichtenheld 已提交
323
    returns database id for given C{archive}.
324 325 326 327 328 329 330 331 332 333 334 335 336

    @type archive: string
    @param archive: the name of the arhive

    @type session: Session
    @param session: Optional SQLA session object (a temporary one will be
    generated if not supplied)

    @rtype: Archive
    @return: Archive object for the given name (None if not present)

    """
    archive = archive.lower()
337

338
    q = session.query(Archive).filter_by(archive_name=archive)
339

340 341 342 343
    try:
        return q.one()
    except NoResultFound:
        return None
344

345
__all__.append('get_archive')
346

M
Mark Hymers 已提交
347 348
################################################################################

349

350 351 352 353 354
class ArchiveFile(object):
    def __init__(self, archive=None, component=None, file=None):
        self.archive = archive
        self.component = component
        self.file = file
355

356 357 358 359 360 361 362 363
    @property
    def path(self):
        return os.path.join(self.archive.path, 'pool', self.component.component_name, self.file.filename)

__all__.append('ArchiveFile')

################################################################################

364

365
class BinContents(ORMObject):
366
    def __init__(self, file=None, binary=None):
367 368 369 370
        self.file = file
        self.binary = binary

    def properties(self):
371
        return ['file', 'binary']
M
Mike O'Connor 已提交
372 373 374 375 376

__all__.append('BinContents')

################################################################################

377

378
class DBBinary(ORMObject):
379 380
    def __init__(self, package=None, source=None, version=None,
        maintainer=None, architecture=None, poolfile=None,
381
        binarytype='deb', fingerprint=None):
382 383 384 385 386 387 388
        self.package = package
        self.source = source
        self.version = version
        self.maintainer = maintainer
        self.architecture = architecture
        self.poolfile = poolfile
        self.binarytype = binarytype
389
        self.fingerprint = fingerprint
M
Mark Hymers 已提交
390

M
Mark Hymers 已提交
391 392 393 394
    @property
    def pkid(self):
        return self.binary_id

395
    def properties(self):
396 397
        return ['package', 'version', 'maintainer', 'source', 'architecture',
            'poolfile', 'binarytype', 'fingerprint', 'install_date',
M
Mark Hymers 已提交
398
            'suites_count', 'binary_id', 'contents_count', 'extra_sources']
399

400 401
    metadata = association_proxy('key', 'value')

402 403 404
    def scan_contents(self):
        '''
        Yields the contents of the package. Only regular files are yielded and
405 406 407
        the path names are normalized after converting them from either utf-8
        or iso8859-1 encoding. It yields the string ' <EMPTY PACKAGE>' if the
        package does not contain any regular file.
408 409
        '''
        fullpath = self.poolfile.fullpath
410 411
        dpkg_cmd = ('dpkg-deb', '--fsys-tarfile', fullpath)
        dpkg = daklib.daksubprocess.Popen(dpkg_cmd, stdout=subprocess.PIPE)
412
        tar = TarFile.open(fileobj=dpkg.stdout, mode='r|')
413
        for member in tar.getmembers():
414
            if not member.isdir():
415 416
                name = normpath(member.name)
                # enforce proper utf-8 encoding
417
                try:
418
                    name.decode('utf-8')
T
bugfix  
Torsten Werner 已提交
419
                except UnicodeDecodeError:
420 421
                    name = name.decode('iso8859-1').encode('utf-8')
                yield name
422
        tar.close()
423 424
        dpkg.stdout.close()
        dpkg.wait()
425

M
Mark Hymers 已提交
426 427 428 429
    def read_control(self):
        '''
        Reads the control information from a binary.

M
Mark Hymers 已提交
430 431
        @rtype: text
        @return: stanza text of the control section.
M
Mark Hymers 已提交
432
        '''
B
Bastian Blank 已提交
433
        from . import utils
M
Mark Hymers 已提交
434
        fullpath = self.poolfile.fullpath
435 436
        with open(fullpath, 'r') as deb_file:
            return utils.deb_extract_control(deb_file)
M
Mark Hymers 已提交
437 438 439 440 441

    def read_control_fields(self):
        '''
        Reads the control information from a binary and return
        as a dictionary.
M
Mark Hymers 已提交
442

M
Mark Hymers 已提交
443 444 445 446 447
        @rtype: dict
        @return: fields of the control section as a dictionary.
        '''
        stanza = self.read_control()
        return apt_pkg.TagSection(stanza)
M
Mark Hymers 已提交
448

449 450 451 452 453 454
    @property
    def proxy(self):
        session = object_session(self)
        query = session.query(BinaryMetadata).filter_by(binary=self)
        return MetadataProxy(session, query)

455
__all__.append('DBBinary')
456

457

458
@session_wrapper
459 460 461 462
def get_suites_binary_in(package, session=None):
    """
    Returns list of Suite objects which given C{package} name is in

J
Joerg Jaspert 已提交
463 464
    @type package: str
    @param package: DBBinary package name to search for
465 466 467 468 469

    @rtype: list
    @return: list of Suite objects for the given package
    """

470
    return session.query(Suite).filter(Suite.binaries.any(DBBinary.package == package)).all()
471 472 473

__all__.append('get_suites_binary_in')

474

475
@session_wrapper
476
def get_component_by_package_suite(package, suite_list, arch_list=[], session=None):
477 478
    '''
    Returns the component name of the newest binary package in suite_list or
479 480
    None if no package is found. The result can be optionally filtered by a list
    of architecture names.
481 482 483

    @type package: str
    @param package: DBBinary package name to search for
484

485 486 487
    @type suite_list: list of str
    @param suite_list: list of suite_name items

488 489 490
    @type arch_list: list of str
    @param arch_list: optional list of arch_string items that defaults to []

491 492 493 494
    @rtype: str or NoneType
    @return: name of component or None
    '''

495
    q = session.query(DBBinary).filter_by(package=package). \
496 497 498 499 500
        join(DBBinary.suites).filter(Suite.suite_name.in_(suite_list))
    if len(arch_list) > 0:
        q = q.join(DBBinary.architecture). \
            filter(Architecture.arch_string.in_(arch_list))
    binary = q.order_by(desc(DBBinary.version)).first()
501 502 503
    if binary is None:
        return None
    else:
504
        return binary.poolfile.component.component_name
505 506

__all__.append('get_component_by_package_suite')
M
Mark Hymers 已提交
507

M
Mark Hymers 已提交
508 509
################################################################################

510

511 512 513 514 515
class BuildQueue(object):
    def __init__(self, *args, **kwargs):
        pass

    def __repr__(self):
516
        return '<BuildQueue %s>' % self.queue_name
517 518 519 520 521

__all__.append('BuildQueue')

################################################################################

522

523
class Component(ORMObject):
524
    def __init__(self, component_name=None):
525
        self.component_name = component_name
M
Mark Hymers 已提交
526

527 528 529 530 531 532 533 534 535 536 537 538
    def __eq__(self, val):
        if isinstance(val, str):
            return (self.component_name == val)
        # This signals to use the normal comparison operator
        return NotImplemented

    def __ne__(self, val):
        if isinstance(val, str):
            return (self.component_name != val)
        # This signals to use the normal comparison operator
        return NotImplemented

539
    def properties(self):
540
        return ['component_name', 'component_id', 'description',
A
Ansgar Burchardt 已提交
541
            'meets_dfsg', 'overrides_count']
542

543 544
__all__.append('Component')

545

546
@session_wrapper
547 548 549 550 551 552 553 554 555 556 557 558
def get_component(component, session=None):
    """
    Returns database id for given C{component}.

    @type component: string
    @param component: The name of the override type

    @rtype: int
    @return: the database id for the given component

    """
    component = component.lower()
559

560
    q = session.query(Component).filter_by(component_name=component)
561

562 563 564 565
    try:
        return q.one()
    except NoResultFound:
        return None
566

567 568
__all__.append('get_component')

569

570 571 572 573 574 575 576 577 578 579
def get_mapped_component_name(component_name):
    cnf = Config()
    for m in cnf.value_list("ComponentMappings"):
        (src, dst) = m.split()
        if component_name == src:
            component_name = dst
    return component_name

__all__.append('get_mapped_component_name')

580

581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598
@session_wrapper
def get_mapped_component(component_name, session=None):
    """get component after mappings

    Evaluate component mappings from ComponentMappings in dak.conf for the
    given component name.

    @todo: ansgar wants to get rid of this. It's currently only used for
           the security archive

    @type  component_name: str
    @param component_name: component name

    @param session: database session

    @rtype:  L{daklib.dbconn.Component} or C{None}
    @return: component after applying maps or C{None}
    """
599
    component_name = get_mapped_component_name(component_name)
600 601 602 603 604
    component = session.query(Component).filter_by(component_name=component_name).first()
    return component

__all__.append('get_mapped_component')

605

M
Mark Hymers 已提交
606 607 608 609 610 611 612 613 614
@session_wrapper
def get_component_names(session=None):
    """
    Returns list of strings of component names.

    @rtype: list
    @return: list of strings of component names
    """

B
Bastian Blank 已提交
615
    return [x.component_name for x in session.query(Component).all()]
M
Mark Hymers 已提交
616 617 618

__all__.append('get_component_names')

M
Mark Hymers 已提交
619 620
################################################################################

621

M
Mark Hymers 已提交
622
class DBConfig(object):
M
Mark Hymers 已提交
623 624
    def __init__(self, *args, **kwargs):
        pass
M
Mark Hymers 已提交
625 626 627 628

    def __repr__(self):
        return '<DBConfig %s>' % self.name

629 630
__all__.append('DBConfig')

M
Mark Hymers 已提交
631 632
################################################################################

633

M
Mark Hymers 已提交
634
class DSCFile(object):
M
Mark Hymers 已提交
635 636
    def __init__(self, *args, **kwargs):
        pass
M
Mark Hymers 已提交
637 638 639 640

    def __repr__(self):
        return '<DSCFile %s>' % self.dscfile_id

641 642
__all__.append('DSCFile')

643

644
@session_wrapper
M
Mark Hymers 已提交
645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672
def get_dscfiles(dscfile_id=None, source_id=None, poolfile_id=None, session=None):
    """
    Returns a list of DSCFiles which may be empty

    @type dscfile_id: int (optional)
    @param dscfile_id: the dscfile_id of the DSCFiles to find

    @type source_id: int (optional)
    @param source_id: the source id related to the DSCFiles to find

    @type poolfile_id: int (optional)
    @param poolfile_id: the poolfile id related to the DSCFiles to find

    @rtype: list
    @return: Possibly empty list of DSCFiles
    """

    q = session.query(DSCFile)

    if dscfile_id is not None:
        q = q.filter_by(dscfile_id=dscfile_id)

    if source_id is not None:
        q = q.filter_by(source_id=source_id)

    if poolfile_id is not None:
        q = q.filter_by(poolfile_id=poolfile_id)

673
    return q.all()
M
Mark Hymers 已提交
674 675 676

__all__.append('get_dscfiles')

M
Mark Hymers 已提交
677 678
################################################################################

679

A
Ansgar Burchardt 已提交
680 681 682 683 684 685 686 687 688 689 690
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')

################################################################################

691

692
class PoolFile(ORMObject):
693
    def __init__(self, filename=None, filesize=-1,
694
        md5sum=None):
695 696 697
        self.filename = filename
        self.filesize = filesize
        self.md5sum = md5sum
M
Mark Hymers 已提交
698

M
Mark Hymers 已提交
699 700
    @property
    def fullpath(self):
701
        session = DBConn().session().object_session(self)
702 703 704
        af = session.query(ArchiveFile).join(Archive) \
                    .filter(ArchiveFile.file == self) \
                    .order_by(Archive.tainted.desc()).first()
705
        return af.path
M
Mark Hymers 已提交
706

707 708 709 710 711 712 713
    @property
    def component(self):
        session = DBConn().session().object_session(self)
        component_id = session.query(ArchiveFile.component_id).filter(ArchiveFile.file == self) \
                              .group_by(ArchiveFile.component_id).one()
        return session.query(Component).get(component_id)

714 715 716 717
    @property
    def basename(self):
        return os.path.basename(self.filename)

718
    def is_valid(self, filesize=-1, md5sum=None):
719
        return self.filesize == long(filesize) and self.md5sum == md5sum
T
Torsten Werner 已提交
720

721
    def properties(self):
722
        return ['filename', 'file_id', 'filesize', 'md5sum', 'sha1sum',
723
            'sha256sum', 'source', 'binary', 'last_used']
724

725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742
    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

743 744
__all__.append('PoolFile')

M
Mark Hymers 已提交
745 746
################################################################################

747

748
class Fingerprint(ORMObject):
749
    def __init__(self, fingerprint=None):
T
Torsten Werner 已提交
750
        self.fingerprint = fingerprint
M
Mark Hymers 已提交
751

752
    def properties(self):
753
        return ['fingerprint', 'fingerprint_id', 'keyring', 'uid',
754 755
            'binary_reject']

756 757
__all__.append('Fingerprint')

758

M
Mark Hymers 已提交
759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785
@session_wrapper
def get_fingerprint(fpr, session=None):
    """
    Returns Fingerprint object for given fpr.

    @type fpr: string
    @param fpr: The fpr to find / add

    @type session: SQLAlchemy
    @param session: Optional SQL session object (a temporary one will be
    generated if not supplied).

    @rtype: Fingerprint
    @return: the Fingerprint object for the given fpr or None
    """

    q = session.query(Fingerprint).filter_by(fingerprint=fpr)

    try:
        ret = q.one()
    except NoResultFound:
        ret = None

    return ret

__all__.append('get_fingerprint')

786

787
@session_wrapper
M
Mark Hymers 已提交
788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806
def get_or_set_fingerprint(fpr, session=None):
    """
    Returns Fingerprint object for given fpr.

    If no matching fpr is found, a row is inserted.

    @type fpr: string
    @param fpr: The fpr to find / add

    @type session: SQLAlchemy
    @param session: Optional SQL session object (a temporary one will be
    generated if not supplied).  If not passed, a commit will be performed at
    the end of the function, otherwise the caller is responsible for commiting.
    A flush will be performed either way.

    @rtype: Fingerprint
    @return: the Fingerprint object for the given fpr
    """

807
    q = session.query(Fingerprint).filter_by(fingerprint=fpr)
808 809 810 811

    try:
        ret = q.one()
    except NoResultFound:
812 813 814
        fingerprint = Fingerprint()
        fingerprint.fingerprint = fpr
        session.add(fingerprint)
815
        session.commit_or_flush()
816
        ret = fingerprint
M
Mark Hymers 已提交
817

818
    return ret
M
Mark Hymers 已提交
819 820 821

__all__.append('get_or_set_fingerprint')

M
Mark Hymers 已提交
822 823
################################################################################

M
Mark Hymers 已提交
824
# Helper routine for Keyring class
825 826


M
Mark Hymers 已提交
827 828 829 830 831 832 833 834 835 836
def get_ldap_name(entry):
    name = []
    for k in ["cn", "mn", "sn"]:
        ret = entry.get(k)
        if ret and ret[0] != "" and ret[0] != "-":
            name.append(ret[0])
    return " ".join(name)

################################################################################

837

M
Mark Hymers 已提交
838
class Keyring(object):
M
Mark Hymers 已提交
839 840 841
    keys = {}
    fpr_lookup = {}

M
Mark Hymers 已提交
842 843
    def __init__(self, *args, **kwargs):
        pass
M
Mark Hymers 已提交
844 845 846 847

    def __repr__(self):
        return '<Keyring %s>' % self.keyring_name

848 849
    def de_escape_gpg_str(self, txt):
        esclist = re.split(r'(\\x..)', txt)
850 851
        for x in range(1, len(esclist), 2):
            esclist[x] = "%c" % (int(esclist[x][2:], 16))
M
Mark Hymers 已提交
852 853
        return "".join(esclist)

T
Torsten Werner 已提交
854 855
    def parse_address(self, uid):
        """parses uid and returns a tuple of real name and email address"""
M
Mark Hymers 已提交
856
        import email.Utils
T
Torsten Werner 已提交
857 858 859 860 861 862
        (name, address) = email.Utils.parseaddr(uid)
        name = re.sub(r"\s*[(].*[)]", "", name)
        name = self.de_escape_gpg_str(name)
        if name == "":
            name = uid
        return (name, address)
M
Mark Hymers 已提交
863

T
Torsten Werner 已提交
864
    def load_keys(self, keyring):
M
Mark Hymers 已提交
865 866 867
        if not self.keyring_id:
            raise Exception('Must be initialized with database information')

868 869 870 871
        cmd = ["gpg", "--no-default-keyring", "--keyring", keyring,
               "--with-colons", "--fingerprint", "--fingerprint"]
        p = daklib.daksubprocess.Popen(cmd, stdout=subprocess.PIPE)

M
Mark Hymers 已提交
872
        key = None
873
        need_fingerprint = False
M
Mark Hymers 已提交
874

875
        for line in p.stdout:
M
Mark Hymers 已提交
876 877 878
            field = line.split(":")
            if field[0] == "pub":
                key = field[4]
T
Torsten Werner 已提交
879 880 881 882
                self.keys[key] = {}
                (name, addr) = self.parse_address(field[9])
                if "@" in addr:
                    self.keys[key]["email"] = addr
M
Mark Hymers 已提交
883
                    self.keys[key]["name"] = name
884
                need_fingerprint = True
M
Mark Hymers 已提交
885
            elif key and field[0] == "uid":
T
Torsten Werner 已提交
886 887 888 889
                (name, addr) = self.parse_address(field[9])
                if "email" not in self.keys[key] and "@" in addr:
                    self.keys[key]["email"] = addr
                    self.keys[key]["name"] = name
890 891
            elif need_fingerprint and field[0] == "fpr":
                self.keys[key]["fingerprints"] = [field[9]]
M
Mark Hymers 已提交
892
                self.fpr_lookup[field[9]] = key
893
                need_fingerprint = False
M
Mark Hymers 已提交
894

895 896 897 898
        r = p.wait()
        if r != 0:
            raise subprocess.CalledProcessError(r, cmd)

M
Mark Hymers 已提交
899 900 901 902 903 904
    def import_users_from_ldap(self, session):
        import ldap
        cnf = Config()

        LDAPDn = cnf["Import-LDAP-Fingerprints::LDAPDn"]
        LDAPServer = cnf["Import-LDAP-Fingerprints::LDAPServer"]
905
        ca_cert_file = cnf.get('Import-LDAP-Fingerprints::CACertFile')
M
Mark Hymers 已提交
906 907

        l = ldap.open(LDAPServer)
908 909

        if ca_cert_file:
A
Ansgar Burchardt 已提交
910 911 912
            l.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_HARD)
            l.set_option(ldap.OPT_X_TLS_CACERTFILE, ca_cert_file)
            l.set_option(ldap.OPT_X_TLS_NEWCTX, True)
913 914
            l.start_tls_s()

915
        l.simple_bind_s("", "")
M
Mark Hymers 已提交
916
        Attrs = l.search_s(LDAPDn, ldap.SCOPE_ONELEVEL,
917
               "(&(keyfingerprint=*)(supplementaryGid=%s))" % (cnf["Import-Users-From-Passwd::ValidGID"]),
M
Mark Hymers 已提交
918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936
               ["uid", "keyfingerprint", "cn", "mn", "sn"])

        ldap_fin_uid_id = {}

        byuid = {}
        byname = {}

        for i in Attrs:
            entry = i[1]
            uid = entry["uid"][0]
            name = get_ldap_name(entry)
            fingerprints = entry["keyFingerPrint"]
            keyid = None
            for f in fingerprints:
                key = self.fpr_lookup.get(f, None)
                if key not in self.keys:
                    continue
                self.keys[key]["uid"] = uid

937
                if keyid is not None:
M
Mark Hymers 已提交
938 939 940 941 942 943 944 945 946 947 948 949
                    continue
                keyid = get_or_set_uid(uid, session).uid_id
                byuid[keyid] = (uid, name)
                byname[uid] = (keyid, name)

        return (byname, byuid)

    def generate_users_from_keyring(self, format, session):
        byuid = {}
        byname = {}
        any_invalid = False
        for x in self.keys.keys():
T
Torsten Werner 已提交
950
            if "email" not in self.keys[x]:
M
Mark Hymers 已提交
951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967
                any_invalid = True
                self.keys[x]["uid"] = format % "invalid-uid"
            else:
                uid = format % self.keys[x]["email"]
                keyid = get_or_set_uid(uid, session).uid_id
                byuid[keyid] = (uid, self.keys[x]["name"])
                byname[uid] = (keyid, self.keys[x]["name"])
                self.keys[x]["uid"] = uid

        if any_invalid:
            uid = format % "invalid-uid"
            keyid = get_or_set_uid(uid, session).uid_id
            byuid[keyid] = (uid, "ungeneratable user id")
            byname[uid] = (keyid, "ungeneratable user id")

        return (byname, byuid)

968 969
__all__.append('Keyring')

970

971
@session_wrapper
M
Mark Hymers 已提交
972
def get_keyring(keyring, session=None):
973
    """
M
Mark Hymers 已提交
974
    If C{keyring} does not have an entry in the C{keyrings} table yet, return None
975 976 977 978 979 980 981 982 983
    If C{keyring} already has an entry, simply return the existing Keyring

    @type keyring: string
    @param keyring: the keyring name

    @rtype: Keyring
    @return: the Keyring object for this keyring
    """

984
    q = session.query(Keyring).filter_by(keyring_name=keyring)
985

986 987 988
    try:
        return q.one()
    except NoResultFound:
M
Mark Hymers 已提交
989
        return None
990

M
Mark Hymers 已提交
991
__all__.append('get_keyring')
992

993

M
Mark Hymers 已提交
994 995 996 997 998 999
@session_wrapper
def get_active_keyring_paths(session=None):
    """
    @rtype: list
    @return: list of active keyring paths
    """
B
Bastian Blank 已提交
1000
    return [x.keyring_name for x in session.query(Keyring).filter(Keyring.active == True).order_by(desc(Keyring.priority)).all()]  # noqa:E712
M
Mark Hymers 已提交
1001 1002 1003

__all__.append('get_active_keyring_paths')

M
Mark Hymers 已提交
1004
################################################################################
1005

1006

M
Mark Hymers 已提交
1007
class DBChange(object):
J
Joerg Jaspert 已提交
1008 1009 1010 1011
    def __init__(self, *args, **kwargs):
        pass

    def __repr__(self):
M
Mark Hymers 已提交
1012
        return '<DBChange %s>' % self.changesname
J
Joerg Jaspert 已提交
1013

M
Mark Hymers 已提交
1014
__all__.append('DBChange')
J
Joerg Jaspert 已提交
1015

1016

J
Joerg Jaspert 已提交
1017
@session_wrapper
M
Mark Hymers 已提交
1018
def get_dbchange(filename, session=None):
J
Joerg Jaspert 已提交
1019
    """
M
Mark Hymers 已提交
1020
    returns DBChange object for given C{filename}.
J
Joerg Jaspert 已提交
1021

J
Joerg Jaspert 已提交
1022 1023
    @type filename: string
    @param filename: the name of the file
J
Joerg Jaspert 已提交
1024 1025 1026 1027 1028

    @type session: Session
    @param session: Optional SQLA session object (a temporary one will be
    generated if not supplied)

J
Joerg Jaspert 已提交
1029 1030
    @rtype: DBChange
    @return:  DBChange object for the given filename (C{None} if not present)
1031

J
Joerg Jaspert 已提交
1032
    """
M
Mark Hymers 已提交
1033
    q = session.query(DBChange).filter_by(changesname=filename)
J
Joerg Jaspert 已提交
1034 1035 1036 1037 1038 1039

    try:
        return q.one()
    except NoResultFound:
        return None

M
Mark Hymers 已提交
1040
__all__.append('get_dbchange')
1041

M
Mark Hymers 已提交
1042 1043
################################################################################

1044

1045
class Maintainer(ORMObject):
1046
    def __init__(self, name=None):
1047
        self.name = name
M
Mark Hymers 已提交
1048

1049 1050 1051
    def properties(self):
        return ['name', 'maintainer_id']

M
Mark Hymers 已提交
1052 1053 1054 1055 1056 1057
    def get_split_maintainer(self):
        if not hasattr(self, 'name') or self.name is None:
            return ('', '', '', '')

        return fix_maintainer(self.name.strip())

1058 1059
__all__.append('Maintainer')

1060

1061
@session_wrapper
M
Mark Hymers 已提交
1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080
def get_or_set_maintainer(name, session=None):
    """
    Returns Maintainer object for given maintainer name.

    If no matching maintainer name is found, a row is inserted.

    @type name: string
    @param name: The maintainer name to add

    @type session: SQLAlchemy
    @param session: Optional SQL session object (a temporary one will be
    generated if not supplied).  If not passed, a commit will be performed at
    the end of the function, otherwise the caller is responsible for commiting.
    A flush will be performed either way.

    @rtype: Maintainer
    @return: the Maintainer object for the given maintainer
    """

1081
    q = session.query(Maintainer).filter_by(name=name)
1082 1083 1084
    try:
        ret = q.one()
    except NoResultFound:
1085 1086 1087
        maintainer = Maintainer()
        maintainer.name = name
        session.add(maintainer)
1088
        session.commit_or_flush()
1089
        ret = maintainer
M
Mark Hymers 已提交
1090

1091
    return ret
M
Mark Hymers 已提交
1092 1093 1094

__all__.append('get_or_set_maintainer')

1095

1096
@session_wrapper
C
Chris Lamb 已提交
1097
def get_maintainer(maintainer_id, session=None):
C
Chris Lamb 已提交
1098
    """
1099 1100
    Return the name of the maintainer behind C{maintainer_id} or None if that
    maintainer_id is invalid.
C
Chris Lamb 已提交
1101 1102 1103 1104

    @type maintainer_id: int
    @param maintainer_id: the id of the maintainer

1105 1106
    @rtype: Maintainer
    @return: the Maintainer with this C{maintainer_id}
C
Chris Lamb 已提交
1107 1108
    """

1109
    return session.query(Maintainer).get(maintainer_id)
C
Chris Lamb 已提交
1110 1111 1112

__all__.append('get_maintainer')

M
Mark Hymers 已提交
1113 1114
################################################################################

1115

M
Mark Hymers 已提交
1116 1117 1118 1119 1120 1121 1122 1123 1124
class NewComment(object):
    def __init__(self, *args, **kwargs):
        pass

    def __repr__(self):
        return '''<NewComment for '%s %s' (%s)>''' % (self.package, self.version, self.comment_id)

__all__.append('NewComment')

1125

1126
@session_wrapper
1127
def has_new_comment(policy_queue, package, version, session=None):
M
Mark Hymers 已提交
1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144
    """
    Returns true if the given combination of C{package}, C{version} has a comment.

    @type package: string
    @param package: name of the package

    @type version: string
    @param version: package version

    @type session: Session
    @param session: Optional SQLA session object (a temporary one will be
    generated if not supplied)

    @rtype: boolean
    @return: true/false
    """

1145
    q = session.query(NewComment).filter_by(policy_queue=policy_queue)
M
Mark Hymers 已提交
1146 1147
    q = q.filter_by(package=package)
    q = q.filter_by(version=version)
1148

1149
    return bool(q.count() > 0)
M
Mark Hymers 已提交
1150 1151 1152

__all__.append('has_new_comment')

1153

1154
@session_wrapper
1155
def get_new_comments(policy_queue, package=None, version=None, comment_id=None, session=None):
M
Mark Hymers 已提交
1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176
    """
    Returns (possibly empty) list of NewComment objects for the given
    parameters

    @type package: string (optional)
    @param package: name of the package

    @type version: string (optional)
    @param version: package version

    @type comment_id: int (optional)
    @param comment_id: An id of a comment

    @type session: Session
    @param session: Optional SQLA session object (a temporary one will be
    generated if not supplied)

    @rtype: list
    @return: A (possibly empty) list of NewComment objects will be returned
    """

1177
    q = session.query(NewComment).filter_by(policy_queue=policy_queue)
1178 1179 1180 1181 1182 1183
    if package is not None:
        q = q.filter_by(package=package)
    if version is not None:
        q = q.filter_by(version=version)
    if comment_id is not None:
        q = q.filter_by(comment_id=comment_id)
M
Mark Hymers 已提交
1184

1185
    return q.all()
M
Mark Hymers 已提交
1186 1187 1188 1189 1190

__all__.append('get_new_comments')

################################################################################

1191

1192
class Override(ORMObject):
1193
    def __init__(self, package=None, suite=None, component=None, overridetype=None,
1194
        section=None, priority=None):
1195 1196 1197 1198 1199 1200
        self.package = package
        self.suite = suite
        self.component = component
        self.overridetype = overridetype
        self.section = section
        self.priority = priority
M
Mark Hymers 已提交
1201

1202
    def properties(self):
1203
        return ['package', 'suite', 'component', 'overridetype', 'section',
1204 1205
            'priority']

1206 1207
__all__.append('Override')

1208

1209
@session_wrapper
1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240
def get_override(package, suite=None, component=None, overridetype=None, session=None):
    """
    Returns Override object for the given parameters

    @type package: string
    @param package: The name of the package

    @type suite: string, list or None
    @param suite: The name of the suite (or suites if a list) to limit to.  If
                  None, don't limit.  Defaults to None.

    @type component: string, list or None
    @param component: The name of the component (or components if a list) to
                      limit to.  If None, don't limit.  Defaults to None.

    @type overridetype: string, list or None
    @param overridetype: The name of the overridetype (or overridetypes if a list) to
                         limit to.  If None, don't limit.  Defaults to None.

    @type session: Session
    @param session: Optional SQLA session object (a temporary one will be
    generated if not supplied)

    @rtype: list
    @return: A (possibly empty) list of Override objects will be returned
    """

    q = session.query(Override)
    q = q.filter_by(package=package)

    if suite is not None:
1241 1242
        if not isinstance(suite, list):
            suite = [suite]
1243 1244 1245
        q = q.join(Suite).filter(Suite.suite_name.in_(suite))

    if component is not None:
1246 1247
        if not isinstance(component, list):
            component = [component]
1248 1249 1250
        q = q.join(Component).filter(Component.component_name.in_(component))

    if overridetype is not None:
1251 1252
        if not isinstance(overridetype, list):
            overridetype = [overridetype]
1253 1254
        q = q.join(OverrideType).filter(OverrideType.overridetype.in_(overridetype))

1255
    return q.all()
1256 1257 1258 1259

__all__.append('get_override')


M
Mark Hymers 已提交
1260 1261
################################################################################

1262
class OverrideType(ORMObject):
1263
    def __init__(self, overridetype=None):
1264
        self.overridetype = overridetype
M
Mark Hymers 已提交
1265

1266
    def properties(self):
1267
        return ['overridetype', 'overridetype_id', 'overrides_count']
1268

1269 1270
__all__.append('OverrideType')

1271

1272
@session_wrapper
1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286
def get_override_type(override_type, session=None):
    """
    Returns OverrideType object for given C{override type}.

    @type override_type: string
    @param override_type: The name of the override type

    @type session: Session
    @param session: Optional SQLA session object (a temporary one will be
    generated if not supplied)

    @rtype: int
    @return: the database id for the given override type
    """
1287

M
Mark Hymers 已提交
1288
    q = session.query(OverrideType).filter_by(overridetype=override_type)
1289

1290 1291 1292 1293
    try:
        return q.one()
    except NoResultFound:
        return None
1294

1295 1296
__all__.append('get_override_type')

M
Mark Hymers 已提交
1297 1298
################################################################################

1299

1300 1301 1302 1303 1304 1305 1306 1307 1308
class PolicyQueue(object):
    def __init__(self, *args, **kwargs):
        pass

    def __repr__(self):
        return '<PolicyQueue %s>' % self.queue_name

__all__.append('PolicyQueue')

1309

1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334
@session_wrapper
def get_policy_queue(queuename, session=None):
    """
    Returns PolicyQueue object for given C{queue name}

    @type queuename: string
    @param queuename: The name of the queue

    @type session: Session
    @param session: Optional SQLA session object (a temporary one will be
    generated if not supplied)

    @rtype: PolicyQueue
    @return: PolicyQueue object for the given queue
    """

    q = session.query(PolicyQueue).filter_by(queue_name=queuename)

    try:
        return q.one()
    except NoResultFound:
        return None

__all__.append('get_policy_queue')

1335 1336
################################################################################

1337

1338
@functools.total_ordering
1339
class PolicyQueueUpload(object):
1340 1341 1342 1343 1344 1345 1346 1347
    def _key(self):
        return (
            self.changes.source,
            AptVersion(self.changes.version),
            self.source is None,
            self.changes.changesname
        )

1348
    def __eq__(self, other):
1349
        return self._key() == other._key()
1350 1351

    def __lt__(self, other):
1352
        return self._key() < other._key()
1353 1354 1355 1356 1357

__all__.append('PolicyQueueUpload')

################################################################################

1358

1359 1360 1361 1362 1363 1364 1365
class PolicyQueueByhandFile(object):
    pass

__all__.append('PolicyQueueByhandFile')

################################################################################

1366

1367
class Priority(ORMObject):
1368
    def __init__(self, priority=None, level=None):
1369 1370 1371 1372 1373 1374
        self.priority = priority
        self.level = level

    def properties(self):
        return ['priority', 'priority_id', 'level', 'overrides_count']

1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386
    def __eq__(self, val):
        if isinstance(val, str):
            return (self.priority == val)
        # This signals to use the normal comparison operator
        return NotImplemented

    def __ne__(self, val):
        if isinstance(val, str):
            return (self.priority != val)
        # This signals to use the normal comparison operator
        return NotImplemented

1387 1388
__all__.append('Priority')

1389

1390
@session_wrapper
1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404
def get_priority(priority, session=None):
    """
    Returns Priority object for given C{priority name}.

    @type priority: string
    @param priority: The name of the priority

    @type session: Session
    @param session: Optional SQLA session object (a temporary one will be
    generated if not supplied)

    @rtype: Priority
    @return: Priority object for the given priority
    """
1405

1406
    q = session.query(Priority).filter_by(priority=priority)
1407

1408 1409 1410 1411
    try:
        return q.one()
    except NoResultFound:
        return None
1412

1413 1414
__all__.append('get_priority')

1415

1416
@session_wrapper
1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437
def get_priorities(session=None):
    """
    Returns dictionary of priority names -> id mappings

    @type session: Session
    @param session: Optional SQL session object (a temporary one will be
    generated if not supplied)

    @rtype: dictionary
    @return: dictionary of priority names -> id mappings
    """

    ret = {}
    q = session.query(Priority)
    for x in q.all():
        ret[x.priority] = x.priority_id

    return ret

__all__.append('get_priorities')

M
Mark Hymers 已提交
1438 1439
################################################################################

1440

1441
from .database.section import Section
1442

1443 1444
__all__.append('Section')

1445

1446
@session_wrapper
1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460
def get_section(section, session=None):
    """
    Returns Section object for given C{section name}.

    @type section: string
    @param section: The name of the section

    @type session: Session
    @param session: Optional SQLA session object (a temporary one will be
    generated if not supplied)

    @rtype: Section
    @return: Section object for the given section name
    """
1461

1462
    q = session.query(Section).filter_by(section=section)
1463

1464 1465 1466 1467
    try:
        return q.one()
    except NoResultFound:
        return None
1468

1469 1470
__all__.append('get_section')

1471

1472
@session_wrapper
1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493
def get_sections(session=None):
    """
    Returns dictionary of section names -> id mappings

    @type session: Session
    @param session: Optional SQL session object (a temporary one will be
    generated if not supplied)

    @rtype: dictionary
    @return: dictionary of section names -> id mappings
    """

    ret = {}
    q = session.query(Section)
    for x in q.all():
        ret[x.section] = x.section_id

    return ret

__all__.append('get_sections')

M
Mark Hymers 已提交
1494 1495
################################################################################

1496

1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512
class SignatureHistory(ORMObject):
    @classmethod
    def from_signed_file(cls, signed_file):
        """signature history entry from signed file

        @type  signed_file: L{daklib.gpg.SignedFile}
        @param signed_file: signed file

        @rtype: L{SignatureHistory}
        """
        self = cls()
        self.fingerprint = signed_file.primary_fingerprint
        self.signature_timestamp = signed_file.signature_timestamp
        self.contents_sha1 = signed_file.contents_sha1()
        return self

1513 1514 1515
    def query(self, session):
        return session.query(SignatureHistory).filter_by(fingerprint=self.fingerprint, signature_timestamp=self.signature_timestamp, contents_sha1=self.contents_sha1).first()

1516 1517 1518 1519
__all__.append('SignatureHistory')

################################################################################

1520

1521
class SrcContents(ORMObject):
1522
    def __init__(self, file=None, source=None):
1523 1524 1525 1526 1527 1528 1529 1530 1531 1532
        self.file = file
        self.source = source

    def properties(self):
        return ['file', 'source']

__all__.append('SrcContents')

################################################################################

A
Ansgar Burchardt 已提交
1533

1534
class DBSource(ORMObject):
1535
    def __init__(self, source=None, version=None, maintainer=None,
1536
        changedby=None, poolfile=None, install_date=None, fingerprint=None):
T
Torsten Werner 已提交
1537 1538
        self.source = source
        self.version = version
1539 1540
        self.maintainer = maintainer
        self.changedby = changedby
T
Torsten Werner 已提交
1541 1542
        self.poolfile = poolfile
        self.install_date = install_date
1543
        self.fingerprint = fingerprint
M
Mark Hymers 已提交
1544

M
Mark Hymers 已提交
1545 1546 1547 1548
    @property
    def pkid(self):
        return self.source_id

1549
    def properties(self):
1550 1551
        return ['source', 'source_id', 'maintainer', 'changedby',
            'fingerprint', 'poolfile', 'version', 'suites_count',
1552
            'install_date', 'binaries_count', 'uploaders_count']
1553

M
Mark Hymers 已提交
1554
    def read_control_fields(self):
M
Mark Hymers 已提交
1555 1556 1557 1558
        '''
        Reads the control information from a dsc

        @rtype: tuple
M
Mark Hymers 已提交
1559
        @return: fields is the dsc information in a dictionary form
M
Mark Hymers 已提交
1560 1561
        '''
        fullpath = self.poolfile.fullpath
1562
        fields = Deb822(open(self.poolfile.fullpath, 'r'))
M
Mark Hymers 已提交
1563 1564
        return fields

1565 1566
    metadata = association_proxy('key', 'value')

1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585
    def scan_contents(self):
        '''
        Returns a set of names for non directories. The path names are
        normalized after converting them from either utf-8 or iso8859-1
        encoding.
        '''
        fullpath = self.poolfile.fullpath
        from daklib.contents import UnpackedSource
        unpacked = UnpackedSource(fullpath)
        fileset = set()
        for name in unpacked.get_all_filenames():
            # enforce proper utf-8 encoding
            try:
                name.decode('utf-8')
            except UnicodeDecodeError:
                name = name.decode('iso8859-1').encode('utf-8')
            fileset.add(name)
        return fileset

1586 1587 1588 1589 1590 1591
    @property
    def proxy(self):
        session = object_session(self)
        query = session.query(SourceMetadata).filter_by(source=self)
        return MetadataProxy(session, query)

1592
__all__.append('DBSource')
1593

1594

1595
@session_wrapper
1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606
def get_suites_source_in(source, session=None):
    """
    Returns list of Suite objects which given C{source} name is in

    @type source: str
    @param source: DBSource package name to search for

    @rtype: list
    @return: list of Suite objects for the given source
    """

1607
    return session.query(Suite).filter(Suite.sources.any(source=source)).all()
1608 1609 1610

__all__.append('get_suites_source_in')

T
Torsten Werner 已提交
1611 1612
# FIXME: This function fails badly if it finds more than 1 source package and
# its implementation is trivial enough to be inlined.
1613 1614


1615
@session_wrapper
1616
def get_source_in_suite(source, suite_name, session=None):
1617
    """
1618
    Returns a DBSource object for a combination of C{source} and C{suite_name}.
1619 1620

      - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
1621
      - B{suite_name} - a suite name, eg. I{unstable}
1622 1623 1624 1625

    @type source: string
    @param source: source package name

1626
    @type suite_name: string
1627 1628 1629 1630 1631 1632
    @param suite: the suite name

    @rtype: string
    @return: the version for I{source} in I{suite}

    """
1633 1634 1635
    suite = get_suite(suite_name, session)
    if suite is None:
        return None
1636
    try:
1637
        return suite.get_sources(source).one()
1638 1639
    except NoResultFound:
        return None
1640

1641 1642
__all__.append('get_source_in_suite')

1643

M
Mark Hymers 已提交
1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670
@session_wrapper
def import_metadata_into_db(obj, session=None):
    """
    This routine works on either DBBinary or DBSource objects and imports
    their metadata into the database
    """
    fields = obj.read_control_fields()
    for k in fields.keys():
        try:
            # Try raw ASCII
            val = str(fields[k])
        except UnicodeEncodeError:
            # Fall back to UTF-8
            try:
                val = fields[k].encode('utf-8')
            except UnicodeEncodeError:
                # Finally try iso8859-1
                val = fields[k].encode('iso8859-1')
                # Otherwise we allow the exception to percolate up and we cause
                # a reject as someone is playing silly buggers

        obj.metadata[get_or_set_metadatakey(k, session)] = val

    session.commit_or_flush()

__all__.append('import_metadata_into_db')

1671 1672
################################################################################

1673

1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684
class SrcFormat(object):
    def __init__(self, *args, **kwargs):
        pass

    def __repr__(self):
        return '<SrcFormat %s>' % (self.format_name)

__all__.append('SrcFormat')

################################################################################

B
Bastian Blank 已提交
1685
SUITE_FIELDS = [('SuiteName', 'suite_name'),
M
Mark Hymers 已提交
1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698
                 ('SuiteID', 'suite_id'),
                 ('Version', 'version'),
                 ('Origin', 'origin'),
                 ('Label', 'label'),
                 ('Description', 'description'),
                 ('Untouchable', 'untouchable'),
                 ('Announce', 'announce'),
                 ('Codename', 'codename'),
                 ('OverrideCodename', 'overridecodename'),
                 ('ValidTime', 'validtime'),
                 ('Priority', 'priority'),
                 ('NotAutomatic', 'notautomatic'),
                 ('CopyChanges', 'copychanges'),
J
Joerg Jaspert 已提交
1699
                 ('OverrideSuite', 'overridesuite')]
M
Mark Hymers 已提交
1700

T
Torsten Werner 已提交
1701 1702
# Why the heck don't we have any UNIQUE constraints in table suite?
# TODO: Add UNIQUE constraints for appropriate columns.
1703 1704


1705
class Suite(ORMObject):
1706
    def __init__(self, suite_name=None, version=None):
1707 1708
        self.suite_name = suite_name
        self.version = version
M
Mark Hymers 已提交
1709

1710
    def properties(self):
1711
        return ['suite_name', 'version', 'sources_count', 'binaries_count',
T
Torsten Werner 已提交
1712
            'overrides_count']
1713

1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725
    def __eq__(self, val):
        if isinstance(val, str):
            return (self.suite_name == val)
        # This signals to use the normal comparison operator
        return NotImplemented

    def __ne__(self, val):
        if isinstance(val, str):
            return (self.suite_name != val)
        # This signals to use the normal comparison operator
        return NotImplemented

M
Mark Hymers 已提交
1726 1727 1728 1729 1730 1731 1732 1733 1734
    def details(self):
        ret = []
        for disp, field in SUITE_FIELDS:
            val = getattr(self, field, None)
            if val is not None:
                ret.append("%s: %s" % (disp, val))

        return "\n".join(ret)

1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750
    def get_architectures(self, skipsrc=False, skipall=False):
        """
        Returns list of Architecture objects

        @type skipsrc: boolean
        @param skipsrc: Whether to skip returning the 'source' architecture entry
        (Default False)

        @type skipall: boolean
        @param skipall: Whether to skip returning the 'all' architecture entry
        (Default False)

        @rtype: list
        @return: list of Architecture objects for the given name (may be empty)
        """

1751
        q = object_session(self).query(Architecture).with_parent(self)
1752 1753 1754 1755 1756 1757
        if skipsrc:
            q = q.filter(Architecture.arch_string != 'source')
        if skipall:
            q = q.filter(Architecture.arch_string != 'all')
        return q.order_by(Architecture.arch_string).all()

T
Torsten Werner 已提交
1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772
    def get_sources(self, source):
        """
        Returns a query object representing DBSource that is part of C{suite}.

          - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}

        @type source: string
        @param source: source package name

        @rtype: sqlalchemy.orm.query.Query
        @return: a query of DBSource

        """

        session = object_session(self)
1773
        return session.query(DBSource).filter_by(source=source). \
1774
            with_parent(self)
T
Torsten Werner 已提交
1775

1776 1777 1778 1779 1780 1781
    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()

1782 1783 1784
    def update_last_changed(self):
        self.last_changed = sqlalchemy.func.now()

1785 1786 1787 1788
    @property
    def path(self):
        return os.path.join(self.archive.path, 'dists', self.suite_name)

1789 1790 1791 1792 1793 1794
    @property
    def release_suite_output(self):
        if self.release_suite is not None:
            return self.release_suite
        return self.suite_name

1795 1796
__all__.append('Suite')

1797

1798
@session_wrapper
1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810
def get_suite(suite, session=None):
    """
    Returns Suite object for given C{suite name}.

    @type suite: string
    @param suite: The name of the suite

    @type session: Session
    @param session: Optional SQLA session object (a temporary one will be
    generated if not supplied)

    @rtype: Suite
F
Frank Lichtenheld 已提交
1811
    @return: Suite object for the requested suite name (None if not present)
1812
    """
1813

1814
    # Start by looking for the dak internal name
1815
    q = session.query(Suite).filter_by(suite_name=suite)
1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826
    try:
        return q.one()
    except NoResultFound:
        pass

    # Now try codename
    q = session.query(Suite).filter_by(codename=suite)
    try:
        return q.one()
    except NoResultFound:
        pass
1827

1828 1829
    # Finally give release_suite a try
    q = session.query(Suite).filter_by(release_suite=suite)
1830 1831 1832 1833
    try:
        return q.one()
    except NoResultFound:
        return None
1834

1835 1836
__all__.append('get_suite')

M
Mark Hymers 已提交
1837 1838
################################################################################

1839

1840
@session_wrapper
1841
def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None):
M
Mark Hymers 已提交
1842
    """
1843 1844
    Returns list of Architecture objects for given C{suite} name. The list is
    empty if suite does not exist.
M
Mark Hymers 已提交
1845

J
Joerg Jaspert 已提交
1846 1847
    @type suite: str
    @param suite: Suite name to search for
M
Mark Hymers 已提交
1848

1849 1850 1851 1852 1853 1854 1855 1856
    @type skipsrc: boolean
    @param skipsrc: Whether to skip returning the 'source' architecture entry
    (Default False)

    @type skipall: boolean
    @param skipall: Whether to skip returning the 'all' architecture entry
    (Default False)

M
Mark Hymers 已提交
1857 1858 1859 1860 1861 1862 1863 1864
    @type session: Session
    @param session: Optional SQL session object (a temporary one will be
    generated if not supplied)

    @rtype: list
    @return: list of Architecture objects for the given name (may be empty)
    """

1865 1866 1867
    try:
        return get_suite(suite, session).get_architectures(skipsrc, skipall)
    except AttributeError:
1868
        return []
M
Mark Hymers 已提交
1869

1870
__all__.append('get_suite_architectures')
M
Mark Hymers 已提交
1871

M
Mark Hymers 已提交
1872 1873
################################################################################

1874

1875
class Uid(ORMObject):
1876
    def __init__(self, uid=None, name=None):
T
Torsten Werner 已提交
1877 1878
        self.uid = uid
        self.name = name
M
Mark Hymers 已提交
1879

1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891
    def __eq__(self, val):
        if isinstance(val, str):
            return (self.uid == val)
        # This signals to use the normal comparison operator
        return NotImplemented

    def __ne__(self, val):
        if isinstance(val, str):
            return (self.uid != val)
        # This signals to use the normal comparison operator
        return NotImplemented

1892 1893 1894
    def properties(self):
        return ['uid', 'name', 'fingerprint']

1895 1896
__all__.append('Uid')

1897

1898
@session_wrapper
M
Mark Hymers 已提交
1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915
def get_or_set_uid(uidname, session=None):
    """
    Returns uid object for given uidname.

    If no matching uidname is found, a row is inserted.

    @type uidname: string
    @param uidname: The uid to add

    @type session: SQLAlchemy
    @param session: Optional SQL session object (a temporary one will be
    generated if not supplied).  If not passed, a commit will be performed at
    the end of the function, otherwise the caller is responsible for commiting.

    @rtype: Uid
    @return: the uid object for the given uidname
    """
1916 1917 1918

    q = session.query(Uid).filter_by(uid=uidname)

1919 1920 1921
    try:
        ret = q.one()
    except NoResultFound:
1922 1923 1924
        uid = Uid()
        uid.uid = uidname
        session.add(uid)
1925
        session.commit_or_flush()
1926
        ret = uid
M
Mark Hymers 已提交
1927

1928
    return ret
M
Mark Hymers 已提交
1929 1930 1931

__all__.append('get_or_set_uid')

1932

1933
@session_wrapper
1934 1935 1936 1937
def get_uid_from_fingerprint(fpr, session=None):
    q = session.query(Uid)
    q = q.join(Fingerprint).filter_by(fingerprint=fpr)

1938 1939 1940 1941
    try:
        return q.one()
    except NoResultFound:
        return None
1942 1943 1944

__all__.append('get_uid_from_fingerprint')

M
Mark Hymers 已提交
1945 1946
################################################################################

1947

T
Torsten Werner 已提交
1948
class MetadataKey(ORMObject):
1949
    def __init__(self, key=None):
T
Torsten Werner 已提交
1950 1951 1952 1953 1954 1955 1956
        self.key = key

    def properties(self):
        return ['key']

__all__.append('MetadataKey')

1957

M
Mark Hymers 已提交
1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989
@session_wrapper
def get_or_set_metadatakey(keyname, session=None):
    """
    Returns MetadataKey object for given uidname.

    If no matching keyname is found, a row is inserted.

    @type uidname: string
    @param uidname: The keyname to add

    @type session: SQLAlchemy
    @param session: Optional SQL session object (a temporary one will be
    generated if not supplied).  If not passed, a commit will be performed at
    the end of the function, otherwise the caller is responsible for commiting.

    @rtype: MetadataKey
    @return: the metadatakey object for the given keyname
    """

    q = session.query(MetadataKey).filter_by(key=keyname)

    try:
        ret = q.one()
    except NoResultFound:
        ret = MetadataKey(keyname)
        session.add(ret)
        session.commit_or_flush()

    return ret

__all__.append('get_or_set_metadatakey')

T
Torsten Werner 已提交
1990 1991
################################################################################

1992

T
Torsten Werner 已提交
1993
class BinaryMetadata(ORMObject):
1994
    def __init__(self, key=None, value=None, binary=None):
T
Torsten Werner 已提交
1995 1996
        self.key = key
        self.value = value
1997 1998
        if binary is not None:
            self.binary = binary
T
Torsten Werner 已提交
1999 2000 2001 2002 2003 2004 2005 2006

    def properties(self):
        return ['binary', 'key', 'value']

__all__.append('BinaryMetadata')

################################################################################

2007

T
Torsten Werner 已提交
2008
class SourceMetadata(ORMObject):
2009
    def __init__(self, key=None, value=None, source=None):
T
Torsten Werner 已提交
2010 2011
        self.key = key
        self.value = value
2012 2013
        if source is not None:
            self.source = source
T
Torsten Werner 已提交
2014 2015 2016 2017 2018 2019 2020 2021

    def properties(self):
        return ['source', 'key', 'value']

__all__.append('SourceMetadata')

################################################################################

2022

2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053
class MetadataProxy(object):
    def __init__(self, session, query):
        self.session = session
        self.query = query

    def _get(self, key):
        metadata_key = self.session.query(MetadataKey).filter_by(key=key).first()
        if metadata_key is None:
            return None
        metadata = self.query.filter_by(key=metadata_key).first()
        return metadata

    def __contains__(self, key):
        if self._get(key) is not None:
            return True
        return False

    def __getitem__(self, key):
        metadata = self._get(key)
        if metadata is None:
            raise KeyError
        return metadata.value

    def get(self, key, default=None):
        try:
            return self[key]
        except KeyError:
            return default

################################################################################

2054

2055 2056
class VersionCheck(ORMObject):
    def __init__(self, *args, **kwargs):
2057
        pass
2058 2059 2060 2061 2062 2063 2064

    def properties(self):
        #return ['suite_id', 'check', 'reference_id']
        return ['check']

__all__.append('VersionCheck')

2065

2066
@session_wrapper
2067
def get_version_checks(suite_name, check=None, session=None):
2068 2069
    suite = get_suite(suite_name, session)
    if not suite:
M
Mark Hymers 已提交
2070 2071 2072
        # Make sure that what we return is iterable so that list comprehensions
        # involving this don't cause a traceback
        return []
2073 2074 2075 2076 2077 2078 2079 2080 2081
    q = session.query(VersionCheck).filter_by(suite=suite)
    if check:
        q = q.filter_by(check=check)
    return q.all()

__all__.append('get_version_checks')

################################################################################

2082

2083
class DBConn(object):
M
Mark Hymers 已提交
2084
    """
2085
    database module init.
M
Mark Hymers 已提交
2086
    """
2087 2088
    __shared_state = {}

B
Bastian Blank 已提交
2089 2090
    db_meta = None

2091 2092
    tbl_architecture = Architecture.__table__

2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156
    tables = (
        'acl',
        'acl_architecture_map',
        'acl_fingerprint_map',
        'acl_per_source',
        'archive',
        'bin_associations',
        'bin_contents',
        'binaries',
        'binaries_metadata',
        'build_queue',
        'changelogs_text',
        'changes',
        'component',
        'component_suite',
        'config',
        'dsc_files',
        'external_files',
        'external_overrides',
        'external_signature_requests',
        'extra_src_references',
        'files',
        'files_archive_map',
        'fingerprint',
        'hashfile',
        'keyrings',
        'maintainer',
        'metadata_keys',
        'new_comments',
        # TODO: the maintainer column in table override should be removed.
        'override',
        'override_type',
        'policy_queue',
        'policy_queue_upload',
        'policy_queue_upload_binaries_map',
        'policy_queue_byhand_file',
        'priority',
        'signature_history',
        'source',
        'source_metadata',
        'src_associations',
        'src_contents',
        'src_format',
        'src_uploaders',
        'suite',
        'suite_acl_map',
        'suite_architectures',
        'suite_build_queue_copy',
        'suite_permission',
        'suite_src_formats',
        'uid',
        'version_check',
    )

    views = (
        'bin_associations_binaries',
        'changelogs',
        'newest_source',
        'newest_src_association',
        'package_list',
        'source_suite',
        'src_associations_src',
    )

M
Mark Hymers 已提交
2157
    def __init__(self, *args, **kwargs):
2158
        self.__dict__ = self.__shared_state
M
Mark Hymers 已提交
2159

2160 2161
        if not getattr(self, 'initialised', False):
            self.initialised = True
2162
            self.debug = 'debug' in kwargs
2163
            self.__createconn()
M
Mark Hymers 已提交
2164

M
Mark Hymers 已提交
2165
    def __setuptables(self):
2166
        for table_name in self.tables:
2167
            table = Table(table_name, self.db_meta,
2168 2169 2170
                autoload=True, useexisting=True)
            setattr(self, 'tbl_%s' % table_name, table)

2171
        for view_name in self.views:
2172 2173 2174
            view = Table(view_name, self.db_meta, autoload=True)
            setattr(self, 'view_%s' % view_name, view)

M
Mark Hymers 已提交
2175
    def __setupmappers(self):
2176
        mapper(ACL, self.tbl_acl,
2177
               properties=dict(
2178 2179 2180 2181 2182
                   architectures=relation(Architecture, secondary=self.tbl_acl_architecture_map, collection_class=set),
                   fingerprints=relation(Fingerprint, secondary=self.tbl_acl_fingerprint_map, collection_class=set),
                   match_keyring=relation(Keyring, primaryjoin=(self.tbl_acl.c.match_keyring_id == self.tbl_keyrings.c.id)),
                   per_source=relation(ACLPerSource, collection_class=set),
                   ))
2183 2184

        mapper(ACLPerSource, self.tbl_acl_per_source,
2185
               properties=dict(
2186 2187 2188 2189
                   acl=relation(ACL),
                   fingerprint=relation(Fingerprint, primaryjoin=(self.tbl_acl_per_source.c.fingerprint_id == self.tbl_fingerprint.c.id)),
                   created_by=relation(Fingerprint, primaryjoin=(self.tbl_acl_per_source.c.created_by_id == self.tbl_fingerprint.c.id)),
                   ))
2190

M
Mark Hymers 已提交
2191
        mapper(Archive, self.tbl_archive,
2192 2193
               properties=dict(archive_id=self.tbl_archive.c.id,
                                 archive_name=self.tbl_archive.c.name))
M
Mike O'Connor 已提交
2194

2195
        mapper(ArchiveFile, self.tbl_files_archive_map,
2196 2197 2198
               properties=dict(archive=relation(Archive, backref='files'),
                                 component=relation(Component),
                                 file=relation(PoolFile, backref='archives')))
2199

2200
        mapper(BuildQueue, self.tbl_build_queue,
2201
               properties=dict(queue_id=self.tbl_build_queue.c.id,
2202
                                 suite=relation(Suite, primaryjoin=(self.tbl_build_queue.c.suite_id == self.tbl_suite.c.id))))
2203

2204
        mapper(DBBinary, self.tbl_binaries,
2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220
               properties=dict(binary_id=self.tbl_binaries.c.id,
                                 package=self.tbl_binaries.c.package,
                                 version=self.tbl_binaries.c.version,
                                 maintainer_id=self.tbl_binaries.c.maintainer,
                                 maintainer=relation(Maintainer),
                                 source_id=self.tbl_binaries.c.source,
                                 source=relation(DBSource, backref='binaries'),
                                 arch_id=self.tbl_binaries.c.architecture,
                                 architecture=relation(Architecture),
                                 poolfile_id=self.tbl_binaries.c.file,
                                 poolfile=relation(PoolFile),
                                 binarytype=self.tbl_binaries.c.type,
                                 fingerprint_id=self.tbl_binaries.c.sig_fpr,
                                 fingerprint=relation(Fingerprint),
                                 install_date=self.tbl_binaries.c.install_date,
                                 suites=relation(Suite, secondary=self.tbl_bin_associations,
M
Mark Hymers 已提交
2221
                                     backref=backref('binaries', lazy='dynamic')),
2222
                                 extra_sources=relation(DBSource, secondary=self.tbl_extra_src_references,
2223
                                     backref=backref('extra_binary_references', lazy='dynamic')),
2224
                                 key=relation(BinaryMetadata, cascade='all',
2225
                                     collection_class=attribute_mapped_collection('key'))),
2226
        )
M
Mark Hymers 已提交
2227 2228

        mapper(Component, self.tbl_component,
2229 2230
               properties=dict(component_id=self.tbl_component.c.id,
                                 component_name=self.tbl_component.c.name),
2231
        )
M
Mark Hymers 已提交
2232 2233

        mapper(DBConfig, self.tbl_config,
2234
               properties=dict(config_id=self.tbl_config.c.id))
M
Mark Hymers 已提交
2235 2236

        mapper(DSCFile, self.tbl_dsc_files,
2237 2238 2239 2240 2241
               properties=dict(dscfile_id=self.tbl_dsc_files.c.id,
                                 source_id=self.tbl_dsc_files.c.source,
                                 source=relation(DBSource),
                                 poolfile_id=self.tbl_dsc_files.c.file,
                                 poolfile=relation(PoolFile)))
M
Mark Hymers 已提交
2242

2243
        mapper(ExternalOverride, self.tbl_external_overrides,
2244 2245 2246 2247 2248
                properties=dict(
                    suite_id=self.tbl_external_overrides.c.suite,
                    suite=relation(Suite),
                    component_id=self.tbl_external_overrides.c.component,
                    component=relation(Component)))
A
Ansgar Burchardt 已提交
2249

M
Mark Hymers 已提交
2250
        mapper(PoolFile, self.tbl_files,
2251 2252
               properties=dict(file_id=self.tbl_files.c.id,
                                 filesize=self.tbl_files.c.size),
2253
        )
M
Mark Hymers 已提交
2254 2255

        mapper(Fingerprint, self.tbl_fingerprint,
2256 2257 2258 2259 2260 2261
               properties=dict(fingerprint_id=self.tbl_fingerprint.c.id,
                                 uid_id=self.tbl_fingerprint.c.uid,
                                 uid=relation(Uid),
                                 keyring_id=self.tbl_fingerprint.c.keyring,
                                 keyring=relation(Keyring),
                                 acl=relation(ACL)),
2262
        )
M
Mark Hymers 已提交
2263 2264

        mapper(Keyring, self.tbl_keyrings,
2265 2266 2267
               properties=dict(keyring_name=self.tbl_keyrings.c.name,
                                 keyring_id=self.tbl_keyrings.c.id,
                                 acl=relation(ACL, primaryjoin=(self.tbl_keyrings.c.acl_id == self.tbl_acl.c.id)))),
M
Mark Hymers 已提交
2268

M
Mark Hymers 已提交
2269
        mapper(DBChange, self.tbl_changes,
2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280
               properties=dict(change_id=self.tbl_changes.c.id,
                                 seen=self.tbl_changes.c.seen,
                                 source=self.tbl_changes.c.source,
                                 binaries=self.tbl_changes.c.binaries,
                                 architecture=self.tbl_changes.c.architecture,
                                 distribution=self.tbl_changes.c.distribution,
                                 urgency=self.tbl_changes.c.urgency,
                                 maintainer=self.tbl_changes.c.maintainer,
                                 changedby=self.tbl_changes.c.changedby,
                                 date=self.tbl_changes.c.date,
                                 version=self.tbl_changes.c.version))
J
Joerg Jaspert 已提交
2281

M
Mark Hymers 已提交
2282
        mapper(Maintainer, self.tbl_maintainer,
2283 2284
               properties=dict(maintainer_id=self.tbl_maintainer.c.id,
                   maintains_sources=relation(DBSource, backref='maintainer',
2285
                       primaryjoin=(self.tbl_maintainer.c.id == self.tbl_source.c.maintainer)),
2286
                   changed_sources=relation(DBSource, backref='changedby',
2287
                       primaryjoin=(self.tbl_maintainer.c.id == self.tbl_source.c.changedby))),
2288
        )
M
Mark Hymers 已提交
2289

M
Mark Hymers 已提交
2290
        mapper(NewComment, self.tbl_new_comments,
2291 2292
               properties=dict(comment_id=self.tbl_new_comments.c.id,
                                 policy_queue=relation(PolicyQueue)))
M
Mark Hymers 已提交
2293

M
Mark Hymers 已提交
2294
        mapper(Override, self.tbl_override,
2295
               properties=dict(suite_id=self.tbl_override.c.suite,
2296
                                 suite=relation(Suite,
T
Torsten Werner 已提交
2297
                                    backref=backref('overrides', lazy='dynamic')),
2298 2299
                                 package=self.tbl_override.c.package,
                                 component_id=self.tbl_override.c.component,
2300
                                 component=relation(Component,
2301
                                    backref=backref('overrides', lazy='dynamic')),
2302
                                 priority_id=self.tbl_override.c.priority,
2303
                                 priority=relation(Priority,
2304
                                    backref=backref('overrides', lazy='dynamic')),
2305
                                 section_id=self.tbl_override.c.section,
2306
                                 section=relation(Section,
2307
                                    backref=backref('overrides', lazy='dynamic')),
2308
                                 overridetype_id=self.tbl_override.c.type,
2309
                                 overridetype=relation(OverrideType,
2310
                                    backref=backref('overrides', lazy='dynamic'))))
M
Mark Hymers 已提交
2311 2312

        mapper(OverrideType, self.tbl_override_type,
2313 2314
               properties=dict(overridetype=self.tbl_override_type.c.type,
                                 overridetype_id=self.tbl_override_type.c.id))
M
Mark Hymers 已提交
2315

2316
        mapper(PolicyQueue, self.tbl_policy_queue,
2317 2318
               properties=dict(policy_queue_id=self.tbl_policy_queue.c.id,
                                 suite=relation(Suite, primaryjoin=(self.tbl_policy_queue.c.suite_id == self.tbl_suite.c.id))))
2319

2320
        mapper(PolicyQueueUpload, self.tbl_policy_queue_upload,
2321 2322 2323 2324 2325 2326
               properties=dict(
                   changes=relation(DBChange),
                   policy_queue=relation(PolicyQueue, backref='uploads'),
                   target_suite=relation(Suite),
                   source=relation(DBSource),
                   binaries=relation(DBBinary, secondary=self.tbl_policy_queue_upload_binaries_map),
2327
                   ))
2328 2329

        mapper(PolicyQueueByhandFile, self.tbl_policy_queue_byhand_file,
2330 2331
               properties=dict(
                   upload=relation(PolicyQueueUpload, backref='byhand'),
2332 2333 2334
                   )
               )

M
Mark Hymers 已提交
2335
        mapper(Priority, self.tbl_priority,
2336
               properties=dict(priority_id=self.tbl_priority.c.id))
M
Mark Hymers 已提交
2337

2338 2339
        mapper(SignatureHistory, self.tbl_signature_history)

2340
        mapper(DBSource, self.tbl_source,
2341 2342 2343 2344 2345 2346 2347 2348 2349
               properties=dict(source_id=self.tbl_source.c.id,
                                 version=self.tbl_source.c.version,
                                 maintainer_id=self.tbl_source.c.maintainer,
                                 poolfile_id=self.tbl_source.c.file,
                                 poolfile=relation(PoolFile),
                                 fingerprint_id=self.tbl_source.c.sig_fpr,
                                 fingerprint=relation(Fingerprint),
                                 changedby_id=self.tbl_source.c.changedby,
                                 srcfiles=relation(DSCFile,
2350
                                                     primaryjoin=(self.tbl_source.c.id == self.tbl_dsc_files.c.source)),
2351
                                 suites=relation(Suite, secondary=self.tbl_src_associations,
2352
                                     backref=backref('sources', lazy='dynamic')),
2353
                                 uploaders=relation(Maintainer,
2354
                                     secondary=self.tbl_src_uploaders),
2355
                                 key=relation(SourceMetadata, cascade='all',
2356
                                     collection_class=attribute_mapped_collection('key'))),
2357
        )
M
Mark Hymers 已提交
2358

2359
        mapper(SrcFormat, self.tbl_src_format,
2360 2361
               properties=dict(src_format_id=self.tbl_src_format.c.id,
                                 format_name=self.tbl_src_format.c.format_name))
2362

M
Mark Hymers 已提交
2363
        mapper(Suite, self.tbl_suite,
2364 2365 2366 2367 2368
               properties=dict(suite_id=self.tbl_suite.c.id,
                                 policy_queue=relation(PolicyQueue, primaryjoin=(self.tbl_suite.c.policy_queue_id == self.tbl_policy_queue.c.id)),
                                 new_queue=relation(PolicyQueue, primaryjoin=(self.tbl_suite.c.new_queue_id == self.tbl_policy_queue.c.id)),
                                 debug_suite=relation(Suite, remote_side=[self.tbl_suite.c.id]),
                                 copy_queues=relation(BuildQueue,
M
Mark Hymers 已提交
2369
                                     secondary=self.tbl_suite_build_queue_copy),
2370
                                 srcformats=relation(SrcFormat, secondary=self.tbl_suite_src_formats,
2371
                                     backref=backref('suites', lazy='dynamic')),
2372 2373 2374
                                 archive=relation(Archive, backref='suites'),
                                 acls=relation(ACL, secondary=self.tbl_suite_acl_map, collection_class=set),
                                 components=relation(Component, secondary=self.tbl_component_suite,
2375
                                                   order_by=self.tbl_component.c.ordering,
2376 2377 2378
                                                   backref=backref('suites')),
                                 architectures=relation(Architecture, secondary=self.tbl_suite_architectures,
                                     backref=backref('suites'))),
2379
        )
M
Mark Hymers 已提交
2380 2381

        mapper(Uid, self.tbl_uid,
2382 2383
               properties=dict(uid_id=self.tbl_uid.c.id,
                                 fingerprint=relation(Fingerprint)),
2384
        )
M
Mark Hymers 已提交
2385

2386
        mapper(BinContents, self.tbl_bin_contents,
2387 2388
            properties=dict(
                binary=relation(DBBinary,
2389
                    backref=backref('contents', lazy='dynamic', cascade='all')),
2390
                file=self.tbl_bin_contents.c.file))
2391

2392
        mapper(SrcContents, self.tbl_src_contents,
2393 2394
            properties=dict(
                source=relation(DBSource,
2395
                    backref=backref('contents', lazy='dynamic', cascade='all')),
2396
                file=self.tbl_src_contents.c.file))
2397

T
Torsten Werner 已提交
2398
        mapper(MetadataKey, self.tbl_metadata_keys,
2399 2400 2401
            properties=dict(
                key_id=self.tbl_metadata_keys.c.key_id,
                key=self.tbl_metadata_keys.c.key))
T
Torsten Werner 已提交
2402 2403

        mapper(BinaryMetadata, self.tbl_binaries_metadata,
2404 2405 2406 2407 2408 2409
            properties=dict(
                binary_id=self.tbl_binaries_metadata.c.bin_id,
                binary=relation(DBBinary),
                key_id=self.tbl_binaries_metadata.c.key_id,
                key=relation(MetadataKey),
                value=self.tbl_binaries_metadata.c.value))
T
Torsten Werner 已提交
2410 2411

        mapper(SourceMetadata, self.tbl_source_metadata,
2412 2413 2414 2415 2416 2417
            properties=dict(
                source_id=self.tbl_source_metadata.c.src_id,
                source=relation(DBSource),
                key_id=self.tbl_source_metadata.c.key_id,
                key=relation(MetadataKey),
                value=self.tbl_source_metadata.c.value))
T
Torsten Werner 已提交
2418

2419
        mapper(VersionCheck, self.tbl_version_check,
2420 2421
            properties=dict(
                suite_id=self.tbl_version_check.c.suite,
2422
                suite=relation(Suite, primaryjoin=self.tbl_version_check.c.suite == self.tbl_suite.c.id),
2423
                reference_id=self.tbl_version_check.c.reference,
2424
                reference=relation(Suite, primaryjoin=self.tbl_version_check.c.reference == self.tbl_suite.c.id, lazy='joined')))
2425

M
Mark Hymers 已提交
2426 2427
    ## Connection functions
    def __createconn(self):
2428
        from .config import Config
2429
        cnf = Config()
2430
        if "DB::Service" in cnf:
2431
            connstr = "postgresql://service=%s" % cnf["DB::Service"]
2432
        elif "DB::Host" in cnf:
M
Mark Hymers 已提交
2433
            # TCP/IP
2434
            connstr = "postgresql://%s" % cnf["DB::Host"]
2435
            if "DB::Port" in cnf and cnf["DB::Port"] != "-1":
M
Mark Hymers 已提交
2436 2437 2438 2439
                connstr += ":%s" % cnf["DB::Port"]
            connstr += "/%s" % cnf["DB::Name"]
        else:
            # Unix Socket
2440
            connstr = "postgresql:///%s" % cnf["DB::Name"]
2441
            if "DB::Port" in cnf and cnf["DB::Port"] != "-1":
M
Mark Hymers 已提交
2442
                connstr += "?port=%s" % cnf["DB::Port"]
2443

B
Bastian Blank 已提交
2444
        engine_args = {'echo': self.debug}
2445
        if 'DB::PoolSize' in cnf:
2446
            engine_args['pool_size'] = int(cnf['DB::PoolSize'])
2447
        if 'DB::MaxOverflow' in cnf:
2448
            engine_args['max_overflow'] = int(cnf['DB::MaxOverflow'])
2449
        if cnf.get('DB::Unicode') == 'false':
2450 2451
            engine_args['use_native_unicode'] = False

2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465
        # Monkey patch a new dialect in in order to support service= syntax
        import sqlalchemy.dialects.postgresql
        from sqlalchemy.dialects.postgresql.psycopg2 import PGDialect_psycopg2
        class PGDialect_psycopg2_dak(PGDialect_psycopg2):
            def create_connect_args(self, url):
                if str(url).startswith('postgresql://service='):
                    # Eww
                    servicename = str(url)[21:]
                    return (['service=%s' % servicename], {})
                else:
                    return PGDialect_psycopg2.create_connect_args(self, url)

        sqlalchemy.dialects.postgresql.base.dialect = PGDialect_psycopg2_dak

2466
        try:
2467
            self.db_pg = create_engine(connstr, **engine_args)
2468 2469 2470 2471
            self.db_smaker = sessionmaker(bind=self.db_pg,
                                          autoflush=True,
                                          autocommit=False)

B
Bastian Blank 已提交
2472 2473 2474 2475 2476
            if self.db_meta is None:
                self.__class__.db_meta = Base.metadata
                self.__class__.db_meta.bind = self.db_pg
                self.__setuptables()
                self.__setupmappers()
2477

2478
        except OperationalError as e:
B
Bastian Blank 已提交
2479
            from . import utils
2480
            utils.fubar("Cannot connect to database (%s)" % str(e))
M
Mark Hymers 已提交
2481

2482
        self.pid = os.getpid()
M
Mark Hymers 已提交
2483

2484
    def session(self, work_mem=0):
2485 2486 2487 2488 2489 2490
        '''
        Returns a new session object. If a work_mem parameter is provided a new
        transaction is started and the work_mem parameter is set for this
        transaction. The work_mem parameter is measured in MB. A default value
        will be used if the parameter is not set.
        '''
2491 2492 2493
        # reinitialize DBConn in new processes
        if self.pid != os.getpid():
            self.__createconn()
2494 2495 2496 2497
        session = self.db_smaker()
        if work_mem > 0:
            session.execute("SET LOCAL work_mem TO '%d MB'" % work_mem)
        return session
M
Mark Hymers 已提交
2498

M
Mark Hymers 已提交
2499
__all__.append('DBConn')