]> git.decadent.org.uk Git - dak.git/blobdiff - dak/transitions.py
Add more exception handling
[dak.git] / dak / transitions.py
index 121edc2f4dd88b7277a3b7160b0e13151cbda8ad..749f18fcfdadd3838a9337f27e8ad9518dd6438e 100755 (executable)
@@ -23,7 +23,7 @@
 
 ################################################################################
 
-import os, pg, sys, time, errno, fcntl, tempfile
+import os, pg, sys, time, errno, fcntl, tempfile, pwd, re
 import apt_pkg
 import daklib.database
 import daklib.utils
@@ -34,8 +34,13 @@ Cnf = None
 Options = None
 projectB = None
 
+re_broken_package = re.compile(r"[a-zA-Z]\w+\s+\-.*")
+
 ################################################################################
 
+#####################################
+#### This may run within sudo !! ####
+#####################################
 def init():
     global Cnf, Options, projectB
 
@@ -47,7 +52,7 @@ def init():
                  ('e',"edit","Edit-Transitions::Options::Edit"),
                  ('i',"import","Edit-Transitions::Options::Import", "HasArg"),
                  ('c',"check","Edit-Transitions::Options::Check"),
-                 ('S',"use-sudo","Edit-Transitions::Options::Sudo"),
+                 ('s',"sudo","Edit-Transitions::Options::Sudo"),
                  ('n',"no-action","Edit-Transitions::Options::No-Action")]
 
     for i in ["help", "no-action", "edit", "import", "check", "sudo"]:
@@ -58,18 +63,24 @@ def init():
 
     Options = Cnf.SubTree("Edit-Transitions::Options")
 
-    projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
-    daklib.database.init(Cnf, projectB)
-    
     if Options["help"]:
         usage()
 
+    whoami = os.getuid()
+    whoamifull = pwd.getpwuid(whoami)
+    username = whoamifull[0]
+    if username != "dak":
+        print "Non-dak user: %s" % username
+        Options["sudo"] = "y"
+
+    projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
+    daklib.database.init(Cnf, projectB)
+    
 ################################################################################
 
 def usage (exit_code=0):
     print """Usage: transitions [OPTION]...
 Update and check the release managers transition file.
-transitions.
 
 Options:
 
@@ -78,27 +89,99 @@ Options:
   -i, --import <file>       check and import transitions from file
   -c, --check               check the transitions file, remove outdated entries
   -S, --sudo                use sudo to update transitions file
-  -n, --no-action           don't do anything"""
+  -n, --no-action           don't do anything (only affects check)"""
 
     sys.exit(exit_code)
 
 ################################################################################
 
+#####################################
+#### This may run within sudo !! ####
+#####################################
 def load_transitions(trans_file):
     # Parse the yaml file
     sourcefile = file(trans_file, 'r')
     sourcecontent = sourcefile.read()
+    failure = False
     try:
         trans = syck.load(sourcecontent)
     except syck.error, msg:
         # Someone fucked it up
         print "ERROR: %s" % (msg)
         return None
-    # could do further validation here
+
+    # lets do further validation here
+    checkkeys = ["source", "reason", "packages", "new", "rm"]
+
+    # If we get an empty definition - we just have nothing to check, no transitions defined
+    if type(trans) != dict:
+        # This can be anything. We could have no transitions defined. Or someone totally fucked up the
+        # file, adding stuff in a way we dont know or want. Then we set it empty - and simply have no
+        # transitions anymore. User will see it in the information display after he quit the editor and
+        # could fix it
+        trans = ""
+        return trans
+
+    try:
+        for test in trans:
+            t = trans[test]
+        
+            # First check if we know all the keys for the transition and if they have
+            # the right type (and for the packages also if the list has the right types
+            # included, ie. not a list in list, but only str in the list)
+            for key in t:
+                if key not in checkkeys:
+                    print "ERROR: Unknown key %s in transition %s" % (key, test)
+                    failure = True
+        
+                if key == "packages":
+                    if type(t[key]) != list:
+                        print "ERROR: Unknown type %s for packages in transition %s." % (type(t[key]), test)
+                        failure = True
+                    try:
+                        for package in t["packages"]:
+                            if type(package) != str:
+                                print "ERROR: Packages list contains invalid type %s (as %s) in transition %s" % (type(package), package, test)
+                                failure = True
+                            if re_broken_package.match(package):
+                                # Someone had a space too much (or not enough), we have something looking like
+                                # "package1 - package2" now.
+                                print "ERROR: Invalid indentation of package list in transition %s, around package(s): %s" % (test, package)
+                                failure = True
+                    except TypeError:
+                        # In case someone has an empty packages list
+                        print "ERROR: No packages defined in transition %s" % (test)
+                        failure = True
+                        continue
+        
+                elif type(t[key]) != str:
+                    if t[key] == "new" and type(t[key]) == int:
+                        # Ok, debian native version
+                    else:
+                        print "ERROR: Unknown type %s for key %s in transition %s" % (type(t[key]), key, test)
+                        failure = True
+        
+            # And now the other way round - are all our keys defined?
+            for key in checkkeys:
+                if key not in t:
+                    print "ERROR: Missing key %s in transition %s" % (key, test)
+                    failure = True
+    except TypeError:
+        # In case someone defined very broken things
+        print "ERROR: Unable to parse the file"
+        failure = True
+
+
+    if failure:
+        return None
+
     return trans
 
 ################################################################################
 
