dbconn.py 74.8 KB
Newer Older
1 2 3 4 5
""" 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 已提交
6
@copyright: 2009, 2010  Joerg Jaspert <joerg@debian.org>
7
@copyright: 2009  Mike O'Connor <stew@debian.org>
8 9
@license: GNU General Public License version 2 or later
"""
M
Mark Hymers 已提交
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33

# 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"

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

34
import apt_pkg
35
import functools
36
import inspect
37
import os
38
from os.path import normpath
M
Mark Hymers 已提交
39
import re
40
import subprocess
41
import warnings
A
Ansgar 已提交
42
from typing import Optional, TYPE_CHECKING, Union
T
Torsten Werner 已提交
43

44
from debian.debfile import Deb822
45
from tarfile import TarFile
M
Mark Hymers 已提交
46

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

M
Mark Hymers 已提交
55 56
# Don't remove this, we re-export the exceptions to scripts which import us
from sqlalchemy.exc import *
57
from sqlalchemy.orm.exc import NoResultFound
M
Mark Hymers 已提交
58

A
Ansgar 已提交
59
import daklib.gpg
60
from .aptversion import AptVersion
61 62
# Only import Config until Queue stuff is changed to store its config
# in the database
63
from .config import Config
64
from .textutils import fix_maintainer
M
Mark Hymers 已提交
65

66
# suppress some deprecation warnings in squeeze related to sqlalchemy
67 68
warnings.filterwarnings('ignore',
    "Predicate of partial index .* ignored during reflection",
69
    SAWarning)
70

B
Bastian Blank 已提交
71 72
from .database.base import Base

A
Ansgar 已提交
73 74 75
if TYPE_CHECKING:
    import sqlalchemy.orm.query

76

M
Mark Hymers 已提交
77 78
################################################################################

79 80 81
# Patch in support for the debversion field type so that it works during
# reflection

82
class DebVersion(sqlalchemy.types.UserDefinedType):
83 84 85
    def get_col_spec(self):
        return "DEBVERSION"

86 87 88
    def bind_processor(self, dialect):
        return None

89
    def result_processor(self, dialect, coltype):
90 91
        return None

92

93 94
from sqlalchemy.databases import postgresql
postgresql.ischema_names['debversion'] = DebVersion
95 96 97

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

98
__all__ = ['IntegrityError', 'SQLAlchemyError', 'DebVersion']
99 100 101

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

102

103
def session_wrapper(fn):
C
Chris Lamb 已提交
104 105 106 107
    """
    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.
108 109 110 111

    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 已提交
112 113
    """

114
    @functools.wraps(fn)
115 116 117
    def wrapped(*args, **kwargs):
        private_transaction = False

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

        if session is None:
122
            if len(args) < len(inspect.getfullargspec(fn).args):
123 124 125 126 127 128
                # 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]
129
                if session is None:
M
fixup  
Mark Hymers 已提交
130
                    args = list(args)
131 132
                    session = args[-1] = DBConn().session()
                    private_transaction = True
133 134 135 136 137

        if private_transaction:
            session.commit_or_flush = session.commit
        else:
            session.commit_or_flush = session.flush
138 139 140 141 142 143

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

    return wrapped

148

F
Frank Lichtenheld 已提交
149 150
__all__.append('session_wrapper')

151 152
################################################################################

153

154
class ORMObject:
155 156
    """
    ORMObject is a base class for all ORM classes mapped by SQLalchemy. All
T
Torsten Werner 已提交
157
    derived classes must implement the properties() method.
158 159 160 161 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
    """

    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.
        '''
191
        return '<%s(...)>' % (self.classname())
192

193 194
    @classmethod
    @session_wrapper
195
    def get(cls, primary_key,  session=None):
196 197 198 199 200 201 202 203 204 205 206 207
        '''
        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)

208
    def session(self):
209 210 211 212 213 214 215
        '''
        Returns the current session that is associated with the object. May
        return None is object is in detached state.
        '''

        return object_session(self)

216
    def clone(self, session=None):
T
Tollef Fog Heen 已提交
217
        """
218 219
        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
220 221
        provided. The function will fail if a session is provided and has
        unflushed changes.
222

223 224 225
        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.
226

227 228 229
        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 已提交
230 231
        resource leaks.
        """
232 233

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

252

253 254 255 256
__all__.append('ORMObject')

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

257

258 259 260 261
class ACL(ORMObject):
    def __repr__(self):
        return "<ACL {0}>".format(self.name)

262

263 264
__all__.append('ACL')

265

266 267 268 269
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)

270

271 272 273 274
__all__.append('ACLPerSource')

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

275

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

278 279
__all__.append('Architecture')

280

281
@session_wrapper
A
Ansgar 已提交
282
def get_architecture(architecture: str, session=None) -> Optional[Architecture]:
283
    """
A
Ansgar 已提交
284
    Returns database id for given `architecture`.
285

A
Ansgar 已提交
286 287 288 289
    :param architecture: The name of the architecture
    :param session: Optional SQLA session object (a temporary one will be
       generated if not supplied)
    :return: Architecture object for the given arch (None if not present)
290
    """
291

292
    q = session.query(Architecture).filter_by(arch_string=architecture)
A
Ansgar 已提交
293
    return q.one_or_none()
294

295

296 297
__all__.append('get_architecture')

M
Mark Hymers 已提交
298 299
################################################################################

300

301
class Archive:
M
Mark Hymers 已提交
302 303
    def __init__(self, *args, **kwargs):
        pass
M
Mark Hymers 已提交
304 305

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

308

309 310
__all__.append('Archive')

311

312
@session_wrapper
A
Ansgar 已提交
313
def get_archive(archive: str, session=None) -> Optional[Archive]:
314
    """
A
Ansgar 已提交
315
    returns database id for given `archive`.
316

A
Ansgar 已提交
317 318 319 320
    :param archive: the name of the arhive
    :param session: Optional SQLA session object (a temporary one will be
       generated if not supplied)
    :return: Archive object for the given name (None if not present)
321 322
    """
    archive = archive.lower()
323

324
    q = session.query(Archive).filter_by(archive_name=archive)
A
Ansgar 已提交
325
    return q.one_or_none()
326

327

328
__all__.append('get_archive')
329

M
Mark Hymers 已提交
330 331
################################################################################

332

333
class ArchiveFile:
334 335 336 337
    def __init__(self, archive=None, component=None, file=None):
        self.archive = archive
        self.component = component
        self.file = file
338

339 340 341 342
    @property
    def path(self):
        return os.path.join(self.archive.path, 'pool', self.component.component_name, self.file.filename)

343

344 345 346 347
__all__.append('ArchiveFile')

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

348

349
class BinContents(ORMObject):
350
    def __init__(self, file=None, binary=None):
351 352 353 354
        self.file = file
        self.binary = binary

    def properties(self):
355
        return ['file', 'binary']
M
Mike O'Connor 已提交
356

357

M
Mike O'Connor 已提交
358 359 360 361
__all__.append('BinContents')

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

362

363
class DBBinary(ORMObject):
364 365
    def __init__(self, package=None, source=None, version=None,
        maintainer=None, architecture=None, poolfile=None,
366
        binarytype='deb', fingerprint=None):
367 368 369 370 371 372 373
        self.package = package
        self.source = source
        self.version = version
        self.maintainer = maintainer
        self.architecture = architecture
        self.poolfile = poolfile
        self.binarytype = binarytype
374
        self.fingerprint = fingerprint
M
Mark Hymers 已提交
375

M
Mark Hymers 已提交
376 377 378 379
    @property
    def pkid(self):
        return self.binary_id

380 381 382 383 384 385 386 387
    @property
    def name(self):
        return self.package

    @property
    def arch_string(self):
        return "%s" % self.architecture

388
    def properties(self):
389 390
        return ['package', 'version', 'maintainer', 'source', 'architecture',
            'poolfile', 'binarytype', 'fingerprint', 'install_date',
M
Mark Hymers 已提交
391
            'suites_count', 'binary_id', 'contents_count', 'extra_sources']
392

393 394
    metadata = association_proxy('key', 'value')

395 396 397
    def scan_contents(self):
        '''
        Yields the contents of the package. Only regular files are yielded and
398 399 400
        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.
401 402
        '''
        fullpath = self.poolfile.fullpath
403
        dpkg_cmd = ('dpkg-deb', '--fsys-tarfile', fullpath)
404
        dpkg = subprocess.Popen(dpkg_cmd, stdout=subprocess.PIPE)
405
        tar = TarFile.open(fileobj=dpkg.stdout, mode='r|')
406
        for member in tar.getmembers():
407
            if not member.isdir():
408 409
                name = normpath(member.name)
                yield name
410
        tar.close()
411 412
        dpkg.stdout.close()
        dpkg.wait()
413

A
Ansgar 已提交
414
    def read_control(self) -> bytes:
