]> scripts.mit.edu Git - wizard.git/blobdiff - wizard/deploy.py
More remaster points!
[wizard.git] / wizard / deploy.py
index d559c79afcb28a8840d7747ce2d430cc96344c06..651091097d407236b9f06507012c8e881da6e879 100644 (file)
@@ -68,7 +68,7 @@ def parse_install_lines(show, versions_store, yield_errors = False, user = None)
             logging.warning("Error with '%s'" % line.rstrip())
             continue
         # filter
             logging.warning("Error with '%s'" % line.rstrip())
             continue
         # filter
-        if name + "-" + str(d.version) in show or name in show:
+        if name + "-" + util.truncate(str(d.version)) in show or name in show:
             pass
         else:
             continue
             pass
         else:
             continue
@@ -86,25 +86,32 @@ def web(dir, url=None):
     if url:
         if isinstance(url, str):
             url = urlparse.urlparse(url)
     if url:
         if isinstance(url, str):
             url = urlparse.urlparse(url)
+        logging.info("wizard.deploy.web: Using default URL %s", url)
         yield url
         return
 
     for entry in pkg_resources.iter_entry_points("wizard.deploy.web"):
         yield url
         return
 
     for entry in pkg_resources.iter_entry_points("wizard.deploy.web"):
+        logging.debug("wizard.deploy.web: Processing %s", entry)
         f = entry.load()
         for r in f(dir):
             if isinstance(r, str):
                 r = urlparse.urlparse(r)
         f = entry.load()
         for r in f(dir):
             if isinstance(r, str):
                 r = urlparse.urlparse(r)
+            logging.info("wizard.deploy.web: Using plugin-supplied URL %s", r)
             yield r
 
     # try the environment
     host = os.getenv("WIZARD_WEB_HOST")
     path = os.getenv("WIZARD_WEB_PATH")
     if host is not None and path is not None:
             yield r
 
     # try the environment
     host = os.getenv("WIZARD_WEB_HOST")
     path = os.getenv("WIZARD_WEB_PATH")
     if host is not None and path is not None:
-        yield urlparse.ParseResult(
-                    "http",
-                    host,
-                    path.rstrip('/'),
-                    "", "", "")
+        r = urlparse.ParseResult(
+                "http",
+                host,
+                path.rstrip('/'),
+                "", "", "")
+        logging.info("wizard.deploy.web: Using environment URL %s", r)
+        yield r
+
+    logging.info("wizard.deploy.web: Exhausted URLs")
 
 ## -- Model Objects --
 
 
 ## -- Model Objects --
 
@@ -119,15 +126,20 @@ def chdir_to_location(f, self, *args, **kwargs):
 
 class Deployment(object):
     """
 
 class Deployment(object):
     """
-    Represents a deployment of an autoinstall, e.g. directory
-    that has ``.scripts`` directory or ``.scripts-version``
-    file in it.  Supply ``version`` with an :class:`ApplicationVersion` only if
-    you were reading from the :term:`versions store` and care about
-    speed (data from there can be stale).
+    Represents a deployment of an autoinstall, e.g. directory that has a
+    ``.wizard`` directory in it.  Supply ``version`` with an
+    :class:`ApplicationVersion` only if you were reading from the
+    :term:`versions store` and care about speed (data from there can be
+    stale).
 
     The Deployment interface is somewhat neutered, so you may
     want to use :class:`WorkingCopy` or :class:`ProductionCopy` for
     more powerful operations.
 
     The Deployment interface is somewhat neutered, so you may
     want to use :class:`WorkingCopy` or :class:`ProductionCopy` for
     more powerful operations.
+
+    .. note::
+
+        For legacy purposes, deployments can also be marked by a
+        ``.scripts`` directory or a ``.scripts-version`` file.
     """
     #: Absolute path to the deployment
     location = None
     """
     #: Absolute path to the deployment
     location = None
@@ -140,6 +152,7 @@ class Deployment(object):
         self._dsn = None
         self._url = None
         self._urlGen = None
         self._dsn = None
         self._url = None
         self._urlGen = None
+        self._wizard_dir = None
     def invalidateCache(self):
         """
         Invalidates all cached variables.  This currently applies to
     def invalidateCache(self):
         """
         Invalidates all cached variables.  This currently applies to
@@ -148,6 +161,13 @@ class Deployment(object):
         self._app_version = None
         self._read_cache = {}
         self._old_log = None
         self._app_version = None
         self._read_cache = {}
         self._old_log = None
+    def setAppVersion(self, app_version):
+        """
+        Manually resets the application version; useful if the working
+        copy is off in space (i.e. not anchored to something we can
+        git describe off of) or there is no metadata to be heard of.
+        """
+        self._app_version = app_version
     def read(self, file, force = False):
         """
         Reads a file's contents, possibly from cache unless ``force``
     def read(self, file, force = False):
         """
         Reads a file's contents, possibly from cache unless ``force``
