]> scripts.mit.edu Git - wizard.git/blobdiff - wizard/command/migrate.py
Allow migration of non old-scripts autoinstalls.
[wizard.git] / wizard / command / migrate.py
index b159427dbed5b2972f378a1867cd1205a94b6d38..d3dce85ef3f10b0e82620b24dea53fca3c6322be 100644 (file)
@@ -1,70 +1,74 @@
 import os
-import itertools
+import os.path
 import shutil
 import logging
-import errno
-import sys
 
-from wizard import command, deploy, shell, util
+from wizard import app, command, deploy, shell, util
 
 def main(argv, baton):
     options, args = parse_args(argv, baton)
-    dir = args[0]
-
-    shell.drop_priviledges(dir, options)
-
+    dir = os.path.abspath(args[0]) if args else os.getcwd()
+    shell.drop_priviledges(dir, options.log_file)
     util.chdir(dir)
+
     sh = shell.Shell(options.dry_run)
 
     logging.info("Migrating %s" % dir)
     logging.debug("uid is %d" % os.getuid())
 
-    deployment = deploy.Deployment(".")
-
-    # deal with old-style migration, remove this later
-    if os.path.isfile(".scripts/old-version") and not os.path.isfile(".scripts-version"):
-        os.rename(".scripts/old-version", ".scripts-version")
+    deployment = deploy.ProductionCopy(".")
 
     os.unsetenv("GIT_DIR") # prevent some perverse errors
 
     try:
         deployment.verify()
         raise AlreadyMigratedError(deployment.location)
+    except deploy.NotAutoinstallError:
+        # Previously, this was a fatal error, but now let's try
+        # a little harder.
+        # XXX: The user still has to tell us what application ; a more
+        # user friendly thing to do is figure it out automatically
+        if not options.force_app:
+            raise
+        # actual version number will get overwritten shortly
+        deployment.setAppVersion(app.ApplicationVersion.make(options.force_app, "unknown"))
     except deploy.NotMigratedError:
+        # LEGACY
         pass
     except (deploy.CorruptedAutoinstallError, AlreadyMigratedError):
-        if options.force:
-            perform_force(options)
-        else:
+        if not options.force:
             raise
 
-    deployment.verifyTag(options.srv_path)
-
     if options.force_version:
-        version = deployment.application.makeVersion(options.force_version)
+        deployment.setAppVersion(deployment.application.makeVersion(options.force_version))
     else:
-        deployment.verifyVersion()
-        version = deployment.app_version
-    repo    = version.application.repository(options.srv_path)
-    tag     = version.scripts_tag
+        try:
+            deployment.verifyVersion()
+        except deploy.VersionMismatchError as e:
+            # well, we'll use that then
+            deployment.setAppVersion(deployment.application.makeVersion(str(e.real_version)))
 
-    # XXX: turn this into a context
+    deployment.verifyTag(options.srv_path)
+
+    repo = deployment.application.repository(options.srv_path)
+    tag = deployment.app_version.wizard_tag
     try:
+        sh.call("git", "--git-dir=%s" % repo, "rev-parse", tag)
+    except shell.CallError:
+        raise UnsupportedVersion(deployment.version)
+
+    with util.LockDirectory(".wizard-migrate-lock"):
         try:
