]> scripts.mit.edu Git - wizard.git/blobdiff - wizard/app/__init__.py
Reduce duplication in test scripts, more logging.
[wizard.git] / wizard / app / __init__.py
index adb9a189b6be8bbd58f557c5c47b781c888c2306..ee9f58deab43bc4990e0fc234033d540bd4f8c45 100644 (file)
@@ -6,6 +6,11 @@ You'll need to know how to overload the :class:`Application` class
 and use some of the functions in this module in order to specify
 new applications.
 
 and use some of the functions in this module in order to specify
 new applications.
 
+There are some submodules for programming languages that define common
+functions and data that may be used by applications in that language.  See:
+
+* :mod:`wizard.app.php`
+
 .. testsetup:: *
 
     import re
 .. testsetup:: *
 
     import re
@@ -76,6 +81,9 @@ class Application(object):
     #: a conflict marker string and a result list.  See :mod:`wizard.resolve`
     #: for more information.
     resolutions = {}
     #: a conflict marker string and a result list.  See :mod:`wizard.resolve`
     #: for more information.
     resolutions = {}
+    #: Instance of :class:`wizard.install.ArgSchema` that defines the arguments
+    #: this application requires.
+    install_schema = None
     def __init__(self, name):
         self.name = name
         self.versions = {}
     def __init__(self, name):
         self.name = name
         self.versions = {}
@@ -222,19 +230,26 @@ class Application(object):
         should provide an implementation.
         """
         raise NotImplementedError
         should provide an implementation.
         """
         raise NotImplementedError
+    def detectVersionFromFile(self, filename, regex):
+        """
+        Helper method that detects a version by using a regular expression
+        from a file.  The regexed value is passed through :mod:`shlex`.
+        This assumes that the current working directory is the deployment.
+        """
+        contents = open(filename).read()
+        match = regex.search(contents)
+        if not match: return None
+        return distutils.version.LooseVersion(shlex.split(match.group(2))[0])
     def download(self, version):
         """
         Returns a URL that can be used to download a tarball of ``version`` of
         this application.
         """
         raise NotImplementedError
     def download(self, version):
         """
         Returns a URL that can be used to download a tarball of ``version`` of
         this application.
         """
         raise NotImplementedError
-    def checkWeb(self, deployment, output=None):
+    def checkWeb(self, deployment):
         """
         """
-        Checks if the autoinstall is viewable from the web.  To get
-        the HTML source that was retrieved, pass a variable containing
-        an empty list to ``output``; it will be mutated to have its
-        first element be the output.  Subclasses should provide an
-        implementation.
+        Checks if the autoinstall is viewable from the web.  Subclasses should
+        provide an implementation.
 
         .. note::
             Finding a reasonable heuristic that works across skinning
 
         .. note::
             Finding a reasonable heuristic that works across skinning
@@ -244,6 +259,17 @@ class Application(object):
             not to depend on pages that are not the main page.
         """
         raise NotImplementedError
             not to depend on pages that are not the main page.
         """
         raise NotImplementedError
+    def checkWebPage(self, deployment, page, output):
+        """
+        Checks if a given page of an autoinstall contains a particular string.
+        """
+        page = deployment.fetch(page)
+        result = page.find(output) != -1
+        if result:
+            logging.debug("checkWebPage (passed):\n\n" + page)
+        else:
+            logging.info("checkWebPage (failed):\n\n" + page)
+        return result
     def checkConfig(self, deployment):
         """
         Checks whether or not an autoinstall has been configured/installed
     def checkConfig(self, deployment):
         """
         Checks whether or not an autoinstall has been configured/installed
@@ -361,7 +387,7 @@ def expand_re(val):
 
 def make_extractors(seed):
     """
 
 def make_extractors(seed):
     """
-    Take a dictionary of ``key``s to ``(file, regex)`` tuples and convert them into
+    Take a dictionary of ``key`` to ``(file, regex)`` tuples and convert them into
     extractor functions (which take a :class:`wizard.deploy.Deployment`
     and return the value of the second subpattern of ``regex`` when matched
     with the contents of ``file``).
     extractor functions (which take a :class:`wizard.deploy.Deployment`
     and return the value of the second subpattern of ``regex`` when matched
     with the contents of ``file``).