+#####################################
+#### This may run within sudo !! ####
+#####################################
 def lock_file(file):
     for retry in range(10):
         lock_fd = os.open(file, os.O_RDWR | os.O_CREAT)
@@ -117,6 +200,9 @@ def lock_file(file):
 
 ################################################################################
 
+#####################################
+#### This may run within sudo !! ####
+#####################################
 def write_transitions(from_trans):
     """Update the active transitions file safely.
        This function takes a parsed input file (which avoids invalid
@@ -143,10 +229,18 @@ def write_transitions(from_trans):
 class ParseException(Exception):
     pass
 
+##########################################
+#### This usually runs within sudo !! ####
+##########################################
 def write_transitions_from_file(from_file):
     """We have a file we think is valid; if we're using sudo, we invoke it
        here, otherwise we just parse the file and call write_transitions"""
 
+    # Lets check if from_file is in the directory we expect it to be in
+    if not os.path.abspath(from_file).startswith(Cnf["Transitions::TempPath"]):
+        print "Will not accept transitions file outside of %s" % (Cnf["Transitions::TempPath"])
+        sys.exit(3)
+
     if Options["sudo"]:
         os.spawnl(os.P_WAIT, "/usr/bin/sudo", "/usr/bin/sudo", "-u", "dak", "-H", 
               "/usr/local/bin/dak", "transitions", "--import", from_file)
@@ -160,8 +254,11 @@ def write_transitions_from_file(from_file):
 
 def temp_transitions_file(transitions):
     # NB: file is unlinked by caller, but fd is never actually closed.
-
-    (fd, path) = tempfile.mkstemp("","transitions")
+    # We need the chmod, as the file is (most possibly) copied from a
+    # sudo-ed script and would be unreadable if it has default mkstemp mode
+    
+    (fd, path) = tempfile.mkstemp("", "transitions", Cnf["Transitions::TempPath"])
+    os.chmod(path, 0644)
     f = open(path, "w")
     syck.dump(transitions, f)
     return path
@@ -185,11 +282,12 @@ def edit_transitions():
 
         if test == None:
             # Edit is broken
-           print "Edit was unparsable."
+            print "Edit was unparsable."
             prompt = "[E]dit again, Drop changes?"
-           default = "E"
-       else:
-           print "Edit looks okay.\n"
+            default = "E"
+        else:
+            print "Edit looks okay.\n"
+            print "The following transitions are defined:"
             print "------------------------------------------------------------------------"
             transition_info(test)
 
@@ -212,9 +310,9 @@ def edit_transitions():
         elif answer == 'S':
             # Ready to save
             break
-       else:
-           print "You pressed something you shouldn't have :("
-           sys.exit(1)
+        else:
+            print "You pressed something you shouldn't have :("
+            sys.exit(1)
 
     # We seem to be done and also have a working file. Copy over.
     write_transitions_from_file(edit_file)
@@ -332,6 +430,9 @@ def transition_info(transitions):
 def main():
     global Cnf
 
+    #####################################
+    #### This can run within sudo !! ####
+    #####################################
     init()
     
     # Check if there is a file defined (and existant)
@@ -343,6 +444,15 @@ def main():
         daklib.utils.warn("ReleaseTransitions file, %s, not found." %
                           (Cnf["Dinstall::Reject::ReleaseTransitions"]))
         sys.exit(1)
+    # Also check if our temp directory is defined and existant
+    temppath = Cnf.get("Transitions::TempPath", "")
+    if temppath == "":
+        daklib.utils.warn("Transitions::TempPath not defined")
+        sys.exit(1)
+    if not os.path.exists(temppath):
+        daklib.utils.warn("Temporary path %s not found." %
+                          (Cnf["Transitions::TempPath"]))
+        sys.exit(1)
    
     if Options["import"]:
         try:
@@ -351,6 +461,9 @@ def main():
             print m
             sys.exit(2)
         sys.exit(0)
+    ##############################################
+    #### Up to here it can run within sudo !! ####
+    ##############################################
 
     # Parse the yaml file
     transitions = load_transitions(transpath)