]> scripts.mit.edu Git - wizard.git/commitdiff
Pluginify strategies.
authorEdward Z. Yang <ezyang@mit.edu>
Mon, 12 Jul 2010 06:01:56 +0000 (23:01 -0700)
committerEdward Z. Yang <ezyang@mit.edu>
Mon, 12 Jul 2010 06:01:56 +0000 (23:01 -0700)
Signed-off-by: Edward Z. Yang <ezyang@mit.edu>
SCRIPTS
TODO
doc/index.rst
doc/module/wizard.install.rst
doc/plugin.rst
plugins/scripts/setup.py
plugins/scripts/wizard_scripts.py
wizard/install/__init__.py

diff --git a/SCRIPTS b/SCRIPTS
index 31af77abe9b5dc9b61145567deea4f5cdfd005e8..bb3dfcab94e5aa4a7c61612d6463c49d9f627d6f 100644 (file)
--- a/SCRIPTS
+++ b/SCRIPTS
@@ -14,12 +14,9 @@ be moved away:
       should instantiate a .wizard directory automatically (which should
       be ignored) that doesn't need to be created in repositories.
 
-    * [HOOK] wizard.install contains strategies for guessing variables
-      for an installation that are Scripts specific
-
-    * We should have `wizard install` do something intelligent about
-      prompting a user about possibilities; not everyone is going to
-      use the scripts-start wrapper.
+    * [DEFER] We should have `wizard install` do something intelligent
+      about prompting a user about possibilities; not everyone is going
+      to use the scripts-start wrapper.
 
     * [DEFER] The mass-* commands are especially designed for Scripts,
       so while they're ostensibly portable, they'd probably need a bit
diff --git a/TODO b/TODO
index bf13a609e926aaaf7b36f2d0cc250b28f42b3b5a..03a2d0e774db188cc75ec79fd636a21478b0adbb 100644 (file)
--- a/TODO
+++ b/TODO
@@ -1,5 +1,8 @@
 - [SCRIPTS] MediaWiki 1.9.3 and 1.6.7
 
+- .scripts/.wizard are kind of ugly when they're not added to the
+  ignore. What to do?
+
 - util.fetch() should use urllib under the hood, not httplib.  Code
   has to be changed.  We should log if we get redirected.
 - Be a little more intelligent when perform web checks; for example,
index 085825af8a604cfaef59003a5d7849ccac3e155d..6c1b044e2eda605c0f2e7d6efaf700b32725da76 100644 (file)
@@ -77,6 +77,7 @@ Modules
     module/wizard.git
     module/wizard.install
     module/wizard.merge
+    module/wizard.plugin
     module/wizard.prompt
     module/wizard.resolve
     module/wizard.shell
index c3800d0c0f7568d56b641310bcbc2a92057510f7..0a3033f2f48fb40cd8cbde59e6faa0ff07f7e7f2 100644 (file)
@@ -29,13 +29,7 @@ Predefined classes
 .. autoclass:: EnvironmentStrategy
     :members:
     :show-inheritance:
-.. autoclass:: ScriptsWebStrategy
-    :members:
-    :show-inheritance:
-.. autoclass:: ScriptsMysqlStrategy
-    :members:
-    :show-inheritance:
-.. autoclass:: ScriptsEmailStrategy
+.. autoclass:: WebStrategy
     :members:
     :show-inheritance:
 
index 3b4ba2369c0d5b026d2e42de1005380347ee007b..347b18b2068172956a8326f798997a433ba92b58 100644 (file)
@@ -47,13 +47,15 @@ For more information on how to create an application, check
 
 
 
-.. _wizard.strategy:
+.. _wizard.install.strategy:
 
-``wizard.strategy``
+``wizard.install.strategy``
 -------------------
 
 Used during installation to automatically determine values for
-installation parameters the user may have omitted.
+installation parameters the user may have omitted.  Plugin should be a
+subclass of :class:`wizard.install.Strategy`.  The class that runs this
+plugin is :class:`wizard.install.ArgSchema`.
 
 
 
index d190dfb69eb259eeff5815b6b0c991cb94c64301..33c727c204500e9753e70d55d2570d052f70aeaa 100644 (file)
@@ -16,5 +16,8 @@ setuptools.setup(
         'wizard.user.passwd': 'scripts = wizard_scripts:user_passwd',
         'wizard.deploy.web': 'scripts = wizard_scripts:deploy_web',
         'wizard.sql.auth': 'scripts = wizard_scripts:sql_auth',
+        'wizard.install.strategy': ['email = wizard_scripts:EmailStrategy',
+                                    'mysql = wizard_scripts:MysqlStrategy',
+                                   ],
     }
 )
