X-Git-Url: https://scripts.mit.edu/gitweb/wizard.git/blobdiff_plain/7c6f5653b8ced26745c92cd0a81df7af46afba70..16907a8e49a97366f0a7336b62c3a93d89ded262:/wizard/command/massmigrate.py diff --git a/wizard/command/massmigrate.py b/wizard/command/massmigrate.py index 5feaa78..ff1ded2 100644 --- a/wizard/command/massmigrate.py +++ b/wizard/command/massmigrate.py @@ -1,67 +1,40 @@ import optparse +import logging import os +import os.path +import pwd import wizard -from wizard import deploy -from wizard import util -from wizard import shell -from wizard import cache -from wizard.command import _base +from wizard import deploy, util, shell, sset, command from wizard.command import migrate -def main(argv, global_options, logger = None): - usage = """usage: %prog massmigrate [ARGS] APPLICATION - -Mass migrates an application to the new repository format. -Essentially equivalent to running '%prog migrate' on all -autoinstalls for a particular application found by parallel-find, -but with advanced reporting. - -When doing an actual run, it is recommended to use --cache to -be able to resume gracefully (without it, massmigrate must -stat every install to find out if it migrated it yet). -""" - parser = _base.WizardOptionParser(usage) - parser.add_option("--no-parallelize", dest="no_parallelize", action="store_true", - default=False, help="Turn off parallelization") - parser.add_option("--dry-run", dest="dry_run", action="store_true", - default=False, help="Print commands that would be run. Implies --no-parallelize") - parser.add_option("--max", dest="max", - default=10, help="Maximum subprocesses to run concurrently") - parser.add_option("--cache", dest="cache", - default=None, help="Cache file to read/write paths of already processed installs. These will be skipped.") - options, args, logger = parser.parse_all(argv, logger) - if len(args) > 1: - parser.error("too many arguments") - elif not args: - parser.error("must specify application to migrate") - if options.dry_run: - options.no_parallelize = True +def main(argv, baton): + options, args = parse_args(argv, baton) app = args[0] + base_args = calculate_base_args(options) + sh = make_shell(options) + seen = make_serialized_set(options) + my_uid = os.getuid() # to see if we have root + # loop stuff errors = {} - base_args = _base.makeBaseArgs(options, dry_run="--dry-run") - # check if we have root - uid = os.getuid() - user = None - # dry run is purposely omitted - if options.no_parallelize: - sh = shell.DummyParallelShell(logger=logger) - else: - sh = shell.ParallelShell(logger=logger, max=int(options.max)) - if options.cache: - seen = cache.Cache(options.cache) - else: - seen = cache.DummyCache() - for line in deploy.getInstallLines(global_options): + uid = None + for line in deploy.get_install_lines(options.versions_path): # validate and filter the deployments try: d = deploy.Deployment.parse(line) except deploy.DeploymentParseError, deploy.NoSuchApplication: continue - name = d.getApplication().name + name = d.application.name if name != app: continue if d.location in seen: continue + # security check: see if the user's directory is the prefix of what + if not my_uid: + uid = util.get_dir_uid(d.location) + real = os.path.realpath(d.location) + if not real.startswith(pwd.getpwuid(uid).pw_dir + "/"): + logging.error("Security check failed, owner of deployment and owner of home directory mismatch for %s" % d.location) + continue # actual meat def make_on_pair(d): def on_success(stdout, stderr): @@ -69,17 +42,72 @@ stat every install to find out if it migrated it yet). def on_error(e): if e.name == "wizard.command.migrate.AlreadyMigratedError": seen.add(d.location) - logger.info("Skipped already migrated %s" % d.location) + logging.info("Skipped already migrated %s" % d.location) else: name = e.name if name not in errors: errors[name] = [] errors[name].append(d) - logger.error("%s in %s" % (name, d.location)) + logging.error("%s in %s" % (name, d.location)) return (on_success, on_error) on_success, on_error = make_on_pair(d) sh.wait() # wait for a parallel processing slot to be available - sh.callAsUser(shell.wizard, "migrate", d.location, *base_args, - user=user, on_success=on_success, on_error=on_error) + sh.callAsUser(shell.wizard_bin, "migrate", d.location, *base_args, + uid=uid, on_success=on_success, on_error=on_error) sh.join() for name, deploys in errors.items(): - logger.warning("%s from %d installs" % (name, len(deploys))) + logging.warning("%s from %d installs" % (name, len(deploys))) + +def parse_args(argv, baton): + usage = """usage: %prog massmigrate [ARGS] APPLICATION + +Mass migrates an application to the new repository format. +Essentially equivalent to running '%prog migrate' on all +autoinstalls for a particular application found by parallel-find, +but with advanced reporting. + +When doing an actual run, it is recommended to use --seen to +be able to resume gracefully (without it, massmigrate must +stat every install to find out if it migrated it yet). + +This command is intended to be run as root on a server with +the scripts AFS patch. You may run it as an unpriviledged +user for testing purposes, but then you MUST NOT run this on +untrusted repositories.""" + parser = command.WizardOptionParser(usage) + parser.add_option("--no-parallelize", dest="no_parallelize", action="store_true", + default=False, help="Turn off parallelization") + parser.add_option("--dry-run", dest="dry_run", action="store_true", + default=False, help="Print commands that would be run. Implies --no-parallelize") + parser.add_option("--max", dest="max", + default=10, help="Maximum subprocesses to run concurrently") + parser.add_option("--seen", dest="seen", + default=None, help="File to read/write paths of already processed installs. These will be skipped.") + baton.push(parser, "versions_path") + options, args, = parser.parse_all(argv) + if len(args) > 1: + parser.error("too many arguments") + elif not args: + parser.error("must specify application to migrate") + if options.dry_run: + options.no_parallelize = True + return options, args + +def calculate_base_args(options): + base_args = command.makeBaseArgs(options, dry_run="--dry-run") + if not options.debug: + base_args.append("--quiet") + return base_args + +def make_shell(options): + if options.no_parallelize: + sh = shell.DummyParallelShell() + else: + sh = shell.ParallelShell(max=int(options.max)) + return sh + +def make_serialized_set(options): + if options.seen: + seen = sset.SerializedSet(options.seen) + else: + seen = sset.DummySerializedSet() + return seen