M
Mark Hymers 已提交
415 416 417
        '''
        Reads the control information from a binary.

A
Ansgar 已提交
418
        :return: stanza text of the control section.
M
Mark Hymers 已提交
419
        '''
B
Bastian Blank 已提交
420
        from . import utils
M
Mark Hymers 已提交
421
        fullpath = self.poolfile.fullpath
422
        return utils.deb_extract_control(fullpath)
M
Mark Hymers 已提交
423

A
Ansgar 已提交
424
    def read_control_fields(self) -> apt_pkg.TagSection:
M
Mark Hymers 已提交
425 426 427
        '''
        Reads the control information from a binary and return
        as a dictionary.
M
Mark Hymers 已提交
428

A
Ansgar 已提交
429
        :return: fields of the control section as a dictionary.
M
Mark Hymers 已提交
430 431 432
        '''
        stanza = self.read_control()
        return apt_pkg.TagSection(stanza)
M
Mark Hymers 已提交
433

434 435 436 437 438 439
    @property
    def proxy(self):
        session = object_session(self)
        query = session.query(BinaryMetadata).filter_by(binary=self)
        return MetadataProxy(session, query)

440

441
__all__.append('DBBinary')
442

443

444
@session_wrapper
A
Ansgar 已提交
445
def get_suites_binary_in(package: str, session=None) -> 'list[Suite]':
446
    """
A
Ansgar 已提交
447
    Returns list of Suite objects which given `package` name is in
448

A
Ansgar 已提交
449 450
    :param package: DBBinary package name to search for
    :return: list of Suite objects for the given package
451 452
    """

453
    return session.query(Suite).filter(Suite.binaries.any(DBBinary.package == package)).all()
454

455

456 457
__all__.append('get_suites_binary_in')

458

459
@session_wrapper
A
Ansgar 已提交
460
def get_component_by_package_suite(package: str, suite_list: list[str], arch_list: Optional[str] = None, session=None) -> Optional[str]:
461 462
    '''
    Returns the component name of the newest binary package in suite_list or
463 464
    None if no package is found. The result can be optionally filtered by a list
    of architecture names.
465

A
Ansgar 已提交
466 467 468 469
    :param package: DBBinary package name to search for
    :param suite_list: list of suite_name items
    :param arch_list: optional list of arch_string items that defaults to []
    :return: name of component or None
470 471
    '''

472
    q = session.query(DBBinary).filter_by(package=package). \
473
        join(DBBinary.suites).filter(Suite.suite_name.in_(suite_list))
N
Niels Thykier 已提交
474
    if arch_list:
475 476 477
        q = q.join(DBBinary.architecture). \
            filter(Architecture.arch_string.in_(arch_list))
    binary = q.order_by(desc(DBBinary.version)).first()
478 479 480
    if binary is None:
        return None
    else:
481
        return binary.poolfile.component.component_name
482

483

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

M
Mark Hymers 已提交
486 487
################################################################################

488

489
class BuildQueue:
490 491 492 493
    def __init__(self, *args, **kwargs):
        pass

    def __repr__(self):
494
        return '<BuildQueue %s>' % self.queue_name
495

496

497 498 499 500
__all__.append('BuildQueue')

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

501

502
class Component(ORMObject):
503
    def __init__(self, component_name=None):
504
        self.component_name = component_name
M
Mark Hymers 已提交
505

506 507
    def __eq__(self, val):
        if isinstance(val, str):
A
Ansgar 已提交
508
            warnings.warn("comparison with a `str` is deprecated", DeprecationWarning, stacklevel=2)
509 510 511 512 513 514
            return (self.component_name == val)
        # This signals to use the normal comparison operator
        return NotImplemented

    def __ne__(self, val):
        if isinstance(val, str):
A
Ansgar 已提交
515
            warnings.warn("comparison with a `str` is deprecated", DeprecationWarning, stacklevel=2)
516 517 518 519
            return (self.component_name != val)
        # This signals to use the normal comparison operator
        return NotImplemented

520 521
    __hash__ = ORMObject.__hash__

522
    def properties(self):
523
        return ['component_name', 'component_id', 'description',
A
Ansgar Burchardt 已提交
524
            'meets_dfsg', 'overrides_count']
525

526

527 528
__all__.append('Component')

529

530
@session_wrapper
A
Ansgar 已提交
531
def get_component(component: str, session=None) -> Optional[Component]:
532
    """
A
Ansgar 已提交
533
    Returns database id for given `component`.
534

A
Ansgar 已提交
535 536
    :param component: The name of the override type
    :return: the database id for the given component
537 538
    """
    component = component.lower()
539

540
    q = session.query(Component).filter_by(component_name=component)
541

A
Ansgar 已提交
542
    return q.one_or_none()
543

544

545 546
__all__.append('get_component')

547

548 549 550 551 552 553 554 555
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

556

557 558
__all__.append('get_mapped_component_name')

559

560
@session_wrapper
A
Ansgar 已提交
561
def get_mapped_component(component_name: str, session=None) -> Optional[Component]:
562 563 564 565 566
    """get component after mappings

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

A
Ansgar 已提交
567
    .. todo::
568

A
Ansgar 已提交
569 570
       ansgar wants to get rid of this. It's currently only used for
       the security archive
571

A
Ansgar 已提交
572 573
    :param component_name: component name
    :param session: database session
A
Ansgar 已提交
574
    :return: component after applying maps or :const:`None`
575
    """
576
    component_name = get_mapped_component_name(component_name)
577 578 579
    component = session.query(Component).filter_by(component_name=component_name).first()
    return component

580

581 582
__all__.append('get_mapped_component')

583

M
Mark Hymers 已提交
584
@session_wrapper
A
Ansgar 已提交
585
def get_component_names(session=None) -> list[str]:
M
Mark Hymers 已提交
586 587 588
    """
    Returns list of strings of component names.

A
Ansgar 已提交
589
    :return: list of strings of component names
M
Mark Hymers 已提交
590 591
    """

B
Bastian Blank 已提交
592
    return [x.component_name for x in session.query(Component).all()]
M
Mark Hymers 已提交
593

594

M
Mark Hymers 已提交
595 596
__all__.append('get_component_names')

M
Mark Hymers 已提交
597 598
################################################################################

599

600
class DBConfig:
M
Mark Hymers 已提交
601 602
    def __init__(self, *args, **kwargs):
        pass
M
Mark Hymers 已提交
603 604 605 606

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

607

608 609
__all__.append('DBConfig')

M
Mark Hymers 已提交
610 611
################################################################################

612

613
class DSCFile:
M
Mark Hymers 已提交
614 615
    def __init__(self, *args, **kwargs):
        pass
M
Mark Hymers 已提交
616 617 618 619

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

620

621 622
__all__.append('DSCFile')

623

624
@session_wrapper
A
Ansgar 已提交
625 626 627 628 629 630
def get_dscfiles(
        dscfile_id: Optional[int] = None,
        source_id: Optional[int] = None,
        poolfile_id: Optional[int] = None,
        session=None
) -> list[DSCFile]:
M
Mark Hymers 已提交
631 632 633
    """
    Returns a list of DSCFiles which may be empty

A
Ansgar 已提交
634 635 636 637
    :param dscfile_id: the dscfile_id of the DSCFiles to find
    :param source_id: the source id related to the DSCFiles to find
    :param poolfile_id: the poolfile id related to the DSCFiles to find
    :return: Possibly empty list of DSCFiles
M
Mark Hymers 已提交
638 639 640 641 642 643 644 645 646 647 648 649 650
    """

    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)

651
    return q.all()
M
Mark Hymers 已提交
652

653

M
Mark Hymers 已提交
654 655
__all__.append('get_dscfiles')

M
Mark Hymers 已提交
656 657
################################################################################

658

A
Ansgar Burchardt 已提交
659 660 661 662 663 664 665
class ExternalOverride(ORMObject):
    def __init__(self, *args, **kwargs):
        pass

    def __repr__(self):
        return '<ExternalOverride %s = %s: %s>' % (self.package, self.key, self.value)

666

A
Ansgar Burchardt 已提交
667 668 669 670
__all__.append('ExternalOverride')

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

671

672
class PoolFile(ORMObject):
673
    def __init__(self, filename=None, filesize=-1,
674
        md5sum=None):
675 676 677
        self.filename = filename
        self.filesize = filesize
        self.md5sum = md5sum
M
Mark Hymers 已提交
678

M
Mark Hymers 已提交
679 680
    @property
    def fullpath(self):
681
        session = DBConn().session().object_session(self)
682 683 684
        af = session.query(ArchiveFile).join(Archive) \
                    .filter(ArchiveFile.file == self) \
                    .order_by(Archive.tainted.desc()).first()
685
        return af.path
M
Mark Hymers 已提交
686

687 688 689 690 691 692 693
    @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)

694 695 696 697
    @property
    def basename(self):
        return os.path.basename(self.filename)

698
    def properties(self):