index b62f28cce8dea4a9c0d380ad4b2297413d289362..10118c89ac4dfeab79933fc338b2859018f1adb4 100644 (file)
@@ -9,9 +9,10 @@ import logging
 import urlparse
 import time
 import errno
+import sqlalchemy
 
 import wizard
-from wizard import shell, util, user
+from wizard import deploy, shell, install, util, user
 
 def deploy_web(dir):
     # try the directory
@@ -113,7 +114,7 @@ def sql_auth(url):
     return None
 
 def user_email(name):
-    # XXX: simplistic strategy which doesn't work most of the time
+    # XXX: simplistic install which doesn't work most of the time
     return "%s@scripts.mit.edu" % name
 
 def user_operator():
@@ -150,3 +151,44 @@ def user_passwd(dir, uid):
     name, password, uid, gid, gecos, homedir, console = result.split(":")
     realname = gecos.split(",")[0]
     return user.Info(name, uid, gid, realname, homedir, console)
+
+class MysqlStrategy(install.Strategy):
+    """
+    Performs scripts specific guesses for MySQL variables.  This
+    may create an appropriate database for the user.
+    """
+    side_effects = True
+    provides = frozenset(["dsn"])
+    def prepare(self):
+        """Uses the SQL programs in the scripts locker"""
+        if self.application.database != "mysql":
+            raise install.StrategyFailed
+        try:
+            self._triplet = shell.eval("/mit/scripts/sql/bin/get-password").split()
+        except shell.CallError:
+            raise install.StrategyFailed
+        if len(self._triplet) != 3:
+            raise install.StrategyFailed
+        self._username = os.getenv('USER')
+        if self._username is None:
+            raise install.StrategyFailed
+    def execute(self, options):
+        """Creates a new database for the user using ``get-next-database`` and ``create-database``."""
+        host, username, password = self._triplet
+        # race condition
+        name = shell.eval("/mit/scripts/sql/bin/get-next-database", os.path.basename(self.dir))
+        database = shell.eval("/mit/scripts/sql/bin/create-database", name)
+        options.dsn = sqlalchemy.engine.url.URL("mysql", username=username, password=password, host=host, database=database)
+
+class EmailStrategy(install.Strategy):
+    """Performs script specific guess for email."""
+    provides = frozenset(["email"])
+    def prepare(self):
+        """Uses :envvar:`USER` and assumes you are an MIT affiliate."""
+        # XXX: This might be buggy, because locker might be set to USER
+        self._user = os.getenv("USER")
+        if self._user is None:
+            raise install.StrategyFailed
+    def execute(self, options):
+        """No-op."""
+        options.email = self._user + "@mit.edu"
index 70bb3fd90d03b5dedfbb370f5b6cfc91b55942da..149dd34301c6a029188cc865fa6e42d7363b72f6 100644 (file)
@@ -24,6 +24,7 @@ import os
 import logging
 import sqlalchemy
 import warnings
+import pkg_resources
 
 import wizard
 from wizard import deploy, shell, sql, util
@@ -74,6 +75,20 @@ class Strategy(object):
     provides = frozenset()
     #: Whether or not this strategy has side effects.
     side_effects = False
+    # Set at runtime
+    #: The :class:`ArgSchema` being created
+    schema = None
+    #: The :class:`wizard.app.Application` being installed.
+    application = None
+    #: The directory we are being installed to.
+    dir = None
+    #: The directory web stub files are being installed to.
+    web_stub_path = None
+    def __init__(self, schema, application, dir, web_stub_path):
+        self.schema = schema
+        self.application = application
+        self.dir = dir
+        self.web_stub_path = web_stub_path
     def prepare(self):
         """
         Performs all side-effectless computation associated with this
@@ -95,10 +110,11 @@ class EnvironmentStrategy(Strategy):
     Fills in values from environment variables, based off of
     :attr:`Arg.envname` from ``schema``.
     """
-    def __init__(self, schema):
+    def __init__(self, *args):
+        Strategy.__init__(self, *args)
         self.provides = set()
         self.envlookup = {}
-        for arg in schema.args.values():
+        for arg in self.schema.args.values():
             if os.getenv(arg.envname) is not None:
                 self.provides.add(arg.name)
                 self.envlookup[arg.name] = arg.envname