-            os.open(".scripts-migrate-lock", os.O_CREAT | os.O_EXCL)
-        except OSError as e:
-            if e.errno == errno.EEXIST:
-                raise DirectoryLockedError
-            elif e.errno == errno.EACCES:
-                raise command.PermissionsError(dir)
-            raise
-        make_repository(sh, options, repo, tag)
-        check_variables(deployment, options)
-    finally:
-        try:
-            os.unlink(".scripts-migrate-lock")
-        except OSError:
-            pass
+            if options.force:
+                perform_force(options)
+            make_repository(sh, options, repo, tag)
+            check_variables(deployment, options)
+        except KeyboardInterrupt:
+            # revert it; barring zany race conditions this is safe
+            if os.path.exists(".wizard"):
+                shutil.rmtree(".wizard")
+            if os.path.exists(".git"):
+                shutil.rmtree(".git")
 
 def parse_args(argv, baton):
     usage = """usage: %prog migrate [ARGS] DIR
@@ -73,49 +77,38 @@ Migrates a directory to our Git-based autoinstall format.
 Performs basic sanity checking and intelligently determines
 what repository and tag to use.
 
-This command is meant to be run as the owner of the install
-it is upgrading (see the scripts AFS kernel patch).  Do
-NOT run this command as root."""
+This command is meant to be run as the owner of the install it is
+upgrading .  Do NOT run this command as root."""
     parser = command.WizardOptionParser(usage)
     baton.push(parser, "srv_path")
     parser.add_option("--dry-run", dest="dry_run", action="store_true",
             default=False, help="Prints would would be run without changing anything")
     parser.add_option("--force", "-f", dest="force", action="store_true",
-            default=False, help="If .git or .scripts directory already exists,"
+            default=False, help="If .git or .wizard directory already exists, "
             "delete them and migrate")
     parser.add_option("--force-version", dest="force_version",
-            default=None, help="If .scripts-version tells lies, explicitly specify"
+            default=None, help="If .scripts-version is corrupted or non-existent, explicitly specify "
             "a version to migrate to.")
+    parser.add_option("--force-app", dest="force_app",
+            default=None, help="If .scripts-version is corrupted or non-existent, explicitly specify "
+            "an application to migrate to.")
     options, args = parser.parse_all(argv)
     if len(args) > 1:
         parser.error("too many arguments")
-    elif not args:
-        parser.error("must specify directory")
     return (options, args)
 
 def perform_force(options):
     has_git = os.path.isdir(".git")
-    has_scripts = os.path.isdir(".scripts")
+    has_wizard = os.path.isdir(".wizard")
 
     if has_git:
         logging.warning("Force removing .git directory")
-        if not options.dry_run: backup = safe_unlink(".git")
+        if not options.dry_run: backup = util.safe_unlink(".git")
         logging.info(".git backed up to %s" % backup)
-    if has_scripts:
-        logging.warning("Force removing .scripts directory")
-        if not options.dry_run: backup = safe_unlink(".scripts")
-        logging.info(".scripts backed up to %s" % backup)
-
-def safe_unlink(file):
-    """Moves a file to a backup location."""
-    prefix = "%s.bak" % file
-    name = None
-    for i in itertools.count():
-        name = "%s.%d" % (prefix, i)
-        if not os.path.exists(name):
-            break
-    os.rename(file, name)
-    return name
+    if has_wizard:
+        logging.warning("Force removing .wizard directory")
+        if not options.dry_run: backup = util.safe_unlink(".wizard")
+        logging.info(".wizard backed up to %s" % backup)
 
 def make_repository(sh, options, repo, tag):
     sh.call("git", "init") # create repository
@@ -132,7 +125,7 @@ def make_repository(sh, options, repo, tag):
     else:
         logging.info("# create %s containing \"%s\"" % (file, data))
         logging.info('# create .htaccess containing "Deny from all"')
-    # configure our remote (this is merely for convenience; wizard scripts
+    # configure our remote (this is merely for convenience; wizard
     # will not rely on this)
     sh.call("git", "remote", "add", "origin", repo)
     # configure what would normally be set up on a 'git clone' for consistency
@@ -142,11 +135,11 @@ def make_repository(sh, options, repo, tag):
     sh.call("git", "fetch", "origin")
     # soft reset to our tag
     sh.call("git", "reset", tag, "--")
-    # checkout the .scripts directory
-    sh.call("git", "checkout", ".scripts")
+    # initialize the .wizard directory
+    util.init_wizard_dir()
     logging.info("Diffstat:\n" + sh.eval("git", "diff", "--stat"))
     # commit user local changes
-    message = "Autoinstall migration of %s locker.\n\n%s" % (util.get_dir_owner(), util.get_git_footer())
+    message = "Autoinstall migration.\n\n%s" % util.get_git_footer()
     util.set_git_env()
     try:
         message += "\nMigrated-by: " + util.get_operator_git()
@@ -168,23 +161,25 @@ class Error(command.Error):
     pass
 
 class AlreadyMigratedError(Error):
+    quiet = True
     def __init__(self, dir):
         self.dir = dir
     def __str__(self):
         return """
 
-ERROR: Directory already contains a .git and
-.scripts directory.  If you force this migration,
-both of these directories will be removed.
+This autoinstall is already migrated; move along, nothing to
+see here.  (If you really want to, you can force a re-migration
+with --force, but this will blow away the existing .git and
+.scripts directories (i.e. your history and Wizard configuration).)
 """
 
-class DirectoryLockedError(Error):
-    def __init__(self, dir):
-        self.dir = dir
+class UnsupportedVersion(Error):
+    def __init__(self, version):
+        self.version = version
     def __str__(self):
         return """
 
-ERROR: Could not acquire lock on directory.  Maybe there is
-another migration process running?
-"""
-
+ERROR: This autoinstall is presently on %s, which is unsupported by
+Wizard.  Please manually upgrade it to one that is supported,
+and then retry the migration; usually the latest version is supported.
+""" % self.version