import optparse import logging import os import os.path import pwd import wizard from wizard import deploy, util, shell, sset, command from wizard.command import migrate 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 = {} 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.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): seen.add(d.location) def on_error(e): if e.name == "wizard.command.migrate.AlreadyMigratedError": seen.add(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) 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, uid=uid, on_success=on_success, on_error=on_error) sh.join() for name, deploys in errors.items(): 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