@@ -112,75 +128,32 @@ class EnvironmentStrategy(Strategy):
                 continue
             setattr(options, name, os.getenv(envname))
 
-class ScriptsWebStrategy(Strategy):
-    """Performs scripts specific guesses for web variables."""
-    # XXX: This actually isn't too scripts specific
+class WebStrategy(Strategy):
+    """Performs guesses for web variables using the URL hook."""
     provides = frozenset(["web_host", "web_path"])
-    def __init__(self, dir):
-        self.dir = dir
     def prepare(self):
         """Uses :func:`deploy.web`."""
         if self.dir is None:
             raise StrategyFailed
-        urls = deploy.web(self.dir, None)
-        if not urls:
-            raise StrategyFailed
+        if not os.path.exists(self.dir):
+            os.mkdir(self.dir)
         try:
-            self._url = urls.next() # pylint: disable-msg=E1101
-        except StopIteration:
-            raise StrategyFailed
+            urls = deploy.web(self.dir, None)
+            if not urls:
+                raise StrategyFailed
+            try:
+                self._url = urls.next() # pylint: disable-msg=E1101
+            except StopIteration:
+                raise install.StrategyFailed
+        except:
+            os.rmdir(self.dir)
+            raise
     def execute(self, options):
         """No-op."""
         options.web_host = self._url.netloc # pylint: disable-msg=E1101
         options.web_path = self._url.path   # pylint: disable-msg=E1101
         options.web_inferred = True # hacky: needed to see if we need a .scripts/url file
 
-class ScriptsMysqlStrategy(Strategy):
-    """
-    Performs scripts specific guesses for MySQL variables.  This
-    may create an appropriate database for the user.
-    """
-    side_effects = True
-    provides = frozenset(["dsn"])
-    def __init__(self, application, dir):
-        self.application = application
-        self.dir = dir
-    def prepare(self):
-        """Uses the SQL programs in the scripts locker"""
-        if self.application.database != "mysql":
-            raise StrategyFailed
-        try:
-            self._triplet = shell.eval("/mit/scripts/sql/bin/get-password").split()
-        except shell.CallError:
-            raise StrategyFailed
-        if len(self._triplet) != 3:
-            raise StrategyFailed
-        self._username = os.getenv('USER')
-        if self._username is None:
-            raise StrategyFailed
-    def execute(self, options):
-        """Creates a new database for the user using ``get-next-database`` and ``create-database``."""
-        host, username, password = self._triplet
-        # race condition
-        name = shell.eval("/mit/scripts/sql/bin/get-next-database", os.path.basename(self.dir))
-        database = shell.eval("/mit/scripts/sql/bin/create-database", name)
-        options.dsn = sqlalchemy.engine.url.URL("mysql", username=username, password=password, host=host, database=database)
-
-class ScriptsEmailStrategy(Strategy):
-    """Performs script specific guess for email."""
-    provides = frozenset(["email"])
-    def prepare(self):
-        """Uses :envvar:`USER` and assumes you are an MIT affiliate."""
-        # XXX: should double-check that you're on a scripts server
-        # and fail if you're not.
-        # XXX: This might be buggy, because locker might be set to USER
-        self._user = os.getenv("USER")
-        if self._user is None:
-            raise StrategyFailed
-    def execute(self, options):
-        """No-op."""
-        options.email = self._user + "@mit.edu"
-
 class Arg(object):
     """
     Represent a required, named argument for installation.
@@ -322,16 +295,16 @@ class ArgSchema(object):
         """Populates :attr:`strategies` and :attr:`provides`"""
         self.strategies = []
         self.provides = set()
-        # XXX: separate out soon
         raw_strategies = [
-                EnvironmentStrategy(self),
-                ScriptsWebStrategy(dir),
-                ScriptsWebStrategy(web_stub_path),
-                ScriptsMysqlStrategy(application, dir),
-                ScriptsEmailStrategy(),
+                EnvironmentStrategy,
+                WebStrategy,
                 ]
-        for strategy in raw_strategies:
+        for entry in pkg_resources.iter_entry_points("wizard.install.strategy"):
+            cls = entry.load()
+            raw_strategies.append(cls)
+        for strategy_cls in raw_strategies:
             try:
+                strategy = strategy_cls(self, application, dir, web_stub_path)
                 strategy.prepare()
                 self.provides |= strategy.provides
                 self.strategies.append(strategy)