changes.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. #!/usr/bin/env python
  2. # vim:set et sw=4:
  3. """
  4. Changes class for dak
  5. @contact: Debian FTP Master <ftpmaster@debian.org>
  6. @copyright: 2001 - 2006 James Troup <james@nocrew.org>
  7. @copyright: 2009 Joerg Jaspert <joerg@debian.org>
  8. @copyright: 2009 Mark Hymers <mhy@debian.org>
  9. @license: GNU General Public License version 2 or later
  10. """
  11. # This program is free software; you can redistribute it and/or modify
  12. # it under the terms of the GNU General Public License as published by
  13. # the Free Software Foundation; either version 2 of the License, or
  14. # (at your option) any later version.
  15. # This program is distributed in the hope that it will be useful,
  16. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. # GNU General Public License for more details.
  19. # You should have received a copy of the GNU General Public License
  20. # along with this program; if not, write to the Free Software
  21. # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  22. ###############################################################################
  23. from __future__ import absolute_import, print_function
  24. import os
  25. import datetime
  26. from apt_pkg import TagSection
  27. from .utils import open_file, poolify, deb_extract_control
  28. from .config import *
  29. from .dbconn import *
  30. ###############################################################################
  31. __all__ = []
  32. ###############################################################################
  33. CHANGESFIELDS_MANDATORY = ["distribution", "source", "architecture",
  34. "version", "maintainer", "urgency", "fingerprint", "changedby822",
  35. "changedby2047", "changedbyname", "maintainer822", "maintainer2047",
  36. "maintainername", "maintaineremail", "closes", "changes"]
  37. __all__.append('CHANGESFIELDS_MANDATORY')
  38. CHANGESFIELDS_OPTIONAL = ["changed-by", "filecontents", "format",
  39. "process-new note", "adv id", "distribution-version", "sponsoremail"]
  40. __all__.append('CHANGESFIELDS_OPTIONAL')
  41. CHANGESFIELDS_FILES = ["package", "version", "architecture", "type", "size",
  42. "md5sum", "sha1sum", "sha256sum", "component", "location id",
  43. "source package", "source version", "maintainer", "dbtype", "files id",
  44. "new", "section", "priority", "othercomponents", "pool name",
  45. "original component"]
  46. __all__.append('CHANGESFIELDS_FILES')
  47. CHANGESFIELDS_DSC = ["source", "version", "maintainer", "fingerprint",
  48. "uploaders", "bts changelog", "dm-upload-allowed"]
  49. __all__.append('CHANGESFIELDS_DSC')
  50. CHANGESFIELDS_DSCFILES_MANDATORY = ["size", "md5sum"]
  51. __all__.append('CHANGESFIELDS_DSCFILES_MANDATORY')
  52. CHANGESFIELDS_DSCFILES_OPTIONAL = ["files id"]
  53. __all__.append('CHANGESFIELDS_DSCFILES_OPTIONAL')
  54. CHANGESFIELDS_ORIGFILES = ["id", "location"]
  55. __all__.append('CHANGESFIELDS_ORIGFILES')
  56. ###############################################################################
  57. class Changes(object):
  58. """ Convenience wrapper to carry around all the package information """
  59. def __init__(self, **kwds):
  60. self.reset()
  61. def reset(self):
  62. self.changes_file = ""
  63. self.changes = {}
  64. self.dsc = {}
  65. self.files = {}
  66. self.dsc_files = {}
  67. self.orig_files = {}
  68. def file_summary(self):
  69. # changes["distribution"] may not exist in corner cases
  70. # (e.g. unreadable changes files)
  71. if "distribution" not in self.changes or not \
  72. isinstance(self.changes["distribution"], dict):
  73. self.changes["distribution"] = {}
  74. byhand = False
  75. new = False
  76. summary = ""
  77. override_summary = ""
  78. for name, entry in sorted(self.files.items()):
  79. if "byhand" in entry:
  80. byhand = True
  81. summary += name + " byhand\n"
  82. elif "new" in entry:
  83. new = True
  84. summary += "(new) %s %s %s\n" % (name, entry["priority"], entry["section"])
  85. if "othercomponents" in entry:
  86. summary += "WARNING: Already present in %s distribution.\n" % (entry["othercomponents"])
  87. if entry["type"] == "deb":
  88. deb_fh = open_file(name)
  89. summary += TagSection(deb_extract_control(deb_fh))["Description"] + '\n'
  90. deb_fh.close()
  91. else:
  92. entry["pool name"] = poolify(self.changes.get("source", ""))
  93. destination = entry["pool name"] + name
  94. summary += name + "\n to " + destination + "\n"
  95. if "type" not in entry:
  96. entry["type"] = "unknown"
  97. if entry["type"] in ["deb", "udeb", "dsc"]:
  98. # (queue/unchecked), there we have override entries already, use them
  99. # (process-new), there we dont have override entries, use the newly generated ones.
  100. override_prio = entry.get("override priority", entry["priority"])
  101. override_sect = entry.get("override section", entry["section"])
  102. override_summary += "%s - %s %s\n" % (name, override_prio, override_sect)
  103. return (byhand, new, summary, override_summary)
  104. def check_override(self):
  105. """
  106. Checks override entries for validity.
  107. Returns an empty string if there are no problems
  108. or the text of a warning if there are
  109. """
  110. summary = ""
  111. # Abandon the check if it's a non-sourceful upload
  112. if "source" not in self.changes["architecture"]:
  113. return summary
  114. for name, entry in sorted(self.files.items()):
  115. if "new" not in entry and entry["type"] == "deb":
  116. if entry["section"] != "-":
  117. if entry["section"].lower() != entry["override section"].lower():
  118. summary += "%s: package says section is %s, override says %s.\n" % (name,
  119. entry["section"],
  120. entry["override section"])
  121. if entry["priority"] != "-":
  122. if entry["priority"] != entry["override priority"]:
  123. summary += "%s: package says priority is %s, override says %s.\n" % (name,
  124. entry["priority"],
  125. entry["override priority"])
  126. return summary
  127. @session_wrapper
  128. def remove_known_changes(self, session=None):
  129. session.delete(get_dbchange(self.changes_file, session))
  130. def mark_missing_fields(self):
  131. """add "missing" in fields which we will require for the known_changes table"""
  132. for key in ['urgency', 'maintainer', 'fingerprint', 'changed-by']:
  133. if (key not in self.changes) or (not self.changes[key]):
  134. self.changes[key] = 'missing'
  135. def __get_file_from_pool(self, filename, entry, session, logger):
  136. cnf = Config()
  137. if "Dinstall::SuiteSuffix" in cnf:
  138. component = cnf["Dinstall::SuiteSuffix"] + entry["component"]
  139. else:
  140. component = entry["component"]
  141. poolname = poolify(entry["source"])
  142. l = get_location(cnf["Dir::Pool"], component, session=session)
  143. found, poolfile = check_poolfile(os.path.join(poolname, filename),
  144. entry['size'],
  145. entry["md5sum"],
  146. l.location_id,
  147. session=session)
  148. if found is None:
  149. if logger is not None:
  150. logger.log(["E: Found multiple files for pool (%s) for %s" % (filename, component)])
  151. return None
  152. elif found is False and poolfile is not None:
  153. if logger is not None:
  154. logger.log(["E: md5sum/size mismatch for %s in pool" % (filename)])
  155. return None
  156. else:
  157. if poolfile is None:
  158. if logger is not None:
  159. logger.log(["E: Could not find %s in pool" % (filename)])
  160. return None
  161. else:
  162. return poolfile
  163. @session_wrapper
  164. def add_known_changes(self, dirpath, in_queue=None, session=None, logger=None):
  165. """add "missing" in fields which we will require for the known_changes table"""
  166. cnf = Config()
  167. changesfile = os.path.join(dirpath, self.changes_file)
  168. filetime = datetime.datetime.fromtimestamp(os.path.getctime(changesfile))
  169. self.mark_missing_fields()
  170. multivalues = {}
  171. for key in ("distribution", "architecture", "binary"):
  172. if isinstance(self.changes[key], dict):
  173. multivalues[key] = " ".join(self.changes[key].keys())
  174. else:
  175. multivalues[key] = self.changes[key]
  176. chg = DBChange()
  177. chg.changesname = self.changes_file
  178. chg.seen = filetime
  179. chg.in_queue_id = in_queue
  180. chg.source = self.changes["source"]
  181. chg.binaries = multivalues["binary"]
  182. chg.architecture = multivalues["architecture"]
  183. chg.version = self.changes["version"]
  184. chg.distribution = multivalues["distribution"]
  185. chg.urgency = self.changes["urgency"]
  186. chg.maintainer = self.changes["maintainer"]
  187. chg.fingerprint = self.changes["fingerprint"]
  188. chg.changedby = self.changes["changed-by"]
  189. chg.date = self.changes["date"]
  190. session.add(chg)
  191. files = []
  192. for chg_fn, entry in self.files.items():
  193. try:
  194. f = open(os.path.join(dirpath, chg_fn))
  195. cpf = ChangePendingFile()
  196. cpf.filename = chg_fn
  197. cpf.size = entry['size']
  198. cpf.md5sum = entry['md5sum']
  199. if 'sha1sum' in entry:
  200. cpf.sha1sum = entry['sha1sum']
  201. else:
  202. f.seek(0)
  203. cpf.sha1sum = apt_pkg.sha1sum(f)
  204. if 'sha256sum' in entry:
  205. cpf.sha256sum = entry['sha256sum']
  206. else:
  207. f.seek(0)
  208. cpf.sha256sum = apt_pkg.sha256sum(f)
  209. session.add(cpf)
  210. files.append(cpf)
  211. f.close()
  212. except IOError:
  213. # Can't find the file, try to look it up in the pool
  214. poolfile = self.__get_file_from_pool(chg_fn, entry, session)
  215. if poolfile:
  216. chg.poolfiles.append(poolfile)
  217. chg.files = files
  218. # Add files referenced in .dsc, but not included in .changes
  219. for name, entry in self.dsc_files.items():
  220. if name in self.files:
  221. continue
  222. entry['source'] = self.changes['source']
  223. poolfile = self.__get_file_from_pool(name, entry, session, logger)
  224. if poolfile:
  225. chg.poolfiles.append(poolfile)
  226. session.commit()
  227. chg = session.query(DBChange).filter_by(changesname=self.changes_file).one()
  228. return chg
  229. def unknown_files_fields(self, name):
  230. return sorted(list(set(self.files[name].keys()) -
  231. set(CHANGESFIELDS_FILES)))
  232. def unknown_changes_fields(self):
  233. return sorted(list(set(self.changes.keys()) -
  234. set(CHANGESFIELDS_MANDATORY + CHANGESFIELDS_OPTIONAL)))
  235. def unknown_dsc_fields(self):
  236. return sorted(list(set(self.dsc.keys()) -
  237. set(CHANGESFIELDS_DSC)))
  238. def unknown_dsc_files_fields(self, name):
  239. return sorted(list(set(self.dsc_files[name].keys()) -
  240. set(CHANGESFIELDS_DSCFILES_MANDATORY + CHANGESFIELDS_DSCFILES_OPTIONAL)))
  241. def str_files(self):
  242. r = []
  243. for name, entry in self.files.items():
  244. r.append(" %s:" % (name))
  245. for i in CHANGESFIELDS_FILES:
  246. if i in entry:
  247. r.append(" %s: %s" % (i.capitalize(), entry[i]))
  248. xfields = self.unknown_files_fields(name)
  249. if len(xfields) > 0:
  250. r.append("files[%s] still has following unrecognised keys: %s" % (name, ", ".join(xfields)))
  251. return r
  252. def str_changes(self):
  253. r = []
  254. for i in CHANGESFIELDS_MANDATORY:
  255. val = self.changes[i]
  256. if isinstance(val, list):
  257. val = " ".join(val)
  258. elif isinstance(val, dict):
  259. val = " ".join(val.keys())
  260. r.append(' %s: %s' % (i.capitalize(), val))
  261. for i in CHANGESFIELDS_OPTIONAL:
  262. if i in self.changes:
  263. r.append(' %s: %s' % (i.capitalize(), self.changes[i]))
  264. xfields = self.unknown_changes_fields()
  265. if len(xfields) > 0:
  266. r.append("Warning: changes still has the following unrecognised fields: %s" % ", ".join(xfields))
  267. return r
  268. def str_dsc(self):
  269. r = []
  270. for i in CHANGESFIELDS_DSC:
  271. if i in self.dsc:
  272. r.append(' %s: %s' % (i.capitalize(), self.dsc[i]))
  273. xfields = self.unknown_dsc_fields()
  274. if len(xfields) > 0:
  275. r.append("Warning: dsc still has the following unrecognised fields: %s" % ", ".join(xfields))
  276. return r
  277. def str_dsc_files(self):
  278. r = []
  279. for name, entry in self.dsc_files.items():
  280. r.append(" %s:" % (name))
  281. for i in CHANGESFIELDS_DSCFILES_MANDATORY:
  282. r.append(" %s: %s" % (i.capitalize(), entry[i]))
  283. for i in CHANGESFIELDS_DSCFILES_OPTIONAL:
  284. if i in entry:
  285. r.append(" %s: %s" % (i.capitalize(), entry[i]))
  286. xfields = self.unknown_dsc_files_fields(name)
  287. if len(xfields) > 0:
  288. r.append("dsc_files[%s] still has following unrecognised keys: %s" % (name, ", ".join(xfields)))
  289. return r
  290. def __str__(self):
  291. r = []
  292. r.append(" Changes:")
  293. r += self.str_changes()
  294. r.append("")
  295. r.append(" Dsc:")
  296. r += self.str_dsc()
  297. r.append("")
  298. r.append(" Files:")
  299. r += self.str_files()
  300. r.append("")
  301. r.append(" Dsc Files:")
  302. r += self.str_dsc_files()
  303. return "\n".join(r)
  304. __all__.append('Changes')