import_repository.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. # Copyright (C) 2015, Ansgar Burchardt <ansgar@debian.org>
  2. #
  3. # This program is free software; you can redistribute it and/or modify
  4. # it under the terms of the GNU General Public License as published by
  5. # the Free Software Foundation; either version 2 of the License, or
  6. # (at your option) any later version.
  7. #
  8. # This program is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. # GNU General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License along
  14. # with this program; if not, write to the Free Software Foundation, Inc.,
  15. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  16. import daklib.compress
  17. import daklib.config
  18. import daklib.dbconn
  19. import daklib.gpg
  20. import daklib.upload
  21. import daklib.regexes
  22. import apt_pkg
  23. import os
  24. import shutil
  25. import tempfile
  26. import urllib2
  27. from daklib.dbconn import DBSource, PoolFile
  28. from sqlalchemy.orm import object_session
  29. # Hmm, maybe use APT directly for all of this?
  30. _release_hashes_fields = ('MD5Sum', 'SHA1', 'SHA256')
  31. class Release(object):
  32. def __init__(self, base, suite_name, data):
  33. self._base = base
  34. self._suite_name = suite_name
  35. self._dict = apt_pkg.TagSection(data)
  36. self._hashes = daklib.upload.parse_file_list(self._dict, False, daklib.regexes.re_file_safe_slash, _release_hashes_fields)
  37. def architectures(self):
  38. return self._dict['Architectures'].split()
  39. def components(self):
  40. return self._dict['Components'].split()
  41. def packages(self, component, architecture):
  42. fn = '{0}/binary-{1}/Packages'.format(component, architecture)
  43. tmp = obtain_release_file(self, fn)
  44. return apt_pkg.TagFile(tmp.fh())
  45. def sources(self, component):
  46. fn = '{0}/source/Sources'.format(component)
  47. tmp = obtain_release_file(self, fn)
  48. return apt_pkg.TagFile(tmp.fh())
  49. def suite(self):
  50. return self._dict['Suite']
  51. def codename(self):
  52. return self._dict['Codename']
  53. # TODO: Handle Date/Valid-Until to make sure we import
  54. # a newer version than before
  55. class File(object):
  56. def __init__(self):
  57. config = daklib.config.Config()
  58. self._tmp = tempfile.NamedTemporaryFile(dir=config['Dir::TempPath'])
  59. def fh(self):
  60. self._tmp.seek(0)
  61. return self._tmp
  62. def hashes(self):
  63. return apt_pkg.Hashes(self.fh())
  64. def obtain_file(base, path):
  65. """Obtain a file 'path' located below 'base'
  66. Returns: daklib.import_repository.File
  67. Note: return type can still change
  68. """
  69. fn = '{0}/{1}'.format(base, path)
  70. tmp = File()
  71. if fn.startswith('http://'):
  72. fh = urllib2.urlopen(fn, timeout=300)
  73. shutil.copyfileobj(fh, tmp._tmp)
  74. fh.close()
  75. else:
  76. with open(fn, 'r') as fh:
  77. shutil.copyfileobj(fh, tmp._tmp)
  78. return tmp
  79. def obtain_release(base, suite_name, keyring, fingerprint=None):
  80. """Obtain release information
  81. Returns: daklib.import_repository.Release
  82. """
  83. tmp = obtain_file(base, 'dists/{0}/InRelease'.format(suite_name))
  84. data = tmp.fh().read()
  85. f = daklib.gpg.SignedFile(data, [keyring])
  86. r = Release(base, suite_name, f.contents)
  87. if r.suite() != suite_name and r.codename() != suite_name:
  88. raise Exception("Suite {0} doesn't match suite or codename from Release file.".format(suite_name))
  89. return r
  90. _compressions = ('.xz', '.gz', '.bz2')
  91. def obtain_release_file(release, filename):
  92. """Obtain file referenced from Release
  93. A compressed version is automatically selected and decompressed if it exists.
  94. Returns: daklib.import_repository.File
  95. """
  96. if filename not in release._hashes:
  97. raise IOError("File {0} not referenced in Release".format(filename))
  98. compressed = False
  99. for ext in _compressions:
  100. compressed_file = filename + ext
  101. if compressed_file in release._hashes:
  102. compressed = True
  103. filename = compressed_file
  104. break
  105. # Obtain file and check hashes
  106. tmp = obtain_file(release._base, 'dists/{0}/{1}'.format(release._suite_name, filename))
  107. hashedfile = release._hashes[filename]
  108. hashedfile.check_fh(tmp.fh())
  109. if compressed:
  110. tmp2 = File()
  111. daklib.compress.decompress(tmp.fh(), tmp2.fh(), filename)
  112. tmp = tmp2
  113. return tmp
  114. def import_source_to_archive(base, entry, transaction, archive, component):
  115. """Import source package described by 'entry' into the given 'archive' and 'component'
  116. 'entry' needs to be a dict-like object with at least the following
  117. keys as used in a Sources index: Directory, Files, Checksums-Sha1,
  118. Checksums-Sha256
  119. Return: daklib.dbconn.DBSource
  120. """
  121. # Obtain and verify files
  122. if not daklib.regexes.re_file_safe_slash.match(entry['Directory']):
  123. raise Exception("Unsafe path in Directory field")
  124. hashed_files = daklib.upload.parse_file_list(entry, False)
  125. files = []
  126. for f in hashed_files.values():
  127. path = os.path.join(entry['Directory'], f.filename)
  128. tmp = obtain_file(base, path)
  129. f.check_fh(tmp.fh())
  130. files.append(tmp)
  131. directory, f.input_filename = os.path.split(tmp.fh().name)
  132. # Inject files into archive
  133. source = daklib.upload.Source(directory, hashed_files.values(), [], require_signature=False)
  134. # TODO: ugly hack!
  135. for f in hashed_files.keys():
  136. if f.endswith('.dsc'):
  137. continue
  138. source.files[f].input_filename = hashed_files[f].input_filename
  139. # TODO: allow changed_by to be NULL
  140. changed_by = source.dsc['Maintainer']
  141. db_changed_by = daklib.dbconn.get_or_set_maintainer(changed_by, transaction.session)
  142. db_source = transaction.install_source_to_archive(directory, source, archive, component, db_changed_by)
  143. return db_source
  144. def import_package_to_suite(base, entry, transaction, suite, component):
  145. """Import binary package described by 'entry' into the given 'suite' and 'component'
  146. 'entry' needs to be a dict-like object with at least the following
  147. keys as used in a Packages index: Filename, Size, MD5sum, SHA1,
  148. SHA256
  149. Returns: daklib.dbconn.DBBinary
  150. """
  151. # Obtain and verify file
  152. filename = entry['Filename']
  153. tmp = obtain_file(base, filename)
  154. directory, fn = os.path.split(tmp.fh().name)
  155. hashedfile = daklib.upload.HashedFile(os.path.basename(filename), long(entry['Size']), entry['MD5sum'], entry['SHA1'], entry['SHA256'], input_filename=fn)
  156. hashedfile.check_fh(tmp.fh())
  157. # Inject file into archive
  158. binary = daklib.upload.Binary(directory, hashedfile)
  159. db_binary = transaction.install_binary(directory, binary, suite, component)
  160. transaction.flush()
  161. return db_binary
  162. def import_source_to_suite(base, entry, transaction, suite, component):
  163. """Import source package described by 'entry' into the given 'suite' and 'component'
  164. 'entry' needs to be a dict-like object with at least the following
  165. keys as used in a Sources index: Directory, Files, Checksums-Sha1,
  166. Checksums-Sha256
  167. Returns: daklib.dbconn.DBBinary
  168. """
  169. source = import_source_to_archive(base, entry, transaction, suite.archive, component)
  170. source.suites.append(suite)
  171. transaction.flush()
  172. def source_in_archive(source, version, archive, component=None):
  173. """Check that source package 'source' with version 'version' exists in 'archive',
  174. with an optional check for the given component 'component'.
  175. @type source: str
  176. @type version: str
  177. @type archive: daklib.dbconn.Archive
  178. @type component: daklib.dbconn.Component or None
  179. @rtype: boolean
  180. Note: This should probably be moved somewhere else
  181. """
  182. session = object_session(archive)
  183. query = session.query(DBSource).filter_by(source=source, version=version) \
  184. .join(DBSource.poolfile).join(PoolFile.archives).filter_by(archive=archive)
  185. if component is not None:
  186. query = query.filter_by(component=component)
  187. return session.query(query.exists()).scalar()