]> scripts.mit.edu Git - wizard.git/blobdiff - wizard/util.py
Set admin e-mail address properly on MediaWiki >= 1.18.0
[wizard.git] / wizard / util.py
index a6e8f243dabf48a8792b442f51e75183ef4698c9..47588e5482b39cd21e92dffea34ecaeddec4f0d7 100644 (file)
@@ -19,8 +19,30 @@ import httplib
 import urllib
 import time
 import logging
+import random
+import string
 
 import wizard
+from wizard import user
+
+def boolish(val):
+    """
+    Parse the contents of an environment variable as a boolean.
+    This recognizes more values as ``False`` than :func:`bool` would.
+
+        >>> boolish("0")
+        False
+        >>> boolish("no")
+        False
+        >>> boolish("1")
+        True
+    """
+    try:
+        return bool(int(val))
+    except (ValueError, TypeError):
+        if val == "No" or val == "no" or val == "false" or val == "False":
+            return False
+        return bool(val)
 
 class ChangeDirectory(object):
     """
@@ -200,24 +222,6 @@ def get_dir_uid(dir):
     """Finds the uid of the person who owns this directory."""
     return os.stat(dir).st_uid
 
-def get_dir_owner(dir = "."):
-    """
-    Finds the name of the locker this directory is in.
-
-    .. note::
-
-        This function uses the passwd database and thus
-        only works on scripts servers when querying directories
-        that live on AFS.
-    """
-    uid = get_dir_uid(dir)
-    try:
-        pwentry = pwd.getpwuid(uid)
-        return pwentry.pw_name
-    except KeyError:
-        # do an pts query to get the name
-        return subprocess.Popen(['pts', 'examine', str(uid)], stdout=subprocess.PIPE).communicate()[0].partition(",")[0].partition(": ")[2]
-
 def get_revision():
     """Returns the commit ID of the current Wizard install."""
     # If you decide to convert this to use wizard.shell, be warned
@@ -226,91 +230,46 @@ def get_revision():
     wizard_git = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), ".git")
     return subprocess.Popen(["git", "--git-dir=" + wizard_git, "rev-parse", "HEAD"], stdout=subprocess.PIPE).communicate()[0].rstrip()
 
-def get_operator_info():
-    """
-    Returns tuple of ``(realname, email)`` about the person running
-    the script.  If run from a scripts server, get info from Hesiod.
-    Otherwise, use the passwd database (email generated probably won't
-    actually accept mail).  Useful when generating commit messages.
-    """
-    username = get_operator_name_from_gssapi()
-    if username:
-        # scripts approach
-        hesinfo = subprocess.Popen(["hesinfo", username, "passwd"],stdout=subprocess.PIPE).communicate()[0]
-        fields = hesinfo.partition(",")[0]
-        realname = fields.rpartition(":")[2]
-        return realname, username + "@mit.edu"
-    else:
-        # more traditional approach, but the email probably doesn't work
-        uid = os.getuid()
-        if not uid:
-            # since root isn't actually a useful designation, but maybe
-            # SUDO_USER contains something helpful
-            sudo_user = os.getenv("SUDO_USER")
-            if not sudo_user:
-                raise NoOperatorInfo
-            pwdentry = pwd.getpwnam(sudo_user)
-        else:
-            pwdentry = pwd.getpwuid(uid)
-        # XXX: error checking might be nice
-        # We follow the Ubuntu convention of gecos being a comma split field
-        # with the person's realname being the first entry.
-        return pwdentry.pw_gecos.split(",")[0], pwdentry.pw_name + "@" + socket.gethostname()
-
 def get_operator_git():
     """
     Returns ``Real Name <username@mit.edu>`` suitable for use in
