X-Git-Url: https://scripts.mit.edu/gitweb/wizard.git/blobdiff_plain/041d4cbfc187c368778e487b091b90f1dc6afae5..9f2c4a526259a93972135cf1e8ddc22aa2631da3:/wizard/command/migrate.py diff --git a/wizard/command/migrate.py b/wizard/command/migrate.py index ac3ed23..259d2e7 100644 --- a/wizard/command/migrate.py +++ b/wizard/command/migrate.py @@ -1,36 +1,73 @@ import os import shutil import logging -import errno -import sys -from wizard import deploy -from wizard import shell -from wizard import util -from wizard.command import _base +from wizard import command, deploy, shell, util -def main(argv, global_options): - options, args = parse_args(argv) - dir = args[0] +def main(argv, baton): + options, args = parse_args(argv, baton) + if args: + dir = args[0] + else: + dir = 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()) - _base.chdir(dir) - check_if_already_migrated(options) + deployment = deploy.ProductionCopy(".") - version = calculate_version() - repo = version.application.getRepository() - tag = version.getScriptsTag() + # 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") os.unsetenv("GIT_DIR") # prevent some perverse errors - sh = shell.Shell(options.dry_run) - check_if_tag_exists(sh, repo, tag) - make_repository(sh, options, repo, tag) + 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) - os.rename(".scripts-version", ".scripts/old-version") # archive + if options.force_version: + version = deployment.application.makeVersion(options.force_version) + else: + try: + deployment.verifyVersion() + version = deployment.app_version + except deploy.VersionMismatchError as e: + # well, we'll use that then + version = deployment.application.makeVersion(str(e.real_version)) + repo = version.application.repository(options.srv_path) + tag = version.scripts_tag + try: + sh.call("git", "--git-dir=%s" % repo, "rev-parse", tag) + except shell.CallError: + raise UnsupportedVersion(version.version) -def parse_args(argv): + 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 parse_args(argv, baton): usage = """usage: %prog migrate [ARGS] DIR Migrates a directory to our Git-based autoinstall format. @@ -40,47 +77,33 @@ 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") + 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") return (options, args) -def check_if_already_migrated(options): - if os.path.isdir(".git") or os.path.isdir(".scripts"): - if not options.force: - raise AlreadyMigratedError(dir) - else: - if os.path.isdir(".git"): - logging.warning("Force removing .git directory") - if not options.dry_run: shutil.rmtree(".git") - if os.path.isdir(".scripts"): - logging.warning("Force removing .scripts directory") - if not options.dry_run: shutil.rmtree(".scripts") - -def calculate_version(): - try: - d = deploy.Deployment.fromDir(".") - return d.getAppVersion() - except IOError as e: - if e.errno == errno.ENOENT: - raise NotAutoinstallError(dir) - else: raise e - -def check_if_tag_exists(sh, repo, tag): - # 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: - sh.call("git", "--git-dir", repo, "rev-parse", tag) - except shell.CallError: - raise NoTagError(version) +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 @@ -91,8 +114,12 @@ def make_repository(sh, options, repo, tag): alternates = open(file, "w") alternates.write(data) alternates.close() + htaccess = open(".git/.htaccess", "w") + htaccess.write("Deny from all\n") + htaccess.close() 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 # will not rely on this) sh.call("git", "remote", "add", "origin", repo) @@ -105,80 +132,49 @@ def make_repository(sh, options, repo, tag): sh.call("git", "reset", tag, "--") # checkout the .scripts directory sh.call("git", "checkout", ".scripts") + logging.info("Diffstat:\n" + sh.eval("git", "diff", "--stat")) # commit user local changes - lines = ["Initial commit after migration." - ,"" - ,"Wizard-version: %s" % util.get_version() - ,"Wizard-args: %s" % " ".join(sys.argv) - ] + message = "Autoinstall migration of %s locker.\n\n%s" % (util.get_dir_owner(), util.get_git_footer()) + util.set_git_env() try: - lines.append("Migrated-by: " + util.get_operator_git()) - # maybe this should go in massmigrate - op_realname, op_email = util.get_operator_info() - os.putenv("GIT_COMMITTER_NAME", op_realname) - os.putenv("GIT_COMMITTER_EMAIL", op_email) + message += "\nMigrated-by: " + util.get_operator_git() except util.NoOperatorInfo: pass - try: - lockername = util.get_dir_owner(".") - os.putenv("GIT_AUTHOR_NAME", "%s locker" % lockername) - os.putenv("GIT_AUTHOR_EMAIL", "%s@scripts.mit.edu" % lockername) - except KeyError: - pass - sh.call("git", "commit", "--allow-empty", "-a", "-m", "\n".join(lines)) - # 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 + 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(_base.Error): +class Error(command.Error): """Base exception for all exceptions raised by migrate""" pass class AlreadyMigratedError(Error): + quiet = True def __init__(self, dir): self.dir = dir def __str__(self): return """ -ERROR: Directory already contains a .git and/or -.scripts directory. Did you already migrate it? -""" - -class NotAutoinstallError(Error): - def __init__(self, dir): - self.dir = dir - def __str__(self): - return """ - -ERROR: Could not find .scripts-version file. Are you sure -this is an autoinstalled application? +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 NoRepositoryError(Error): - def __init__(self, app): - self.app = app - def __str__(self): - return """ - -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 - -class NoTagError(Error): +class UnsupportedVersion(Error): def __init__(self, version): self.version = version def __str__(self): return """ -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) +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