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: edit_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", "edit-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)
189 prompt = "Broken edit: [E]dit again, Drop changes?"
191 while prompt.find(answer) == -1:
192 answer = daklib.utils.our_raw_input(prompt)
195 answer = answer[:1].upper()
201 print "OK, discarding changes"
204 # No problems in loading the new file, jump out of the while loop
207 # We seem to be done and also have a working file. Copy over.
208 write_transitions_from_file(edit_file)
211 # Before we finish print out transition info again
212 print "\n\n------------------------------------------------------------------------"
213 print "Edit done, file saved, currently defined transitions:\n"
214 transition_info(load_transitions(trans_file))
216 ################################################################################
218 def check_transitions(transitions):
221 # Now look through all defined transitions
222 for trans in transitions:
223 t = transitions[trans]
227 # Will be None if nothing is in testing.
228 current = daklib.database.get_suite_version(source, "testing")
230 print_info(trans, source, expected, t["rm"], t["reason"], t["packages"])
233 # No package in testing
234 print "Transition source %s not in testing, transition still ongoing." % (source)
236 compare = apt_pkg.VersionCompare(current, expected)
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)
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)
246 print "-------------------------------------------------------------------------"
249 prompt = "Removing: "
250 for remove in to_remove:
254 prompt += " Commit Changes? (y/N)"
257 if Options["no-action"]:
260 answer = daklib.utils.our_raw_input(prompt).lower()
266 print "Not committing changes"
270 for remove in to_remove:
271 del transitions[remove]
273 edit_file = temp_transitions_file(transitions)
274 write_transitions_from_file(edit_file)
278 print "WTF are you typing?"
281 ################################################################################
283 def print_info(trans, source, expected, rm, reason, packages):
285 Looking at transition: %s
290 Blocked Packages (total: %d): %s
291 """ % (trans, source, expected, rm, reason, len(packages), ", ".join(packages))
294 ################################################################################
296 def transition_info(transitions):
297 for trans in transitions:
298 t = transitions[trans]
302 # Will be None if nothing is in testing.
303 current = daklib.database.get_suite_version(source, "testing")
305 print_info(trans, source, expected, t["rm"], t["reason"], t["packages"])
308 # No package in testing
309 print "Transition source %s not in testing, transition still ongoing." % (source)
311 compare = apt_pkg.VersionCompare(current, expected)
312 print "Apt compare says: %s" % (compare)
314 # This is still valid, the current version in database is older than
315 # the new version we wait for
316 print "This transition is still ongoing, we currently have version %s" % (current)
318 print "This transition is over, the target package reached testing, should be removed"
319 print "%s wanted version: %s, has %s" % (source, expected, current)
320 print "-------------------------------------------------------------------------"
322 ################################################################################
329 # Check if there is a file defined (and existant)
330 transpath = Cnf.get("Dinstall::Reject::ReleaseTransitions", "")
332 daklib.utils.warn("Dinstall::Reject::ReleaseTransitions not defined")
334 if not os.path.exists(transpath):
335 daklib.utils.warn("ReleaseTransitions file, %s, not found." %
336 (Cnf["Dinstall::Reject::ReleaseTransitions"]))
339 if Options["import"]:
341 write_transitions_from_file(Options["import"])
342 except ParseException, m:
347 # Parse the yaml file
348 transitions = load_transitions(transpath)
349 if transitions == None:
350 # Something very broken with the transitions, exit
351 daklib.utils.warn("Could not parse existing transitions file. Aborting.")
355 # Let's edit the transitions file
357 elif Options["check"]:
358 # Check and remove outdated transitions
359 check_transitions(transitions)
361 # Output information about the currently defined transitions.
362 print "Currently defined transitions:"
363 transition_info(transitions)
367 ################################################################################
369 if __name__ == '__main__':