-    Git ``Something-by:`` string.
-    """
-    return "%s <%s>" % get_operator_info()
-
-def get_operator_name_from_gssapi():
-    """
-    Returns username of the person operating this script based
-    off of the :envvar:`SSH_GSSAPI_NAME` environment variable.
-
-    .. note::
-
-        :envvar:`SSH_GSSAPI_NAME` is not set by a vanilla OpenSSH
-        distributions.  Scripts servers are patched to support this
-        environment variable.
+    Git ``Something-by:`` string.  Throws :exc:`NoOperatorInfo` if
+    no operator information is available.
     """
-    principal = os.getenv("SSH_GSSAPI_NAME")
-    if not principal:
-        return None
-    instance, _, _ = principal.partition("@")
-    if instance.endswith("/root"):
-        username, _, _ = principal.partition("/")
-    else:
-        username = instance
-    return username
+    op = user.operator()
+    if op is None:
+        raise NoOperatorInfo
+    info = user.pwnam(op)
+    return "%s <%s>" % (info.realname, info.email)
 
 def set_operator_env():
     """
     Sets :envvar:`GIT_COMMITTER_NAME` and :envvar:`GIT_COMMITTER_EMAIL`
-    environment variables if applicable.  Does nothing if
-    :func:`get_operator_info` throws :exc:`NoOperatorInfo`.
+    environment variables if applicable.  Does nothing if no information
+    is available
     """
-    try:
-        op_realname, op_email = get_operator_info()
-        os.putenv("GIT_COMMITTER_NAME", op_realname)
-        os.putenv("GIT_COMMITTER_EMAIL", op_email)
-    except NoOperatorInfo:
-        pass
+    op = user.operator()
+    if op is None:
+        return
+    info = user.pwnam(op)
+    if not info.realname:
+        return
+    os.putenv("GIT_COMMITTER_NAME", info.realname)
+    os.putenv("GIT_COMMITTER_EMAIL", info.email)
 
 def set_author_env():
     """
