--- /dev/null
+"""
+Common code for installation scripts that live in .scripts/install
+
+.. testsetup:: *
+
+ from wizard.install import *
+"""
+
+import optparse
+import os
+import httplib
+import urllib
+import subprocess
+
+import wizard
+from wizard import util
+
+def fetch(options, path, post=None):
+ h = httplib.HTTPConnection(options.web_host)
+ fullpath = options.web_path + "/" + path
+ headers = {"Content-type": "application/x-www-form-urlencoded"}
+ if post:
+ h.request("POST", fullpath, urllib.urlencode(post), headers)
+ else:
+ h.request("GET", fullpath)
+ r = h.getresponse()
+ data = r.read()
+ h.close()
+ return data
+
+def attr_to_option(variable):
+ """
+ Converts Python attribute names to command line options.
+
+ >>> attr_to_option("foo_bar")
+ '--foo-bar'
+ """
+ return '--' + variable.replace('_', '-')
+
+def calculate_web(options):
+ """Performs scripts specific guesses for web variables."""
+ # XXX: THIS CODE SUCKS
+ if options.web_path or options.web_host: return
+ _, _, web_path = os.getcwd().partition("/web_scripts")
+ if not web_path: return
+ options.web_path = web_path
+ options.web_host = util.get_dir_owner() + ".scripts.mit.edu"
+
+def calculate_mysql(options):
+ """
+ Performs scripts specific guesses for mysql variables.
+
+ .. note::
+
+ This might create the database using the sql signup service.
+ """
+ if options.mysql_host or options.mysql_db or options.mysql_user or options.mysql_password: return
+ try:
+ triplet = subprocess.Popen("/mit/scripts/sql/bin/get-password", stdout=subprocess.PIPE).communicate()[0].rstrip().split()
+ except:
+ raise
+ name = os.path.basename(os.getcwd())
+ username = os.getenv('USER')
+ options.mysql_host, options.mysql_user, options.mysql_password = triplet
+ options.mysql_db = username + '+' + subprocess.Popen(["/mit/scripts/sql/bin/get-next-database", name], stdout=subprocess.PIPE).communicate()[0].rstrip()
+ subprocess.Popen(["/mit/scripts/sql/bin/create-database", options.mysql_db], stdout=subprocess.PIPE).communicate()
+
+def calculate_email(options):
+ """Performs script specific guess for email."""
+ options.email = os.getenv("USER") + "@mit.edu"
+
+class OptionParser(object):
+ """
+ Wrapper around :class:`optparse.OptionParser` which adds support
+ for required name parameters (i.e. "required options") and also
+ includes support for common parameters (from ``preloads``) that you'd want
+ when installing an application.
+
+ Valid preloads are:
+
+ * ``mysql``, which populates the options ``mysql_host``, ``mysql_db``,
+ ``mysql_user`` and ``mysql_password``.
+ * ``admin``, which populates the options ``admin_name`` and
+ ``admin_password``.
+ * ``email``, which populates the option ``email``.
+
+ The options ``web_path`` and ``web_host`` are automatically required.
+
+ Example::
+
+ parser = OptionParser("sql", "admin", "email")
+ """
+ #: Instance of :class:`optparse.OptionParser`, for specifying
+ #: non-interactive options.
+ parser = None
+ #: Parameters that are required by this parser.
+ params = None
+ def __init__(self, *preloads):
+ self.parser = optparse.OptionParser()
+ self.params = set()
+ self._user_params = set()
+ self._calculators = []
+ preloads = set(preloads)
+ self.add_param("web_path", auto=True, help="Path to your install, e.g. /myapp")
+ self.add_param("web_host", auto=True, help="Host of your install, e.g. example.com")
+ self._calculators.append(calculate_web)
+ if "mysql" in preloads:
+ preloads.remove("mysql")
+ self.add_param("mysql_host", auto=True, help="Host that your MySQL server lives on")
+ self.add_param("mysql_db", auto=True, help="Name of the database to populate")
+ self.add_param("mysql_user", auto=True, help="Name of user to access database with")
+ self.add_param("mysql_password", auto=True, help="Password of the database user")
+ self._calculators.append(calculate_mysql)
+ if "admin" in preloads:
+ preloads.remove("admin")
+ self.add_param("admin_name", help="Name of admin user to create")
+ self.add_param("admin_password", help="Password of admin user")
+ if "email" in preloads:
+ preloads.remove("email")
+ self.add_param("email", auto=True, help="Administrative email")
+ self._calculators.append(calculate_email)
+ if preloads:
+ raise UnrecognizedPreloads(preloads)
+ def add_param(self, name, auto=False, help=None):
+ """
+ Adds a required parameter ``name``. This parameter can be asked
+ for interactively or specified command line with ``--name`` (with
+ underscores replaced with dashes). The ``help`` shows up both
+ in ``--help`` output as well as interactive operation. If
+ ``auto`` is ``True``, that means that we might be able to
+ automatically calculate a sensible value.
+
+ .. note:
+
+ This API is most certainly going to change.
+ """
+ self.parser.add_option(attr_to_option(name), dest=name,
+ default=None, help=help)
+ self.params.add(name)
+ if not auto: self._user_params.add(name)
+ def parse_args(self):
+ """Performs option parsing and required option validation."""
+ options, args = self.parser.parse_args()
+ # XXX: Something more robust, as in "here are the parameters
+ # that need to be set at a certain point in time, and incrementally
+ # increase validation as things go on"
+ for param in self._user_params:
+ if getattr(options, param) is None:
+ raise MissingRequiredParam(param)
+ for calculator in self._calculators:
+ calculator(options)
+ for param in self.params:
+ if getattr(options, param) is None:
+ raise MissingRequiredParam(param)
+ return options, param
+
+class Error(wizard.Error):
+ """Base error class for this module"""
+ pass
+
+class Failure(Error):
+ """Web install process failed"""
+ pass
+
+class UnrecognizedPreloads(Error):
+ """You passed a preload that was not recognized."""
+ #: The preloads that were not recognized
+ preloads = None
+ def __init__(self, preloads):
+ self.preloads = preloads
+ self.message = str(self)
+ def __str__(self):
+ return "Did not recognize these preloads: " + ", ".join(self.preloads)
+
+class MissingRequiredParam(Error):
+ """You missed a required parameter, and we couldn't generate it."""
+ #: The param variable name that was not recognized
+ param = None
+ def __init__(self, param):
+ self.param = param
+ self.message = str(self)
+ def __str__(self):
+ return "Missing required parameter %s; try specifying %s" % (self.param, attr_to_option(self.param))