3 # Display, edit and check the release manager's transition file.
4 # Copyright (C) 2008 Joerg Jaspert <joerg@debian.org>
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 ################################################################################
22 # <elmo> if klecker.d.o died, I swear to god, I'm going to migrate to gentoo.
24 ################################################################################
26 import os, pg, sys, time, errno, fcntl, tempfile
28 import daklib.database
37 ################################################################################
40 global Cnf, Options, projectB
44 Cnf = daklib.utils.get_conf()
46 Arguments = [('h',"help","Edit-Transitions::Options::Help"),
47 ('e',"edit","Edit-Transitions::Options::Edit"),
48 ('i',"import","Edit-Transitions::Options::Import", "HasArg"),
49 ('c',"check","Edit-Transitions::Options::Check"),
50 ('S',"use-sudo","Edit-Transitions::Options::Sudo"),
51 ('n',"no-action","Edit-Transitions::Options::No-Action")]
53 for i in ["help", "no-action", "edit", "import", "check", "sudo"]:
54 if not Cnf.has_key("Edit-Transitions::Options::%s" % (i)):
55 Cnf["Edit-Transitions::Options::%s" % (i)] = ""
57 apt_pkg.ParseCommandLine(Cnf, Arguments, sys.argv)
59 Options = Cnf.SubTree("Edit-Transitions::Options")
61 projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
62 daklib.database.init(Cnf, projectB)
67 ################################################################################
69 def usage (exit_code=0):
70 print """Usage: transitions [OPTION]...
71 Update and check the release managers transition file.
76 -h, --help show this help and exit.
77 -e, --edit edit the transitions file
78 -i, --import <file> check and import transitions from file
79 -c, --check check the transitions file, remove outdated entries
80 -S, --sudo use sudo to update transitions file
81 -n, --no-action don't do anything"""
85 ################################################################################
87 def load_transitions(trans_file):
89 sourcefile = file(trans_file, 'r')
90 sourcecontent = sourcefile.read()
92 trans = syck.load(sourcecontent)
93 except syck.error, msg:
94 # Someone fucked it up
95 print "ERROR: %s" % (msg)
97 # could do further validation here
100 ################################################################################
103 for retry in range(10):
104 lock_fd = os.open(file, os.O_RDWR | os.O_CREAT)
106 fcntl.lockf(lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
109 if errno.errorcode[e.errno] == 'EACCES' or errno.errorcode[e.errno] == 'EEXIST':
110 print "Unable to get lock for %s (try %d of 10)" % \
116 daklib.utils.fubar("Couldn't obtain lock for %s." % (lockfile))
118 ################################################################################
120 def write_transitions(from_trans):
121 """Update the active transitions file safely.
122 This function takes a parsed input file (which avoids invalid
123 files or files that may be be modified while the function is
124 active), and ensure the transitions file is updated atomically
127 trans_file = Cnf["Dinstall::Reject::ReleaseTransitions"]
128 trans_temp = trans_file + ".tmp"
130 trans_lock = lock_file(trans_file)
131 temp_lock = lock_file(trans_temp)
133 destfile = file(trans_temp, 'w')
134 syck.dump(from_trans, destfile)
137 os.rename(trans_temp, trans_file)
141 ################################################################################
143 class ParseException(Exception):
146 def write_transitions_from_file(from_file):
147 """We have a file we think is valid; if we're using sudo, we invoke it
148 here, otherwise we just parse the file and call write_transitions"""
151 os.spawnl(os.P_WAIT, "/usr/bin/sudo", "/usr/bin/sudo", "-u", "dak", "-H",
152 "/usr/local/bin/dak", "transitions", "--import", from_file)
154 trans = load_transitions(from_file)
156 raise ParseException, "Unparsable transitions file %s" % (file)
157 write_transitions(trans)
159 ################################################################################
161 def temp_transitions_file(transitions):
162 # NB: file is unlinked by caller, but fd is never actually closed.
164 (fd, path) = tempfile.mkstemp("","transitions")
166 syck.dump(transitions, f)
169 ################################################################################
171 def edit_transitions():
172 trans_file = Cnf["Dinstall::Reject::ReleaseTransitions"]
173 edit_file = temp_transitions_file(load_transitions(trans_file))
175 editor = os.environ.get("EDITOR", "vi")
178 result = os.system("%s %s" % (editor, edit_file))
181 daklib.utils.fubar("%s invocation failed for %s, not removing tempfile." % (editor, edit_file))
183 # Now try to load the new file
184 test = load_transitions(edit_file)
188 print "Edit was unparsable."
189 prompt = "[E]dit again, Drop changes?"
192 print "Edit looks okay.\n"
193 print "------------------------------------------------------------------------"
194 transition_info(test)
196 prompt = "[S]ave, Edit again, Drop changes?"
200 while prompt.find(answer) == -1:
201 answer = daklib.utils.our_raw_input(prompt)
204 answer = answer[:1].upper()
210 print "OK, discarding changes"
216 print "You pressed something you shouldn't have :("
219 # We seem to be done and also have a working file. Copy over.
220 write_transitions_from_file(edit_file)
223 print "Transitions file updated."
225 ################################################################################
227 def check_transitions(transitions):
230 # Now look through all defined transitions
231 for trans in transitions:
232 t = transitions[trans]
236 # Will be None if nothing is in testing.
237 current = daklib.database.get_suite_version(source, "testing")
239 print_info(trans, source, expected, t["rm"], t["reason"], t["packages"])
242 # No package in testing
243 print "Transition source %s not in testing, transition still ongoing." % (source)
245 compare = apt_pkg.VersionCompare(current, expected)
247 # This is still valid, the current version in database is older than
248 # the new version we wait for
249 print "This transition is still ongoing, we currently have version %s" % (current)
251 print "REMOVE: This transition is over, the target package reached testing. REMOVE"
252 print "%s wanted version: %s, has %s" % (source, expected, current)
253 to_remove.append(trans)
255 print "-------------------------------------------------------------------------"
258 prompt = "Removing: "
259 for remove in to_remove:
263 prompt += " Commit Changes? (y/N)"
266 if Options["no-action"]:
269 answer = daklib.utils.our_raw_input(prompt).lower()
275 print "Not committing changes"
279 for remove in to_remove:
280 del transitions[remove]
282 edit_file = temp_transitions_file(transitions)
283 write_transitions_from_file(edit_file)
287 print "WTF are you typing?"
290 ################################################################################
292 def print_info(trans, source, expected, rm, reason, packages):
293 print """Looking at transition: %s
298 Blocked Packages (total: %d): %s
299 """ % (trans, source, expected, rm, reason, len(packages), ", ".join(packages))
302 ################################################################################
304 def transition_info(transitions):
305 for trans in transitions:
306 t = transitions[trans]
310 # Will be None if nothing is in testing.
311 current = daklib.database.get_suite_version(source, "testing")
313 print_info(trans, source, expected, t["rm"], t["reason"], t["packages"])
316 # No package in testing
317 print "Transition source %s not in testing, transition still ongoing." % (source)
319 compare = apt_pkg.VersionCompare(current, expected)
320 print "Apt compare says: %s" % (compare)
322 # This is still valid, the current version in database is older than
323 # the new version we wait for
324 print "This transition is still ongoing, we currently have version %s" % (current)
326 print "This transition is over, the target package reached testing, should be removed"
327 print "%s wanted version: %s, has %s" % (source, expected, current)
328 print "-------------------------------------------------------------------------"
330 ################################################################################
337 # Check if there is a file defined (and existant)
338 transpath = Cnf.get("Dinstall::Reject::ReleaseTransitions", "")
340 daklib.utils.warn("Dinstall::Reject::ReleaseTransitions not defined")
342 if not os.path.exists(transpath):
343 daklib.utils.warn("ReleaseTransitions file, %s, not found." %
344 (Cnf["Dinstall::Reject::ReleaseTransitions"]))
347 if Options["import"]:
349 write_transitions_from_file(Options["import"])
350 except ParseException, m:
355 # Parse the yaml file
356 transitions = load_transitions(transpath)
357 if transitions == None:
358 # Something very broken with the transitions, exit
359 daklib.utils.warn("Could not parse existing transitions file. Aborting.")
363 # Let's edit the transitions file
365 elif Options["check"]:
366 # Check and remove outdated transitions
367 check_transitions(transitions)
369 # Output information about the currently defined transitions.
370 print "Currently defined transitions:"
371 transition_info(transitions)
375 ################################################################################
377 if __name__ == '__main__':