]> git.decadent.org.uk Git - dak.git/blob - daklib/changes.py
make decode_dot_dak use Changes class
[dak.git] / daklib / changes.py
1 #!/usr/bin/env python
2 # vim:set et sw=4:
3
4 """
5 Changes class for dak
6
7 @contact: Debian FTP Master <ftpmaster@debian.org>
8 @copyright: 2001 - 2006 James Troup <james@nocrew.org>
9 @copyright: 2009  Joerg Jaspert <joerg@debian.org>
10 @copyright: 2009  Mark Hymers <mhy@debian.org>
11 @license: GNU General Public License version 2 or later
12 """
13
14 # This program is free software; you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation; either version 2 of the License, or
17 # (at your option) any later version.
18
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22 # GNU General Public License for more details.
23
24 # You should have received a copy of the GNU General Public License
25 # along with this program; if not, write to the Free Software
26 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
27
28 ###############################################################################
29
30 import os
31 import stat
32 from cPickle import Unpickler, Pickler
33 from errno import EPERM
34
35 from apt_inst import debExtractControl
36 from apt_pkg import ParseSection
37
38 from utils import open_file, fubar, poolify
39
40 ###############################################################################
41
42 __all__ = []
43
44 ###############################################################################
45
46 CHANGESFIELDS_MANDATORY = [ "distribution", "source", "architecture",
47         "version", "maintainer", "urgency", "fingerprint", "changedby822",
48         "changedby2047", "changedbyname", "maintainer822", "maintainer2047",
49         "maintainername", "maintaineremail", "closes", "changes" ]
50
51 __all__.append('CHANGESFIELDS_MANDATORY')
52
53 CHANGESFIELDS_OPTIONAL = [ "changed-by", "filecontents", "format",
54         "process-new note", "adv id", "distribution-version", "sponsoremail" ]
55
56 __all__.append('CHANGESFIELDS_OPTIONAL')
57
58 CHANGESFIELDS_FILES = [ "package", "version", "architecture", "type", "size",
59         "md5sum", "sha1sum", "sha256sum", "component", "location id",
60         "source package", "source version", "maintainer", "dbtype", "files id",
61         "new", "section", "priority", "othercomponents", "pool name",
62         "original component" ]
63
64 __all__.append('CHANGESFIELDS_FILES')
65
66 CHANGESFIELDS_DSC = [ "source", "version", "maintainer", "fingerprint",
67         "uploaders", "bts changelog", "dm-upload-allowed" ]
68
69 __all__.append('CHANGESFIELDS_DSC')
70
71 CHANGESFIELDS_DSCFILES_MANDATORY = [ "size", "md5sum" ]
72
73 __all__.append('CHANGESFIELDS_DSCFILES_MANDATORY')
74
75 CHANGESFIELDS_DSCFILES_OPTIONAL = [ "files id" ]
76
77 __all__.append('CHANGESFIELDS_DSCFILES_OPTIONAL')
78
79 ###############################################################################
80
81 class Changes(object):
82     """ Convenience wrapper to carry around all the package information """
83
84     def __init__(self, **kwds):
85         self.reset()
86
87     def reset(self):
88         self.changes_file = ""
89
90         self.changes = {}
91         self.dsc = {}
92         self.files = {}
93         self.dsc_files = {}
94
95         self.orig_tar_id = None
96         self.orig_tar_location = ""
97         self.orig_tar_gz = None
98
99     def file_summary(self):
100         # changes["distribution"] may not exist in corner cases
101         # (e.g. unreadable changes files)
102         if not self.changes.has_key("distribution") or not \
103                isinstance(self.changes["distribution"], dict):
104             self.changes["distribution"] = {}
105
106         byhand = False
107         new = False
108         summary = ""
109         override_summary = ""
110
111         for name, entry in sorted(self.files.items()):
112             if entry.has_key("byhand"):
113                 byhand = True
114                 summary += name + " byhand\n"
115
116             elif entry.has_key("new"):
117                 new = True
118                 summary += "(new) %s %s %s\n" % (name, entry["priority"], entry["section"])
119
120                 if entry.has_key("othercomponents"):
121                     summary += "WARNING: Already present in %s distribution.\n" % (entry["othercomponents"])
122
123                 if entry["type"] == "deb":
124                     deb_fh = open_file(name)
125                     summary += ParseSection(debExtractControl(deb_fh))["Description"] + '\n'
126                     deb_fh.close()
127
128             else:
129                 entry["pool name"] = poolify(self.changes.get("source", ""), entry["component"])
130                 destination = entry["pool name"] + name
131                 summary += name + "\n  to " + destination + "\n"
132
133                 if not entry.has_key("type"):
134                     entry["type"] = "unknown"
135
136                 if entry["type"] in ["deb", "udeb", "dsc"]:
137                     # (queue/unchecked), there we have override entries already, use them
138                     # (process-new), there we dont have override entries, use the newly generated ones.
139                     override_prio = entry.get("override priority", entry["priority"])
140                     override_sect = entry.get("override section", entry["section"])
141                     override_summary += "%s - %s %s\n" % (name, override_prio, override_sect)
142
143         return (byhand, new, summary, override_summary)
144
145     def check_override(self):
146         """
147         Checks override entries for validity.
148
149         Returns an empty string if there are no problems
150         or the text of a warning if there are
151         """
152
153         conf = Config()
154         summary = ""
155
156         # Abandon the check if it's a non-sourceful upload
157         if not self.changes["architecture"].has_key("source"):
158             return summary
159
160         for name, entry in sorted(self.files.items()):
161             if not entry.has_key("new") and entry["type"] == "deb":
162                 if entry["section"] != "-":
163                     if entry["section"].lower() != entry["override section"].lower():
164                         summary += "%s: package says section is %s, override says %s.\n" % (name,
165                                                                                             entry["section"],
166                                                                                             entry["override section"])
167
168                 if entry["priority"] != "-":
169                     if entry["priority"] != entry["override_priority"]:
170                         summary += "%s: package says priority is %s, override says %s.\n" % (name,
171                                                                                              entry["priority"],
172                                                                                              entry["override priority"])
173
174         return summary
175
176
177     def load_dot_dak(self, changesfile):
178         """
179         Update ourself by reading a previously created cPickle .dak dumpfile.
180         """
181
182         self.changes_file = changesfile
183         dump_filename = self.changes_file[:-8]+".dak"
184         dump_file = open_file(dump_filename)
185
186         p = Unpickler(dump_file)
187
188         self.changes.update(p.load())
189         self.dsc.update(p.load())
190         self.files.update(p.load())
191         self.dsc_files.update(p.load())
192
193         self.orig_tar_id = p.load()
194         self.orig_tar_location = p.load()
195
196         dump_file.close()
197
198     def sanitised_files(self):
199         ret = {}
200         for name, entry in self.files.items():
201             ret[name] = {}
202             for i in CHANGESFIELDS_FILES:
203                 if entry.has_key(i):
204                     ret[name][i] = entry[i]
205
206         return ret
207
208     def sanitised_changes(self):
209         ret = {}
210         # Mandatory changes fields
211         for i in CHANGESFIELDS_MANDATORY:
212             ret[i] = self.changes[i]
213
214         # Optional changes fields
215         for i in CHANGESFIELDS_OPTIONAL:
216             if self.changes.has_key(i):
217                 ret[i] = self.changes[i]
218
219         return ret
220
221     def sanitised_dsc(self):
222         ret = {}
223         for i in CHANGESFIELDS_DSC:
224             if self.dsc.has_key(i):
225                 ret[i] = self.dsc[i]
226
227         return ret
228
229     def sanitised_dsc_files(self):
230         ret = {}
231         for name, entry in self.dsc_files.items():
232             ret[name] = {}
233             # Mandatory dsc_files fields
234             for i in CHANGESFIELDS_DSCFILES_MANDATORY:
235                 ret[name][i] = entry[i]
236
237             # Optional dsc_files fields
238             for i in CHANGESFIELDS_DSCFILES_OPTIONAL:
239                 if entry.has_key(i):
240                     ret[name][i] = entry[i]
241
242         return ret
243
244     def write_dot_dak(self, dest_dir):
245         """
246         Dump ourself into a cPickle file.
247
248         @type dest_dir: string
249         @param dest_dir: Path where the dumpfile should be stored
250
251         @note: This could just dump the dictionaries as is, but I'd like to avoid this so
252                there's some idea of what process-accepted & process-new use from
253                process-unchecked. (JT)
254
255         """
256
257         dump_filename = os.path.join(dest_dir, self.changes_file[:-8] + ".dak")
258         dump_file = open_file(dump_filename, 'w')
259
260         try:
261             os.chmod(dump_filename, 0664)
262         except OSError, e:
263             # chmod may fail when the dumpfile is not owned by the user
264             # invoking dak (like e.g. when NEW is processed by a member
265             # of ftpteam)
266             if e.errno == EPERM:
267                 perms = stat.S_IMODE(os.stat(dump_filename)[stat.ST_MODE])
268                 # security precaution, should never happen unless a weird
269                 # umask is set anywhere
270                 if perms & stat.S_IWOTH:
271                     fubar("%s is world writable and chmod failed." % \
272                         (dump_filename,))
273                 # ignore the failed chmod otherwise as the file should
274                 # already have the right privileges and is just, at worst,
275                 # unreadable for world
276             else:
277                 raise
278
279         p = Pickler(dump_file, 1)
280
281         p.dump(self.sanitised_changes())
282         p.dump(self.sanitised_dsc())
283         p.dump(self.sanitised_files())
284         p.dump(self.sanitised_dsc_files())
285         p.dump(self.orig_tar_id)
286         p.dump(self.orig_tar_location)
287
288         dump_file.close()
289
290     def unknown_files_fields(self, name):
291         return sorted(list( set(self.files[name].keys()) -
292                             set(CHANGESFIELDS_FILES)))
293
294     def unknown_changes_fields(self):
295         return sorted(list( set(self.changes.keys()) -
296                             set(CHANGESFIELDS_MANDATORY + CHANGESFIELDS_OPTIONAL)))
297
298     def unknown_dsc_fields(self):
299         return sorted(list( set(self.dsc.keys()) -
300                             set(CHANGESFIELDS_DSC)))
301
302     def unknown_dsc_files_fields(self, name):
303         return sorted(list( set(self.dsc_files[name].keys()) -
304                             set(CHANGESFIELDS_DSCFILES_MANDATORY + CHANGESFIELDS_DSCFILES_OPTIONAL)))
305
306     def str_files(self):
307         r = []
308         for name, entry in self.files.items():
309             r.append("  %s:" % (name))
310             for i in CHANGESFIELDS_FILES:
311                 if entry.has_key(i):
312                     r.append("   %s: %s" % (i.capitalize(), entry[i]))
313             xfields = self.unknown_files_fields(name)
314             if len(xfields) > 0:
315                 r.append("files[%s] still has following unrecognised keys: %s" % (name, ", ".join(xfields)))
316
317         return r
318
319     def str_changes(self):
320         r = []
321         for i in CHANGESFIELDS_MANDATORY:
322             val = self.changes[i]
323             if isinstance(val, list):
324                 val = " ".join(val)
325             elif isinstance(val, dict):
326                 val = " ".join(val.keys())
327             r.append('  %s: %s' % (i.capitalize(), val))
328
329         for i in CHANGESFIELDS_OPTIONAL:
330             if self.changes.has_key(i):
331                 r.append('  %s: %s' % (i.capitalize(), self.changes[i]))
332
333         xfields = self.unknown_changes_fields()
334         if len(xfields) > 0:
335             r.append("Warning: changes still has the following unrecognised fields: %s" % ", ".join(xfields))
336
337         return r
338
339     def str_dsc(self):
340         r = []
341         for i in CHANGESFIELDS_DSC:
342             if self.dsc.has_key(i):
343                 r.append('  %s: %s' % (i.capitalize(), self.dsc[i]))
344
345         xfields = self.unknown_dsc_fields()
346         if len(xfields) > 0:
347             r.append("Warning: dsc still has the following unrecognised fields: %s" % ", ".join(xfields))
348
349         return r
350
351     def str_dsc_files(self):
352         r = []
353         for name, entry in self.dsc_files.items():
354             r.append("  %s:" % (name))
355             for i in CHANGESFIELDS_DSCFILES_MANDATORY:
356                 r.append("   %s: %s" % (i.capitalize(), entry[i]))
357             for i in CHANGESFIELDS_DSCFILES_OPTIONAL:
358                 if entry.has_key(i):
359                     r.append("   %s: %s" % (i.capitalize(), entry[i]))
360             xfields = self.unknown_dsc_files_fields(name)
361             if len(xfields) > 0:
362                 r.append("dsc_files[%s] still has following unrecognised keys: %s" % (name, ", ".join(xfields)))
363
364         return r
365
366     def __str__(self):
367         r = []
368
369         r.append(" Changes:")
370         r += self.str_changes()
371
372         r.append("")
373
374         r.append(" Dsc:")
375         r += self.str_dsc()
376
377         r.append("")
378
379         r.append(" Files:")
380         r += self.str_files()
381
382         r.append("")
383
384         r.append(" Dsc Files:")
385         r += self.str_dsc_files()
386
387         return "\n".join(r)
388
389 __all__.append('Changes')