699
        return ['filename', 'file_id', 'filesize', 'md5sum', 'sha1sum',
700
            'sha256sum', 'source', 'binary', 'last_used']
701

702

703 704
__all__.append('PoolFile')

M
Mark Hymers 已提交
705 706
################################################################################

707

708
class Fingerprint(ORMObject):
709
    def __init__(self, fingerprint=None):
T
Torsten Werner 已提交
710
        self.fingerprint = fingerprint
M
Mark Hymers 已提交
711

712
    def properties(self):
713
        return ['fingerprint', 'fingerprint_id', 'keyring', 'uid',
714 715
            'binary_reject']

716

717 718
__all__.append('Fingerprint')

719

M
Mark Hymers 已提交
720
@session_wrapper
A
Ansgar 已提交
721
def get_fingerprint(fpr: str, session=None) -> Optional[Fingerprint]:
M
Mark Hymers 已提交
722 723 724
    """
    Returns Fingerprint object for given fpr.

A
Ansgar 已提交
725 726 727 728
    :param fpr: The fpr to find / add
    :param session: Optional SQL session object (a temporary one will be
       generated if not supplied).
    :return: the Fingerprint object for the given fpr or None
M
Mark Hymers 已提交
729 730 731
    """

    q = session.query(Fingerprint).filter_by(fingerprint=fpr)
A
Ansgar 已提交
732
    return q.one_or_none()
M
Mark Hymers 已提交
733

734

M
Mark Hymers 已提交
735 736
__all__.append('get_fingerprint')

737

738
@session_wrapper
A
Ansgar 已提交
739
def get_or_set_fingerprint(fpr: str, session=None) -> Fingerprint:
M
Mark Hymers 已提交
740 741 742 743 744
    """
    Returns Fingerprint object for given fpr.

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

A
Ansgar 已提交
745 746 747 748 749 750
    :param fpr: The fpr to find / add
    :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.
    :return: the Fingerprint object for the given fpr
M
Mark Hymers 已提交
751 752
    """

753
    q = session.query(Fingerprint).filter_by(fingerprint=fpr)
754 755 756 757

    try:
        ret = q.one()
    except NoResultFound:
758 759 760
        fingerprint = Fingerprint()
        fingerprint.fingerprint = fpr
        session.add(fingerprint)
761
        session.commit_or_flush()
762
        ret = fingerprint
M
Mark Hymers 已提交
763

764
    return ret
M
Mark Hymers 已提交
765

766

M
Mark Hymers 已提交
767 768
__all__.append('get_or_set_fingerprint')

M
Mark Hymers 已提交
769 770
################################################################################

M
Mark Hymers 已提交
771
# Helper routine for Keyring class
772 773


M
Mark Hymers 已提交
774 775 776
def get_ldap_name(entry):
    name = []
    for k in ["cn", "mn", "sn"]:
777 778 779
        ret = entry.get(k)
        if not ret:
            continue
A
Ansgar 已提交
780
        value = ret[0].decode()
781 782
        if value and value[0] != "-":
            name.append(value)
M
Mark Hymers 已提交
783 784 785 786
    return " ".join(name)

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

787

788
class Keyring:
M
Mark Hymers 已提交
789 790 791
    keys = {}
    fpr_lookup = {}

M
Mark Hymers 已提交
792 793
    def __init__(self, *args, **kwargs):
        pass
M
Mark Hymers 已提交
794 795 796 797

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

798 799
    def de_escape_gpg_str(self, txt):
        esclist = re.split(r'(\\x..)', txt)
800 801
        for x in range(1, len(esclist), 2):
            esclist[x] = "%c" % (int(esclist[x][2:], 16))
M
Mark Hymers 已提交
802 803
        return "".join(esclist)

T
Torsten Werner 已提交
804 805
    def parse_address(self, uid):
        """parses uid and returns a tuple of real name and email address"""
806 807
        import email.utils
        (name, address) = email.utils.parseaddr(uid)
T
Torsten Werner 已提交
808 809 810 811 812
        name = re.sub(r"\s*[(].*[)]", "", name)
        name = self.de_escape_gpg_str(name)
        if name == "":
            name = uid
        return (name, address)
M
Mark Hymers 已提交
813

T
Torsten Werner 已提交
814
    def load_keys(self, keyring):
M
Mark Hymers 已提交
815 816 817
        if not self.keyring_id:
            raise Exception('Must be initialized with database information')

818 819
        cmd = ["gpg", "--no-default-keyring", "--keyring", keyring,
               "--with-colons", "--fingerprint", "--fingerprint"]
820
        p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
821

M
Mark Hymers 已提交
822
        key = None
823
        need_fingerprint = False
M
Mark Hymers 已提交
824

825
        for line_raw in p.stdout:
826
            try:
A
Ansgar 已提交
827
                line = line_raw.decode()
828 829 830
            except UnicodeDecodeError:
                # Some old UIDs might not use UTF-8 encoding. We assume they
                # use latin1.
A
Ansgar 已提交
831
                line = line_raw.decode('latin1')
M
Mark Hymers 已提交
832 833 834
            field = line.split(":")
            if field[0] == "pub":
                key = field[4]
T
Torsten Werner 已提交
835 836 837 838
                self.keys[key] = {}
                (name, addr) = self.parse_address(field[9])
                if "@" in addr:
                    self.keys[key]["email"] = addr
M
Mark Hymers 已提交
839
                    self.keys[key]["name"] = name
840
                need_fingerprint = True
M
Mark Hymers 已提交
841
            elif key and field[0] == "uid":
T
Torsten Werner 已提交
842 843 844 845
                (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
846 847
            elif need_fingerprint and field[0] == "fpr":
                self.keys[key]["fingerprints"] = [field[9]]
M
Mark Hymers 已提交
848
                self.fpr_lookup[field[9]] = key
849
                need_fingerprint = False
M
Mark Hymers 已提交
850

851 852
        (out, err) = p.communicate()
        r = p.returncode
853
        if r != 0:
A
Ansgar 已提交
854
            raise daklib.gpg.GpgException("command failed: %s\nstdout: %s\nstderr: %s\n" % (cmd, out, err))
855

M
Mark Hymers 已提交
856
    def import_users_from_ldap(self, session):
857
        from .utils import open_ldap_connection
A
Ansgar 已提交
858
        import ldap  # type: ignore
859
        l = open_ldap_connection()
M
Mark Hymers 已提交
860
        cnf = Config()
A
Ansgar 已提交
861
        LDAPDn = cnf["Import-LDAP-Fingerprints::LDAPDn"]
M
Mark Hymers 已提交
862
        Attrs = l.search_s(LDAPDn, ldap.SCOPE_ONELEVEL,
863
               "(&(keyfingerprint=*)(supplementaryGid=%s))" % (cnf["Import-Users-From-Passwd::ValidGID"]),
M
Mark Hymers 已提交
864 865 866 867 868 869 870
               ["uid", "keyfingerprint", "cn", "mn", "sn"])

        byuid = {}
        byname = {}

        for i in Attrs:
            entry = i[1]
A
Ansgar 已提交
871
            uid = entry["uid"][0].decode()
M
Mark Hymers 已提交
872 873 874
            name = get_ldap_name(entry)
            fingerprints = entry["keyFingerPrint"]
            keyid = None
A
Ansgar 已提交
875 876
            for f_raw in fingerprints:
                f = f_raw.decode()
M
Mark Hymers 已提交
877 878 879 880 881
                key = self.fpr_lookup.get(f, None)
                if key not in self.keys:
                    continue
                self.keys[key]["uid"] = uid

882
                if keyid is not None:
M
Mark Hymers 已提交
883 884 885 886 887 888 889 890 891 892 893
                    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
894
        for x in list(self.keys.keys()):
T
Torsten Werner 已提交
895
            if "email" not in self.keys[x]:
M
Mark Hymers 已提交
896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912
                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)

913

914 915
__all__.append('Keyring')

916

917
@session_wrapper
A
Ansgar 已提交
918
def get_keyring(keyring: str, session=None) -> Optional[Keyring]:
919
    """
A
Ansgar 已提交
920 921
    If `keyring` does not have an entry in the `keyrings` table yet, return None
    If `keyring` already has an entry, simply return the existing :class:`Keyring`
922

A
Ansgar 已提交
923
    :param keyring: the keyring name
A
Ansgar 已提交
924
    :return: the :class:`Keyring` object for this keyring
925 926
    """

927
    q = session.query(Keyring).filter_by(keyring_name=keyring)
A
Ansgar 已提交
928
    return q.one_or_none()
929

930

M
Mark Hymers 已提交
931
__all__.append('get_keyring')
932

933

M
Mark Hymers 已提交
934
@session_wrapper
A
Ansgar 已提交
935
def get_active_keyring_paths(session=None) -> list[str]:
M
Mark Hymers 已提交
936
    """
A
Ansgar 已提交
937
    :return: list of active keyring paths
M
Mark Hymers 已提交
938
    """
B
Bastian Blank 已提交
939
    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 已提交