@@ -167,19 +187,25 @@ class Deployment(object):
         """
         return self.application.extract(self)
 
         """
         return self.application.extract(self)
 
-    def verify(self):
+    def verify(self, no_touch=False):
         """
         Checks if this is an autoinstall, throws an exception if there
         """
         Checks if this is an autoinstall, throws an exception if there
-        are problems.
+        are problems.  If ``no_touch`` is ``True``, it will not attempt
+        edit the installation.
         """
         with util.ChangeDirectory(self.location):
             has_git = os.path.isdir(".git")
         """
         with util.ChangeDirectory(self.location):
             has_git = os.path.isdir(".git")
-            has_scripts = os.path.isdir(".scripts")
-            if not has_git and has_scripts:
+            has_wizard = os.path.isdir(".wizard")
+            if not has_wizard and os.path.isdir(".scripts"):
+                # LEGACY
+                os.symlink(".scripts", ".wizard")
+                has_wizard = True
+            if not has_git and has_wizard:
                 raise CorruptedAutoinstallError(self.location)
                 raise CorruptedAutoinstallError(self.location)
-            elif has_git and not has_scripts:
+            elif has_git and not has_wizard:
                 raise AlreadyVersionedError(self.location)
                 raise AlreadyVersionedError(self.location)
-            elif not has_git and not has_scripts:
+            # LEGACY
+            elif not has_git and not has_wizard:
                 if os.path.isfile(".scripts-version"):
                     raise NotMigratedError(self.location)
                 else:
                 if os.path.isfile(".scripts-version"):
                     raise NotMigratedError(self.location)
                 else:
@@ -192,9 +218,9 @@ class Deployment(object):
         """
         repo = self.application.repository(srv_path)
         try:
         """
         repo = self.application.repository(srv_path)
         try:
-            shell.eval("git", "--git-dir", repo, "rev-parse", self.app_version.scripts_tag, '--')
+            shell.eval("git", "--git-dir", repo, "rev-parse", self.app_version.wizard_tag, '--')
         except shell.CallError:
         except shell.CallError:
-            raise NoTagError(self.app_version.scripts_tag)
+            raise NoTagError(self.app_version.wizard_tag)
 
     def verifyGit(self, srv_path):
         """
 
     def verifyGit(self, srv_path):
         """
@@ -216,12 +242,14 @@ class Deployment(object):
                 return repo_rev_parse(tag) == self_rev_parse(tag)
             if not compare_tags(self.app_version.pristine_tag):
                 raise InconsistentPristineTagError(self.app_version.pristine_tag)
                 return repo_rev_parse(tag) == self_rev_parse(tag)
             if not compare_tags(self.app_version.pristine_tag):
                 raise InconsistentPristineTagError(self.app_version.pristine_tag)
-            if not compare_tags(self.app_version.scripts_tag):
-                raise InconsistentScriptsTagError(self.app_version.scripts_tag)
-            parent = repo_rev_parse(self.app_version.scripts_tag)
+            if not compare_tags(self.app_version.wizard_tag):
+                # XXX Try remastering
+                raise InconsistentWizardTagError(self.app_version.wizard_tag)
+            parent = repo_rev_parse(self.app_version.wizard_tag)
             merge_base = shell.safeCall("git", "merge-base", parent, "HEAD", strip=True)
             if merge_base != parent:
             merge_base = shell.safeCall("git", "merge-base", parent, "HEAD", strip=True)
             if merge_base != parent:
-                raise HeadNotDescendantError(self.app_version.scripts_tag)
+                # XXX Try remastering
+                raise HeadNotDescendantError(self.app_version.wizard_tag)
 
     def verifyConfigured(self):
         """
 
     def verifyConfigured(self):
         """
@@ -261,34 +289,42 @@ class Deployment(object):
     @property
     def migrated(self):
         """Whether or not the autoinstalls has been migrated."""
     @property
     def migrated(self):
         """Whether or not the autoinstalls has been migrated."""
-        return os.path.isdir(self.scripts_dir)
+        return os.path.isdir(self.wizard_dir)
     @property
     @property
-    def scripts_dir(self):
-        """The absolute path of the ``.scripts`` directory."""
-        return os.path.join(self.location, '.scripts')
+    def wizard_dir(self):
+        """The absolute path of the Wizard directory."""
+        return os.path.join(self.location, ".wizard")
+    @property
+    def backup_dir(self):
+        """The absolute path to ``.wizard/backups``."""
+        return os.path.join(self.wizard_dir, "backups")
+    # LEGACY
     @property
     def old_version_file(self):
         """
     @property
     def old_version_file(self):
         """
