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()
98 trans = syck.load(sourcecontent)
99 except syck.error, msg:
100 # Someone fucked it up
101 print "ERROR: %s" % (msg)
103 # could do further validation here
106 ################################################################################
109 for retry in range(10):
110 lock_fd = os.open(file, os.O_RDWR | os.O_CREAT)
112 fcntl.lockf(lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
115 if errno.errorcode[e.errno] == 'EACCES' or errno.errorcode[e.errno] == 'EEXIST':
116 print "Unable to get lock for %s (try %d of 10)" % \
122 daklib.utils.fubar("Couldn't obtain lock for %s." % (lockfile))
124 ################################################################################
126 def write_transitions(from_trans):
127 """Update the active transitions file safely.
128 This function takes a parsed input file (which avoids invalid
129 files or files that may be be modified while the function is
130 active), and ensure the transitions file is updated atomically
133 trans_file = Cnf["Dinstall::Reject::ReleaseTransitions"]
134 trans_temp = trans_file + ".tmp"
136 trans_lock = lock_file(trans_file)
137 temp_lock = lock_file(trans_temp)
139 destfile = file(trans_temp, 'w')
140 syck.dump(from_trans, destfile)
143 os.rename(trans_temp, trans_file)
147 ################################################################################
149 class ParseException(Exception):
152 def write_transitions_from_file(from_file):
153 """We have a file we think is valid; if we're using sudo, we invoke it
154 here, otherwise we just parse the file and call write_transitions"""
157 os.spawnl(os.P_WAIT, "/usr/bin/sudo", "/usr/bin/sudo", "-u", "dak", "-H",
158 "/usr/local/bin/dak", "transitions", "--import", from_file)
160 trans = load_transitions(from_file)
162 raise ParseException, "Unparsable transitions file %s" % (file)
163 write_transitions(trans)
165 ################################################################################
167 def temp_transitions_file(transitions):
168 # NB: file is unlinked by caller, but fd is never actually closed.
169 # We need the chmod, as the file is (most possibly) copied from a
170 # sudo-ed script and would be unreadable if it has default mkstemp mode
172 (fd, path) = tempfile.mkstemp("","transitions")
175 syck.dump(transitions, f)
178 ################################################################################
180 def edit_transitions():
181 trans_file = Cnf["Dinstall::Reject::ReleaseTransitions"]
182 edit_file = temp_transitions_file(load_transitions(trans_file))
184 editor = os.environ.get("EDITOR", "vi")
187 result = os.system("%s %s" % (editor, edit_file))
190 daklib.utils.fubar("%s invocation failed for %s, not removing tempfile." % (editor, edit_file))
192 # Now try to load the new file
193 test = load_transitions(edit_file)
197 print "Edit was unparsable."
198 prompt = "[E]dit again, Drop changes?"
201 print "Edit looks okay.\n"
202 print "The following transitions are defined:"
203 print "------------------------------------------------------------------------"
204 transition_info(test)
206 prompt = "[S]ave, Edit again, Drop changes?"
210 while prompt.find(answer) == -1:
211 answer = daklib.utils.our_raw_input(prompt)
214 answer = answer[:1].upper()
220 print "OK, discarding changes"
226 print "You pressed something you shouldn't have :("
229 # We seem to be done and also have a working file. Copy over.
230 write_transitions_from_file(edit_file)
233 print "Transitions file updated."
235 ################################################################################
237 def check_transitions(transitions):
240 # Now look through all defined transitions
241 for trans in transitions:
242 t = transitions[trans]
246 # Will be None if nothing is in testing.
247 current = daklib.database.get_suite_version(source, "testing")
249 print_info(trans, source, expected, t["rm"], t["reason"], t["packages"])
252 # No package in testing
253 print "Transition source %s not in testing, transition still ongoing." % (source)
255 compare = apt_pkg.VersionCompare(current, expected)
257 # This is still valid, the current version in database is older than
258 # the new version we wait for
259 print "This transition is still ongoing, we currently have version %s" % (current)
261 print "REMOVE: This transition is over, the target package reached testing. REMOVE"
262 print "%s wanted version: %s, has %s" % (source, expected, current)
263 to_remove.append(trans)
265 print "-------------------------------------------------------------------------"
268 prompt = "Removing: "
269 for remove in to_remove:
273 prompt += " Commit Changes? (y/N)"
276 if Options["no-action"]:
279 answer = daklib.utils.our_raw_input(prompt).lower()
285 print "Not committing changes"
289 for remove in to_remove:
290 del transitions[remove]
292 edit_file = temp_transitions_file(transitions)
293 write_transitions_from_file(edit_file)
297 print "WTF are you typing?"
300 ################################################################################
302 def print_info(trans, source, expected, rm, reason, packages):
303 print """Looking at transition: %s
308 Blocked Packages (total: %d): %s
309 """ % (trans, source, expected, rm, reason, len(packages), ", ".join(packages))
312 ################################################################################
314 def transition_info(transitions):
315 for trans in transitions:
316 t = transitions[trans]
320 # Will be None if nothing is in testing.
321 current = daklib.database.get_suite_version(source, "testing")
323 print_info(trans, source, expected, t["rm"], t["reason"], t["packages"])
326 # No package in testing
327 print "Transition source %s not in testing, transition still ongoing." % (source)
329 compare = apt_pkg.VersionCompare(current, expected)
330 print "Apt compare says: %s" % (compare)
332 # This is still valid, the current version in database is older than
333 # the new version we wait for
334 print "This transition is still ongoing, we currently have version %s" % (current)
336 print "This transition is over, the target package reached testing, should be removed"
337 print "%s wanted version: %s, has %s" % (source, expected, current)
338 print "-------------------------------------------------------------------------"
340 ################################################################################
347 # Check if there is a file defined (and existant)
348 transpath = Cnf.get("Dinstall::Reject::ReleaseTransitions", "")
350 daklib.utils.warn("Dinstall::Reject::ReleaseTransitions not defined")
352 if not os.path.exists(transpath):
353 daklib.utils.warn("ReleaseTransitions file, %s, not found." %
354 (Cnf["Dinstall::Reject::ReleaseTransitions"]))
357 if Options["import"]:
359 write_transitions_from_file(Options["import"])
360 except ParseException, m:
365 # Parse the yaml file
366 transitions = load_transitions(transpath)
367 if transitions == None:
368 # Something very broken with the transitions, exit
369 daklib.utils.warn("Could not parse existing transitions file. Aborting.")
373 # Let's edit the transitions file
375 elif Options["check"]:
376 # Check and remove outdated transitions
377 check_transitions(transitions)
379 # Output information about the currently defined transitions.
380 print "Currently defined transitions:"
381 transition_info(transitions)
385 ################################################################################
387 if __name__ == '__main__':