]> git.decadent.org.uk Git - dak.git/blob - daklib/dbconn.py
Merge branch 'master' into contents
[dak.git] / daklib / dbconn.py
1 #!/usr/bin/python
2
3 """ DB access class
4
5 @contact: Debian FTPMaster <ftpmaster@debian.org>
6 @copyright: 2000, 2001, 2002, 2003, 2004, 2006  James Troup <james@nocrew.org>
7 @copyright: 2008-2009  Mark Hymers <mhy@debian.org>
8 @copyright: 2009  Joerg Jaspert <joerg@debian.org>
9 @copyright: 2009  Mike O'Connor <stew@debian.org>
10 @license: GNU General Public License version 2 or later
11 """
12
13 # This program is free software; you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation; either version 2 of the License, or
16 # (at your option) any later version.
17
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21 # GNU General Public License for more details.
22
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
26
27 ################################################################################
28
29 # < mhy> I need a funny comment
30 # < sgran> two peanuts were walking down a dark street
31 # < sgran> one was a-salted
32 #  * mhy looks up the definition of "funny"
33
34 ################################################################################
35
36 import os
37 import re
38 import psycopg2
39 import traceback
40 from datetime import datetime, timedelta
41 from errno import ENOENT
42 from tempfile import mkstemp, mkdtemp
43
44 from inspect import getargspec
45
46 import sqlalchemy
47 from sqlalchemy import create_engine, Table, MetaData
48 from sqlalchemy.orm import sessionmaker, mapper, relation
49 from sqlalchemy import types as sqltypes
50
51 # Don't remove this, we re-export the exceptions to scripts which import us
52 from sqlalchemy.exc import *
53 from sqlalchemy.orm.exc import NoResultFound
54
55 # Only import Config until Queue stuff is changed to store its config
56 # in the database
57 from config import Config
58 from textutils import fix_maintainer
59
60 ################################################################################
61
62 # Patch in support for the debversion field type so that it works during
63 # reflection
64
65 class DebVersion(sqltypes.Text):
66     def get_col_spec(self):
67         return "DEBVERSION"
68
69 sa_major_version = sqlalchemy.__version__[0:3]
70 if sa_major_version == "0.5":
71     from sqlalchemy.databases import postgres
72     postgres.ischema_names['debversion'] = DebVersion
73 else:
74     raise Exception("dak isn't ported to SQLA versions != 0.5 yet.  See daklib/dbconn.py")
75
76 ################################################################################
77
78 __all__ = ['IntegrityError', 'SQLAlchemyError']
79
80 ################################################################################
81
82 def session_wrapper(fn):
83     """
84     Wrapper around common ".., session=None):" handling. If the wrapped
85     function is called without passing 'session', we create a local one
86     and destroy it when the function ends.
87
88     Also attaches a commit_or_flush method to the session; if we created a
89     local session, this is a synonym for session.commit(), otherwise it is a
90     synonym for session.flush().
91     """
92
93     def wrapped(*args, **kwargs):
94         private_transaction = False
95
96         # Find the session object
97         session = kwargs.get('session')
98
99         if session is None:
100             if len(args) <= len(getargspec(fn)[0]) - 1:
101                 # No session specified as last argument or in kwargs
102                 private_transaction = True
103                 session = kwargs['session'] = DBConn().session()
104             else:
105                 # Session is last argument in args
106                 session = args[-1]
107                 if session is None:
108                     args = list(args)
109                     session = args[-1] = DBConn().session()
110                     private_transaction = True
111
112         if private_transaction:
113             session.commit_or_flush = session.commit
114         else:
115             session.commit_or_flush = session.flush
116
117         try:
118             return fn(*args, **kwargs)
119         finally:
120             if private_transaction:
121                 # We created a session; close it.
122                 session.close()
123
124     wrapped.__doc__ = fn.__doc__
125     wrapped.func_name = fn.func_name
126
127     return wrapped
128
129 __all__.append('session_wrapper')
130
131 ################################################################################
132
133 class Architecture(object):
134     def __init__(self, *args, **kwargs):
135         pass
136
137     def __eq__(self, val):
138         if isinstance(val, str):
139             return (self.arch_string== val)
140         # This signals to use the normal comparison operator
141         return NotImplemented
142
143     def __ne__(self, val):
144         if isinstance(val, str):
145             return (self.arch_string != val)
146         # This signals to use the normal comparison operator
147         return NotImplemented
148
149     def __repr__(self):
150         return '<Architecture %s>' % self.arch_string
151
152 __all__.append('Architecture')
153
154 @session_wrapper
155 def get_architecture(architecture, session=None):
156     """
157     Returns database id for given C{architecture}.
158
159     @type architecture: string
160     @param architecture: The name of the architecture
161
162     @type session: Session
163     @param session: Optional SQLA session object (a temporary one will be
164     generated if not supplied)
165
166     @rtype: Architecture
167     @return: Architecture object for the given arch (None if not present)
168     """
169
170     q = session.query(Architecture).filter_by(arch_string=architecture)
171
172     try:
173         return q.one()
174     except NoResultFound:
175         return None
176
177 __all__.append('get_architecture')
178
179 @session_wrapper
180 def get_architecture_suites(architecture, session=None):
181     """
182     Returns list of Suite objects for given C{architecture} name
183
184     @type source: str
185     @param source: Architecture name to search for
186
187     @type session: Session
188     @param session: Optional SQL session object (a temporary one will be
189     generated if not supplied)
190
191     @rtype: list
192     @return: list of Suite objects for the given name (may be empty)
193     """
194
195     q = session.query(Suite)
196     q = q.join(SuiteArchitecture)
197     q = q.join(Architecture).filter_by(arch_string=architecture).order_by('suite_name')
198
199     ret = q.all()
200
201     return ret
202
203 __all__.append('get_architecture_suites')
204
205 ################################################################################
206
207 class Archive(object):
208     def __init__(self, *args, **kwargs):
209         pass
210
211     def __repr__(self):
212         return '<Archive %s>' % self.archive_name
213
214 __all__.append('Archive')
215
216 @session_wrapper
217 def get_archive(archive, session=None):
218     """
219     returns database id for given C{archive}.
220
221     @type archive: string
222     @param archive: the name of the arhive
223
224     @type session: Session
225     @param session: Optional SQLA session object (a temporary one will be
226     generated if not supplied)
227
228     @rtype: Archive
229     @return: Archive object for the given name (None if not present)
230
231     """
232     archive = archive.lower()
233
234     q = session.query(Archive).filter_by(archive_name=archive)
235
236     try:
237         return q.one()
238     except NoResultFound:
239         return None
240
241 __all__.append('get_archive')
242
243 ################################################################################
244
245 class BinAssociation(object):
246     def __init__(self, *args, **kwargs):
247         pass
248
249     def __repr__(self):
250         return '<BinAssociation %s (%s, %s)>' % (self.ba_id, self.binary, self.suite)
251
252 __all__.append('BinAssociation')
253
254 ################################################################################
255
256 class BinContents(object):
257     def __init__(self, *args, **kwargs):
258         pass
259
260     def __repr__(self):
261         return '<BinContents (%s, %s)>' % (self.binary, self.filename)
262
263 __all__.append('BinContents')
264
265 ################################################################################
266
267 class DBBinary(object):
268     def __init__(self, *args, **kwargs):
269         pass
270
271     def __repr__(self):
272         return '<DBBinary %s (%s, %s)>' % (self.package, self.version, self.architecture)
273
274 __all__.append('DBBinary')
275
276 @session_wrapper
277 def get_suites_binary_in(package, session=None):
278     """
279     Returns list of Suite objects which given C{package} name is in
280
281     @type source: str
282     @param source: DBBinary package name to search for
283
284     @rtype: list
285     @return: list of Suite objects for the given package
286     """
287
288     return session.query(Suite).join(BinAssociation).join(DBBinary).filter_by(package=package).all()
289
290 __all__.append('get_suites_binary_in')
291
292 @session_wrapper
293 def get_binary_from_id(binary_id, session=None):
294     """
295     Returns DBBinary object for given C{id}
296
297     @type binary_id: int
298     @param binary_id: Id of the required binary
299
300     @type session: Session
301     @param session: Optional SQLA session object (a temporary one will be
302     generated if not supplied)
303
304     @rtype: DBBinary
305     @return: DBBinary object for the given binary (None if not present)
306     """
307
308     q = session.query(DBBinary).filter_by(binary_id=binary_id)
309
310     try:
311         return q.one()
312     except NoResultFound:
313         return None
314
315 __all__.append('get_binary_from_id')
316
317 @session_wrapper
318 def get_binaries_from_name(package, version=None, architecture=None, session=None):
319     """
320     Returns list of DBBinary objects for given C{package} name
321
322     @type package: str
323     @param package: DBBinary package name to search for
324
325     @type version: str or None
326     @param version: Version to search for (or None)
327
328     @type package: str, list or None
329     @param package: Architectures to limit to (or None if no limit)
330
331     @type session: Session
332     @param session: Optional SQL session object (a temporary one will be
333     generated if not supplied)
334
335     @rtype: list
336     @return: list of DBBinary objects for the given name (may be empty)
337     """
338
339     q = session.query(DBBinary).filter_by(package=package)
340
341     if version is not None:
342         q = q.filter_by(version=version)
343
344     if architecture is not None:
345         if not isinstance(architecture, list):
346             architecture = [architecture]
347         q = q.join(Architecture).filter(Architecture.arch_string.in_(architecture))
348
349     ret = q.all()
350
351     return ret
352
353 __all__.append('get_binaries_from_name')
354
355 @session_wrapper
356 def get_binaries_from_source_id(source_id, session=None):
357     """
358     Returns list of DBBinary objects for given C{source_id}
359
360     @type source_id: int
361     @param source_id: source_id to search for
362
363     @type session: Session
364     @param session: Optional SQL session object (a temporary one will be
365     generated if not supplied)
366
367     @rtype: list
368     @return: list of DBBinary objects for the given name (may be empty)
369     """
370
371     return session.query(DBBinary).filter_by(source_id=source_id).all()
372
373 __all__.append('get_binaries_from_source_id')
374
375 @session_wrapper
376 def get_binary_from_name_suite(package, suitename, session=None):
377     ### For dak examine-package
378     ### XXX: Doesn't use object API yet
379
380     sql = """SELECT DISTINCT(b.package), b.version, c.name, su.suite_name
381              FROM binaries b, files fi, location l, component c, bin_associations ba, suite su
382              WHERE b.package=:package
383                AND b.file = fi.id
384                AND fi.location = l.id
385                AND l.component = c.id
386                AND ba.bin=b.id
387                AND ba.suite = su.id
388                AND su.suite_name=:suitename
389           ORDER BY b.version DESC"""
390
391     return session.execute(sql, {'package': package, 'suitename': suitename})
392
393 __all__.append('get_binary_from_name_suite')
394
395 @session_wrapper
396 def get_binary_components(package, suitename, arch, session=None):
397     # Check for packages that have moved from one component to another
398     query = """SELECT c.name FROM binaries b, bin_associations ba, suite s, location l, component c, architecture a, files f
399     WHERE b.package=:package AND s.suite_name=:suitename
400       AND (a.arch_string = :arch OR a.arch_string = 'all')
401       AND ba.bin = b.id AND ba.suite = s.id AND b.architecture = a.id
402       AND f.location = l.id
403       AND l.component = c.id
404       AND b.file = f.id"""
405
406     vals = {'package': package, 'suitename': suitename, 'arch': arch}
407
408     return session.execute(query, vals)
409
410 __all__.append('get_binary_components')
411
412 ################################################################################
413
414 class BinaryACL(object):
415     def __init__(self, *args, **kwargs):
416         pass
417
418     def __repr__(self):
419         return '<BinaryACL %s>' % self.binary_acl_id
420
421 __all__.append('BinaryACL')
422
423 ################################################################################
424
425 class BinaryACLMap(object):
426     def __init__(self, *args, **kwargs):
427         pass
428
429     def __repr__(self):
430         return '<BinaryACLMap %s>' % self.binary_acl_map_id
431
432 __all__.append('BinaryACLMap')
433
434 ################################################################################
435
436 MINIMAL_APT_CONF="""
437 Dir
438 {
439    ArchiveDir "%(archivepath)s";
440    OverrideDir "/srv/ftp.debian.org/scripts/override/";
441    CacheDir "/srv/ftp.debian.org/database/";
442 };
443
444 Default
445 {
446    Packages::Compress ". bzip2 gzip";
447    Sources::Compress ". bzip2 gzip";
448    DeLinkLimit 0;
449    FileMode 0664;
450 }
451
452 bindirectory "incoming"
453 {
454    Packages "Packages";
455    Contents " ";
456
457    BinOverride "override.sid.all3";
458    BinCacheDB "packages-accepted.db";
459
460    FileList "%(filelist)s";
461
462    PathPrefix "";
463    Packages::Extensions ".deb .udeb";
464 };
465
466 bindirectory "incoming/"
467 {
468    Sources "Sources";
469    BinOverride "override.sid.all3";
470    SrcOverride "override.sid.all3.src";
471    FileList "%(filelist)s";
472 };
473 """
474
475 class BuildQueue(object):
476     def __init__(self, *args, **kwargs):
477         pass
478
479     def __repr__(self):
480         return '<BuildQueue %s>' % self.queue_name
481
482     def write_metadata(self, starttime, force=False):
483         # Do we write out metafiles?
484         if not (force or self.generate_metadata):
485             return
486
487         session = DBConn().session().object_session(self)
488
489         fl_fd = fl_name = ac_fd = ac_name = None
490         tempdir = None
491         arches = " ".join([ a.arch_string for a in session.query(Architecture).all() if a.arch_string != 'source' ])
492         startdir = os.getcwd()
493
494         try:
495             # Grab files we want to include
496             newer = session.query(BuildQueueFile).filter_by(build_queue_id = self.queue_id).filter(BuildQueueFile.lastused + timedelta(seconds=self.stay_of_execution) > starttime).all()
497             # Write file list with newer files
498             (fl_fd, fl_name) = mkstemp()
499             for n in newer:
500                 os.write(fl_fd, '%s\n' % n.fullpath)
501             os.close(fl_fd)
502
503             # Write minimal apt.conf
504             # TODO: Remove hardcoding from template
505             (ac_fd, ac_name) = mkstemp()
506             os.write(ac_fd, MINIMAL_APT_CONF % {'archivepath': self.path,
507                                                 'filelist': fl_name})
508             os.close(ac_fd)
509
510             # Run apt-ftparchive generate
511             os.chdir(os.path.dirname(ac_name))
512             os.system('apt-ftparchive -qq -o APT::FTPArchive::Contents=off generate %s' % os.path.basename(ac_name))
513
514             # Run apt-ftparchive release
515             # TODO: Eww - fix this
516             bname = os.path.basename(self.path)
517             os.chdir(self.path)
518             os.chdir('..')
519
520             # We have to remove the Release file otherwise it'll be included in the
521             # new one
522             try:
523                 os.unlink(os.path.join(bname, 'Release'))
524             except OSError:
525                 pass
526
527             os.system("""apt-ftparchive -qq -o APT::FTPArchive::Release::Origin="%s" -o APT::FTPArchive::Release::Label="%s" -o APT::FTPArchive::Release::Description="%s" -o APT::FTPArchive::Release::Architectures="%s" release %s > Release""" % (self.origin, self.label, self.releasedescription, arches, bname))
528
529             # Sign if necessary
530             if self.signingkey:
531                 cnf = Config()
532                 keyring = "--secret-keyring \"%s\"" % cnf["Dinstall::SigningKeyring"]
533                 if cnf.has_key("Dinstall::SigningPubKeyring"):
534                     keyring += " --keyring \"%s\"" % cnf["Dinstall::SigningPubKeyring"]
535
536                 os.system("gpg %s --no-options --batch --no-tty --armour --default-key %s --detach-sign -o Release.gpg Release""" % (keyring, self.signingkey))
537
538             # Move the files if we got this far
539             os.rename('Release', os.path.join(bname, 'Release'))
540             if self.signingkey:
541                 os.rename('Release.gpg', os.path.join(bname, 'Release.gpg'))
542
543         # Clean up any left behind files
544         finally:
545             os.chdir(startdir)
546             if fl_fd:
547                 try:
548                     os.close(fl_fd)
549                 except OSError:
550                     pass
551
552             if fl_name:
553                 try:
554                     os.unlink(fl_name)
555                 except OSError:
556                     pass
557
558             if ac_fd:
559                 try:
560                     os.close(ac_fd)
561                 except OSError:
562                     pass
563
564             if ac_name:
565                 try:
566                     os.unlink(ac_name)
567                 except OSError:
568                     pass
569
570     def clean_and_update(self, starttime, Logger, dryrun=False):
571         """WARNING: This routine commits for you"""
572         session = DBConn().session().object_session(self)
573
574         if self.generate_metadata and not dryrun:
575             self.write_metadata(starttime)
576
577         # Grab files older than our execution time
578         older = session.query(BuildQueueFile).filter_by(build_queue_id = self.queue_id).filter(BuildQueueFile.lastused + timedelta(seconds=self.stay_of_execution) <= starttime).all()
579
580         for o in older:
581             killdb = False
582             try:
583                 if dryrun:
584                     Logger.log(["I: Would have removed %s from the queue" % o.fullpath])
585                 else:
586                     Logger.log(["I: Removing %s from the queue" % o.fullpath])
587                     os.unlink(o.fullpath)
588                     killdb = True
589             except OSError, e:
590                 # If it wasn't there, don't worry
591                 if e.errno == ENOENT:
592                     killdb = True
593                 else:
594                     # TODO: Replace with proper logging call
595                     Logger.log(["E: Could not remove %s" % o.fullpath])
596
597             if killdb:
598                 session.delete(o)
599
600         session.commit()
601
602         for f in os.listdir(self.path):
603             if f.startswith('Packages') or f.startswith('Source') or f.startswith('Release'):
604                 continue
605
606             try:
607                 r = session.query(BuildQueueFile).filter_by(build_queue_id = self.queue_id).filter_by(filename = f).one()
608             except NoResultFound:
609                 fp = os.path.join(self.path, f)
610                 if dryrun:
611                     Logger.log(["I: Would remove unused link %s" % fp])
612                 else:
613                     Logger.log(["I: Removing unused link %s" % fp])
614                     try:
615                         os.unlink(fp)
616                     except OSError:
617                         Logger.log(["E: Failed to unlink unreferenced file %s" % r.fullpath])
618
619     def add_file_from_pool(self, poolfile):
620         """Copies a file into the pool.  Assumes that the PoolFile object is
621         attached to the same SQLAlchemy session as the Queue object is.
622
623         The caller is responsible for committing after calling this function."""
624         poolfile_basename = poolfile.filename[poolfile.filename.rindex(os.sep)+1:]
625
626         # Check if we have a file of this name or this ID already
627         for f in self.queuefiles:
628             if f.fileid is not None and f.fileid == poolfile.file_id or \
629                f.poolfile.filename == poolfile_basename:
630                    # In this case, update the BuildQueueFile entry so we
631                    # don't remove it too early
632                    f.lastused = datetime.now()
633                    DBConn().session().object_session(poolfile).add(f)
634                    return f
635
636         # Prepare BuildQueueFile object
637         qf = BuildQueueFile()
638         qf.build_queue_id = self.queue_id
639         qf.lastused = datetime.now()
640         qf.filename = poolfile_basename
641
642         targetpath = poolfile.fullpath
643         queuepath = os.path.join(self.path, poolfile_basename)
644
645         try:
646             if self.copy_files:
647                 # We need to copy instead of symlink
648                 import utils
649                 utils.copy(targetpath, queuepath)
650                 # NULL in the fileid field implies a copy
651                 qf.fileid = None
652             else:
653                 os.symlink(targetpath, queuepath)
654                 qf.fileid = poolfile.file_id
655         except OSError:
656             return None
657
658         # Get the same session as the PoolFile is using and add the qf to it
659         DBConn().session().object_session(poolfile).add(qf)
660
661         return qf
662
663
664 __all__.append('BuildQueue')
665
666 @session_wrapper
667 def get_build_queue(queuename, session=None):
668     """
669     Returns BuildQueue object for given C{queue name}, creating it if it does not
670     exist.
671
672     @type queuename: string
673     @param queuename: The name of the queue
674
675     @type session: Session
676     @param session: Optional SQLA session object (a temporary one will be
677     generated if not supplied)
678
679     @rtype: BuildQueue
680     @return: BuildQueue object for the given queue
681     """
682
683     q = session.query(BuildQueue).filter_by(queue_name=queuename)
684
685     try:
686         return q.one()
687     except NoResultFound:
688         return None
689
690 __all__.append('get_build_queue')
691
692 ################################################################################
693
694 class BuildQueueFile(object):
695     def __init__(self, *args, **kwargs):
696         pass
697
698     def __repr__(self):
699         return '<BuildQueueFile %s (%s)>' % (self.filename, self.build_queue_id)
700
701     @property
702     def fullpath(self):
703         return os.path.join(self.buildqueue.path, self.filename)
704
705
706 __all__.append('BuildQueueFile')
707
708 ################################################################################
709
710 class ChangePendingBinary(object):
711     def __init__(self, *args, **kwargs):
712         pass
713
714     def __repr__(self):
715         return '<ChangePendingBinary %s>' % self.change_pending_binary_id
716
717 __all__.append('ChangePendingBinary')
718
719 ################################################################################
720
721 class ChangePendingFile(object):
722     def __init__(self, *args, **kwargs):
723         pass
724
725     def __repr__(self):
726         return '<ChangePendingFile %s>' % self.change_pending_file_id
727
728 __all__.append('ChangePendingFile')
729
730 ################################################################################
731
732 class ChangePendingSource(object):
733     def __init__(self, *args, **kwargs):
734         pass
735
736     def __repr__(self):
737         return '<ChangePendingSource %s>' % self.change_pending_source_id
738
739 __all__.append('ChangePendingSource')
740
741 ################################################################################
742
743 class Component(object):
744     def __init__(self, *args, **kwargs):
745         pass
746
747     def __eq__(self, val):
748         if isinstance(val, str):
749             return (self.component_name == val)
750         # This signals to use the normal comparison operator
751         return NotImplemented
752
753     def __ne__(self, val):
754         if isinstance(val, str):
755             return (self.component_name != val)
756         # This signals to use the normal comparison operator
757         return NotImplemented
758
759     def __repr__(self):
760         return '<Component %s>' % self.component_name
761
762
763 __all__.append('Component')
764
765 @session_wrapper
766 def get_component(component, session=None):
767     """
768     Returns database id for given C{component}.
769
770     @type component: string
771     @param component: The name of the override type
772
773     @rtype: int
774     @return: the database id for the given component
775
776     """
777     component = component.lower()
778
779     q = session.query(Component).filter_by(component_name=component)
780
781     try:
782         return q.one()
783     except NoResultFound:
784         return None
785
786 __all__.append('get_component')
787
788 ################################################################################
789
790 class DBConfig(object):
791     def __init__(self, *args, **kwargs):
792         pass
793
794     def __repr__(self):
795         return '<DBConfig %s>' % self.name
796
797 __all__.append('DBConfig')
798
799 ################################################################################
800
801 @session_wrapper
802 def get_or_set_contents_file_id(filename, session=None):
803     """
804     Returns database id for given filename.
805
806     If no matching file is found, a row is inserted.
807
808     @type filename: string
809     @param filename: The filename
810     @type session: SQLAlchemy
811     @param session: Optional SQL session object (a temporary one will be
812     generated if not supplied).  If not passed, a commit will be performed at
813     the end of the function, otherwise the caller is responsible for commiting.
814
815     @rtype: int
816     @return: the database id for the given component
817     """
818
819     q = session.query(ContentFilename).filter_by(filename=filename)
820
821     try:
822         ret = q.one().cafilename_id
823     except NoResultFound:
824         cf = ContentFilename()
825         cf.filename = filename
826         session.add(cf)
827         session.commit_or_flush()
828         ret = cf.cafilename_id
829
830     return ret
831
832 __all__.append('get_or_set_contents_file_id')
833
834 @session_wrapper
835 def get_contents(suite, overridetype, section=None, session=None):
836     """
837     Returns contents for a suite / overridetype combination, limiting
838     to a section if not None.
839
840     @type suite: Suite
841     @param suite: Suite object
842
843     @type overridetype: OverrideType
844     @param overridetype: OverrideType object
845
846     @type section: Section
847     @param section: Optional section object to limit results to
848
849     @type session: SQLAlchemy
850     @param session: Optional SQL session object (a temporary one will be
851     generated if not supplied)
852
853     @rtype: ResultsProxy
854     @return: ResultsProxy object set up to return tuples of (filename, section,
855     package, arch_id)
856     """
857
858     # find me all of the contents for a given suite
859     contents_q = """SELECT (p.path||'/'||n.file) AS fn,
860                             s.section,
861                             b.package,
862                             b.architecture
863                    FROM content_associations c join content_file_paths p ON (c.filepath=p.id)
864                    JOIN content_file_names n ON (c.filename=n.id)
865                    JOIN binaries b ON (b.id=c.binary_pkg)
866                    JOIN override o ON (o.package=b.package)
867                    JOIN section s ON (s.id=o.section)
868                    WHERE o.suite = :suiteid AND o.type = :overridetypeid
869                    AND b.type=:overridetypename"""
870
871     vals = {'suiteid': suite.suite_id,
872             'overridetypeid': overridetype.overridetype_id,
873             'overridetypename': overridetype.overridetype}
874
875     if section is not None:
876         contents_q += " AND s.id = :sectionid"
877         vals['sectionid'] = section.section_id
878
879     contents_q += " ORDER BY fn"
880
881     return session.execute(contents_q, vals)
882
883 __all__.append('get_contents')
884
885 ################################################################################
886
887 class ContentFilepath(object):
888     def __init__(self, *args, **kwargs):
889         pass
890
891     def __repr__(self):
892         return '<ContentFilepath %s>' % self.filepath
893
894 __all__.append('ContentFilepath')
895
896 @session_wrapper
897 def get_or_set_contents_path_id(filepath, session=None):
898     """
899     Returns database id for given path.
900
901     If no matching file is found, a row is inserted.
902
903     @type filename: string
904     @param filename: The filepath
905     @type session: SQLAlchemy
906     @param session: Optional SQL session object (a temporary one will be
907     generated if not supplied).  If not passed, a commit will be performed at
908     the end of the function, otherwise the caller is responsible for commiting.
909
910     @rtype: int
911     @return: the database id for the given path
912     """
913
914     q = session.query(ContentFilepath).filter_by(filepath=filepath)
915
916     try:
917         ret = q.one().cafilepath_id
918     except NoResultFound:
919         cf = ContentFilepath()
920         cf.filepath = filepath
921         session.add(cf)
922         session.commit_or_flush()
923         ret = cf.cafilepath_id
924
925     return ret
926
927 __all__.append('get_or_set_contents_path_id')
928
929 ################################################################################
930
931 class ContentAssociation(object):
932     def __init__(self, *args, **kwargs):
933         pass
934
935     def __repr__(self):
936         return '<ContentAssociation %s>' % self.ca_id
937
938 __all__.append('ContentAssociation')
939
940 def insert_content_paths(binary_id, fullpaths, session=None):
941     """
942     Make sure given path is associated with given binary id
943
944     @type binary_id: int
945     @param binary_id: the id of the binary
946     @type fullpaths: list
947     @param fullpaths: the list of paths of the file being associated with the binary
948     @type session: SQLAlchemy session
949     @param session: Optional SQLAlchemy session.  If this is passed, the caller
950     is responsible for ensuring a transaction has begun and committing the
951     results or rolling back based on the result code.  If not passed, a commit
952     will be performed at the end of the function, otherwise the caller is
953     responsible for commiting.
954
955     @return: True upon success
956     """
957
958     privatetrans = False
959     if session is None:
960         session = DBConn().session()
961         privatetrans = True
962
963     try:
964         # Insert paths
965         pathcache = {}
966
967         def generate_path_dicts():
968             for fullpath in fullpaths:
969                 if fullpath.startswith( './' ):
970                     fullpath = fullpath[2:]
971
972                 yield {'fulename':fullpath, 'id': binary_id }
973
974         session.execute( "INSERT INTO bin_contents ( file, binary_id ) VALUES ( :filename, :id )",
975                          generate_path_dicts() )
976
977         session.commit()
978         if privatetrans:
979             session.close()
980         return True
981
982     except:
983         traceback.print_exc()
984
985         # Only rollback if we set up the session ourself
986         if privatetrans:
987             session.rollback()
988             session.close()
989
990         return False
991
992 __all__.append('insert_content_paths')
993
994 ################################################################################
995
996 class DSCFile(object):
997     def __init__(self, *args, **kwargs):
998         pass
999
1000     def __repr__(self):
1001         return '<DSCFile %s>' % self.dscfile_id
1002
1003 __all__.append('DSCFile')
1004
1005 @session_wrapper
1006 def get_dscfiles(dscfile_id=None, source_id=None, poolfile_id=None, session=None):
1007     """
1008     Returns a list of DSCFiles which may be empty
1009
1010     @type dscfile_id: int (optional)
1011     @param dscfile_id: the dscfile_id of the DSCFiles to find
1012
1013     @type source_id: int (optional)
1014     @param source_id: the source id related to the DSCFiles to find
1015
1016     @type poolfile_id: int (optional)
1017     @param poolfile_id: the poolfile id related to the DSCFiles to find
1018
1019     @rtype: list
1020     @return: Possibly empty list of DSCFiles
1021     """
1022
1023     q = session.query(DSCFile)
1024
1025     if dscfile_id is not None:
1026         q = q.filter_by(dscfile_id=dscfile_id)
1027
1028     if source_id is not None:
1029         q = q.filter_by(source_id=source_id)
1030
1031     if poolfile_id is not None:
1032         q = q.filter_by(poolfile_id=poolfile_id)
1033
1034     return q.all()
1035
1036 __all__.append('get_dscfiles')
1037
1038 ################################################################################
1039
1040 class PoolFile(object):
1041     def __init__(self, *args, **kwargs):
1042         pass
1043
1044     def __repr__(self):
1045         return '<PoolFile %s>' % self.filename
1046
1047     @property
1048     def fullpath(self):
1049         return os.path.join(self.location.path, self.filename)
1050
1051 __all__.append('PoolFile')
1052
1053 @session_wrapper
1054 def check_poolfile(filename, filesize, md5sum, location_id, session=None):
1055     """
1056     Returns a tuple:
1057      (ValidFileFound [boolean or None], PoolFile object or None)
1058
1059     @type filename: string
1060     @param filename: the filename of the file to check against the DB
1061
1062     @type filesize: int
1063     @param filesize: the size of the file to check against the DB
1064
1065     @type md5sum: string
1066     @param md5sum: the md5sum of the file to check against the DB
1067
1068     @type location_id: int
1069     @param location_id: the id of the location to look in
1070
1071     @rtype: tuple
1072     @return: Tuple of length 2.
1073              If more than one file found with that name:
1074                     (None,  None)
1075              If valid pool file found: (True, PoolFile object)
1076              If valid pool file not found:
1077                     (False, None) if no file found
1078                     (False, PoolFile object) if file found with size/md5sum mismatch
1079     """
1080
1081     q = session.query(PoolFile).filter_by(filename=filename)
1082     q = q.join(Location).filter_by(location_id=location_id)
1083
1084     ret = None
1085
1086     if q.count() > 1:
1087         ret = (None, None)
1088     elif q.count() < 1:
1089         ret = (False, None)
1090     else:
1091         obj = q.one()
1092         if obj.md5sum != md5sum or obj.filesize != int(filesize):
1093             ret = (False, obj)
1094
1095     if ret is None:
1096         ret = (True, obj)
1097
1098     return ret
1099
1100 __all__.append('check_poolfile')
1101
1102 @session_wrapper
1103 def get_poolfile_by_id(file_id, session=None):
1104     """
1105     Returns a PoolFile objects or None for the given id
1106
1107     @type file_id: int
1108     @param file_id: the id of the file to look for
1109
1110     @rtype: PoolFile or None
1111     @return: either the PoolFile object or None
1112     """
1113
1114     q = session.query(PoolFile).filter_by(file_id=file_id)
1115
1116     try:
1117         return q.one()
1118     except NoResultFound:
1119         return None
1120
1121 __all__.append('get_poolfile_by_id')
1122
1123
1124 @session_wrapper
1125 def get_poolfile_by_name(filename, location_id=None, session=None):
1126     """
1127     Returns an array of PoolFile objects for the given filename and
1128     (optionally) location_id
1129
1130     @type filename: string
1131     @param filename: the filename of the file to check against the DB
1132
1133     @type location_id: int
1134     @param location_id: the id of the location to look in (optional)
1135
1136     @rtype: array
1137     @return: array of PoolFile objects
1138     """
1139
1140     q = session.query(PoolFile).filter_by(filename=filename)
1141
1142     if location_id is not None:
1143         q = q.join(Location).filter_by(location_id=location_id)
1144
1145     return q.all()
1146
1147 __all__.append('get_poolfile_by_name')
1148
1149 @session_wrapper
1150 def get_poolfile_like_name(filename, session=None):
1151     """
1152     Returns an array of PoolFile objects which are like the given name
1153
1154     @type filename: string
1155     @param filename: the filename of the file to check against the DB
1156
1157     @rtype: array
1158     @return: array of PoolFile objects
1159     """
1160
1161     # TODO: There must be a way of properly using bind parameters with %FOO%
1162     q = session.query(PoolFile).filter(PoolFile.filename.like('%%/%s' % filename))
1163
1164     return q.all()
1165
1166 __all__.append('get_poolfile_like_name')
1167
1168 @session_wrapper
1169 def add_poolfile(filename, datadict, location_id, session=None):
1170     """
1171     Add a new file to the pool
1172
1173     @type filename: string
1174     @param filename: filename
1175
1176     @type datadict: dict
1177     @param datadict: dict with needed data
1178
1179     @type location_id: int
1180     @param location_id: database id of the location
1181
1182     @rtype: PoolFile
1183     @return: the PoolFile object created
1184     """
1185     poolfile = PoolFile()
1186     poolfile.filename = filename
1187     poolfile.filesize = datadict["size"]
1188     poolfile.md5sum = datadict["md5sum"]
1189     poolfile.sha1sum = datadict["sha1sum"]
1190     poolfile.sha256sum = datadict["sha256sum"]
1191     poolfile.location_id = location_id
1192
1193     session.add(poolfile)
1194     # Flush to get a file id (NB: This is not a commit)
1195     session.flush()
1196
1197     return poolfile
1198
1199 __all__.append('add_poolfile')
1200
1201 ################################################################################
1202
1203 class Fingerprint(object):
1204     def __init__(self, *args, **kwargs):
1205         pass
1206
1207     def __repr__(self):
1208         return '<Fingerprint %s>' % self.fingerprint
1209
1210 __all__.append('Fingerprint')
1211
1212 @session_wrapper
1213 def get_fingerprint(fpr, session=None):
1214     """
1215     Returns Fingerprint object for given fpr.
1216
1217     @type fpr: string
1218     @param fpr: The fpr to find / add
1219
1220     @type session: SQLAlchemy
1221     @param session: Optional SQL session object (a temporary one will be
1222     generated if not supplied).
1223
1224     @rtype: Fingerprint
1225     @return: the Fingerprint object for the given fpr or None
1226     """
1227
1228     q = session.query(Fingerprint).filter_by(fingerprint=fpr)
1229
1230     try:
1231         ret = q.one()
1232     except NoResultFound:
1233         ret = None
1234
1235     return ret
1236
1237 __all__.append('get_fingerprint')
1238
1239 @session_wrapper
1240 def get_or_set_fingerprint(fpr, session=None):
1241     """
1242     Returns Fingerprint object for given fpr.
1243
1244     If no matching fpr is found, a row is inserted.
1245
1246     @type fpr: string
1247     @param fpr: The fpr to find / add
1248
1249     @type session: SQLAlchemy
1250     @param session: Optional SQL session object (a temporary one will be
1251     generated if not supplied).  If not passed, a commit will be performed at
1252     the end of the function, otherwise the caller is responsible for commiting.
1253     A flush will be performed either way.
1254
1255     @rtype: Fingerprint
1256     @return: the Fingerprint object for the given fpr
1257     """
1258
1259     q = session.query(Fingerprint).filter_by(fingerprint=fpr)
1260
1261     try:
1262         ret = q.one()
1263     except NoResultFound:
1264         fingerprint = Fingerprint()
1265         fingerprint.fingerprint = fpr
1266         session.add(fingerprint)
1267         session.commit_or_flush()
1268         ret = fingerprint
1269
1270     return ret
1271
1272 __all__.append('get_or_set_fingerprint')
1273
1274 ################################################################################
1275
1276 # Helper routine for Keyring class
1277 def get_ldap_name(entry):
1278     name = []
1279     for k in ["cn", "mn", "sn"]:
1280         ret = entry.get(k)
1281         if ret and ret[0] != "" and ret[0] != "-":
1282             name.append(ret[0])
1283     return " ".join(name)
1284
1285 ################################################################################
1286
1287 class Keyring(object):
1288     gpg_invocation = "gpg --no-default-keyring --keyring %s" +\
1289                      " --with-colons --fingerprint --fingerprint"
1290
1291     keys = {}
1292     fpr_lookup = {}
1293
1294     def __init__(self, *args, **kwargs):
1295         pass
1296
1297     def __repr__(self):
1298         return '<Keyring %s>' % self.keyring_name
1299
1300     def de_escape_gpg_str(self, txt):
1301         esclist = re.split(r'(\\x..)', txt)
1302         for x in range(1,len(esclist),2):
1303             esclist[x] = "%c" % (int(esclist[x][2:],16))
1304         return "".join(esclist)
1305
1306     def load_keys(self, keyring):
1307         import email.Utils
1308
1309         if not self.keyring_id:
1310             raise Exception('Must be initialized with database information')
1311
1312         k = os.popen(self.gpg_invocation % keyring, "r")
1313         key = None
1314         signingkey = False
1315
1316         for line in k.xreadlines():
1317             field = line.split(":")
1318             if field[0] == "pub":
1319                 key = field[4]
1320                 (name, addr) = email.Utils.parseaddr(field[9])
1321                 name = re.sub(r"\s*[(].*[)]", "", name)
1322                 if name == "" or addr == "" or "@" not in addr:
1323                     name = field[9]
1324                     addr = "invalid-uid"
1325                 name = self.de_escape_gpg_str(name)
1326                 self.keys[key] = {"email": addr}
1327                 if name != "":
1328                     self.keys[key]["name"] = name
1329                 self.keys[key]["aliases"] = [name]
1330                 self.keys[key]["fingerprints"] = []
1331                 signingkey = True
1332             elif key and field[0] == "sub" and len(field) >= 12:
1333                 signingkey = ("s" in field[11])
1334             elif key and field[0] == "uid":
1335                 (name, addr) = email.Utils.parseaddr(field[9])
1336                 if name and name not in self.keys[key]["aliases"]:
1337                     self.keys[key]["aliases"].append(name)
1338             elif signingkey and field[0] == "fpr":
1339                 self.keys[key]["fingerprints"].append(field[9])
1340                 self.fpr_lookup[field[9]] = key
1341
1342     def import_users_from_ldap(self, session):
1343         import ldap
1344         cnf = Config()
1345
1346         LDAPDn = cnf["Import-LDAP-Fingerprints::LDAPDn"]
1347         LDAPServer = cnf["Import-LDAP-Fingerprints::LDAPServer"]
1348
1349         l = ldap.open(LDAPServer)
1350         l.simple_bind_s("","")
1351         Attrs = l.search_s(LDAPDn, ldap.SCOPE_ONELEVEL,
1352                "(&(keyfingerprint=*)(gidnumber=%s))" % (cnf["Import-Users-From-Passwd::ValidGID"]),
1353                ["uid", "keyfingerprint", "cn", "mn", "sn"])
1354
1355         ldap_fin_uid_id = {}
1356
1357         byuid = {}
1358         byname = {}
1359
1360         for i in Attrs:
1361             entry = i[1]
1362             uid = entry["uid"][0]
1363             name = get_ldap_name(entry)
1364             fingerprints = entry["keyFingerPrint"]
1365             keyid = None
1366             for f in fingerprints:
1367                 key = self.fpr_lookup.get(f, None)
1368                 if key not in self.keys:
1369                     continue
1370                 self.keys[key]["uid"] = uid
1371
1372                 if keyid != None:
1373                     continue
1374                 keyid = get_or_set_uid(uid, session).uid_id
1375                 byuid[keyid] = (uid, name)
1376                 byname[uid] = (keyid, name)
1377
1378         return (byname, byuid)
1379
1380     def generate_users_from_keyring(self, format, session):
1381         byuid = {}
1382         byname = {}
1383         any_invalid = False
1384         for x in self.keys.keys():
1385             if self.keys[x]["email"] == "invalid-uid":
1386                 any_invalid = True
1387                 self.keys[x]["uid"] = format % "invalid-uid"
1388             else:
1389                 uid = format % self.keys[x]["email"]
1390                 keyid = get_or_set_uid(uid, session).uid_id
1391                 byuid[keyid] = (uid, self.keys[x]["name"])
1392                 byname[uid] = (keyid, self.keys[x]["name"])
1393                 self.keys[x]["uid"] = uid
1394
1395         if any_invalid:
1396             uid = format % "invalid-uid"
1397             keyid = get_or_set_uid(uid, session).uid_id
1398             byuid[keyid] = (uid, "ungeneratable user id")
1399             byname[uid] = (keyid, "ungeneratable user id")
1400
1401         return (byname, byuid)
1402
1403 __all__.append('Keyring')
1404
1405 @session_wrapper
1406 def get_keyring(keyring, session=None):
1407     """
1408     If C{keyring} does not have an entry in the C{keyrings} table yet, return None
1409     If C{keyring} already has an entry, simply return the existing Keyring
1410
1411     @type keyring: string
1412     @param keyring: the keyring name
1413
1414     @rtype: Keyring
1415     @return: the Keyring object for this keyring
1416     """
1417
1418     q = session.query(Keyring).filter_by(keyring_name=keyring)
1419
1420     try:
1421         return q.one()
1422     except NoResultFound:
1423         return None
1424
1425 __all__.append('get_keyring')
1426
1427 ################################################################################
1428
1429 class KeyringACLMap(object):
1430     def __init__(self, *args, **kwargs):
1431         pass
1432
1433     def __repr__(self):
1434         return '<KeyringACLMap %s>' % self.keyring_acl_map_id
1435
1436 __all__.append('KeyringACLMap')
1437
1438 ################################################################################
1439
1440 class DBChange(object):
1441     def __init__(self, *args, **kwargs):
1442         pass
1443
1444     def __repr__(self):
1445         return '<DBChange %s>' % self.changesname
1446
1447 __all__.append('DBChange')
1448
1449 @session_wrapper
1450 def get_dbchange(filename, session=None):
1451     """
1452     returns DBChange object for given C{filename}.
1453
1454     @type archive: string
1455     @param archive: the name of the arhive
1456
1457     @type session: Session
1458     @param session: Optional SQLA session object (a temporary one will be
1459     generated if not supplied)
1460
1461     @rtype: Archive
1462     @return: Archive object for the given name (None if not present)
1463
1464     """
1465     q = session.query(DBChange).filter_by(changesname=filename)
1466
1467     try:
1468         return q.one()
1469     except NoResultFound:
1470         return None
1471
1472 __all__.append('get_dbchange')
1473
1474 ################################################################################
1475
1476 class Location(object):
1477     def __init__(self, *args, **kwargs):
1478         pass
1479
1480     def __repr__(self):
1481         return '<Location %s (%s)>' % (self.path, self.location_id)
1482
1483 __all__.append('Location')
1484
1485 @session_wrapper
1486 def get_location(location, component=None, archive=None, session=None):
1487     """
1488     Returns Location object for the given combination of location, component
1489     and archive
1490
1491     @type location: string
1492     @param location: the path of the location, e.g. I{/srv/ftp.debian.org/ftp/pool/}
1493
1494     @type component: string
1495     @param component: the component name (if None, no restriction applied)
1496
1497     @type archive: string
1498     @param archive_id: the archive name (if None, no restriction applied)
1499
1500     @rtype: Location / None
1501     @return: Either a Location object or None if one can't be found
1502     """
1503
1504     q = session.query(Location).filter_by(path=location)
1505
1506     if archive is not None:
1507         q = q.join(Archive).filter_by(archive_name=archive)
1508
1509     if component is not None:
1510         q = q.join(Component).filter_by(component_name=component)
1511
1512     try:
1513         return q.one()
1514     except NoResultFound:
1515         return None
1516
1517 __all__.append('get_location')
1518
1519 ################################################################################
1520
1521 class Maintainer(object):
1522     def __init__(self, *args, **kwargs):
1523         pass
1524
1525     def __repr__(self):
1526         return '''<Maintainer '%s' (%s)>''' % (self.name, self.maintainer_id)
1527
1528     def get_split_maintainer(self):
1529         if not hasattr(self, 'name') or self.name is None:
1530             return ('', '', '', '')
1531
1532         return fix_maintainer(self.name.strip())
1533
1534 __all__.append('Maintainer')
1535
1536 @session_wrapper
1537 def get_or_set_maintainer(name, session=None):
1538     """
1539     Returns Maintainer object for given maintainer name.
1540
1541     If no matching maintainer name is found, a row is inserted.
1542
1543     @type name: string
1544     @param name: The maintainer name to add
1545
1546     @type session: SQLAlchemy
1547     @param session: Optional SQL session object (a temporary one will be
1548     generated if not supplied).  If not passed, a commit will be performed at
1549     the end of the function, otherwise the caller is responsible for commiting.
1550     A flush will be performed either way.
1551
1552     @rtype: Maintainer
1553     @return: the Maintainer object for the given maintainer
1554     """
1555
1556     q = session.query(Maintainer).filter_by(name=name)
1557     try:
1558         ret = q.one()
1559     except NoResultFound:
1560         maintainer = Maintainer()
1561         maintainer.name = name
1562         session.add(maintainer)
1563         session.commit_or_flush()
1564         ret = maintainer
1565
1566     return ret
1567
1568 __all__.append('get_or_set_maintainer')
1569
1570 @session_wrapper
1571 def get_maintainer(maintainer_id, session=None):
1572     """
1573     Return the name of the maintainer behind C{maintainer_id} or None if that
1574     maintainer_id is invalid.
1575
1576     @type maintainer_id: int
1577     @param maintainer_id: the id of the maintainer
1578
1579     @rtype: Maintainer
1580     @return: the Maintainer with this C{maintainer_id}
1581     """
1582
1583     return session.query(Maintainer).get(maintainer_id)
1584
1585 __all__.append('get_maintainer')
1586
1587 ################################################################################
1588
1589 class NewComment(object):
1590     def __init__(self, *args, **kwargs):
1591         pass
1592
1593     def __repr__(self):
1594         return '''<NewComment for '%s %s' (%s)>''' % (self.package, self.version, self.comment_id)
1595
1596 __all__.append('NewComment')
1597
1598 @session_wrapper
1599 def has_new_comment(package, version, session=None):
1600     """
1601     Returns true if the given combination of C{package}, C{version} has a comment.
1602
1603     @type package: string
1604     @param package: name of the package
1605
1606     @type version: string
1607     @param version: package version
1608
1609     @type session: Session
1610     @param session: Optional SQLA session object (a temporary one will be
1611     generated if not supplied)
1612
1613     @rtype: boolean
1614     @return: true/false
1615     """
1616
1617     q = session.query(NewComment)
1618     q = q.filter_by(package=package)
1619     q = q.filter_by(version=version)
1620
1621     return bool(q.count() > 0)
1622
1623 __all__.append('has_new_comment')
1624
1625 @session_wrapper
1626 def get_new_comments(package=None, version=None, comment_id=None, session=None):
1627     """
1628     Returns (possibly empty) list of NewComment objects for the given
1629     parameters
1630
1631     @type package: string (optional)
1632     @param package: name of the package
1633
1634     @type version: string (optional)
1635     @param version: package version
1636
1637     @type comment_id: int (optional)
1638     @param comment_id: An id of a comment
1639
1640     @type session: Session
1641     @param session: Optional SQLA session object (a temporary one will be
1642     generated if not supplied)
1643
1644     @rtype: list
1645     @return: A (possibly empty) list of NewComment objects will be returned
1646     """
1647
1648     q = session.query(NewComment)
1649     if package is not None: q = q.filter_by(package=package)
1650     if version is not None: q = q.filter_by(version=version)
1651     if comment_id is not None: q = q.filter_by(comment_id=comment_id)
1652
1653     return q.all()
1654
1655 __all__.append('get_new_comments')
1656
1657 ################################################################################
1658
1659 class Override(object):
1660     def __init__(self, *args, **kwargs):
1661         pass
1662
1663     def __repr__(self):
1664         return '<Override %s (%s)>' % (self.package, self.suite_id)
1665
1666 __all__.append('Override')
1667
1668 @session_wrapper
1669 def get_override(package, suite=None, component=None, overridetype=None, session=None):
1670     """
1671     Returns Override object for the given parameters
1672
1673     @type package: string
1674     @param package: The name of the package
1675
1676     @type suite: string, list or None
1677     @param suite: The name of the suite (or suites if a list) to limit to.  If
1678                   None, don't limit.  Defaults to None.
1679
1680     @type component: string, list or None
1681     @param component: The name of the component (or components if a list) to
1682                       limit to.  If None, don't limit.  Defaults to None.
1683
1684     @type overridetype: string, list or None
1685     @param overridetype: The name of the overridetype (or overridetypes if a list) to
1686                          limit to.  If None, don't limit.  Defaults to None.
1687
1688     @type session: Session
1689     @param session: Optional SQLA session object (a temporary one will be
1690     generated if not supplied)
1691
1692     @rtype: list
1693     @return: A (possibly empty) list of Override objects will be returned
1694     """
1695
1696     q = session.query(Override)
1697     q = q.filter_by(package=package)
1698
1699     if suite is not None:
1700         if not isinstance(suite, list): suite = [suite]
1701         q = q.join(Suite).filter(Suite.suite_name.in_(suite))
1702
1703     if component is not None:
1704         if not isinstance(component, list): component = [component]
1705         q = q.join(Component).filter(Component.component_name.in_(component))
1706
1707     if overridetype is not None:
1708         if not isinstance(overridetype, list): overridetype = [overridetype]
1709         q = q.join(OverrideType).filter(OverrideType.overridetype.in_(overridetype))
1710
1711     return q.all()
1712
1713 __all__.append('get_override')
1714
1715
1716 ################################################################################
1717
1718 class OverrideType(object):
1719     def __init__(self, *args, **kwargs):
1720         pass
1721
1722     def __repr__(self):
1723         return '<OverrideType %s>' % self.overridetype
1724
1725 __all__.append('OverrideType')
1726
1727 @session_wrapper
1728 def get_override_type(override_type, session=None):
1729     """
1730     Returns OverrideType object for given C{override type}.
1731
1732     @type override_type: string
1733     @param override_type: The name of the override type
1734
1735     @type session: Session
1736     @param session: Optional SQLA session object (a temporary one will be
1737     generated if not supplied)
1738
1739     @rtype: int
1740     @return: the database id for the given override type
1741     """
1742
1743     q = session.query(OverrideType).filter_by(overridetype=override_type)
1744
1745     try:
1746         return q.one()
1747     except NoResultFound:
1748         return None
1749
1750 __all__.append('get_override_type')
1751
1752 ################################################################################
1753
1754 class DebContents(object):
1755     def __init__(self, *args, **kwargs):
1756         pass
1757
1758     def __repr__(self):
1759         return '<DebConetnts %s: %s>' % (self.package.package,self.file)
1760
1761 __all__.append('DebContents')
1762
1763
1764 class UdebContents(object):
1765     def __init__(self, *args, **kwargs):
1766         pass
1767
1768     def __repr__(self):
1769         return '<UdebConetnts %s: %s>' % (self.package.package,self.file)
1770
1771 __all__.append('UdebContents')
1772
1773 class PendingBinContents(object):
1774     def __init__(self, *args, **kwargs):
1775         pass
1776
1777     def __repr__(self):
1778         return '<PendingBinContents %s>' % self.contents_id
1779
1780 __all__.append('PendingBinContents')
1781
1782 def insert_pending_content_paths(package,
1783                                  is_udeb,
1784                                  fullpaths,
1785                                  session=None):
1786     """
1787     Make sure given paths are temporarily associated with given
1788     package
1789
1790     @type package: dict
1791     @param package: the package to associate with should have been read in from the binary control file
1792     @type fullpaths: list
1793     @param fullpaths: the list of paths of the file being associated with the binary
1794     @type session: SQLAlchemy session
1795     @param session: Optional SQLAlchemy session.  If this is passed, the caller
1796     is responsible for ensuring a transaction has begun and committing the
1797     results or rolling back based on the result code.  If not passed, a commit
1798     will be performed at the end of the function
1799
1800     @return: True upon success, False if there is a problem
1801     """
1802
1803     privatetrans = False
1804
1805     if session is None:
1806         session = DBConn().session()
1807         privatetrans = True
1808
1809     try:
1810         arch = get_architecture(package['Architecture'], session)
1811         arch_id = arch.arch_id
1812
1813         # Remove any already existing recorded files for this package
1814         q = session.query(PendingBinContents)
1815         q = q.filter_by(package=package['Package'])
1816         q = q.filter_by(version=package['Version'])
1817         q = q.filter_by(architecture=arch_id)
1818         q.delete()
1819
1820         for fullpath in fullpaths:
1821
1822             if fullpath.startswith( "./" ):
1823                 fullpath = fullpath[2:]
1824
1825             pca = PendingBinContents()
1826             pca.package = package['Package']
1827             pca.version = package['Version']
1828             pca.file = fullpath
1829             pca.architecture = arch_id
1830
1831             if isudeb:
1832                 pca.type = 8 # gross
1833             else:
1834                 pca.type = 7 # also gross
1835             session.add(pca)
1836
1837         # Only commit if we set up the session ourself
1838         if privatetrans:
1839             session.commit()
1840             session.close()
1841         else:
1842             session.flush()
1843
1844         return True
1845     except Exception, e:
1846         traceback.print_exc()
1847
1848         # Only rollback if we set up the session ourself
1849         if privatetrans:
1850             session.rollback()
1851             session.close()
1852
1853         return False
1854
1855 __all__.append('insert_pending_content_paths')
1856
1857 ################################################################################
1858
1859 class PolicyQueue(object):
1860     def __init__(self, *args, **kwargs):
1861         pass
1862
1863     def __repr__(self):
1864         return '<PolicyQueue %s>' % self.queue_name
1865
1866 __all__.append('PolicyQueue')
1867
1868 @session_wrapper
1869 def get_policy_queue(queuename, session=None):
1870     """
1871     Returns PolicyQueue object for given C{queue name}
1872
1873     @type queuename: string
1874     @param queuename: The name of the queue
1875
1876     @type session: Session
1877     @param session: Optional SQLA session object (a temporary one will be
1878     generated if not supplied)
1879
1880     @rtype: PolicyQueue
1881     @return: PolicyQueue object for the given queue
1882     """
1883
1884     q = session.query(PolicyQueue).filter_by(queue_name=queuename)
1885
1886     try:
1887         return q.one()
1888     except NoResultFound:
1889         return None
1890
1891 __all__.append('get_policy_queue')
1892
1893 ################################################################################
1894
1895 class Priority(object):
1896     def __init__(self, *args, **kwargs):
1897         pass
1898
1899     def __eq__(self, val):
1900         if isinstance(val, str):
1901             return (self.priority == val)
1902         # This signals to use the normal comparison operator
1903         return NotImplemented
1904
1905     def __ne__(self, val):
1906         if isinstance(val, str):
1907             return (self.priority != val)
1908         # This signals to use the normal comparison operator
1909         return NotImplemented
1910
1911     def __repr__(self):
1912         return '<Priority %s (%s)>' % (self.priority, self.priority_id)
1913
1914 __all__.append('Priority')
1915
1916 @session_wrapper
1917 def get_priority(priority, session=None):
1918     """
1919     Returns Priority object for given C{priority name}.
1920
1921     @type priority: string
1922     @param priority: The name of the priority
1923
1924     @type session: Session
1925     @param session: Optional SQLA session object (a temporary one will be
1926     generated if not supplied)
1927
1928     @rtype: Priority
1929     @return: Priority object for the given priority
1930     """
1931
1932     q = session.query(Priority).filter_by(priority=priority)
1933
1934     try:
1935         return q.one()
1936     except NoResultFound:
1937         return None
1938
1939 __all__.append('get_priority')
1940
1941 @session_wrapper
1942 def get_priorities(session=None):
1943     """
1944     Returns dictionary of priority names -> id mappings
1945
1946     @type session: Session
1947     @param session: Optional SQL session object (a temporary one will be
1948     generated if not supplied)
1949
1950     @rtype: dictionary
1951     @return: dictionary of priority names -> id mappings
1952     """
1953
1954     ret = {}
1955     q = session.query(Priority)
1956     for x in q.all():
1957         ret[x.priority] = x.priority_id
1958
1959     return ret
1960
1961 __all__.append('get_priorities')
1962
1963 ################################################################################
1964
1965 class Section(object):
1966     def __init__(self, *args, **kwargs):
1967         pass
1968
1969     def __eq__(self, val):
1970         if isinstance(val, str):
1971             return (self.section == val)
1972         # This signals to use the normal comparison operator
1973         return NotImplemented
1974
1975     def __ne__(self, val):
1976         if isinstance(val, str):
1977             return (self.section != val)
1978         # This signals to use the normal comparison operator
1979         return NotImplemented
1980
1981     def __repr__(self):
1982         return '<Section %s>' % self.section
1983
1984 __all__.append('Section')
1985
1986 @session_wrapper
1987 def get_section(section, session=None):
1988     """
1989     Returns Section object for given C{section name}.
1990
1991     @type section: string
1992     @param section: The name of the section
1993
1994     @type session: Session
1995     @param session: Optional SQLA session object (a temporary one will be
1996     generated if not supplied)
1997
1998     @rtype: Section
1999     @return: Section object for the given section name
2000     """
2001
2002     q = session.query(Section).filter_by(section=section)
2003
2004     try:
2005         return q.one()
2006     except NoResultFound:
2007         return None
2008
2009 __all__.append('get_section')
2010
2011 @session_wrapper
2012 def get_sections(session=None):
2013     """
2014     Returns dictionary of section names -> id mappings
2015
2016     @type session: Session
2017     @param session: Optional SQL session object (a temporary one will be
2018     generated if not supplied)
2019
2020     @rtype: dictionary
2021     @return: dictionary of section names -> id mappings
2022     """
2023
2024     ret = {}
2025     q = session.query(Section)
2026     for x in q.all():
2027         ret[x.section] = x.section_id
2028
2029     return ret
2030
2031 __all__.append('get_sections')
2032
2033 ################################################################################
2034
2035 class DBSource(object):
2036     def __init__(self, *args, **kwargs):
2037         pass
2038
2039     def __repr__(self):
2040         return '<DBSource %s (%s)>' % (self.source, self.version)
2041
2042 __all__.append('DBSource')
2043
2044 @session_wrapper
2045 def source_exists(source, source_version, suites = ["any"], session=None):
2046     """
2047     Ensure that source exists somewhere in the archive for the binary
2048     upload being processed.
2049       1. exact match     => 1.0-3
2050       2. bin-only NMU    => 1.0-3+b1 , 1.0-3.1+b1
2051
2052     @type package: string
2053     @param package: package source name
2054
2055     @type source_version: string
2056     @param source_version: expected source version
2057
2058     @type suites: list
2059     @param suites: list of suites to check in, default I{any}
2060
2061     @type session: Session
2062     @param session: Optional SQLA session object (a temporary one will be
2063     generated if not supplied)
2064
2065     @rtype: int
2066     @return: returns 1 if a source with expected version is found, otherwise 0
2067
2068     """
2069
2070     cnf = Config()
2071     ret = 1
2072
2073     for suite in suites:
2074         q = session.query(DBSource).filter_by(source=source)
2075         if suite != "any":
2076             # source must exist in suite X, or in some other suite that's
2077             # mapped to X, recursively... silent-maps are counted too,
2078             # unreleased-maps aren't.
2079             maps = cnf.ValueList("SuiteMappings")[:]
2080             maps.reverse()
2081             maps = [ m.split() for m in maps ]
2082             maps = [ (x[1], x[2]) for x in maps
2083                             if x[0] == "map" or x[0] == "silent-map" ]
2084             s = [suite]
2085             for x in maps:
2086                 if x[1] in s and x[0] not in s:
2087                     s.append(x[0])
2088
2089             q = q.join(SrcAssociation).join(Suite)
2090             q = q.filter(Suite.suite_name.in_(s))
2091
2092         # Reduce the query results to a list of version numbers
2093         ql = [ j.version for j in q.all() ]
2094
2095         # Try (1)
2096         if source_version in ql:
2097             continue
2098
2099         # Try (2)
2100         from daklib.regexes import re_bin_only_nmu
2101         orig_source_version = re_bin_only_nmu.sub('', source_version)
2102         if orig_source_version in ql:
2103             continue
2104
2105         # No source found so return not ok
2106         ret = 0
2107
2108     return ret
2109
2110 __all__.append('source_exists')
2111
2112 @session_wrapper
2113 def get_suites_source_in(source, session=None):
2114     """
2115     Returns list of Suite objects which given C{source} name is in
2116
2117     @type source: str
2118     @param source: DBSource package name to search for
2119
2120     @rtype: list
2121     @return: list of Suite objects for the given source
2122     """
2123
2124     return session.query(Suite).join(SrcAssociation).join(DBSource).filter_by(source=source).all()
2125
2126 __all__.append('get_suites_source_in')
2127
2128 @session_wrapper
2129 def get_sources_from_name(source, version=None, dm_upload_allowed=None, session=None):
2130     """
2131     Returns list of DBSource objects for given C{source} name and other parameters
2132
2133     @type source: str
2134     @param source: DBSource package name to search for
2135
2136     @type source: str or None
2137     @param source: DBSource version name to search for or None if not applicable
2138
2139     @type dm_upload_allowed: bool
2140     @param dm_upload_allowed: If None, no effect.  If True or False, only
2141     return packages with that dm_upload_allowed setting
2142
2143     @type session: Session
2144     @param session: Optional SQL session object (a temporary one will be
2145     generated if not supplied)
2146
2147     @rtype: list
2148     @return: list of DBSource objects for the given name (may be empty)
2149     """
2150
2151     q = session.query(DBSource).filter_by(source=source)
2152
2153     if version is not None:
2154         q = q.filter_by(version=version)
2155
2156     if dm_upload_allowed is not None:
2157         q = q.filter_by(dm_upload_allowed=dm_upload_allowed)
2158
2159     return q.all()
2160
2161 __all__.append('get_sources_from_name')
2162
2163 @session_wrapper
2164 def get_source_in_suite(source, suite, session=None):
2165     """
2166     Returns list of DBSource objects for a combination of C{source} and C{suite}.
2167
2168       - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
2169       - B{suite} - a suite name, eg. I{unstable}
2170
2171     @type source: string
2172     @param source: source package name
2173
2174     @type suite: string
2175     @param suite: the suite name
2176
2177     @rtype: string
2178     @return: the version for I{source} in I{suite}
2179
2180     """
2181
2182     q = session.query(SrcAssociation)
2183     q = q.join('source').filter_by(source=source)
2184     q = q.join('suite').filter_by(suite_name=suite)
2185
2186     try:
2187         return q.one().source
2188     except NoResultFound:
2189         return None
2190
2191 __all__.append('get_source_in_suite')
2192
2193 ################################################################################
2194
2195 @session_wrapper
2196 def add_dsc_to_db(u, filename, session=None):
2197     entry = u.pkg.files[filename]
2198     source = DBSource()
2199     pfs = []
2200
2201     source.source = u.pkg.dsc["source"]
2202     source.version = u.pkg.dsc["version"] # NB: not files[file]["version"], that has no epoch
2203     source.maintainer_id = get_or_set_maintainer(u.pkg.dsc["maintainer"], session).maintainer_id
2204     source.changedby_id = get_or_set_maintainer(u.pkg.changes["changed-by"], session).maintainer_id
2205     source.fingerprint_id = get_or_set_fingerprint(u.pkg.changes["fingerprint"], session).fingerprint_id
2206     source.install_date = datetime.now().date()
2207
2208     dsc_component = entry["component"]
2209     dsc_location_id = entry["location id"]
2210
2211     source.dm_upload_allowed = (u.pkg.dsc.get("dm-upload-allowed", '') == "yes")
2212
2213     # Set up a new poolfile if necessary
2214     if not entry.has_key("files id") or not entry["files id"]:
2215         filename = entry["pool name"] + filename
2216         poolfile = add_poolfile(filename, entry, dsc_location_id, session)
2217         session.flush()
2218         pfs.append(poolfile)
2219         entry["files id"] = poolfile.file_id
2220
2221     source.poolfile_id = entry["files id"]
2222     session.add(source)
2223     session.flush()
2224
2225     for suite_name in u.pkg.changes["distribution"].keys():
2226         sa = SrcAssociation()
2227         sa.source_id = source.source_id
2228         sa.suite_id = get_suite(suite_name).suite_id
2229         session.add(sa)
2230
2231     session.flush()
2232
2233     # Add the source files to the DB (files and dsc_files)
2234     dscfile = DSCFile()
2235     dscfile.source_id = source.source_id
2236     dscfile.poolfile_id = entry["files id"]
2237     session.add(dscfile)
2238
2239     for dsc_file, dentry in u.pkg.dsc_files.items():
2240         df = DSCFile()
2241         df.source_id = source.source_id
2242
2243         # If the .orig tarball is already in the pool, it's
2244         # files id is stored in dsc_files by check_dsc().
2245         files_id = dentry.get("files id", None)
2246
2247         # Find the entry in the files hash
2248         # TODO: Bail out here properly
2249         dfentry = None
2250         for f, e in u.pkg.files.items():
2251             if f == dsc_file:
2252                 dfentry = e
2253                 break
2254
2255         if files_id is None:
2256             filename = dfentry["pool name"] + dsc_file
2257
2258             (found, obj) = check_poolfile(filename, dentry["size"], dentry["md5sum"], dsc_location_id)
2259             # FIXME: needs to check for -1/-2 and or handle exception
2260             if found and obj is not None:
2261                 files_id = obj.file_id
2262                 pfs.append(obj)
2263
2264             # If still not found, add it
2265             if files_id is None:
2266                 # HACK: Force sha1sum etc into dentry
2267                 dentry["sha1sum"] = dfentry["sha1sum"]
2268                 dentry["sha256sum"] = dfentry["sha256sum"]
2269                 poolfile = add_poolfile(filename, dentry, dsc_location_id, session)
2270                 pfs.append(poolfile)
2271                 files_id = poolfile.file_id
2272         else:
2273             poolfile = get_poolfile_by_id(files_id, session)
2274             if poolfile is None:
2275                 utils.fubar("INTERNAL ERROR. Found no poolfile with id %d" % files_id)
2276             pfs.append(poolfile)
2277
2278         df.poolfile_id = files_id
2279         session.add(df)
2280
2281     session.flush()
2282
2283     # Add the src_uploaders to the DB
2284     uploader_ids = [source.maintainer_id]
2285     if u.pkg.dsc.has_key("uploaders"):
2286         for up in u.pkg.dsc["uploaders"].split(","):
2287             up = up.strip()
2288             uploader_ids.append(get_or_set_maintainer(up, session).maintainer_id)
2289
2290     added_ids = {}
2291     for up in uploader_ids:
2292         if added_ids.has_key(up):
2293             utils.warn("Already saw uploader %s for source %s" % (up, source.source))
2294             continue
2295
2296         added_ids[u]=1
2297
2298         su = SrcUploader()
2299         su.maintainer_id = up
2300         su.source_id = source.source_id
2301         session.add(su)
2302
2303     session.flush()
2304
2305     return dsc_component, dsc_location_id, pfs
2306
2307 __all__.append('add_dsc_to_db')
2308
2309 @session_wrapper
2310 def add_deb_to_db(u, filename, session=None):
2311     """
2312     Contrary to what you might expect, this routine deals with both
2313     debs and udebs.  That info is in 'dbtype', whilst 'type' is
2314     'deb' for both of them
2315     """
2316     cnf = Config()
2317     entry = u.pkg.files[filename]
2318
2319     bin = DBBinary()
2320     bin.package = entry["package"]
2321     bin.version = entry["version"]
2322     bin.maintainer_id = get_or_set_maintainer(entry["maintainer"], session).maintainer_id
2323     bin.fingerprint_id = get_or_set_fingerprint(u.pkg.changes["fingerprint"], session).fingerprint_id
2324     bin.arch_id = get_architecture(entry["architecture"], session).arch_id
2325     bin.binarytype = entry["dbtype"]
2326
2327     # Find poolfile id
2328     filename = entry["pool name"] + filename
2329     fullpath = os.path.join(cnf["Dir::Pool"], filename)
2330     if not entry.get("location id", None):
2331         entry["location id"] = get_location(cnf["Dir::Pool"], entry["component"], session=session).location_id
2332
2333     if entry.get("files id", None):
2334         poolfile = get_poolfile_by_id(bin.poolfile_id)
2335         bin.poolfile_id = entry["files id"]
2336     else:
2337         poolfile = add_poolfile(filename, entry, entry["location id"], session)
2338         bin.poolfile_id = entry["files id"] = poolfile.file_id
2339
2340     # Find source id
2341     bin_sources = get_sources_from_name(entry["source package"], entry["source version"], session=session)
2342     if len(bin_sources) != 1:
2343         raise NoSourceFieldError, "Unable to find a unique source id for %s (%s), %s, file %s, type %s, signed by %s" % \
2344                                   (bin.package, bin.version, bin.architecture.arch_string,
2345                                    filename, bin.binarytype, u.pkg.changes["fingerprint"])
2346
2347     bin.source_id = bin_sources[0].source_id
2348
2349     # Add and flush object so it has an ID
2350     session.add(bin)
2351     session.flush()
2352
2353     # Add BinAssociations
2354     for suite_name in u.pkg.changes["distribution"].keys():
2355         ba = BinAssociation()
2356         ba.binary_id = bin.binary_id
2357         ba.suite_id = get_suite(suite_name).suite_id
2358         session.add(ba)
2359
2360     session.flush()
2361
2362     # Deal with contents - disabled for now
2363     #contents = copy_temporary_contents(bin.package, bin.version, bin.architecture.arch_string, os.path.basename(filename), None, session)
2364     #if not contents:
2365     #    print "REJECT\nCould not determine contents of package %s" % bin.package
2366     #    session.rollback()
2367     #    raise MissingContents, "No contents stored for package %s, and couldn't determine contents of %s" % (bin.package, filename)
2368
2369     return poolfile
2370
2371 __all__.append('add_deb_to_db')
2372
2373 ################################################################################
2374
2375 class SourceACL(object):
2376     def __init__(self, *args, **kwargs):
2377         pass
2378
2379     def __repr__(self):
2380         return '<SourceACL %s>' % self.source_acl_id
2381
2382 __all__.append('SourceACL')
2383
2384 ################################################################################
2385
2386 class SrcAssociation(object):
2387     def __init__(self, *args, **kwargs):
2388         pass
2389
2390     def __repr__(self):
2391         return '<SrcAssociation %s (%s, %s)>' % (self.sa_id, self.source, self.suite)
2392
2393 __all__.append('SrcAssociation')
2394
2395 ################################################################################
2396
2397 class SrcFormat(object):
2398     def __init__(self, *args, **kwargs):
2399         pass
2400
2401     def __repr__(self):
2402         return '<SrcFormat %s>' % (self.format_name)
2403
2404 __all__.append('SrcFormat')
2405
2406 ################################################################################
2407
2408 class SrcUploader(object):
2409     def __init__(self, *args, **kwargs):
2410         pass
2411
2412     def __repr__(self):
2413         return '<SrcUploader %s>' % self.uploader_id
2414
2415 __all__.append('SrcUploader')
2416
2417 ################################################################################
2418
2419 SUITE_FIELDS = [ ('SuiteName', 'suite_name'),
2420                  ('SuiteID', 'suite_id'),
2421                  ('Version', 'version'),
2422                  ('Origin', 'origin'),
2423                  ('Label', 'label'),
2424                  ('Description', 'description'),
2425                  ('Untouchable', 'untouchable'),
2426                  ('Announce', 'announce'),
2427                  ('Codename', 'codename'),
2428                  ('OverrideCodename', 'overridecodename'),
2429                  ('ValidTime', 'validtime'),
2430                  ('Priority', 'priority'),
2431                  ('NotAutomatic', 'notautomatic'),
2432                  ('CopyChanges', 'copychanges'),
2433                  ('CopyDotDak', 'copydotdak'),
2434                  ('CommentsDir', 'commentsdir'),
2435                  ('OverrideSuite', 'overridesuite'),
2436                  ('ChangelogBase', 'changelogbase')]
2437
2438
2439 class Suite(object):
2440     def __init__(self, *args, **kwargs):
2441         pass
2442
2443     def __repr__(self):
2444         return '<Suite %s>' % self.suite_name
2445
2446     def __eq__(self, val):
2447         if isinstance(val, str):
2448             return (self.suite_name == val)
2449         # This signals to use the normal comparison operator
2450         return NotImplemented
2451
2452     def __ne__(self, val):
2453         if isinstance(val, str):
2454             return (self.suite_name != val)
2455         # This signals to use the normal comparison operator
2456         return NotImplemented
2457
2458     def details(self):
2459         ret = []
2460         for disp, field in SUITE_FIELDS:
2461             val = getattr(self, field, None)
2462             if val is not None:
2463                 ret.append("%s: %s" % (disp, val))
2464
2465         return "\n".join(ret)
2466
2467 __all__.append('Suite')
2468
2469 @session_wrapper
2470 def get_suite_architecture(suite, architecture, session=None):
2471     """
2472     Returns a SuiteArchitecture object given C{suite} and ${arch} or None if it
2473     doesn't exist
2474
2475     @type suite: str
2476     @param suite: Suite name to search for
2477
2478     @type architecture: str
2479     @param architecture: Architecture name to search for
2480
2481     @type session: Session
2482     @param session: Optional SQL session object (a temporary one will be
2483     generated if not supplied)
2484
2485     @rtype: SuiteArchitecture
2486     @return: the SuiteArchitecture object or None
2487     """
2488
2489     q = session.query(SuiteArchitecture)
2490     q = q.join(Architecture).filter_by(arch_string=architecture)
2491     q = q.join(Suite).filter_by(suite_name=suite)
2492
2493     try:
2494         return q.one()
2495     except NoResultFound:
2496         return None
2497
2498 __all__.append('get_suite_architecture')
2499
2500 @session_wrapper
2501 def get_suite(suite, session=None):
2502     """
2503     Returns Suite object for given C{suite name}.
2504
2505     @type suite: string
2506     @param suite: The name of the suite
2507
2508     @type session: Session
2509     @param session: Optional SQLA session object (a temporary one will be
2510     generated if not supplied)
2511
2512     @rtype: Suite
2513     @return: Suite object for the requested suite name (None if not present)
2514     """
2515
2516     q = session.query(Suite).filter_by(suite_name=suite)
2517
2518     try:
2519         return q.one()
2520     except NoResultFound:
2521         return None
2522
2523 __all__.append('get_suite')
2524
2525 ################################################################################
2526
2527 class SuiteArchitecture(object):
2528     def __init__(self, *args, **kwargs):
2529         pass
2530
2531     def __repr__(self):
2532         return '<SuiteArchitecture (%s, %s)>' % (self.suite_id, self.arch_id)
2533
2534 __all__.append('SuiteArchitecture')
2535
2536 @session_wrapper
2537 def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None):
2538     """
2539     Returns list of Architecture objects for given C{suite} name
2540
2541     @type source: str
2542     @param source: Suite name to search for
2543
2544     @type skipsrc: boolean
2545     @param skipsrc: Whether to skip returning the 'source' architecture entry
2546     (Default False)
2547
2548     @type skipall: boolean
2549     @param skipall: Whether to skip returning the 'all' architecture entry
2550     (Default False)
2551
2552     @type session: Session
2553     @param session: Optional SQL session object (a temporary one will be
2554     generated if not supplied)
2555
2556     @rtype: list
2557     @return: list of Architecture objects for the given name (may be empty)
2558     """
2559
2560     q = session.query(Architecture)
2561     q = q.join(SuiteArchitecture)
2562     q = q.join(Suite).filter_by(suite_name=suite)
2563
2564     if skipsrc:
2565         q = q.filter(Architecture.arch_string != 'source')
2566
2567     if skipall:
2568         q = q.filter(Architecture.arch_string != 'all')
2569
2570     q = q.order_by('arch_string')
2571
2572     return q.all()
2573
2574 __all__.append('get_suite_architectures')
2575
2576 ################################################################################
2577
2578 class SuiteSrcFormat(object):
2579     def __init__(self, *args, **kwargs):
2580         pass
2581
2582     def __repr__(self):
2583         return '<SuiteSrcFormat (%s, %s)>' % (self.suite_id, self.src_format_id)
2584
2585 __all__.append('SuiteSrcFormat')
2586
2587 @session_wrapper
2588 def get_suite_src_formats(suite, session=None):
2589     """
2590     Returns list of allowed SrcFormat for C{suite}.
2591
2592     @type suite: str
2593     @param suite: Suite name to search for
2594
2595     @type session: Session
2596     @param session: Optional SQL session object (a temporary one will be
2597     generated if not supplied)
2598
2599     @rtype: list
2600     @return: the list of allowed source formats for I{suite}
2601     """
2602
2603     q = session.query(SrcFormat)
2604     q = q.join(SuiteSrcFormat)
2605     q = q.join(Suite).filter_by(suite_name=suite)
2606     q = q.order_by('format_name')
2607
2608     return q.all()
2609
2610 __all__.append('get_suite_src_formats')
2611
2612 ################################################################################
2613
2614 class Uid(object):
2615     def __init__(self, *args, **kwargs):
2616         pass
2617
2618     def __eq__(self, val):
2619         if isinstance(val, str):
2620             return (self.uid == val)
2621         # This signals to use the normal comparison operator
2622         return NotImplemented
2623
2624     def __ne__(self, val):
2625         if isinstance(val, str):
2626             return (self.uid != val)
2627         # This signals to use the normal comparison operator
2628         return NotImplemented
2629
2630     def __repr__(self):
2631         return '<Uid %s (%s)>' % (self.uid, self.name)
2632
2633 __all__.append('Uid')
2634
2635 @session_wrapper
2636 def add_database_user(uidname, session=None):
2637     """
2638     Adds a database user
2639
2640     @type uidname: string
2641     @param uidname: The uid of the user to add
2642
2643     @type session: SQLAlchemy
2644     @param session: Optional SQL session object (a temporary one will be
2645     generated if not supplied).  If not passed, a commit will be performed at
2646     the end of the function, otherwise the caller is responsible for commiting.
2647
2648     @rtype: Uid
2649     @return: the uid object for the given uidname
2650     """
2651
2652     session.execute("CREATE USER :uid", {'uid': uidname})
2653     session.commit_or_flush()
2654
2655 __all__.append('add_database_user')
2656
2657 @session_wrapper
2658 def get_or_set_uid(uidname, session=None):
2659     """
2660     Returns uid object for given uidname.
2661
2662     If no matching uidname is found, a row is inserted.
2663
2664     @type uidname: string
2665     @param uidname: The uid to add
2666
2667     @type session: SQLAlchemy
2668     @param session: Optional SQL session object (a temporary one will be
2669     generated if not supplied).  If not passed, a commit will be performed at
2670     the end of the function, otherwise the caller is responsible for commiting.
2671
2672     @rtype: Uid
2673     @return: the uid object for the given uidname
2674     """
2675
2676     q = session.query(Uid).filter_by(uid=uidname)
2677
2678     try:
2679         ret = q.one()
2680     except NoResultFound:
2681         uid = Uid()
2682         uid.uid = uidname
2683         session.add(uid)
2684         session.commit_or_flush()
2685         ret = uid
2686
2687     return ret
2688
2689 __all__.append('get_or_set_uid')
2690
2691 @session_wrapper
2692 def get_uid_from_fingerprint(fpr, session=None):
2693     q = session.query(Uid)
2694     q = q.join(Fingerprint).filter_by(fingerprint=fpr)
2695
2696     try:
2697         return q.one()
2698     except NoResultFound:
2699         return None
2700
2701 __all__.append('get_uid_from_fingerprint')
2702
2703 ################################################################################
2704
2705 class UploadBlock(object):
2706     def __init__(self, *args, **kwargs):
2707         pass
2708
2709     def __repr__(self):
2710         return '<UploadBlock %s (%s)>' % (self.source, self.upload_block_id)
2711
2712 __all__.append('UploadBlock')
2713
2714 ################################################################################
2715
2716 class DBConn(object):
2717     """
2718     database module init.
2719     """
2720     __shared_state = {}
2721
2722     def __init__(self, *args, **kwargs):
2723         self.__dict__ = self.__shared_state
2724
2725         if not getattr(self, 'initialised', False):
2726             self.initialised = True
2727             self.debug = kwargs.has_key('debug')
2728             self.__createconn()
2729
2730     def __setuptables(self):
2731         tables = (
2732             'architecture',
2733             'archive',
2734             'bin_associations',
2735             'binaries',
2736             'binary_acl',
2737             'binary_acl_map',
2738             'bin_contents'
2739             'build_queue',
2740             'build_queue_files',
2741             'component',
2742             'config',
2743             'changes_pending_binaries',
2744             'changes_pending_files',
2745             'changes_pending_files_map',
2746             'changes_pending_source',
2747             'changes_pending_source_files',
2748             'changes_pool_files',
2749             'deb_contents',
2750             'dsc_files',
2751             'files',
2752             'fingerprint',
2753             'keyrings',
2754             'changes',
2755             'keyring_acl_map',
2756             'location',
2757             'maintainer',
2758             'new_comments',
2759             'override',
2760             'override_type',
2761             'pending_bin_contents',
2762             'policy_queue',
2763             'priority',
2764             'section',
2765             'source',
2766             'source_acl',
2767             'src_associations',
2768             'src_format',
2769             'src_uploaders',
2770             'suite',
2771             'suite_architectures',
2772             'suite_src_formats',
2773             'suite_build_queue_copy',
2774             'udeb_contents',
2775             'uid',
2776             'upload_blocks',
2777         )
2778
2779         for table_name in tables:
2780             table = Table(table_name, self.db_meta, autoload=True)
2781             setattr(self, 'tbl_%s' % table_name, table)
2782
2783     def __setupmappers(self):
2784         mapper(Architecture, self.tbl_architecture,
2785                properties = dict(arch_id = self.tbl_architecture.c.id))
2786
2787         mapper(Archive, self.tbl_archive,
2788                properties = dict(archive_id = self.tbl_archive.c.id,
2789                                  archive_name = self.tbl_archive.c.name))
2790
2791         mapper(BinAssociation, self.tbl_bin_associations,
2792                properties = dict(ba_id = self.tbl_bin_associations.c.id,
2793                                  suite_id = self.tbl_bin_associations.c.suite,
2794                                  suite = relation(Suite),
2795                                  binary_id = self.tbl_bin_associations.c.bin,
2796                                  binary = relation(DBBinary)))
2797
2798         mapper(PendingBinContents, self.tbl_pending_bin_contents,
2799                properties = dict(contents_id =self.tbl_pending_bin_contents.c.id,
2800                                  filename = self.tbl_pending_bin_contents.c.filename,
2801                                  package = self.tbl_pending_bin_contents.c.package,
2802                                  version = self.tbl_pending_bin_contents.c.version,
2803                                  arch = self.tbl_pending_bin_contents.c.arch,
2804                                  otype = self.tbl_pending_bin_contents.c.type))
2805
2806         mapper(DebContents, self.tbl_deb_contents,
2807                properties = dict(binary_id=self.tbl_deb_contents.c.binary_id,
2808                                  package=self.tbl_deb_contents.c.package,
2809                                  component=self.tbl_deb_contents.c.component,
2810                                  arch=self.tbl_deb_contents.c.arch,
2811                                  section=self.tbl_deb_contents.c.section,
2812                                  filename=self.tbl_deb_contents.c.filename))
2813
2814         mapper(UdebContents, self.tbl_udeb_contents,
2815                properties = dict(binary_id=self.tbl_udeb_contents.c.binary_id,
2816                                  package=self.tbl_udeb_contents.c.package,
2817                                  component=self.tbl_udeb_contents.c.component,
2818                                  arch=self.tbl_udeb_contents.c.arch,
2819                                  section=self.tbl_udeb_contents.c.section,
2820                                  filename=self.tbl_udeb_contents.c.filename))
2821
2822         mapper(DBBinary, self.tbl_binaries,
2823                properties = dict(binary_id = self.tbl_binaries.c.id,
2824                                  package = self.tbl_binaries.c.package,
2825                                  version = self.tbl_binaries.c.version,
2826                                  maintainer_id = self.tbl_binaries.c.maintainer,
2827                                  maintainer = relation(Maintainer),
2828                                  source_id = self.tbl_binaries.c.source,
2829                                  source = relation(DBSource),
2830                                  arch_id = self.tbl_binaries.c.architecture,
2831                                  architecture = relation(Architecture),
2832                                  poolfile_id = self.tbl_binaries.c.file,
2833                                  poolfile = relation(PoolFile),
2834                                  binarytype = self.tbl_binaries.c.type,
2835                                  fingerprint_id = self.tbl_binaries.c.sig_fpr,
2836                                  fingerprint = relation(Fingerprint),
2837                                  install_date = self.tbl_binaries.c.install_date,
2838                                  binassociations = relation(BinAssociation,
2839                                                             primaryjoin=(self.tbl_binaries.c.id==self.tbl_bin_associations.c.bin))))
2840
2841         mapper(BinaryACL, self.tbl_binary_acl,
2842                properties = dict(binary_acl_id = self.tbl_binary_acl.c.id))
2843
2844         mapper(BinaryACLMap, self.tbl_binary_acl_map,
2845                properties = dict(binary_acl_map_id = self.tbl_binary_acl_map.c.id,
2846                                  fingerprint = relation(Fingerprint, backref="binary_acl_map"),
2847                                  architecture = relation(Architecture)))
2848
2849         mapper(Component, self.tbl_component,
2850                properties = dict(component_id = self.tbl_component.c.id,
2851                                  component_name = self.tbl_component.c.name))
2852
2853         mapper(DBConfig, self.tbl_config,
2854                properties = dict(config_id = self.tbl_config.c.id))
2855
2856         mapper(DSCFile, self.tbl_dsc_files,
2857                properties = dict(dscfile_id = self.tbl_dsc_files.c.id,
2858                                  source_id = self.tbl_dsc_files.c.source,
2859                                  source = relation(DBSource),
2860                                  poolfile_id = self.tbl_dsc_files.c.file,
2861                                  poolfile = relation(PoolFile)))
2862
2863         mapper(PoolFile, self.tbl_files,
2864                properties = dict(file_id = self.tbl_files.c.id,
2865                                  filesize = self.tbl_files.c.size,
2866                                  location_id = self.tbl_files.c.location,
2867                                  location = relation(Location)))
2868
2869         mapper(Fingerprint, self.tbl_fingerprint,
2870                properties = dict(fingerprint_id = self.tbl_fingerprint.c.id,
2871                                  uid_id = self.tbl_fingerprint.c.uid,
2872                                  uid = relation(Uid),
2873                                  keyring_id = self.tbl_fingerprint.c.keyring,
2874                                  keyring = relation(Keyring),
2875                                  source_acl = relation(SourceACL),
2876                                  binary_acl = relation(BinaryACL)))
2877
2878         mapper(Keyring, self.tbl_keyrings,
2879                properties = dict(keyring_name = self.tbl_keyrings.c.name,
2880                                  keyring_id = self.tbl_keyrings.c.id))
2881
2882         mapper(DBChange, self.tbl_changes,
2883                properties = dict(change_id = self.tbl_changes.c.id,
2884                                  poolfiles = relation(PoolFile,
2885                                                       secondary=self.tbl_changes_pool_files,
2886                                                       backref="changeslinks"),
2887                                  files = relation(ChangePendingFile,
2888                                                   secondary=self.tbl_changes_pending_files_map,
2889                                                   backref="changesfile"),
2890                                  in_queue_id = self.tbl_changes.c.in_queue,
2891                                  in_queue = relation(PolicyQueue,
2892                                                      primaryjoin=(self.tbl_changes.c.in_queue==self.tbl_policy_queue.c.id)),
2893                                  approved_for_id = self.tbl_changes.c.approved_for))
2894
2895         mapper(ChangePendingBinary, self.tbl_changes_pending_binaries,
2896                properties = dict(change_pending_binary_id = self.tbl_changes_pending_binaries.c.id))
2897
2898         mapper(ChangePendingFile, self.tbl_changes_pending_files,
2899                properties = dict(change_pending_file_id = self.tbl_changes_pending_files.c.id))
2900
2901         mapper(ChangePendingSource, self.tbl_changes_pending_source,
2902                properties = dict(change_pending_source_id = self.tbl_changes_pending_source.c.id,
2903                                  change = relation(DBChange),
2904                                  maintainer = relation(Maintainer,
2905                                                        primaryjoin=(self.tbl_changes_pending_source.c.maintainer_id==self.tbl_maintainer.c.id)),
2906                                  changedby = relation(Maintainer,
2907                                                       primaryjoin=(self.tbl_changes_pending_source.c.changedby_id==self.tbl_maintainer.c.id)),
2908                                  fingerprint = relation(Fingerprint),
2909                                  source_files = relation(ChangePendingFile,
2910                                                          secondary=self.tbl_changes_pending_source_files,
2911                                                          backref="pending_sources")))
2912                                  files = relation(KnownChangePendingFile, backref="changesfile")))
2913
2914         mapper(KnownChangePendingFile, self.tbl_changes_pending_files,
2915                properties = dict(known_change_pending_file_id = self.tbl_changes_pending_files.c.id))
2916
2917         mapper(KeyringACLMap, self.tbl_keyring_acl_map,
2918                properties = dict(keyring_acl_map_id = self.tbl_keyring_acl_map.c.id,
2919                                  keyring = relation(Keyring, backref="keyring_acl_map"),
2920                                  architecture = relation(Architecture)))
2921
2922         mapper(Location, self.tbl_location,
2923                properties = dict(location_id = self.tbl_location.c.id,
2924                                  component_id = self.tbl_location.c.component,
2925                                  component = relation(Component),
2926                                  archive_id = self.tbl_location.c.archive,
2927                                  archive = relation(Archive),
2928                                  archive_type = self.tbl_location.c.type))
2929
2930         mapper(Maintainer, self.tbl_maintainer,
2931                properties = dict(maintainer_id = self.tbl_maintainer.c.id))
2932
2933         mapper(NewComment, self.tbl_new_comments,
2934                properties = dict(comment_id = self.tbl_new_comments.c.id))
2935
2936         mapper(Override, self.tbl_override,
2937                properties = dict(suite_id = self.tbl_override.c.suite,
2938                                  suite = relation(Suite),
2939                                  package = self.tbl_override.c.package,
2940                                  component_id = self.tbl_override.c.component,
2941                                  component = relation(Component),
2942                                  priority_id = self.tbl_override.c.priority,
2943                                  priority = relation(Priority),
2944                                  section_id = self.tbl_override.c.section,
2945                                  section = relation(Section),
2946                                  overridetype_id = self.tbl_override.c.type,
2947                                  overridetype = relation(OverrideType)))
2948
2949         mapper(OverrideType, self.tbl_override_type,
2950                properties = dict(overridetype = self.tbl_override_type.c.type,
2951                                  overridetype_id = self.tbl_override_type.c.id))
2952
2953         mapper(PolicyQueue, self.tbl_policy_queue,
2954                properties = dict(policy_queue_id = self.tbl_policy_queue.c.id))
2955
2956         mapper(Priority, self.tbl_priority,
2957                properties = dict(priority_id = self.tbl_priority.c.id))
2958
2959         mapper(Section, self.tbl_section,
2960                properties = dict(section_id = self.tbl_section.c.id,
2961                                  section=self.tbl_section.c.section))
2962
2963         mapper(DBSource, self.tbl_source,
2964                properties = dict(source_id = self.tbl_source.c.id,
2965                                  version = self.tbl_source.c.version,
2966                                  maintainer_id = self.tbl_source.c.maintainer,
2967                                  maintainer = relation(Maintainer,
2968                                                        primaryjoin=(self.tbl_source.c.maintainer==self.tbl_maintainer.c.id)),
2969                                  poolfile_id = self.tbl_source.c.file,
2970                                  poolfile = relation(PoolFile),
2971                                  fingerprint_id = self.tbl_source.c.sig_fpr,
2972                                  fingerprint = relation(Fingerprint),
2973                                  changedby_id = self.tbl_source.c.changedby,
2974                                  changedby = relation(Maintainer,
2975                                                       primaryjoin=(self.tbl_source.c.changedby==self.tbl_maintainer.c.id)),
2976                                  srcfiles = relation(DSCFile,
2977                                                      primaryjoin=(self.tbl_source.c.id==self.tbl_dsc_files.c.source)),
2978                                  srcassociations = relation(SrcAssociation,
2979                                                             primaryjoin=(self.tbl_source.c.id==self.tbl_src_associations.c.source)),
2980                                  srcuploaders = relation(SrcUploader)))
2981
2982         mapper(SourceACL, self.tbl_source_acl,
2983                properties = dict(source_acl_id = self.tbl_source_acl.c.id))
2984
2985         mapper(SrcAssociation, self.tbl_src_associations,
2986                properties = dict(sa_id = self.tbl_src_associations.c.id,
2987                                  suite_id = self.tbl_src_associations.c.suite,
2988                                  suite = relation(Suite),
2989                                  source_id = self.tbl_src_associations.c.source,
2990                                  source = relation(DBSource)))
2991
2992         mapper(SrcFormat, self.tbl_src_format,
2993                properties = dict(src_format_id = self.tbl_src_format.c.id,
2994                                  format_name = self.tbl_src_format.c.format_name))
2995
2996         mapper(SrcUploader, self.tbl_src_uploaders,
2997                properties = dict(uploader_id = self.tbl_src_uploaders.c.id,
2998                                  source_id = self.tbl_src_uploaders.c.source,
2999                                  source = relation(DBSource,
3000                                                    primaryjoin=(self.tbl_src_uploaders.c.source==self.tbl_source.c.id)),
3001                                  maintainer_id = self.tbl_src_uploaders.c.maintainer,
3002                                  maintainer = relation(Maintainer,
3003                                                        primaryjoin=(self.tbl_src_uploaders.c.maintainer==self.tbl_maintainer.c.id))))
3004
3005         mapper(Suite, self.tbl_suite,
3006                properties = dict(suite_id = self.tbl_suite.c.id,
3007                                  policy_queue = relation(PolicyQueue),
3008                                  copy_queues = relation(BuildQueue, secondary=self.tbl_suite_build_queue_copy)))
3009
3010         mapper(SuiteArchitecture, self.tbl_suite_architectures,
3011                properties = dict(suite_id = self.tbl_suite_architectures.c.suite,
3012                                  suite = relation(Suite, backref='suitearchitectures'),
3013                                  arch_id = self.tbl_suite_architectures.c.architecture,
3014                                  architecture = relation(Architecture)))
3015
3016         mapper(SuiteSrcFormat, self.tbl_suite_src_formats,
3017                properties = dict(suite_id = self.tbl_suite_src_formats.c.suite,
3018                                  suite = relation(Suite, backref='suitesrcformats'),
3019                                  src_format_id = self.tbl_suite_src_formats.c.src_format,
3020                                  src_format = relation(SrcFormat)))
3021
3022         mapper(Uid, self.tbl_uid,
3023                properties = dict(uid_id = self.tbl_uid.c.id,
3024                                  fingerprint = relation(Fingerprint)))
3025
3026         mapper(UploadBlock, self.tbl_upload_blocks,
3027                properties = dict(upload_block_id = self.tbl_upload_blocks.c.id,
3028                                  fingerprint = relation(Fingerprint, backref="uploadblocks"),
3029                                  uid = relation(Uid, backref="uploadblocks")))
3030
3031     ## Connection functions
3032     def __createconn(self):
3033         from config import Config
3034         cnf = Config()
3035         if cnf["DB::Host"]:
3036             # TCP/IP
3037             connstr = "postgres://%s" % cnf["DB::Host"]
3038             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
3039                 connstr += ":%s" % cnf["DB::Port"]
3040             connstr += "/%s" % cnf["DB::Name"]
3041         else:
3042             # Unix Socket
3043             connstr = "postgres:///%s" % cnf["DB::Name"]
3044             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
3045                 connstr += "?port=%s" % cnf["DB::Port"]
3046
3047         self.db_pg   = create_engine(connstr, echo=self.debug)
3048         self.db_meta = MetaData()
3049         self.db_meta.bind = self.db_pg
3050         self.db_smaker = sessionmaker(bind=self.db_pg,
3051                                       autoflush=True,
3052                                       autocommit=False)
3053
3054         self.__setuptables()
3055         self.__setupmappers()
3056
3057     def session(self):
3058         return self.db_smaker()
3059
3060 __all__.append('DBConn')
3061
3062