-    Sets :envvar:`GIT_AUTHOR_NAME` and :envvar:`GIT_AUTHOR_EMAIL` environment
-    variables if applicable. Does nothing if :func:`get_dir_owner` fails.
+    Sets :envvar:`GIT_AUTHOR_NAME` and :envvar:`GIT_AUTHOR_EMAIL`
+    environment variables if applicable. Does nothing if
+    :func:`wizard.user.passwd` fails.
     """
-    try:
-        # XXX: should check if the directory is in AFS, and if not, use
-        # a more traditional metric
-        lockername = get_dir_owner()
-        os.putenv("GIT_AUTHOR_NAME", "%s locker" % lockername)
-        os.putenv("GIT_AUTHOR_EMAIL", "%s@scripts.mit.edu" % lockername)
-    except KeyError: # XXX: This doesn't actually make sense
-        pass
+    info = user.passwd()
+    if info is None:
+        return
+    if not info.realname:
+        return
+    os.putenv("GIT_AUTHOR_NAME", "%s" % info.realname)
+    os.putenv("GIT_AUTHOR_EMAIL", "%s" % info.email)
 
 def set_git_env():
     """Sets all appropriate environment variables for Git commits."""
@@ -325,13 +284,13 @@ def get_git_footer():
 
 def safe_unlink(file):
     """Moves a file/dir to a backup location."""
-    if not os.path.exists(file):
+    if not os.path.lexists(file):
         return None
     prefix = "%s.bak" % file
     name = None
     for i in itertools.count():
         name = "%s.%d" % (prefix, i)
-        if not os.path.exists(name):
+        if not os.path.lexists(name):
             break
     os.rename(file, name)
     return name
@@ -343,18 +302,43 @@ def soft_unlink(file):
     except OSError:
         pass
 
+def makedirs(path):
+    """
+    Create a directory path (a la ``mkdir -p`` or ``os.makedirs``),
+    but don't complain if it already exists.
+    """
+    try:
+        os.makedirs(path)
+    except OSError as exc:
+        if exc.errno == errno.EEXIST:
+            pass
+        else:
+            raise
+
 def fetch(host, path, subpath, post=None):
-    h = httplib.HTTPConnection(host)
-    fullpath = path.rstrip("/") + "/" + subpath.lstrip("/") # to be lenient about input we accept
-    if post:
-        headers = {"Content-type": "application/x-www-form-urlencoded"}
-        h.request("POST", fullpath, urllib.urlencode(post), headers)
-    else:
-        h.request("GET", fullpath)
-    r = h.getresponse()
-    data = r.read()
-    h.close()
-    return data
+    try:
+        # XXX: Should use urllib instead
+        h = httplib.HTTPConnection(host)
+        fullpath = path.rstrip("/") + "/" + subpath.lstrip("/") # to be lenient about input we accept
+        if post:
+            headers = {"Content-type": "application/x-www-form-urlencoded"}
+            logging.info("POST request to http://%s%s", host, fullpath)
+            logging.debug("POST contents:\n" + urllib.urlencode(post))
+            h.request("POST", fullpath, urllib.urlencode(post), headers)
+        else:
+            logging.info("GET request to http://%s%s", host, fullpath)
+            h.request("GET", fullpath)
+        r = h.getresponse()
+        logging.debug("Response code: %d", r.status)
+        logging.debug("Response headers: %s", r.msg)
+        data = r.read()
+        h.close()
+        return data
+    except socket.gaierror as e:
+        if e.errno == socket.EAI_NONAME:
+            raise DNSError(host)
+        else:
+            raise
 
 def mixed_newlines(filename):
     """Returns ``True`` if ``filename`` has mixed newlines."""
@@ -364,6 +348,48 @@ def mixed_newlines(filename):
     f.close() # just to be safe
     return ret
 
+def disk_usage(dir=None, excluded_dir=".git"):
+    """
+    Recursively determines the disk usage of a directory, excluding
+    .git directories.  Value is in bytes.  If ``dir`` is omitted, the
+    current working directory is assumed.
+    """
+    if dir is None: dir = os.getcwd()
+    sum_sizes = 0
+    for root, _, files in os.walk(dir):
+        for name in files:
+            if not os.path.join(root, name).startswith(os.path.join(dir, excluded_dir)):
+                file = os.path.join(root, name)
+                try:
+                    if os.path.islink(file): continue
+                    sum_sizes += os.path.getsize(file)
+                except OSError as e:
+                    if e.errno == errno.ENOENT:
+                        logging.warning("%s disappeared before we could stat", file)
+                    else:
+                        raise
+    return sum_sizes
+
+def random_key(length=30):
+    """Generates a random alphanumeric key of ``length`` size."""
+    return ''.join(random.choice(string.letters + string.digits) for i in xrange(length))
+
+def truncate(version):
+    """Truncates the Scripts specific version number."""
+    return str(version).partition('-scripts')[0]
+
+def init_wizard_dir():
+    """
+    Generates a .wizard directory and initializes it with some common
+    files.  This operation is idempotent.
+    """
+    # no harm in doing this repeatedly
+    wizard_dir = ".wizard"
+    if not os.path.isdir(wizard_dir):
+        os.mkdir(wizard_dir)
+    open(os.path.join(wizard_dir, ".htaccess"), "w").write("Deny from all\n")
+    open(os.path.join(wizard_dir, ".gitignore"), "w").write("*\n")
+
 class NoOperatorInfo(wizard.Error):
     """No information could be found about the operator from Kerberos."""
     pass
@@ -384,3 +410,14 @@ ERROR: Could not acquire lock on directory.  Maybe there is
 another migration process running?
 """
 
+class DNSError(socket.gaierror):
+    errno = socket.EAI_NONAME
+    #: Hostname that could not resolve name
+    host = None
+    def __init__(self, host):
+        self.host = host
+    def __str__(self):
+        return """
+
+ERROR: Could not resolve hostname %s.
+""" % self.host