-        The absolute path of either ``.scripts-version`` (for unmigrated
-        installs) or ``.scripts/version``.
-
-        .. note::
-
-            Use of this is discouraged for migrated installs.
+        The absolute path of either ``.scripts-version``.
         """
         return os.path.join(self.location, '.scripts-version')
     @property
         """
         return os.path.join(self.location, '.scripts-version')
     @property
+    def blacklisted_file(self):
+        """The absolute path of the ``.wizard/blacklisted`` file."""
+        return os.path.join(self.wizard_dir, 'blacklisted')
+    @property
+    def pending_file(self):
+        """The absolute path of the ``.wizard/pending`` file."""
+        return os.path.join(self.wizard_dir, 'pending')
+    @property
     def version_file(self):
     def version_file(self):
-        """The absolute path of the ``.scripts/version`` file."""
-        return os.path.join(self.scripts_dir, 'version')
+        """The absolute path of the ``.wizard/version`` file."""
+        return os.path.join(self.wizard_dir, 'version')
     @property
     def dsn_file(self):
     @property
     def dsn_file(self):
-        """The absolute path of the :file:`.scripts/dsn` override file."""
-        return os.path.join(self.scripts_dir, 'dsn')
+        """The absolute path of the :file:`.wizard/dsn` override file."""
+        return os.path.join(self.wizard_dir, 'dsn')
     @property
     def url_file(self):
     @property
     def url_file(self):
-        """The absolute path of the :file:`.scripts/url` override file."""
-        return os.path.join(self.scripts_dir, 'url')
+        """The absolute path of the :file:`.wizard/url` override file."""
+        return os.path.join(self.wizard_dir, 'url')
     @property
     def application(self):
         """The :class:`app.Application` of this deployment."""
     @property
     def application(self):
         """The :class:`app.Application` of this deployment."""
@@ -321,6 +357,7 @@ class Deployment(object):
                 except shell.CallError:
                     pass
         if not self._app_version:
                 except shell.CallError:
                     pass
         if not self._app_version:
+            # LEGACY
             try:
                 self._app_version = self.old_log[-1].version
             except old_log.ScriptsVersionNoSuchFile:
             try:
                 self._app_version = self.old_log[-1].version
             except old_log.ScriptsVersionNoSuchFile:
@@ -396,10 +433,9 @@ class ProductionCopy(Deployment):
         # There are retarded amounts of race-safety in this function,
         # because we do NOT want to claim to have made a backup, when
         # actually something weird happened to it.
         # There are retarded amounts of race-safety in this function,
         # because we do NOT want to claim to have made a backup, when
         # actually something weird happened to it.
-        backupdir = os.path.join(self.scripts_dir, "backups")
-        if not os.path.exists(backupdir):
+        if not os.path.exists(self.backup_dir):
             try:
             try:
-                os.mkdir(backupdir)
+                os.mkdir(self.backup_dir)
             except OSError as e:
                 if e.errno == errno.EEXIST:
                     pass
             except OSError as e:
                 if e.errno == errno.EEXIST:
                     pass
@@ -413,10 +449,10 @@ class ProductionCopy(Deployment):
             shutil.rmtree(tmpdir)
             raise
         backup = None
             shutil.rmtree(tmpdir)
             raise
         backup = None
-        with util.LockDirectory(os.path.join(backupdir, "lock")):
+        with util.LockDirectory(os.path.join(self.backup_dir, "lock")):
             while 1:
                 backup = str(self.version) + "-" + datetime.datetime.today().strftime("%Y-%m-%dT%H%M%S")
             while 1:
                 backup = str(self.version) + "-" + datetime.datetime.today().strftime("%Y-%m-%dT%H%M%S")
-                outdir = os.path.join(backupdir, backup)
+                outdir = os.path.join(self.backup_dir, backup)
                 if os.path.exists(outdir):
                     logging.warning("Backup: A backup occurred in the last second. Trying again in a second...")
                     time.sleep(1)
                 if os.path.exists(outdir):
                     logging.warning("Backup: A backup occurred in the last second. Trying again in a second...")
                     time.sleep(1)
@@ -440,8 +476,7 @@ class ProductionCopy(Deployment):
         does, so you probably do NOT want to call this elsewhere unless
         you know what you're doing (call 'wizard restore' instead).
         """
         does, so you probably do NOT want to call this elsewhere unless
         you know what you're doing (call 'wizard restore' instead).
         """
-        backup_dir = os.path.join(".scripts", "backups", backup)
-        return self.application.restore(self, backup_dir, options)
+        return self.application.restore(self, os.path.join(self.backup_dir, backup), options)
     @chdir_to_location
     def remove(self, options):
         """
     @chdir_to_location
     def remove(self, options):
         """
@@ -481,13 +516,6 @@ class WorkingCopy(Deployment):
     modifications to without fear of interfering with a production
     deployment.  More operations are permitted on these copies.
     """
     modifications to without fear of interfering with a production
     deployment.  More operations are permitted on these copies.
     """
