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, pwd
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',"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")
62 whoamifull = pwd.getpwuid(whoami)
63 username = whoamifull[0]
65 print "Non-dak user: %s" % username
68 projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
69 daklib.database.init(Cnf, projectB)
74 ################################################################################
76 def usage (exit_code=0):
77 print """Usage: transitions [OPTION]...
78 Update and check the release managers transition file.
83 -h, --help show this help and exit.
84 -e, --edit edit the transitions file
85 -i, --import <file> check and import transitions from file
86 -c, --check check the transitions file, remove outdated entries
87 -S, --sudo use sudo to update transitions file
88 -n, --no-action don't do anything"""
92 ################################################################################
94 def load_transitions(trans_file):
96 sourcefile = file(trans_file, 'r')
97 sourcecontent = sourcefile.read()
99 trans = syck.load(sourcecontent)
100 except syck.error, msg:
101 # Someone fucked it up
102 print "ERROR: %s" % (msg)
104 # could do further validation here
107 ################################################################################
110 for retry in range(10):
111 lock_fd = os.open(file, os.O_RDWR | os.O_CREAT)
113 fcntl.lockf(lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
116 if errno.errorcode[e.errno] == 'EACCES' or errno.errorcode[e.errno] == 'EEXIST':
117 print "Unable to get lock for %s (try %d of 10)" % \
123 daklib.utils.fubar("Couldn't obtain lock for %s." % (lockfile))
125 ################################################################################
127 def write_transitions(from_trans):
128 """Update the active transitions file safely.
129 This function takes a parsed input file (which avoids invalid
130 files or files that may be be modified while the function is
131 active), and ensure the transitions file is updated atomically
134 trans_file = Cnf["Dinstall::Reject::ReleaseTransitions"]
135 trans_temp = trans_file + ".tmp"
137 trans_lock = lock_file(trans_file)
138 temp_lock = lock_file(trans_temp)
140 destfile = file(trans_temp, 'w')
141 syck.dump(from_trans, destfile)
144 os.rename(trans_temp, trans_file)
148 ################################################################################
150 class ParseException(Exception):
153 def write_transitions_from_file(from_file):
154 """We have a file we think is valid; if we're using sudo, we invoke it
155 here, otherwise we just parse the file and call write_transitions"""
158 os.spawnl(os.P_WAIT, "/usr/bin/sudo", "/usr/bin/sudo", "-u", "dak", "-H",
159 "/usr/local/bin/dak", "transitions", "--import", from_file)
161 trans = load_transitions(from_file)
163 raise ParseException, "Unparsable transitions file %s" % (file)
164 write_transitions(trans)
166 ################################################################################
168 def temp_transitions_file(transitions):
169 # NB: file is unlinked by caller, but fd is never actually closed.
171 (fd, path) = tempfile.mkstemp("","transitions")
173 syck.dump(transitions, f)
176 ################################################################################
178 def edit_transitions():
179 trans_file = Cnf["Dinstall::Reject::ReleaseTransitions"]
180 edit_file = temp_transitions_file(load_transitions(trans_file))
182 editor = os.environ.get("EDITOR", "vi")
185 result = os.system("%s %s" % (editor, edit_file))
188 daklib.utils.fubar("%s invocation failed for %s, not removing tempfile." % (editor, edit_file))
190 # Now try to load the new file
191 test = load_transitions(edit_file)
195 print "Edit was unparsable."
196 prompt = "[E]dit again, Drop changes?"
199 print "Edit looks okay.\n"
200 print "------------------------------------------------------------------------"
201 transition_info(test)
203 prompt = "[S]ave, Edit again, Drop changes?"
207 while prompt.find(answer) == -1:
208 answer = daklib.utils.our_raw_input(prompt)
211 answer = answer[:1].upper()
217 print "OK, discarding changes"
223 print "You pressed something you shouldn't have :("
226 # We seem to be done and also have a working file. Copy over.
227 write_transitions_from_file(edit_file)
230 print "Transitions file updated."
232 ################################################################################
234 def check_transitions(transitions):
237 # Now look through all defined transitions
238 for trans in transitions:
239 t = transitions[trans]
243 # Will be None if nothing is in testing.
244 current = daklib.database.get_suite_version(source, "testing")
246 print_info(trans, source, expected, t["rm"], t["reason"], t["packages"])
249 # No package in testing
250 print "Transition source %s not in testing, transition still ongoing." % (source)
252 compare = apt_pkg.VersionCompare(current, expected)
254 # This is still valid, the current version in database is older than
255 # the new version we wait for
256 print "This transition is still ongoing, we currently have version %s" % (current)
258 print "REMOVE: This transition is over, the target package reached testing. REMOVE"
259 print "%s wanted version: %s, has %s" % (source, expected, current)
260 to_remove.append(trans)
262 print "-------------------------------------------------------------------------"
265 prompt = "Removing: "
266 for remove in to_remove:
270 prompt += " Commit Changes? (y/N)"
273 if Options["no-action"]:
276 answer = daklib.utils.our_raw_input(prompt).lower()
282 print "Not committing changes"
286 for remove in to_remove:
287 del transitions[remove]
289 edit_file = temp_transitions_file(transitions)
290 write_transitions_from_file(edit_file)
294 print "WTF are you typing?"
297 ################################################################################
299 def print_info(trans, source, expected, rm, reason, packages):
300 print """Looking at transition: %s
305 Blocked Packages (total: %d): %s
306 """ % (trans, source, expected, rm, reason, len(packages), ", ".join(packages))
309 ################################################################################
311 def transition_info(transitions):
312 for trans in transitions:
313 t = transitions[trans]
317 # Will be None if nothing is in testing.
318 current = daklib.database.get_suite_version(source, "testing")
320 print_info(trans, source, expected, t["rm"], t["reason"], t["packages"])
323 # No package in testing
324 print "Transition source %s not in testing, transition still ongoing." % (source)
326 compare = apt_pkg.VersionCompare(current, expected)
327 print "Apt compare says: %s" % (compare)
329 # This is still valid, the current version in database is older than
330 # the new version we wait for
331 print "This transition is still ongoing, we currently have version %s" % (current)
333 print "This transition is over, the target package reached testing, should be removed"
334 print "%s wanted version: %s, has %s" % (source, expected, current)
335 print "-------------------------------------------------------------------------"
337 ################################################################################
344 # Check if there is a file defined (and existant)
345 transpath = Cnf.get("Dinstall::Reject::ReleaseTransitions", "")
347 daklib.utils.warn("Dinstall::Reject::ReleaseTransitions not defined")
349 if not os.path.exists(transpath):
350 daklib.utils.warn("ReleaseTransitions file, %s, not found." %
351 (Cnf["Dinstall::Reject::ReleaseTransitions"]))
354 if Options["import"]:
356 write_transitions_from_file(Options["import"])
357 except ParseException, m:
362 # Parse the yaml file
363 transitions = load_transitions(transpath)
364 if transitions == None:
365 # Something very broken with the transitions, exit
366 daklib.utils.warn("Could not parse existing transitions file. Aborting.")
370 # Let's edit the transitions file
372 elif Options["check"]:
373 # Check and remove outdated transitions
374 check_transitions(transitions)
376 # Output information about the currently defined transitions.
377 print "Currently defined transitions:"
378 transition_info(transitions)
382 ################################################################################
384 if __name__ == '__main__':