@@ -370,7 +396,7 @@ def make_extractors(seed):
 
 def make_substitutions(seed):
     """
 
 def make_substitutions(seed):
     """
-    Take a dictionary of ``key``s to ``(file, regex)`` tuples and convert them into substitution
+    Take a dictionary of ``key`` to ``(file, regex)`` tuples and convert them into substitution
     functions (which take a :class:`wizard.deploy.Deployment`, replace the second subpattern
     of ``regex`` with ``key`` in ``file``, and returns the number of substitutions made.)
     """
     functions (which take a :class:`wizard.deploy.Deployment`, replace the second subpattern
     of ``regex`` with ``key`` in ``file``, and returns the number of substitutions made.)
     """
@@ -508,7 +534,7 @@ def get_mysql_args(d):
     # XXX: add support for getting these out of options
     vars = d.extract()
     if 'WIZARD_DBNAME' not in vars:
     # XXX: add support for getting these out of options
     vars = d.extract()
     if 'WIZARD_DBNAME' not in vars:
-        raise app.BackupFailure("Could not determine database name")
+        raise BackupFailure("Could not determine database name")
     triplet = scripts.get_sql_credentials(vars)
     args = []
     if triplet is not None:
     triplet = scripts.get_sql_credentials(vars)
     args = []
     if triplet is not None:
@@ -522,10 +548,6 @@ class Error(wizard.Error):
     """Generic error class for this module."""
     pass
 
     """Generic error class for this module."""
     pass
 
-class NonfatalFailure(Error):
-    """Bad parameters given to installer."""
-    pass
-
 class NoRepositoryError(Error):
     """
     :class:`Application` does not appear to have a Git repository
 class NoRepositoryError(Error):
     """
     :class:`Application` does not appear to have a Git repository
@@ -563,7 +585,33 @@ class NoSuchApplication(Error):
     def __init__(self, app):
         self.app = app
 
     def __init__(self, app):
         self.app = app
 
-class UpgradeFailure(Error):
+class Failure(Error):
+    """
+    Represents a failure when performing some double-dispatched operation
+    such as an installation or an upgrade.  Failure classes are postfixed
+    with Failure, not Error.
+    """
+    pass
+
+class InstallFailure(Error):
+    """Installation failed for unknown reason."""
+    def __str__(self):
+        return """Installation failed for unknown reason."""
+
+class RecoverableInstallFailure(InstallFailure):
+    """
+    Installation failed, but we were able to determine what the
+    error was, and should give the user a second chance if we were
+    running interactively.
+    """
+    #: List of the errors that were found.
+    errors = None
+    def __init__(self, errors):
+        self.errors = errors
+    def __str__(self):
+        return """Installation failed due to the following errors: %s""" % ", ".join(self.errors)
+
+class UpgradeFailure(Failure):
     """Upgrade script failed."""
     #: String details of failure (possibly stdout or stderr output)
     details = None
     """Upgrade script failed."""
     #: String details of failure (possibly stdout or stderr output)
     details = None
@@ -576,20 +624,15 @@ ERROR: Upgrade script failed, details:
 
 %s""" % self.details
 
 
 %s""" % self.details
 
-class UpgradeVerificationFailure(Error):
+class UpgradeVerificationFailure(Failure):
     """Upgrade script passed, but website wasn't accessible afterwards"""
     """Upgrade script passed, but website wasn't accessible afterwards"""
-    #: String details of failure (possibly stdout or stderr output)
-    details = None
-    def __init__(self, details):
-        self.details = details
     def __str__(self):
         return """
 
     def __str__(self):
         return """
 
-ERROR: Upgrade script passed, but website wasn't accessible afterwards. Details:
-
-%s""" % self.details
+ERROR: Upgrade script passed, but website wasn't accessible afterwards.  Check
+the debug logs for the contents of the page."""
 
 
-class BackupFailure(Error):
+class BackupFailure(Failure):
     """Backup script failed."""
     #: String details of failure
     details = None
     """Backup script failed."""
     #: String details of failure
     details = None
@@ -602,7 +645,7 @@ ERROR: Backup script failed, details:
 
 %s""" % self.details
 
 
 %s""" % self.details
 
-class RestoreFailure(Error):
+class RestoreFailure(Failure):
     """Restore script failed."""
     #: String details of failure
     details = None
     """Restore script failed."""
     #: String details of failure
     details = None