-    def setAppVersion(self, app_version):
-        """
-        Manually resets the application version; useful if the working
-        copy is off in space (i.e. not anchored to something we can
-        git describe off of.)
-        """
-        self._app_version = app_version
     @chdir_to_location
     def parametrize(self, deployment):
         """
     @chdir_to_location
     def parametrize(self, deployment):
         """
@@ -527,10 +555,11 @@ class Error(wizard.Error):
     """Base error class for this module"""
     pass
 
     """Base error class for this module"""
     pass
 
+# LEGACY
 class NotMigratedError(Error):
     """
     The deployment contains a .scripts-version file, but no .git
 class NotMigratedError(Error):
     """
     The deployment contains a .scripts-version file, but no .git
-    or .scripts directory.
+    or .wizard directory.
     """
     #: Directory of deployment
     dir = None
     """
     #: Directory of deployment
     dir = None
@@ -540,7 +569,7 @@ class NotMigratedError(Error):
         return """This installation was not migrated"""
 
 class AlreadyVersionedError(Error):
         return """This installation was not migrated"""
 
 class AlreadyVersionedError(Error):
-    """The deployment contained a .git directory but no .scripts directory."""
+    """The deployment contained a .git directory but no .wizard directory."""
     #: Directory of deployment
     dir = None
     def __init__(self, dir):
     #: Directory of deployment
     dir = None
     def __init__(self, dir):
@@ -549,7 +578,7 @@ class AlreadyVersionedError(Error):
         return """
 
 ERROR: Directory contains a .git directory, but not
         return """
 
 ERROR: Directory contains a .git directory, but not
-a .scripts directory.  If this is not a corrupt
+a .wizard directory.  If this is not a corrupt
 migration, this means that the user was versioning their
 install using Git."""
 
 migration, this means that the user was versioning their
 install using Git."""
 
@@ -566,7 +595,7 @@ ERROR: The install was well-formed, but not configured
 (essential configuration files were not found.)"""
 
 class CorruptedAutoinstallError(Error):
 (essential configuration files were not found.)"""
 
 class CorruptedAutoinstallError(Error):
-    """The install was missing a .git directory, but had a .scripts directory."""
+    """The install was missing a .git directory, but had a .wizard directory."""
     #: Directory of the corrupted install
     dir = None
     def __init__(self, dir):
     #: Directory of the corrupted install
     dir = None
     def __init__(self, dir):
@@ -574,7 +603,7 @@ class CorruptedAutoinstallError(Error):
     def __str__(self):
         return """
 
     def __str__(self):
         return """
 
-ERROR: Directory contains a .scripts directory,
+ERROR: Directory contains a .wizard directory,
 but not a .git directory."""
 
 class NotAutoinstallError(Error):
 but not a .git directory."""
 
 class NotAutoinstallError(Error):
@@ -628,8 +657,8 @@ class InconsistentPristineTagError(Error):
 ERROR: Local pristine tag %s did not match repository's.  This
 probably means an upstream rebase occured.""" % self.tag
 
 ERROR: Local pristine tag %s did not match repository's.  This
 probably means an upstream rebase occured.""" % self.tag
 
-class InconsistentScriptsTagError(Error):
-    """Scripts tag commit ID does not match upstream scripts tag commit ID."""
+class InconsistentWizardTagError(Error):
+    """Wizard tag commit ID does not match upstream wizard tag commit ID."""
     #: Inconsistent tag
     tag = None
     def __init__(self, tag):
     #: Inconsistent tag
     tag = None
     def __init__(self, tag):
@@ -637,8 +666,9 @@ class InconsistentScriptsTagError(Error):
     def __str__(self):
         return """
 
     def __str__(self):
         return """
 
-ERROR: Local scripts tag %s did not match repository's.  This
-probably means an upstream rebase occurred.""" % self.tag
+ERROR: Local wizard tag %s did not match repository's.  This
+probably means an upstream rebase occurred.  Try
+'git fetch --tags && wizard remaster'.""" % self.tag
 
 class HeadNotDescendantError(Error):
     """HEAD is not connected to tag."""
 
 class HeadNotDescendantError(Error):
     """HEAD is not connected to tag."""
@@ -651,7 +681,8 @@ class HeadNotDescendantError(Error):
 
 ERROR: HEAD is not a descendant of %s.  This probably
 means that an upstream rebase occurred, and new tags were
 
 ERROR: HEAD is not a descendant of %s.  This probably
 means that an upstream rebase occurred, and new tags were
-pulled, but local user commits were never rebased.""" % self.tag
+pulled, but local user commits were never rebased.  Try
+running 'wizard remaster'.""" % self.tag
 
 class VersionDetectionError(Error):
     """Could not detect real version of application."""
 
 class VersionDetectionError(Error):
     """Could not detect real version of application."""