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")
65 whoamifull = pwd.getpwuid(whoami)
66 username = whoamifull[0]
68 print "Non-dak user: %s" % username
71 projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
72 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.
82 -h, --help show this help and exit.
83 -e, --edit edit the transitions file
84 -i, --import <file> check and import transitions from file
85 -c, --check check the transitions file, remove outdated entries
86 -S, --sudo use sudo to update transitions file
87 -n, --no-action don't do anything (only affects check)"""
91 ################################################################################
93 def load_transitions(trans_file):
95 sourcefile = file(trans_file, 'r')
96 sourcecontent = sourcefile.read()
99 trans = syck.load(sourcecontent)
100 except syck.error, msg:
101 # Someone fucked it up
102 print "ERROR: %s" % (msg)
105 # lets do further validation here
106 checkkeys = ["source", "reason", "packages", "new", "rm"]
110 # First check if we know all the keys for the transition and if they have
111 # the right type (and for the packages also if the list has the right types
112 # included, ie. not a list in list, but only str in the list)
114 if key not in checkkeys:
115 print "ERROR: Unknown key %s in transition %s" % (key, test)
118 if key == "packages":
119 if type(t[key]) != list:
120 print "ERROR: Unknown type %s for packages in transition %s." % (type(t[key]), test)
124 for package in t["packages"]:
125 if type(package) != str:
126 print "ERROR: Packages list contains invalid type %s (as %s) in transition %s" % (type(package), package, test)
129 # In case someone has an empty packages list
130 print "ERROR: No packages defined in transition %s" % (test)
134 elif type(t[key]) != str:
135 print "ERROR: Unknown type %s for key %s in transition %s" % (type(t[key]), key, test)
138 # And now the other way round - are all our keys defined?
139 for key in checkkeys:
141 print "ERROR: Missing key %s in transition %s" % (key, test)
149 ################################################################################
152 for retry in range(10):
153 lock_fd = os.open(file, os.O_RDWR | os.O_CREAT)
155 fcntl.lockf(lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
158 if errno.errorcode[e.errno] == 'EACCES' or errno.errorcode[e.errno] == 'EEXIST':
159 print "Unable to get lock for %s (try %d of 10)" % \
165 daklib.utils.fubar("Couldn't obtain lock for %s." % (lockfile))
167 ################################################################################
169 def write_transitions(from_trans):
170 """Update the active transitions file safely.
171 This function takes a parsed input file (which avoids invalid
172 files or files that may be be modified while the function is
173 active), and ensure the transitions file is updated atomically
176 trans_file = Cnf["Dinstall::Reject::ReleaseTransitions"]
177 trans_temp = trans_file + ".tmp"
179 trans_lock = lock_file(trans_file)
180 temp_lock = lock_file(trans_temp)
182 destfile = file(trans_temp, 'w')
183 syck.dump(from_trans, destfile)
186 os.rename(trans_temp, trans_file)
190 ################################################################################
192 class ParseException(Exception):
195 def write_transitions_from_file(from_file):
196 """We have a file we think is valid; if we're using sudo, we invoke it
197 here, otherwise we just parse the file and call write_transitions"""
200 os.spawnl(os.P_WAIT, "/usr/bin/sudo", "/usr/bin/sudo", "-u", "dak", "-H",
201 "/usr/local/bin/dak", "transitions", "--import", from_file)
203 trans = load_transitions(from_file)
205 raise ParseException, "Unparsable transitions file %s" % (file)
206 write_transitions(trans)
208 ################################################################################
210 def temp_transitions_file(transitions):
211 # NB: file is unlinked by caller, but fd is never actually closed.
212 # We need the chmod, as the file is (most possibly) copied from a
213 # sudo-ed script and would be unreadable if it has default mkstemp mode
215 (fd, path) = tempfile.mkstemp("","transitions")
218 syck.dump(transitions, f)
221 ################################################################################
223 def edit_transitions():
224 trans_file = Cnf["Dinstall::Reject::ReleaseTransitions"]
225 edit_file = temp_transitions_file(load_transitions(trans_file))
227 editor = os.environ.get("EDITOR", "vi")
230 result = os.system("%s %s" % (editor, edit_file))
233 daklib.utils.fubar("%s invocation failed for %s, not removing tempfile." % (editor, edit_file))
235 # Now try to load the new file
236 test = load_transitions(edit_file)
240 print "Edit was unparsable."
241 prompt = "[E]dit again, Drop changes?"
244 print "Edit looks okay.\n"
245 print "The following transitions are defined:"
246 print "------------------------------------------------------------------------"
247 transition_info(test)
249 prompt = "[S]ave, Edit again, Drop changes?"
253 while prompt.find(answer) == -1:
254 answer = daklib.utils.our_raw_input(prompt)
257 answer = answer[:1].upper()
263 print "OK, discarding changes"
269 print "You pressed something you shouldn't have :("
272 # We seem to be done and also have a working file. Copy over.
273 write_transitions_from_file(edit_file)
276 print "Transitions file updated."
278 ################################################################################
280 def check_transitions(transitions):
283 # Now look through all defined transitions
284 for trans in transitions:
285 t = transitions[trans]
289 # Will be None if nothing is in testing.
290 current = daklib.database.get_suite_version(source, "testing")
292 print_info(trans, source, expected, t["rm"], t["reason"], t["packages"])
295 # No package in testing
296 print "Transition source %s not in testing, transition still ongoing." % (source)
298 compare = apt_pkg.VersionCompare(current, expected)
300 # This is still valid, the current version in database is older than
301 # the new version we wait for
302 print "This transition is still ongoing, we currently have version %s" % (current)
304 print "REMOVE: This transition is over, the target package reached testing. REMOVE"
305 print "%s wanted version: %s, has %s" % (source, expected, current)
306 to_remove.append(trans)
308 print "-------------------------------------------------------------------------"
311 prompt = "Removing: "
312 for remove in to_remove:
316 prompt += " Commit Changes? (y/N)"
319 if Options["no-action"]:
322 answer = daklib.utils.our_raw_input(prompt).lower()
328 print "Not committing changes"
332 for remove in to_remove:
333 del transitions[remove]
335 edit_file = temp_transitions_file(transitions)
336 write_transitions_from_file(edit_file)
340 print "WTF are you typing?"
343 ################################################################################
345 def print_info(trans, source, expected, rm, reason, packages):
346 print """Looking at transition: %s
351 Blocked Packages (total: %d): %s
352 """ % (trans, source, expected, rm, reason, len(packages), ", ".join(packages))
355 ################################################################################
357 def transition_info(transitions):
358 for trans in transitions:
359 t = transitions[trans]
363 # Will be None if nothing is in testing.
364 current = daklib.database.get_suite_version(source, "testing")
366 print_info(trans, source, expected, t["rm"], t["reason"], t["packages"])
369 # No package in testing
370 print "Transition source %s not in testing, transition still ongoing." % (source)
372 compare = apt_pkg.VersionCompare(current, expected)
373 print "Apt compare says: %s" % (compare)
375 # This is still valid, the current version in database is older than
376 # the new version we wait for
377 print "This transition is still ongoing, we currently have version %s" % (current)
379 print "This transition is over, the target package reached testing, should be removed"
380 print "%s wanted version: %s, has %s" % (source, expected, current)
381 print "-------------------------------------------------------------------------"
383 ################################################################################
390 # Check if there is a file defined (and existant)
391 transpath = Cnf.get("Dinstall::Reject::ReleaseTransitions", "")
393 daklib.utils.warn("Dinstall::Reject::ReleaseTransitions not defined")
395 if not os.path.exists(transpath):
396 daklib.utils.warn("ReleaseTransitions file, %s, not found." %
397 (Cnf["Dinstall::Reject::ReleaseTransitions"]))
400 if Options["import"]:
402 write_transitions_from_file(Options["import"])
403 except ParseException, m:
408 # Parse the yaml file
409 transitions = load_transitions(transpath)
410 if transitions == None:
411 # Something very broken with the transitions, exit
412 daklib.utils.warn("Could not parse existing transitions file. Aborting.")
416 # Let's edit the transitions file
418 elif Options["check"]:
419 # Check and remove outdated transitions
420 check_transitions(transitions)
422 # Output information about the currently defined transitions.
423 print "Currently defined transitions:"
424 transition_info(transitions)
428 ################################################################################
430 if __name__ == '__main__':