940

941

M
Mark Hymers 已提交
942 943
__all__.append('get_active_keyring_paths')

M
Mark Hymers 已提交
944
################################################################################
945

946

947
class DBChange:
J
Joerg Jaspert 已提交
948 949 950 951
    def __init__(self, *args, **kwargs):
        pass

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

954

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

957

J
Joerg Jaspert 已提交
958
@session_wrapper
A
Ansgar 已提交
959
def get_dbchange(filename: str, session=None) -> Optional[DBChange]:
J
Joerg Jaspert 已提交
960
    """
A
Ansgar 已提交
961
    returns DBChange object for given `filename`.
J
Joerg Jaspert 已提交
962

A
Ansgar 已提交
963 964 965
    :param filename: the name of the file
    :param session: Optional SQLA session object (a temporary one will be
       generated if not supplied)
A
Ansgar 已提交
966
    :return:  DBChange object for the given filename (:const:`None` if not present)
J
Joerg Jaspert 已提交
967
    """
M
Mark Hymers 已提交
968
    q = session.query(DBChange).filter_by(changesname=filename)
A
Ansgar 已提交
969
    return q.one_or_none()
J
Joerg Jaspert 已提交
970

971

M
Mark Hymers 已提交
972
__all__.append('get_dbchange')
973

M
Mark Hymers 已提交
974 975
################################################################################

976

977
class Maintainer(ORMObject):
978
    def __init__(self, name=None):
979
        self.name = name
M
Mark Hymers 已提交
980

981 982 983
    def properties(self):
        return ['name', 'maintainer_id']

M
Mark Hymers 已提交
984 985 986 987 988 989
    def get_split_maintainer(self):
        if not hasattr(self, 'name') or self.name is None:
            return ('', '', '', '')

        return fix_maintainer(self.name.strip())

990

991 992
__all__.append('Maintainer')

993

994
@session_wrapper
A
Ansgar 已提交
995
def get_or_set_maintainer(name: str, session=None) -> Maintainer:
M
Mark Hymers 已提交
996 997 998 999 1000
    """
    Returns Maintainer object for given maintainer name.

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

A
Ansgar 已提交
1001 1002 1003 1004 1005 1006
    :param name: The maintainer name to add
    :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.
    :return: the Maintainer object for the given maintainer
M
Mark Hymers 已提交
1007 1008
    """

1009
    q = session.query(Maintainer).filter_by(name=name)
1010 1011 1012
    try:
        ret = q.one()
    except NoResultFound:
1013 1014 1015
        maintainer = Maintainer()
        maintainer.name = name
        session.add(maintainer)
1016
        session.commit_or_flush()
1017
        ret = maintainer
M
Mark Hymers 已提交
1018

1019
    return ret
M
Mark Hymers 已提交
1020

1021

M
Mark Hymers 已提交
1022 1023
__all__.append('get_or_set_maintainer')

1024

1025
@session_wrapper
A
Ansgar 已提交
1026
def get_maintainer(maintainer_id: int, session=None) -> Optional[Maintainer]:
C
Chris Lamb 已提交
1027
    """
A
Ansgar 已提交
1028 1029
    Return the name of the maintainer behind `maintainer_id` or :const:`None`
    if that `maintainer_id` is invalid.
C
Chris Lamb 已提交
1030

A
Ansgar 已提交
1031
    :param maintainer_id: the id of the maintainer
A
Ansgar 已提交
1032
    :return: the Maintainer with this `maintainer_id`
C
Chris Lamb 已提交
1033 1034
    """

1035
    return session.query(Maintainer).get(maintainer_id)
C
Chris Lamb 已提交
1036

1037

C
Chris Lamb 已提交
1038 1039
__all__.append('get_maintainer')

M
Mark Hymers 已提交
1040 1041
################################################################################

1042

1043
class NewComment:
M
Mark Hymers 已提交
1044 1045 1046 1047 1048 1049
    def __init__(self, *args, **kwargs):
        pass

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

1050

M
Mark Hymers 已提交
1051 1052
__all__.append('NewComment')

1053

1054
@session_wrapper
A
Ansgar 已提交
1055
def has_new_comment(policy_queue, package: str, version: str, session=None) -> bool:
M
Mark Hymers 已提交
1056
    """
A
Ansgar 已提交
1057
    Returns :const:`True` if the given combination of `package`, `version` has a comment.
M
Mark Hymers 已提交
1058

A
Ansgar 已提交
1059 1060 1061 1062
    :param package: name of the package
    :param version: package version
    :param session: Optional SQLA session object (a temporary one will be
       generated if not supplied)
M
Mark Hymers 已提交
1063 1064
    """

1065
    q = session.query(NewComment).filter_by(policy_queue=policy_queue)
M
Mark Hymers 已提交
1066 1067
    q = q.filter_by(package=package)
    q = q.filter_by(version=version)
1068

1069
    return bool(q.count() > 0)
M
Mark Hymers 已提交
1070

1071

M
Mark Hymers 已提交
1072 1073
__all__.append('has_new_comment')

1074

1075
@session_wrapper
A
Ansgar 已提交
1076 1077 1078 1079 1080 1081 1082
def get_new_comments(
        policy_queue,
        package: Optional[str] = None,
        version: Optional[str] = None,
        comment_id: Optional[int] = None,
        session=None
) -> list[NewComment]:
M
Mark Hymers 已提交
1083 1084 1085 1086
    """
    Returns (possibly empty) list of NewComment objects for the given
    parameters

A
Ansgar 已提交
1087 1088 1089 1090 1091 1092
    :param package: name of the package
    :param version: package version
    :param comment_id: An id of a comment
    :param session: Optional SQLA session object (a temporary one will be
       generated if not supplied)
    :return: A (possibly empty) list of NewComment objects will be returned
M
Mark Hymers 已提交
1093 1094
    """

1095
    q = session.query(NewComment).filter_by(policy_queue=policy_queue)
1096 1097 1098 1099 1100 1101
    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 已提交
1102

1103
    return q.all()
M
Mark Hymers 已提交
1104

1105

M
Mark Hymers 已提交
1106 1107 1108 1109
__all__.append('get_new_comments')

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

1110

1111
class Override(ORMObject):
1112
    def __init__(self, package=None, suite=None, component=None, overridetype=None,
1113
        section=None, priority=None):
1114 1115 1116 1117 1118 1119
        self.package = package
        self.suite = suite
        self.component = component
        self.overridetype = overridetype
        self.section = section
        self.priority = priority
M
Mark Hymers 已提交
1120

1121
    def properties(self):
1122
        return ['package', 'suite', 'component', 'overridetype', 'section',
1123 1124
            'priority']

1125

1126 1127
__all__.append('Override')

1128

1129
@session_wrapper
A
Ansgar 已提交
1130 1131 1132 1133 1134 1135 1136
def get_override(
        package: str,
        suite: Union[str, list[str], None] = None,
        component: Union[str, list[str], None] = None,
        overridetype: Union[str, list[str], None] = None,
        session=None
) -> list[Override]:
1137 1138 1139
    """
    Returns Override object for the given parameters

A
Ansgar 已提交
1140 1141
    :param package: The name of the package
    :param suite: The name of the suite (or suites if a list) to limit to.  If
1142
                  None, don't limit.  Defaults to None.
A
Ansgar 已提交
1143
    :param component: The name of the component (or components if a list) to
1144
                      limit to.  If None, don't limit.  Defaults to None.
A
Ansgar 已提交
1145
    :param overridetype: The name of the overridetype (or overridetypes if a list) to
1146
                         limit to.  If None, don't limit.  Defaults to None.
A
Ansgar 已提交
1147 1148 1149
    :param session: Optional SQLA session object (a temporary one will be
       generated if not supplied)
    :return: A (possibly empty) list of Override objects will be returned
1150 1151 1152 1153 1154 1155
    """

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

    if suite is not None:
1156 1157
        if not isinstance(suite, list):
            suite = [suite]
1158 1159 1160
        q = q.join(Suite).filter(Suite.suite_name.in_(suite))

    if component is not None:
1161 1162
        if not isinstance(component, list):
            component = [component]
1163 1164 1165
        q = q.join(Component).filter(Component.component_name.in_(component))

    if overridetype is not None:
1166 1167
        if not isinstance(overridetype, list):
            overridetype = [overridetype]
1168 1169
        q = q.join(OverrideType).filter(OverrideType.overridetype.in_(overridetype))

1170
    return q.all()
1171

1172

1173 1174 1175
__all__.append('get_override')


M
Mark Hymers 已提交
1176 1177
################################################################################

1178
class OverrideType(ORMObject):
1179
    def __init__(self, overridetype=None):
1180
        self.overridetype = overridetype
M
Mark Hymers 已提交
1181

1182
    def properties(self):
1183
        return ['overridetype', 'overridetype_id', 'overrides_count']
1184

1185

1186 1187
__all__.append('OverrideType')

