import os import itertools import shutil import logging import errno import sys from wizard import command, deploy, shell, util def main(argv, baton): options, args = parse_args(argv, baton) dir = args[0] shell.drop_priviledges(dir, options) 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") os.unsetenv("GIT_DIR") # prevent some perverse errors try: deployment.verify() raise AlreadyMigratedError(deployment.location) except deploy.NotMigratedError: pass except (deploy.CorruptedAutoinstallError, AlreadyMigratedError): if options.force: perform_force(options) else: raise deployment.verifyTag(options.srv_path) version = deployment.app_version repo = version.application.repository(options.srv_path) tag = version.scripts_tag # XXX: turn this into a context try: 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 def parse_args(argv, baton): usage = """usage: %prog migrate [ARGS] DIR 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.""" 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 = 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") if has_git: logging.warning("Force removing .git directory") if not options.dry_run: backup = 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 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" if not options.dry_run: 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) # configure what would normally be set up on a 'git clone' for consistency sh.call("git", "config", "branch.master.remote", "origin") sh.call("git", "config", "branch.master.merge", "refs/heads/master") # perform the initial fetch sh.call("git", "fetch", "origin") # soft reset to our 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 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. """ class DirectoryLockedError(Error): def __init__(self, dir): self.dir = dir def __str__(self): return """ ERROR: Could not acquire lock on directory. Maybe there is another migration process running? """