]> git.decadent.org.uk Git - dak.git/blob - dak/edit_transitions.py
merge from joerg
[dak.git] / dak / edit_transitions.py
1 #!/usr/bin/env python
2
3 # Edit and then check the release managers transition file for correctness
4 # and outdated transitions
5 # Copyright (C) 2008 Joerg Jaspert <joerg@debian.org>
6
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2 of the License, or
10 # (at your option) any later version.
11
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20
21 ################################################################################
22
23 # <elmo> if klecker.d.o died, I swear to god, I'm going to migrate to gentoo.
24
25 ################################################################################
26
27 import os, pg, sys, time, errno
28 import apt_pkg
29 import daklib.database
30 import daklib.utils
31 import syck
32
33 # Globals
34 Cnf = None
35 Options = None
36 projectB = None
37
38 ################################################################################
39
40 def init():
41     global Cnf, Options, projectB
42
43     apt_pkg.init()
44
45     Cnf = daklib.utils.get_conf()
46
47     Arguments = [('h',"help","Edit-Transitions::Options::Help"),
48                  ('e',"edit","Edit-Transitions::Options::Edit"),
49                  ('c',"check","Edit-Transitions::Options::check"),
50                  ('n',"no-action","Edit-Transitions::Options::No-Action")]
51
52     for i in ["help", "no-action", "edit", "check"]:
53         if not Cnf.has_key("Edit-Transitions::Options::%s" % (i)):
54             Cnf["Edit-Transitions::Options::%s" % (i)] = ""
55
56     apt_pkg.ParseCommandLine(Cnf, Arguments, sys.argv)
57
58     Options = Cnf.SubTree("Edit-Transitions::Options")
59
60     projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
61     daklib.database.init(Cnf, projectB)
62     
63     if Options["help"]:
64         usage()
65
66 ################################################################################
67
68 def usage (exit_code=0):
69     print """Usage: edit_transitions [OPTION]...
70   Check the release managers transition file for correctness and outdated transitions
71   -h, --help                show this help and exit.
72   -e, --edit                edit the transitions file
73   -c, --check               check the transitions file, remove outdated entries
74   -n, --no-action           don't do anything
75
76   Called without an option this tool will check the transition file for outdated
77   transitions and remove them."""
78     sys.exit(exit_code)
79
80 ################################################################################
81
82 def lock_file(lockfile):
83     retry = 0
84     while retry < 10:
85         try:
86             lock_fd = os.open(lockfile, os.O_RDONLY | os.O_CREAT | os.O_EXCL)
87             retry = 10
88         except OSError, e:
89             if errno.errorcode[e.errno] == 'EACCES' or errno.errorcode[e.errno] == 'EEXIST':
90                 retry += 1
91                 if (retry >= 10):
92                     daklib.utils.fubar("Couldn't obtain lock for %s." % (lockfile) )
93                 else:
94                     print("Unable to get lock for %s (try %d of 10)" % (lockfile, retry) )
95                     time.sleep(60)
96             else:
97                 raise
98
99
100 ################################################################################
101
102 def edit_transitions():
103     trans_file = Cnf["Dinstall::Reject::ReleaseTransitions"]
104
105     lockfile="/tmp/transitions.lock"
106     lock_file(lockfile)
107
108     tempfile = "./%s.transition.tmp" % (os.getpid() )
109
110     daklib.utils.copy(trans_file, tempfile)
111
112     editor = os.environ.get("EDITOR", "vi")
113
114     while True:
115         result = os.system("%s %s" % (editor, tempfile))
116         if result != 0:
117             os.unlink(tempfile)
118             os.unlink(lockfile)
119             daklib.utils.fubar("%s invocation failed for %s, not removing tempfile." % (editor, tempfile))
120     
121         # Now try to load the new file
122         test = load_transitions(tempfile)
123
124         if test == None:
125             # Edit is broken
126             answer = "XXX"
127             prompt = "Broken edit: [E]dit again, Drop changes?"
128
129             while prompt.find(answer) == -1:
130                 answer = daklib.utils.our_raw_input(prompt)
131                 if answer == "":
132                     answer = "E"
133                 answer = answer[:1].upper()
134
135             if answer == 'E':
136                 continue
137             elif answer == 'D':
138                 os.unlink(tempfile)
139                 os.unlink(lockfile)
140                 print "OK, discarding changes"
141                 sys.exit(0)
142         else:
143             # No problems in loading the new file, jump out of the while loop
144             break
145
146     # We seem to be done and also have a working file. Copy over.
147     # We are not using daklib.utils.copy here, as we use sudo to get this file copied, to
148     # limit the rights needed to edit transitions
149     os.spawnl(os.P_WAIT, "/usr/bin/sudo", "/usr/bin/sudo", "-u", "dak", "-H", 
150               "cp", tempfile, trans_file)
151
152     os.unlink(tempfile)
153     os.unlink(lockfile)
154
155     # Before we finish print out transition info again
156     print "\n\n------------------------------------------------------------------------"
157     print "Edit done, file saved, currently defined transitions:\n"
158     transitions = load_transitions(Cnf["Dinstall::Reject::ReleaseTransitions"])
159     transition_info(transitions)
160
161 ################################################################################
162
163 def load_transitions(trans_file):
164     # Parse the yaml file
165     sourcefile = file(trans_file, 'r')
166     sourcecontent = sourcefile.read()
167     try:
168         trans = syck.load(sourcecontent)
169     except syck.error, msg:
170         # Someone fucked it up
171         print "ERROR: %s" % (msg)
172         return None
173     return trans
174
175 ################################################################################
176
177 def print_info(trans, source, expected, rm, reason, packages):
178         print """
179 Looking at transition: %s
180  Source:      %s
181  New Version: %s
182  Responsible: %s
183  Description: %s
184  Blocked Packages (total: %d): %s
185 """ % (trans, source, expected, rm, reason, len(packages), ", ".join(packages))
186         return
187
188 ################################################################################
189
190 def transition_info(transitions):
191     for trans in transitions:
192         t = transitions[trans]
193         source = t["source"]
194         expected = t["new"]
195
196         # Will be None if nothing is in testing.
197         current = daklib.database.get_suite_version(source, "testing")
198
199         print_info(trans, source, expected, t["rm"], t["reason"], t["packages"])
200
201         if current == None:
202             # No package in testing
203             print "Transition source %s not in testing, transition still ongoing." % (source)
204         else:
205             compare = apt_pkg.VersionCompare(current, expected)
206             print "Apt compare says: %s" % (compare)
207             if compare < 0:
208                 # This is still valid, the current version in database is older than
209                 # the new version we wait for
210                 print "This transition is still ongoing, we currently have version %s" % (current)
211             else:
212                 print "This transition is over, the target package reached testing, should be removed"
213                 print "%s wanted version: %s, has %s" % (source, expected, current)
214         print "-------------------------------------------------------------------------"
215
216 ################################################################################
217
218 def check_transitions(transitions):
219     to_dump = 0
220     to_remove = []
221     # Now look through all defined transitions
222     for trans in transitions:
223         t = transitions[trans]
224         source = t["source"]
225         expected = t["new"]
226
227         # Will be None if nothing is in testing.
228         current = daklib.database.get_suite_version(source, "testing")
229
230         print_info(trans, source, expected, t["rm"], t["reason"], t["packages"])
231
232         if current == None:
233             # No package in testing
234             print "Transition source %s not in testing, transition still ongoing." % (source)
235         else:
236             compare = apt_pkg.VersionCompare(current, expected)
237             if compare < 0:
238                 # This is still valid, the current version in database is older than
239                 # the new version we wait for
240                 print "This transition is still ongoing, we currently have version %s" % (current)
241             else:
242                 print "REMOVE: This transition is over, the target package reached testing. REMOVE"
243                 print "%s wanted version: %s, has %s" % (source, expected, current)
244                 to_remove.append(trans)
245                 to_dump = 1
246         print "-------------------------------------------------------------------------"
247
248     if to_dump:
249         prompt = "Removing: "
250         for remove in to_remove:
251             prompt += remove
252             prompt += ","
253
254         prompt += " Commit Changes? (y/N)"
255         answer = ""
256
257         if Options["no-action"]:
258             answer="n"
259         else:
260             answer = daklib.utils.our_raw_input(prompt).lower()
261
262         if answer == "":
263             answer = "n"
264
265         if answer == 'n':
266             print "Not committing changes"
267             sys.exit(0)
268         elif answer == 'y':
269             print "Committing"
270             for remove in to_remove:
271                 del transitions[remove]
272
273             lockfile="/tmp/transitions.lock"
274             lock_file(lockfile)
275             tempfile = "./%s.transition.tmp" % (os.getpid() )
276
277             destfile = file(tempfile, 'w')
278             syck.dump(transitions, destfile)
279             destfile.close()
280
281             os.spawnl(os.P_WAIT, "/usr/bin/sudo", "/usr/bin/sudo", "-u", "dak", "-H", 
282                       "cp", tempfile, Cnf["Dinstall::Reject::ReleaseTransitions"])
283
284             os.unlink(tempfile)
285             os.unlink(lockfile)
286             print "Done"
287         else:
288             print "WTF are you typing?"
289             sys.exit(0)
290
291 ################################################################################
292
293 def main():
294     global Cnf
295
296     init()
297     
298     # Only check if there is a file defined (and existant) with checks. It's a little bit
299     # specific to Debian, not much use for others, so return early there.
300     if not Cnf.has_key("Dinstall::Reject::ReleaseTransitions") or not os.path.exists("%s" % (Cnf["Dinstall::Reject::ReleaseTransitions"])):
301         daklib.utils.warn("Dinstall::Reject::ReleaseTransitions not defined or file %s not existant." %
302                           (Cnf["Dinstall::Reject::ReleaseTransitions"]))
303         sys.exit(1)
304     
305     # Parse the yaml file
306     transitions = load_transitions(Cnf["Dinstall::Reject::ReleaseTransitions"])
307     if transitions == None:
308         # Something very broken with the transitions, exit
309         daklib.utils.warn("Not doing any work, someone fucked up the transitions file outside our control")
310         sys.exit(2)
311
312     if Options["edit"]:
313         # Output information about the currently defined transitions.
314         print "Currently defined transitions:"
315         transition_info(transitions)
316         daklib.utils.our_raw_input("Press enter to continue...")
317
318         # Lets edit the transitions file
319         edit_transitions()
320     elif Options["check"]:
321         # Check and remove outdated transitions
322         check_transitions(transitions)
323     else:
324         # Output information about the currently defined transitions.
325         transition_info(transitions)
326
327         # Nothing requested, doing nothing besides the above display of the transitions
328         sys.exit(0)
329     
330
331 ################################################################################
332
333 if __name__ == '__main__':
334     main()