update_suite.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. #! /usr/bin/env python
  2. #
  3. # Copyright (C) 2015, Ansgar Burchardt <ansgar@debian.org>
  4. #
  5. # This program is free software; you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation; either version 2 of the License, or
  8. # (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License along
  16. # with this program; if not, write to the Free Software Foundation, Inc.,
  17. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  18. from daklib.archive import ArchiveTransaction
  19. from daklib.dbconn import *
  20. import daklib.daklog
  21. import daklib.utils
  22. from sqlalchemy.orm.exc import NoResultFound
  23. import sqlalchemy.sql as sql
  24. import sys
  25. """
  26. Idea:
  27. dak update-suite testing testing-kfreebsd
  28. -> grab all source & binary packages from testing with a higher version
  29. than in testing-kfreebsd (or not in -kfreebsd) and copy them
  30. -> limited to architectures in testing-kfreebsd
  31. -> obeys policy queues
  32. -> copies to build queues
  33. dak update-suite --create-in=ftp-master stable testing
  34. -> create suite "testing" based on "stable" in archive "ftp-master"
  35. Additional switches:
  36. --skip-policy-queue: skip target suite's policy queue
  37. --skip-build-queues: do not copy to build queue
  38. --no-new-packages: do not copy new packages
  39. -> source-based, new binaries from existing sources will be added
  40. --only-new-packages: do not update existing packages
  41. -> source-based, do not copy new binaries w/o source!
  42. --also-policy-queue: also copy pending packages from policy queue
  43. --update-overrides: update overrides as well (if different overrides are used)
  44. --no-act
  45. """
  46. def usage():
  47. print("dak update-suite [-n|--no-act] <origin> <target>")
  48. sys.exit(0)
  49. class SuiteUpdater(object):
  50. def __init__(self, transaction, origin, target,
  51. new_packages=True, also_from_policy_queue=False,
  52. obey_policy_queue=True, obey_build_queues=True,
  53. update_overrides=False, dry_run=False):
  54. self.transaction = transaction
  55. self.origin = origin
  56. self.target = target
  57. self.new_packages = new_packages
  58. self.also_from_policy_queue = also_from_policy_queue
  59. self.obey_policy_queue = obey_policy_queue
  60. self.obey_build_queues = obey_build_queues
  61. self.update_overrides = update_overrides
  62. self.dry_run = dry_run
  63. if obey_policy_queue and target.policy_queue_id is not None:
  64. raise Exception('Not implemented...')
  65. self.logger = None if dry_run else daklib.daklog.Logger("update-suite")
  66. def query_new_binaries(self, additional_sources):
  67. # Candidates are binaries in the origin suite, and optionally in its policy queue.
  68. query = """
  69. SELECT b.*
  70. FROM binaries b
  71. JOIN bin_associations ba ON b.id = ba.bin AND ba.suite = :origin
  72. """
  73. if self.also_from_policy_queue:
  74. query += """
  75. UNION
  76. SELECT b.*
  77. FROM binaries b
  78. JOIN policy_queue_upload_binaries_map pqubm ON pqubm.binary_id = b.id
  79. JOIN policy_queue_upload pqu ON pqu.id = pqubm.policy_queue_upload_id
  80. WHERE pqu.target_suite_id = :origin
  81. AND pqu.policy_queue_id = (SELECT policy_queue_id FROM suite WHERE id = :origin)
  82. """
  83. # Only take binaries that are for a architecture part of the target suite,
  84. # and whose source was just added to the target suite (i.e. listed in additional_sources)
  85. # or that have the source already available in the target suite
  86. # or in the target suite's policy queue if we obey policy queues,
  87. # and filter out binaries with a lower version than already in the target suite.
  88. if self.obey_policy_queue:
  89. cond_source_in_policy_queue = """
  90. EXISTS (SELECT 1
  91. FROM policy_queue_upload pqu
  92. WHERE tmp.source = pqu.source_id
  93. AND pqu.target_suite_id = :target
  94. AND pqu.policy_queue_id = (SELECT policy_queue_id FROM suite WHERE id = :target))
  95. """
  96. else:
  97. cond_source_in_policy_queue = "FALSE"
  98. query = """
  99. WITH tmp AS ({0})
  100. SELECT DISTINCT *
  101. FROM tmp
  102. WHERE tmp.architecture IN (SELECT architecture FROM suite_architectures WHERE suite = :target)
  103. AND (tmp.source IN :additional_sources
  104. OR EXISTS (SELECT 1
  105. FROM src_associations sa
  106. WHERE tmp.source = sa.source AND sa.suite = :target)
  107. OR {1})
  108. AND NOT EXISTS (SELECT 1
  109. FROM binaries b2
  110. JOIN bin_associations ba2 ON b2.id = ba2.bin AND ba2.suite = :target
  111. WHERE tmp.package = b2.package AND tmp.architecture = b2.architecture AND b2.version >= tmp.version)
  112. ORDER BY package, version, architecture
  113. """.format(query, cond_source_in_policy_queue)
  114. # An empty tuple generates a SQL statement with "tmp.source IN ()"
  115. # which is not valid. Inject an invalid value in this case:
  116. # "tmp.source IN (NULL)" is always false.
  117. if len(additional_sources) == 0:
  118. additional_sources = tuple([None])
  119. params = {
  120. 'origin': self.origin.suite_id,
  121. 'target': self.target.suite_id,
  122. 'additional_sources': additional_sources,
  123. }
  124. return self.transaction.session.query(DBBinary).from_statement(sql.text(query)).params(params)
  125. def query_new_sources(self):
  126. # Candidates are source packages in the origin suite, and optionally in its policy queue.
  127. query = """
  128. SELECT s.*
  129. FROM source s
  130. JOIN src_associations sa ON s.id = sa.source AND sa.suite = :origin
  131. """
  132. if self.also_from_policy_queue:
  133. query += """
  134. UNION
  135. SELECT s.*
  136. FROM source s
  137. JOIN policy_queue_upload pqu ON pqu.source_id = s.id
  138. WHERE pqu.target_suite_id = :origin
  139. AND pqu.policy_queue_id = (SELECT policy_queue_id FROM suite WHERE id = :origin)
  140. """
  141. # Filter out source packages with a lower version than already in the target suite.
  142. query = """
  143. WITH tmp AS ({0})
  144. SELECT DISTINCT *
  145. FROM tmp
  146. WHERE NOT EXISTS (SELECT 1
  147. FROM source s2
  148. JOIN src_associations sa2 ON s2.id = sa2.source AND sa2.suite = :target
  149. WHERE s2.source = tmp.source AND s2.version >= tmp.version)
  150. """.format(query)
  151. # Optionally filter out source packages that are not already in the target suite.
  152. if not self.new_packages:
  153. query += """
  154. AND EXISTS (SELECT 1
  155. FROM source s2
  156. JOIN src_associations sa2 ON s2.id = sa2.source AND sa2.suite = :target
  157. WHERE s2.source = tmp.source)
  158. """
  159. query += "ORDER BY source, version"
  160. params = {'origin': self.origin.suite_id, 'target': self.target.suite_id}
  161. return self.transaction.session.query(DBSource).from_statement(sql.text(query)).params(params)
  162. def _components_for_binary(self, binary, suite):
  163. session = self.transaction.session
  164. return session.query(Component) \
  165. .join(ArchiveFile, Component.component_id == ArchiveFile.component_id) \
  166. .join(ArchiveFile.file).filter(PoolFile.file_id == binary.poolfile_id) \
  167. .filter(ArchiveFile.archive_id == suite.archive_id)
  168. def install_binaries(self, binaries, suite):
  169. if len(binaries) == 0:
  170. return
  171. # If origin and target suites are in the same archive, we can skip the
  172. # overhead from ArchiveTransaction.copy_binary()
  173. if self.origin.archive_id == suite.archive_id:
  174. query = "INSERT INTO bin_associations (bin, suite) VALUES (:bin, :suite)"
  175. target_id = suite.suite_id
  176. params = [{'bin': b.binary_id, 'suite': target_id} for b in binaries]
  177. self.transaction.session.execute(query, params)
  178. else:
  179. for b in binaries:
  180. for c in self._components_for_binary(b, suite):
  181. self.transaction.copy_binary(b, suite, c)
  182. def _components_for_source(self, source, suite):
  183. session = self.transaction.session
  184. return session.query(Component) \
  185. .join(ArchiveFile, Component.component_id == ArchiveFile.component_id) \
  186. .join(ArchiveFile.file).filter(PoolFile.file_id == source.poolfile_id) \
  187. .filter(ArchiveFile.archive_id == suite.archive_id)
  188. def install_sources(self, sources, suite):
  189. if len(sources) == 0:
  190. return
  191. # If origin and target suites are in the same archive, we can skip the
  192. # overhead from ArchiveTransaction.copy_source()
  193. if self.origin.archive_id == suite.archive_id:
  194. query = "INSERT INTO src_associations (source, suite) VALUES (:source, :suite)"
  195. target_id = suite.suite_id
  196. params = [{'source': s.source_id, 'suite': target_id} for s in sources]
  197. self.transaction.session.execute(query, params)
  198. else:
  199. for s in sources:
  200. for c in self._components_for_source(s, suite):
  201. self.transaction.copy_source(s, suite, c)
  202. def update_suite(self):
  203. targets = set([self.target])
  204. if self.obey_build_queues:
  205. targets.update([bq.suite for bq in self.target.copy_queues])
  206. target_names = [s.suite_name for s in targets]
  207. target_names.sort()
  208. target_name = ",".join(target_names)
  209. new_sources = self.query_new_sources().all()
  210. additional_sources = tuple(s.source_id for s in new_sources)
  211. for s in new_sources:
  212. self.log(["add-source", target_name, s.source, s.version])
  213. if not self.dry_run:
  214. for target in targets:
  215. self.install_sources(new_sources, target)
  216. new_binaries = self.query_new_binaries(additional_sources).all()
  217. for b in new_binaries:
  218. self.log(["add-binary", target_name, b.package, b.version, b.architecture.arch_string])
  219. if not self.dry_run:
  220. for target in targets:
  221. self.install_binaries(new_binaries, target)
  222. def log(self, args):
  223. if self.logger:
  224. self.logger.log(args)
  225. else:
  226. print(args)
  227. def main():
  228. from daklib.config import Config
  229. config = Config()
  230. import apt_pkg
  231. arguments = [
  232. ('h', 'help', 'Update-Suite::Options::Help'),
  233. ('n', 'no-act', 'Update-Suite::options::NoAct'),
  234. ]
  235. argv = apt_pkg.parse_commandline(config.Cnf, arguments, sys.argv)
  236. try:
  237. options = config.subtree("Update-Suite::Options")
  238. except KeyError:
  239. options = {}
  240. if 'Help' in options or len(argv) != 2:
  241. usage()
  242. origin_name = argv[0]
  243. target_name = argv[1]
  244. dry_run = True if 'NoAct' in options else False
  245. with ArchiveTransaction() as transaction:
  246. session = transaction.session
  247. try:
  248. origin = session.query(Suite).filter_by(suite_name=origin_name).one()
  249. except NoResultFound:
  250. daklib.utils.fubar("Origin suite '{0}' is unknown.".format(origin_name))
  251. try:
  252. target = session.query(Suite).filter_by(suite_name=target_name).one()
  253. except NoResultFound:
  254. daklib.utils.fubar("Target suite '{0}' is unknown.".format(target_name))
  255. su = SuiteUpdater(transaction, origin, target, dry_run=dry_run)
  256. su.update_suite()
  257. if dry_run:
  258. transaction.rollback()
  259. else:
  260. transaction.commit()
  261. if __name__ == '__main__':
  262. pass