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):
"""
"""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
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():
+ Git ``Something-by:`` string. Throws :exc:`NoOperatorInfo` if
+ no operator information is available.
"""
- 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.
- """
- 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."""
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
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):
try:
- # XXX: Special case if it's https; not sure why this data isn't
- # passed
+ # 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()
data = r.read()
f.close() # just to be safe
return ret
-def assert_all_writable(dir="."):
- """Recursively checks if all files and directories in a directory are
- writable. Raises :exc:`PermissionsError` if this is not true."""
- for dirpath, dirname, filenames in os.walk(dir):
- if not os.access(dirpath, os.W_OK):
- raise PermissionsError
- for filename in filenames:
- if not os.access(os.path.join(dirpath, filename), os.W_OK):
- raise PermissionsError
+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."""