1188

1189
@session_wrapper
A
Ansgar 已提交
1190
def get_override_type(override_type: str, session=None) -> Optional[OverrideType]:
1191
    """
A
Ansgar 已提交
1192
    Returns OverrideType object for given `override_type`.
1193

A
Ansgar 已提交
1194 1195 1196 1197
    :param override_type: The name of the override type
    :param session: Optional SQLA session object (a temporary one will be
       generated if not supplied)
    :return: the database id for the given override type
1198
    """
1199

M
Mark Hymers 已提交
1200
    q = session.query(OverrideType).filter_by(overridetype=override_type)
A
Ansgar 已提交
1201
    return q.one_or_none()
1202

1203

1204 1205
__all__.append('get_override_type')

M
Mark Hymers 已提交
1206 1207
################################################################################

1208

1209
class PolicyQueue:
1210 1211 1212 1213 1214 1215
    def __init__(self, *args, **kwargs):
        pass

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

1216

1217 1218
__all__.append('PolicyQueue')

1219

1220
@session_wrapper
A
Ansgar 已提交
1221
def get_policy_queue(queuename: str, session=None) -> Optional[PolicyQueue]:
1222
    """
A
Ansgar 已提交
1223
    Returns PolicyQueue object for given `queuename`
1224

A
Ansgar 已提交
1225 1226 1227 1228
    :param queuename: The name of the queue
    :param session: Optional SQLA session object (a temporary one will be
       generated if not supplied)
    :return: PolicyQueue object for the given queue
1229 1230 1231
    """

    q = session.query(PolicyQueue).filter_by(queue_name=queuename)
A
Ansgar 已提交
1232
    return q.one_or_none()
1233

1234

1235 1236
__all__.append('get_policy_queue')

1237 1238
################################################################################

1239

1240
@functools.total_ordering
1241
class PolicyQueueUpload:
1242 1243 1244 1245 1246 1247 1248 1249
    def _key(self):
        return (
            self.changes.source,
            AptVersion(self.changes.version),
            self.source is None,
            self.changes.changesname
        )

1250
    def __eq__(self, other):
1251
        return self._key() == other._key()
1252 1253

    def __lt__(self, other):
1254
        return self._key() < other._key()
1255

1256

1257 1258 1259 1260
__all__.append('PolicyQueueUpload')

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

1261

1262
class PolicyQueueByhandFile:
1263 1264
    pass

1265

1266 1267 1268 1269
__all__.append('PolicyQueueByhandFile')

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

1270

1271
class Priority(ORMObject):
1272
    def __init__(self, priority=None, level=None):
1273 1274 1275 1276 1277 1278
        self.priority = priority
        self.level = level

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

1279 1280
    def __eq__(self, val):
        if isinstance(val, str):
A
Ansgar 已提交
1281
            warnings.warn("comparison with a `str` is deprecated", DeprecationWarning, stacklevel=2)
1282 1283 1284 1285 1286 1287
            return (self.priority == val)
        # This signals to use the normal comparison operator
        return NotImplemented

    def __ne__(self, val):
        if isinstance(val, str):
A
Ansgar 已提交
1288
            warnings.warn("comparison with a `str` is deprecated", DeprecationWarning, stacklevel=2)
1289 1290 1291 1292
            return (self.priority != val)
        # This signals to use the normal comparison operator
        return NotImplemented

1293 1294
    __hash__ = ORMObject.__hash__

1295

1296 1297
__all__.append('Priority')

1298

1299
@session_wrapper
A
Ansgar 已提交
1300
def get_priority(priority: str, session=None) -> Optional[Priority]:
1301
    """
A
Ansgar 已提交
1302
    Returns Priority object for given `priority` name.
1303

A
Ansgar 已提交
1304 1305 1306 1307
    :param priority: The name of the priority
    :param session: Optional SQLA session object (a temporary one will be
       generated if not supplied)
    :return: Priority object for the given priority
1308
    """
1309

1310
    q = session.query(Priority).filter_by(priority=priority)
A
Ansgar 已提交
1311
    return q.one_or_none()
1312

1313

1314 1315
__all__.append('get_priority')

1316

1317
@session_wrapper
A
Ansgar 已提交
1318
def get_priorities(session=None) -> dict[str, int]:
1319 1320 1321
    """
    Returns dictionary of priority names -> id mappings

A
Ansgar 已提交
1322 1323 1324
    :param session: Optional SQL session object (a temporary one will be
       generated if not supplied)
    :return: dictionary of priority names -> id mappings
1325 1326 1327 1328 1329 1330 1331 1332 1333
    """

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

    return ret

1334

1335 1336
__all__.append('get_priorities')

M
Mark Hymers 已提交
1337 1338
################################################################################

1339

1340
from .database.section import Section
1341

1342 1343
__all__.append('Section')

1344

1345
@session_wrapper
A
Ansgar 已提交
1346
def get_section(section: str, session=None) -> Optional[Section]:
1347
    """
A
Ansgar 已提交
1348
    Returns Section object for given `section` name.
1349

A
Ansgar 已提交
1350 1351 1352 1353
    :param section: The name of the section
    :param session: Optional SQLA session object (a temporary one will be
       generated if not supplied)
    :return: Section object for the given section name
1354
    """
1355

1356
    q = session.query(Section).filter_by(section=section)
A
Ansgar 已提交
1357
    return q.one_or_none()
1358

1359

1360 1361
__all__.append('get_section')

1362

1363
@session_wrapper
A
Ansgar 已提交
1364
def get_sections(session=None) -> dict[str, int]:
1365 1366 1367
    """
    Returns dictionary of section names -> id mappings

A
Ansgar 已提交
1368 1369 1370
    :param session: Optional SQL session object (a temporary one will be
       generated if not supplied)
    :return: dictionary of section names -> id mappings
1371 1372 1373 1374 1375 1376 1377 1378 1379
    """

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

    return ret

1380

1381 1382
__all__.append('get_sections')

M
Mark Hymers 已提交
1383 1384
################################################################################

1385

1386 1387
class SignatureHistory(ORMObject):
    @classmethod
A
Ansgar 已提交
1388
    def from_signed_file(cls, signed_file: 'daklib.gpg.SignedFile') -> 'SignatureHistory':
1389 1390
        """signature history entry from signed file

A
Ansgar 已提交
1391
        :param signed_file: signed file
1392 1393 1394 1395 1396 1397 1398
        """
        self = cls()
        self.fingerprint = signed_file.primary_fingerprint
        self.signature_timestamp = signed_file.signature_timestamp
        self.contents_sha1 = signed_file.contents_sha1()
        return self

1399 1400 1401
    def query(self, session):
        return session.query(SignatureHistory).filter_by(fingerprint=self.fingerprint, signature_timestamp=self.signature_timestamp, contents_sha1=self.contents_sha1).first()

1402

1403 1404 1405 1406
__all__.append('SignatureHistory')

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

1407

1408
class SrcContents(ORMObject):
1409
    def __init__(self, file=None, source=None):
1410 1411 1412 1413 1414 1415
        self.file = file
        self.source = source

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

1416

1417 1418 1419 1420
__all__.append('SrcContents')

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

A
Ansgar Burchardt 已提交
1421

1422
class DBSource(ORMObject):
1423
    def __init__(self, source=None, version=None, maintainer=None,
1424
        changedby=None, poolfile=None, install_date=None, fingerprint=None):
T
Torsten Werner 已提交
1425 1426
        self.source = source
        self.version = version
1427 1428
        self.maintainer = maintainer
        self.changedby = changedby
T
Torsten Werner 已提交
1429 1430
        self.poolfile = poolfile
        self.install_date = install_date
1431
        self.fingerprint = fingerprint
M
Mark Hymers 已提交
1432

M
Mark Hymers 已提交
1433 1434 1435 1436
    @property
    def pkid(self):
        return self.source_id

1437 1438 1439 1440 1441 1442 1443 1444
    @property
    def name(self):
        return self.source

    @property
    def arch_string(self):
        return 'source'

1445
    def properties(self):
1446 1447
        return ['source', 'source_id', 'maintainer', 'changedby',
            'fingerprint', 'poolfile', 'version', 'suites_count',
1448
            'install_date', 'binaries_count', 'uploaders_count']
1449

A
Ansgar 已提交
1450
    def read_control_fields(self) -> Deb822:
M
Mark Hymers 已提交
1451 1452 1453
        '''
        Reads the control information from a dsc

A
Ansgar 已提交
1454
        :return: fields is the dsc information in a dictionary form
M
Mark Hymers 已提交
1455
        '''
1456 1457
        with open(self.poolfile.fullpath, 'r') as fd:
            fields = Deb822(fd)
M
Mark Hymers 已提交
1458 1459
        return fields

1460 1461
    metadata = association_proxy('key', 'value')

1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475
    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():
            fileset.add(name)
        return fileset

