X-Git-Url: https://scripts.mit.edu/gitweb/wizard.git/blobdiff_plain/416eb714361b036bfb5f55acad5075330e1668aa..ee8e5d33462267936f1b03e1d22c075c65d36854:/wizard/command/migrate.py diff --git a/wizard/command/migrate.py b/wizard/command/migrate.py index 1851885..0393c8f 100644 --- a/wizard/command/migrate.py +++ b/wizard/command/migrate.py @@ -1,61 +1,65 @@ -import optparse -import sys import os +import itertools import shutil -import logging.handlers +import logging import errno +import sys -from wizard import deploy -from wizard import shell -from wizard.command import _base +from wizard import command, deploy, shell, util -class Error(_base.Error): - """Base exception for all exceptions raised by migrate""" - pass +def main(argv, baton): + options, args = parse_args(argv, baton) + dir = args[0] -class AlreadyMigratedError(Error): - def __init__(self, dir): - self.dir = dir - def __str__(self): - return """ + shell.drop_priviledges(dir, options.log_file) -ERROR: Directory already contains a .git and/or -.scripts directory. Did you already migrate it? -""" + util.chdir(dir) + sh = shell.Shell(options.dry_run) -class NotAutoinstallError(Error): - def __init__(self, dir): - self.dir = dir - def __str__(self): - return """ + logging.info("Migrating %s" % dir) + logging.debug("uid is %d" % os.getuid()) -ERROR: Could not find .scripts-version file. Are you sure -this is an autoinstalled application? -""" + deployment = deploy.ProductionCopy(".") -class NoRepositoryError(Error): - def __init__(self, app): - self.app = app - def __str__(self): - return """ + # 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") -ERROR: Could not find repository for this application. Have -you converted the repository over? Is the name %s -the same as the name of the .git folder? -""" % self.app + os.unsetenv("GIT_DIR") # prevent some perverse errors -class NoTagError(Error): - def __init__(self, version): - self.version = version - def __str__(self): - return """ + try: + deployment.verify() + raise AlreadyMigratedError(deployment.location) + except deploy.NotMigratedError: + pass + except (deploy.CorruptedAutoinstallError, AlreadyMigratedError): + if not options.force: + raise + + deployment.verifyTag(options.srv_path) -ERROR: Could not find tag v%s-scripts in repository -for %s. Double check and make sure -the repository was prepared with all necessary tags! -""" % (self.version.version, self.version.application.name) + if options.force_version: + version = deployment.application.makeVersion(options.force_version) + else: + deployment.verifyVersion() + version = deployment.app_version + repo = version.application.repository(options.srv_path) + tag = version.scripts_tag + + with util.LockDirectory(".scripts-migrate-lock"): + try: + 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(".scripts"): + shutil.rmtree(".scripts") + if os.path.exists(".git"): + shutil.rmtree(".git") -def main(argv, global_options, logger = None): +def parse_args(argv, baton): usage = """usage: %prog migrate [ARGS] DIR Migrates a directory to our Git-based autoinstall format. @@ -65,66 +69,38 @@ 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.""" - parser = _base.WizardOptionParser(usage) + 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, delete them and migrate") - options, args, logger = parser.parse_all(argv, logger) + default=False, help="If .git or .scripts 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" + "a version 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") - logger.debug("uid is %d" % os.getuid()) - dir = args[0] - try: - os.chdir(dir) - except OSError as e: - if e.errno == errno.EACCES: - raise _base.PermissionsError(dir) - elif e.errno == errno.ENOENT: - raise _base.NoSuchDirectoryError(dir) - else: raise e - if os.path.isdir(".git") or os.path.isdir(".scripts"): - if not options.force: - raise AlreadyMigratedError(dir) - else: - if os.path.isdir(".git"): - logger.warning("Force removing .git directory") - if not options.dry_run: shutil.rmtree(".git") - if os.path.isdir(".scripts"): - logger.warning("Force removing .scripts directory") - if not options.dry_run: shutil.rmtree(".scripts") - try: - d = deploy.Deployment.fromDir(".") - version = d.getAppVersion() - except IOError as e: - if e.errno == errno.ENOENT: - raise NotAutoinstallError(dir) - else: raise e - # calculate the repository we'll be pulling out of - application = version.application - app = application.name - repo = os.path.join("/afs/athena.mit.edu/contrib/scripts/wizard/srv", app + ".git") - if not os.path.isdir(repo): - raise NoRepositoryError(app) - # begin the command line process - sh = shell.Shell(logger, options.dry_run) - # check if the version we're trying to convert exists. We assume - # a convention here, namely, v1.2.3-scripts is what we want. If - # you broke the convention... shame on you. - try: - tag = "v%s-scripts" % version.version - sh.call("git", "--git-dir", repo, "rev-parse", tag) - except shell.CallError: - raise NoTagError(version) - did_git_init = False - did_git_checkout_scripts = False - # prevent some perverse errors - os.unsetenv("GIT_DIR") - # create repository - sh.call("git", "init") - did_git_init = True + return (options, args) + +def perform_force(options): + has_git = os.path.isdir(".git") + has_scripts = os.path.isdir(".scripts") + + if has_git: + logging.warning("Force removing .git directory") + 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 = util.safe_unlink(".scripts") + logging.info(".scripts backed up to %s" % backup) + +def make_repository(sh, options, repo, tag): + sh.call("git", "init") # create repository # configure our alternates (to save space and make this quick) data = os.path.join(repo, "objects") file = ".git/objects/info/alternates" @@ -132,11 +108,14 @@ NOT run this command as root.""" alternates = open(file, "w") alternates.write(data) alternates.close() + htaccess = open(".git/.htaccess", "w") + htaccess.write("Deny from all\n") + htaccess.close() else: - logger.info("# create %s containing \"%s\"" % (file, data)) - # configure our remote - # (later on, we won't actually use these ourselves, since we - # might want to use an arbitrary repository) + 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 + # 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 sh.call("git", "config", "branch.master.remote", "origin") @@ -147,15 +126,37 @@ NOT run this command as root.""" sh.call("git", "reset", tag, "--") # checkout the .scripts directory sh.call("git", "checkout", ".scripts") - did_git_checkout_scripts = True - # XXX: setup .scripts/version??? - # for verbose purposes, give us a git status and git diff - if options.verbose: - try: - sh.call("git", "status") - except shell.CallError: - pass - try: - sh.call("git", "diff") - except shell.CallError: - pass + 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()) + util.set_git_env() + try: + message += "\nMigrated-by: " + util.get_operator_git() + except util.NoOperatorInfo: + pass + sh.call("git", "commit", "--allow-empty", "-a", "-m", message) + +def check_variables(d, options): + """Attempt to extract variables and complain if some are missing.""" + variables = d.extract() + for k,v in variables.items(): + if v is None and k not in d.application.deprecated_keys: + logging.warning("Variable %s not found" % k) + else: + logging.debug("Variable %s is %s" % (k,v)) + +class Error(command.Error): + """Base exception for all exceptions raised by migrate""" + pass + +class AlreadyMigratedError(Error): + 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. +""" +