1476 1477 1478 1479 1480 1481
    @property
    def proxy(self):
        session = object_session(self)
        query = session.query(SourceMetadata).filter_by(source=self)
        return MetadataProxy(session, query)

1482

1483
__all__.append('DBSource')
1484

1485

1486
@session_wrapper
A
Ansgar 已提交
1487
def get_suites_source_in(source: str, session=None) -> 'list[Suite]':
1488
    """
A
Ansgar 已提交
1489
    Returns list of Suite objects which given `source` name is in
1490

A
Ansgar 已提交
1491 1492
    :param source: DBSource package name to search for
    :return: list of Suite objects for the given source
1493 1494
    """

1495
    return session.query(Suite).filter(Suite.sources.any(source=source)).all()
1496

1497

1498 1499
__all__.append('get_suites_source_in')

T
Torsten Werner 已提交
1500 1501
# FIXME: This function fails badly if it finds more than 1 source package and
# its implementation is trivial enough to be inlined.
1502 1503


1504
@session_wrapper
A
Ansgar 已提交
1505
def get_source_in_suite(source: str, suite_name: Optional[str], session=None) -> Optional[DBSource]:
1506
    """
A
Ansgar 已提交
1507
    Returns a DBSource object for a combination of `source` and `suite_name`.
1508

A
Ansgar 已提交
1509 1510
    :param source: source package name
    :param suite_name: the suite name
A
Ansgar 已提交
1511
    :return: the version for `source` in `suite`
1512
    """
1513 1514 1515
    suite = get_suite(suite_name, session)
    if suite is None:
        return None
A
Ansgar 已提交
1516
    return suite.get_sources(source).one_or_none()
1517

1518

1519 1520
__all__.append('get_source_in_suite')

1521

M
Mark Hymers 已提交
1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546
@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()

1547

M
Mark Hymers 已提交
1548 1549
__all__.append('import_metadata_into_db')

1550 1551
################################################################################

1552

1553
class SrcFormat:
1554 1555 1556 1557 1558 1559
    def __init__(self, *args, **kwargs):
        pass

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

1560

1561 1562 1563 1564
__all__.append('SrcFormat')

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

B
Bastian Blank 已提交
1565
SUITE_FIELDS = [('SuiteName', 'suite_name'),
M
Mark Hymers 已提交
1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578
                 ('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 已提交
1579
                 ('OverrideSuite', 'overridesuite')]
M
Mark Hymers 已提交
1580

T
Torsten Werner 已提交
1581 1582
# Why the heck don't we have any UNIQUE constraints in table suite?
# TODO: Add UNIQUE constraints for appropriate columns.
1583 1584


1585
class Suite(ORMObject):
1586
    def __init__(self, suite_name=None, version=None):
1587 1588
        self.suite_name = suite_name
        self.version = version
M
Mark Hymers 已提交
1589

1590
    def properties(self):
1591
        return ['suite_name', 'version', 'sources_count', 'binaries_count',
T
Torsten Werner 已提交
1592
            'overrides_count']
1593

1594 1595
    def __eq__(self, val):
        if isinstance(val, str):
A
Ansgar 已提交
1596
            warnings.warn("comparison with a `str` is deprecated", DeprecationWarning, stacklevel=2)
1597 1598 1599 1600 1601 1602
            return (self.suite_name == val)
        # This signals to use the normal comparison operator
        return NotImplemented

    def __ne__(self, val):
        if isinstance(val, str):
A
Ansgar 已提交
1603
            warnings.warn("comparison with a `str` is deprecated", DeprecationWarning, stacklevel=2)
1604 1605 1606 1607
            return (self.suite_name != val)
        # This signals to use the normal comparison operator
        return NotImplemented

1608 1609
    __hash__ = ORMObject.__hash__

M
Mark Hymers 已提交
1610 1611 1612 1613 1614 1615 1616 1617 1618
    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)

A
Ansgar 已提交
1619
    def get_architectures(self, skipsrc: bool = False, skipall: bool = False) -> list[Architecture]:
1620 1621 1622
        """
        Returns list of Architecture objects

A
Ansgar 已提交
1623 1624 1625
        :param skipsrc: Whether to skip returning the 'source' architecture entry
        :param skipall: Whether to skip returning the 'all' architecture entry
        :return: list of Architecture objects for the given name (may be empty)
1626 1627
        """

1628
        q = object_session(self).query(Architecture).with_parent(self)
1629 1630 1631 1632 1633 1634
        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()

A
Ansgar 已提交
1635
    def get_sources(self, source: str) -> sqlalchemy.orm.query.Query:
T
Torsten Werner 已提交
1636
        """
A
Ansgar 已提交
1637
        Returns a query object representing DBSource that is part of this suite.
T
Torsten Werner 已提交
1638

A
Ansgar 已提交
1639 1640
        :param source: source package name
        :return: a query of DBSource
T
Torsten Werner 已提交
1641 1642 1643
        """

        session = object_session(self)
1644
        return session.query(DBSource).filter_by(source=source). \
1645
            with_parent(self)
T
Torsten Werner 已提交
1646

1647 1648 1649 1650 1651 1652
    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()

1653 1654 1655
    def update_last_changed(self):
        self.last_changed = sqlalchemy.func.now()

1656 1657 1658 1659
    @property
    def path(self):
        return os.path.join(self.archive.path, 'dists', self.suite_name)

1660 1661 1662 1663 1664 1665
    @property
    def release_suite_output(self):
        if self.release_suite is not None:
            return self.release_suite
        return self.suite_name

1666

1667 1668
__all__.append('Suite')

1669

1670
@session_wrapper
A
Ansgar 已提交
1671
def get_suite(suite: str, session=None) -> Optional[Suite]:
1672
    """
A
Ansgar 已提交
1673
    Returns Suite object for given `suite` name.
1674

A
Ansgar 已提交
1675 1676 1677 1678
    :param suite: The name of the suite
    :param session: Optional SQLA session object (a temporary one will be
       generated if not supplied)
    :return: Suite object for the requested suite name (None if not present)
1679
    """
1680

1681
    # Start by looking for the dak internal name
1682
    q = session.query(Suite).filter_by(suite_name=suite)
1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693
    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
1694

1695 1696
    # Finally give release_suite a try
    q = session.query(Suite).filter_by(release_suite=suite)
A
Ansgar 已提交
1697
    return q.one_or_none()
1698

1699

1700 1701
__all__.append('get_suite')

M
Mark Hymers 已提交
1702 1703
################################################################################

1704

1705
@session_wrapper
A
Ansgar 已提交
1706
def get_suite_architectures(suite: str, skipsrc: bool = False, skipall: bool = False, session=None) -> list[Architecture]:
M
Mark Hymers 已提交
1707
    """
A
Ansgar 已提交
1708 1709
    Returns list of Architecture objects for given `suite` name. The list is
    empty if `suite` does not exist.
M
Mark Hymers 已提交
1710

A
Ansgar 已提交
1711 1712 1713 1714 1715 1716
    :param suite: Suite name to search for
    :param skipsrc: Whether to skip returning the 'source' architecture entry
    :param skipall: Whether to skip returning the 'all' architecture entry
    :param session: Optional SQL session object (a temporary one will be
       generated if not supplied)
    :return: list of Architecture objects for the given name (may be empty)
M
Mark Hymers 已提交
1717 1718
    """

1719 1720 1721
    try:
        return get_suite(suite, session).get_architectures(skipsrc, skipall)
    except AttributeError:
1722
        return []
M
Mark Hymers 已提交
1723

1724

1725
__all__.append('get_suite_architectures')
M
Mark Hymers 已提交
1726

M
Mark Hymers 已提交
1727 1728
################################################################################

1729

1730
class Uid(ORMObject):
1731
    def __init__(self, uid=None, name=None):
T
Torsten Werner 已提交
1732 1733
        self.uid = uid
        self.name = name
M
Mark Hymers 已提交
1734

1735 1736
    def __eq__(self, val):
        if isinstance(val, str):
A
Ansgar 已提交
1737
            warnings.warn("comparison with a `str` is deprecated", DeprecationWarning, stacklevel=2)
1738 1739 1740 1741 1742 1743
            return (self.uid == val)
        # This signals to use the normal comparison operator
        return NotImplemented

    def __ne__(self, val):
        if isinstance(val, str):
A
Ansgar 已提交
1744
            warnings.warn("comparison with a `str` is deprecated", DeprecationWarning, stacklevel=2)
1745 1746 1747 1748
            return (self.uid != val)
        # This signals to use the normal comparison operator
        return NotImplemented

1749 1750
    __hash__ = ORMObject.__hash__

1751 1752 1753
    def properties(self):
        return ['uid', 'name', 'fingerprint']

1754

1755 1756
__all__.append('Uid')

1757

1758
@session_wrapper
A
Ansgar 已提交
1759
def get_or_set_uid(uidname: str, session=None) -> Uid:
M
Mark Hymers 已提交
1760 1761 1762 1763 1764
    """
    Returns uid object for given uidname.

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

A
Ansgar 已提交
1765 1766 1767 1768 1769
    :param uidname: The uid to add
    :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.
    :return: the uid object for the given uidname
M
Mark Hymers 已提交
1770
    """
1771 1772 1773

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

1774 1775 1776
    try:
        ret = q.one()
    except NoResultFound:
1777 1778 1779
        uid = Uid()
        uid.uid = uidname
        session.add(uid)
1780
        session.commit_or_flush()
1781
        ret = uid
M
Mark Hymers 已提交
1782

1783
    return ret
M
Mark Hymers 已提交
1784

1785

M
Mark Hymers 已提交
1786 1787
__all__.append('get_or_set_uid')

1788

1789
@session_wrapper
1790 1791 1792 1793
def get_uid_from_fingerprint(fpr, session=None):
    q = session.query(Uid)
    q = q.join(Fingerprint).filter_by(fingerprint=fpr)

A
Ansgar 已提交
1794
    return q.one_or_none()
1795

1796

1797 1798
__all__.append('get_uid_from_fingerprint')

M
Mark Hymers 已提交
1799 1800
################################################################################

1801

T
Torsten Werner 已提交
1802
class MetadataKey(ORMObject):
1803
    def __init__(self, key=None):
T
Torsten Werner 已提交
1804 1805 1806 1807 1808
        self.key = key

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

1809

T
Torsten Werner 已提交
1810 1811
__all__.append('MetadataKey')

1812

M
Mark Hymers 已提交
1813
@session_wrapper
A
Ansgar 已提交
1814
def get_or_set_metadatakey(keyname: str, session=None) -> MetadataKey:
M
Mark Hymers 已提交
1815 1816 1817 1818 1819
    """
    Returns MetadataKey object for given uidname.

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

A
Ansgar 已提交
1820 1821 1822 1823 1824
    :param keyname: The keyname to add
    :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.
    :return: the metadatakey object for the given keyname
M
Mark Hymers 已提交
1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837
    """

    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

1838

M
Mark Hymers 已提交
1839 1840
__all__.append('get_or_set_metadatakey')

T
Torsten Werner 已提交
1841 1842
################################################################################

1843

T
Torsten Werner 已提交
1844
class BinaryMetadata(ORMObject):
1845
    def __init__(self, key=None, value=None, binary=None):
T
Torsten Werner 已提交
1846 1847
        self.key = key
        self.value = value
1848 1849
        if binary is not None:
            self.binary = binary
T
Torsten Werner 已提交
1850 1851 1852 1853

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

1854

T
Torsten Werner 已提交
1855 1856 1857 1858
__all__.append('BinaryMetadata')

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

1859

T
Torsten Werner 已提交
1860
class SourceMetadata(ORMObject):
1861
    def __init__(self, key=None, value=None, source=None):
T
Torsten Werner 已提交
1862 1863
        self.key = key
        self.value = value
1864 1865
        if source is not None:
            self.source = source
T
Torsten Werner 已提交
1866 1867 1868 1869

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

1870

T
Torsten Werner 已提交
1871 1872 1873 1874
__all__.append('SourceMetadata')

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

1875

1876
class MetadataProxy:
1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906
    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

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

1907

1908 1909
class VersionCheck(ORMObject):
    def __init__(self, *args, **kwargs):
1910
        pass
1911 1912 1913 1914

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

1915

1916 1917
__all__.append('VersionCheck')

1918

1919
@session_wrapper
1920
def get_version_checks(suite_name, check=None, session=None):
1921 1922
    suite = get_suite(suite_name, session)
    if not suite:
M
Mark Hymers 已提交
1923 1924 1925
        # Make sure that what we return is iterable so that list comprehensions
        # involving this don't cause a traceback
        return []
1926 1927 1928 1929 1930
    q = session.query(VersionCheck).filter_by(suite=suite)
    if check:
        q = q.filter_by(check=check)
    return q.all()

1931

1932 1933 1934 1935
__all__.append('get_version_checks')

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

1936

1937
class DBConn:
M
Mark Hymers 已提交
1938
    """
1939
    database module init.
M
Mark Hymers 已提交
1940
    """
1941 1942
    __shared_state = {}

B
Bastian Blank 已提交
1943 1944
    db_meta = None

1945 1946
    tbl_architecture = Architecture.__table__

1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 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 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010
    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 已提交
2011
    def __init__(self, *args, **kwargs):
2012
        self.__dict__ = self.__shared_state
M
Mark Hymers 已提交
2013

2014 2015
        if not getattr(self, 'initialised', False):
            self.initialised = True
2016
            self.debug = 'debug' in kwargs
2017
            self.__createconn()
M
Mark Hymers 已提交
2018

M
Mark Hymers 已提交
2019
    def __setuptables(self):
2020
        for table_name in self.tables:
2021
            table = Table(table_name, self.db_meta,
2022
                autoload=True, extend_existing=True)
2023 2024
            setattr(self, 'tbl_%s' % table_name, table)

2025
        for view_name in self.views:
2026 2027 2028
            view = Table(view_name, self.db_meta, autoload=True)
            setattr(self, 'view_%s' % view_name, view)

M
Mark Hymers 已提交
2029
    def __setupmappers(self):
2030
        mapper(ACL, self.tbl_acl,
2031
               properties=dict(
2032 2033 2034 2035
                   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),
S
Stéphane Blondon 已提交
2036
            ))
2037 2038

        mapper(ACLPerSource, self.tbl_acl_per_source,
2039
               properties=dict(
2040 2041 2042
                   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)),
S
Stéphane Blondon 已提交
2043
            ))
2044

M
Mark Hymers 已提交
2045
        mapper(Archive, self.tbl_archive,
2046 2047
               properties=dict(archive_id=self.tbl_archive.c.id,
                                 archive_name=self.tbl_archive.c.name))
M
Mike O'Connor 已提交
2048

2049
        mapper(ArchiveFile, self.tbl_files_archive_map,
2050 2051 2052
               properties=dict(archive=relation(Archive, backref='files'),
                                 component=relation(Component),
                                 file=relation(PoolFile, backref='archives')))
2053

2054
        mapper(BuildQueue, self.tbl_build_queue,
2055
               properties=dict(queue_id=self.tbl_build_queue.c.id,
2056
                                 suite=relation(Suite, primaryjoin=(self.tbl_build_queue.c.suite_id == self.tbl_suite.c.id))))
2057

2058
        mapper(DBBinary, self.tbl_binaries,
2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074
               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 已提交
2075
                                     backref=backref('binaries', lazy='dynamic')),
2076
                                 extra_sources=relation(DBSource, secondary=self.tbl_extra_src_references,
2077
                                     backref=backref('extra_binary_references', lazy='dynamic')),
2078
                                 key=relation(BinaryMetadata, cascade='all',
2079
                                     collection_class=attribute_mapped_collection('key'))),
2080
        )
M
Mark Hymers 已提交
2081 2082

        mapper(Component, self.tbl_component,
2083 2084
               properties=dict(component_id=self.tbl_component.c.id,
                                 component_name=self.tbl_component.c.name),
2085
        )
M
Mark Hymers 已提交
2086 2087

        mapper(DBConfig, self.tbl_config,
2088
               properties=dict(config_id=self.tbl_config.c.id))
M
Mark Hymers 已提交
2089 2090

        mapper(DSCFile, self.tbl_dsc_files,
2091 2092 2093 2094 2095
               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 已提交
2096

2097
        mapper(ExternalOverride, self.tbl_external_overrides,
2098 2099 2100 2101 2102
                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 已提交
2103

M
Mark Hymers 已提交
2104
        mapper(PoolFile, self.tbl_files,
2105 2106
               properties=dict(file_id=self.tbl_files.c.id,
                                 filesize=self.tbl_files.c.size),
2107
        )
M
Mark Hymers 已提交
2108 2109

        mapper(Fingerprint, self.tbl_fingerprint,
2110 2111 2112 2113 2114 2115
               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)),
2116
        )
M
Mark Hymers 已提交
2117 2118

        mapper(Keyring, self.tbl_keyrings,
2119 2120 2121
               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 已提交
2122

M
Mark Hymers 已提交
2123
        mapper(DBChange, self.tbl_changes,
2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134
               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 已提交
2135

M
Mark Hymers 已提交
2136
        mapper(Maintainer, self.tbl_maintainer,
2137 2138
               properties=dict(maintainer_id=self.tbl_maintainer.c.id,
                   maintains_sources=relation(DBSource, backref='maintainer',
2139
                       primaryjoin=(self.tbl_maintainer.c.id == self.tbl_source.c.maintainer)),
2140
                   changed_sources=relation(DBSource, backref='changedby',
2141
                       primaryjoin=(self.tbl_maintainer.c.id == self.tbl_source.c.changedby))),
2142
        )
M
Mark Hymers 已提交
2143

M
Mark Hymers 已提交
2144
        mapper(NewComment, self.tbl_new_comments,
2145 2146
               properties=dict(comment_id=self.tbl_new_comments.c.id,
                                 policy_queue=relation(PolicyQueue)))
M
Mark Hymers 已提交
2147

M
Mark Hymers 已提交
2148
        mapper(Override, self.tbl_override,
2149
               properties=dict(suite_id=self.tbl_override.c.suite,
2150
                                 suite=relation(Suite,
T
Torsten Werner 已提交
2151
                                    backref=backref('overrides', lazy='dynamic')),
2152 2153
                                 package=self.tbl_override.c.package,
                                 component_id=self.tbl_override.c.component,
2154
                                 component=relation(Component,
2155
                                    backref=backref('overrides', lazy='dynamic')),
2156
                                 priority_id=self.tbl_override.c.priority,
2157
                                 priority=relation(Priority,
2158
                                    backref=backref('overrides', lazy='dynamic')),
2159
                                 section_id=self.tbl_override.c.section,
2160
                                 section=relation(Section,
2161
                                    backref=backref('overrides', lazy='dynamic')),
2162
                                 overridetype_id=self.tbl_override.c.type,
2163
                                 overridetype=relation(OverrideType,
2164
                                    backref=backref('overrides', lazy='dynamic'))))
M
Mark Hymers 已提交
2165 2166

        mapper(OverrideType, self.tbl_override_type,
2167 2168
               properties=dict(overridetype=self.tbl_override_type.c.type,
                                 overridetype_id=self.tbl_override_type.c.id))
M
Mark Hymers 已提交
2169

2170
        mapper(PolicyQueue, self.tbl_policy_queue,
2171 2172
               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))))
2173

2174
        mapper(PolicyQueueUpload, self.tbl_policy_queue_upload,
2175 2176 2177 2178 2179 2180
               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),
S
Stéphane Blondon 已提交
2181
            ))
2182 2183

        mapper(PolicyQueueByhandFile, self.tbl_policy_queue_byhand_file,
2184 2185
               properties=dict(
                   upload=relation(PolicyQueueUpload, backref='byhand'),
S
Stéphane Blondon 已提交
2186
            ))
2187

M
Mark Hymers 已提交
2188
        mapper(Priority, self.tbl_priority,
2189
               properties=dict(priority_id=self.tbl_priority.c.id))
M
Mark Hymers 已提交
2190

2191 2192
        mapper(SignatureHistory, self.tbl_signature_history)

2193
        mapper(DBSource, self.tbl_source,
2194 2195 2196 2197 2198 2199 2200 2201 2202
               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,
2203
                                                     primaryjoin=(self.tbl_source.c.id == self.tbl_dsc_files.c.source)),
2204
                                 suites=relation(Suite, secondary=self.tbl_src_associations,
2205
                                     backref=backref('sources', lazy='dynamic')),
2206
                                 uploaders=relation(Maintainer,
2207
                                     secondary=self.tbl_src_uploaders),
2208
                                 key=relation(SourceMetadata, cascade='all',
2209
                                     collection_class=attribute_mapped_collection('key'))),
2210
        )
M
Mark Hymers 已提交
2211

2212
        mapper(SrcFormat, self.tbl_src_format,
2213 2214
               properties=dict(src_format_id=self.tbl_src_format.c.id,
                                 format_name=self.tbl_src_format.c.format_name))
2215

M
Mark Hymers 已提交
2216
        mapper(Suite, self.tbl_suite,
2217 2218 2219 2220 2221
               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 已提交
2222
                                     secondary=self.tbl_suite_build_queue_copy),
2223
                                 srcformats=relation(SrcFormat, secondary=self.tbl_suite_src_formats,
2224
                                     backref=backref('suites', lazy='dynamic')),
2225 2226 2227
                                 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,
2228
                                                   order_by=self.tbl_component.c.ordering,
2229 2230 2231
                                                   backref=backref('suites')),
                                 architectures=relation(Architecture, secondary=self.tbl_suite_architectures,
                                     backref=backref('suites'))),
2232
        )
M
Mark Hymers 已提交
2233 2234

        mapper(Uid, self.tbl_uid,
2235 2236
               properties=dict(uid_id=self.tbl_uid.c.id,
                                 fingerprint=relation(Fingerprint)),
2237
        )
M
Mark Hymers 已提交
2238

2239
        mapper(BinContents, self.tbl_bin_contents,
2240 2241
            properties=dict(
                binary=relation(DBBinary,
2242
                    backref=backref('contents', lazy='dynamic', cascade='all')),
2243
                file=self.tbl_bin_contents.c.file))
2244

2245
        mapper(SrcContents, self.tbl_src_contents,
2246 2247
            properties=dict(
                source=relation(DBSource,
2248
                    backref=backref('contents', lazy='dynamic', cascade='all')),
2249
                file=self.tbl_src_contents.c.file))
2250

T
Torsten Werner 已提交
2251
        mapper(MetadataKey, self.tbl_metadata_keys,
2252 2253 2254
            properties=dict(
                key_id=self.tbl_metadata_keys.c.key_id,
                key=self.tbl_metadata_keys.c.key))
T
Torsten Werner 已提交
2255 2256

        mapper(BinaryMetadata, self.tbl_binaries_metadata,
2257 2258 2259 2260 2261 2262
            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 已提交
2263 2264

        mapper(SourceMetadata, self.tbl_source_metadata,
2265 2266 2267 2268 2269 2270
            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 已提交
2271

2272
        mapper(VersionCheck, self.tbl_version_check,
2273 2274
            properties=dict(
                suite_id=self.tbl_version_check.c.suite,
2275
                suite=relation(Suite, primaryjoin=self.tbl_version_check.c.suite == self.tbl_suite.c.id),
2276
                reference_id=self.tbl_version_check.c.reference,
2277
                reference=relation(Suite, primaryjoin=self.tbl_version_check.c.reference == self.tbl_suite.c.id, lazy='joined')))
2278

M
Mark Hymers 已提交
2279 2280
    ## Connection functions
    def __createconn(self):
2281
        from .config import Config
2282
        cnf = Config()
2283
        if "DB::Service" in cnf:
2284
            connstr = "postgresql://service=%s" % cnf["DB::Service"]
2285
        elif "DB::Host" in cnf:
M
Mark Hymers 已提交
2286
            # TCP/IP
2287
            connstr = "postgresql://%s" % cnf["DB::Host"]
2288
            if "DB::Port" in cnf and cnf["DB::Port"] != "-1":
M
Mark Hymers 已提交
2289 2290 2291 2292
                connstr += ":%s" % cnf["DB::Port"]
            connstr += "/%s" % cnf["DB::Name"]
        else:
            # Unix Socket
2293
            connstr = "postgresql:///%s" % cnf["DB::Name"]
2294
            if "DB::Port" in cnf and cnf["DB::Port"] != "-1":
M
Mark Hymers 已提交
2295
                connstr += "?port=%s" % cnf["DB::Port"]
2296

B
Bastian Blank 已提交
2297
        engine_args = {'echo': self.debug}
2298
        if 'DB::PoolSize' in cnf:
2299
            engine_args['pool_size'] = int(cnf['DB::PoolSize'])
2300
        if 'DB::MaxOverflow' in cnf:
2301
            engine_args['max_overflow'] = int(cnf['DB::MaxOverflow'])
2302 2303
        # we don't support non-utf-8 connections
        engine_args['client_encoding'] = 'utf-8'
2304

2305 2306 2307
        # Monkey patch a new dialect in in order to support service= syntax
        import sqlalchemy.dialects.postgresql
        from sqlalchemy.dialects.postgresql.psycopg2 import PGDialect_psycopg2
2308

2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319
        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

2320
        try:
2321
            self.db_pg = create_engine(connstr, **engine_args)
2322 2323 2324 2325
            self.db_smaker = sessionmaker(bind=self.db_pg,
                                          autoflush=True,
                                          autocommit=False)

B
Bastian Blank 已提交
2326 2327 2328 2329 2330
            if self.db_meta is None:
                self.__class__.db_meta = Base.metadata
                self.__class__.db_meta.bind = self.db_pg
                self.__setuptables()
                self.__setupmappers()
2331

2332
        except OperationalError as e:
B
Bastian Blank 已提交
2333
            from . import utils
2334
            utils.fubar("Cannot connect to database (%s)" % str(e))
M
Mark Hymers 已提交
2335

2336
        self.pid = os.getpid()
M
Mark Hymers 已提交
2337

2338
    def session(self, work_mem=0):
2339 2340 2341 2342 2343 2344
        '''
        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.
        '''
2345 2346 2347
        # reinitialize DBConn in new processes
        if self.pid != os.getpid():
            self.__createconn()
2348 2349 2350 2351
        session = self.db_smaker()
        if work_mem > 0:
            session.execute("SET LOCAL work_mem TO '%d MB'" % work_mem)
        return session
M
Mark Hymers 已提交
2352

2353

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