8 from wizard import deploy, util, shell, sset, command
9 from wizard.command import migrate
11 def main(argv, baton):
12 options, args = parse_args(argv, baton)
14 base_args = calculate_base_args(options)
15 sh = make_shell(options)
16 seen = make_serialized_set(options)
17 my_uid = os.getuid() # to see if we have root
21 for line in deploy.get_install_lines(options.versions_path):
22 # validate and filter the deployments
24 d = deploy.Deployment.parse(line)
25 except deploy.DeploymentParseError, deploy.NoSuchApplication:
27 name = d.application.name
28 if name != app: continue
29 if d.location in seen:
31 # security check: see if the user's directory is the prefix of what
33 uid = util.get_dir_uid(d.location)
34 real = os.path.realpath(d.location)
35 if not real.startswith(pwd.getpwuid(uid).pw_dir + "/"):
36 logging.error("Security check failed, owner of deployment and owner of home directory mismatch for %s" % d.location)
40 def on_success(stdout, stderr):
43 if e.name == "wizard.command.migrate.AlreadyMigratedError":
45 logging.info("Skipped already migrated %s" % d.location)
48 if name not in errors: errors[name] = []
49 errors[name].append(d)
50 logging.error("%s in %s" % (name, d.location))
51 return (on_success, on_error)
52 on_success, on_error = make_on_pair(d)
53 sh.wait() # wait for a parallel processing slot to be available
54 sh.callAsUser("wizard", "migrate", d.location, *base_args,
55 uid=uid, on_success=on_success, on_error=on_error)
57 for name, deploys in errors.items():
58 logging.warning("%s from %d installs" % (name, len(deploys)))
60 def parse_args(argv, baton):
61 usage = """usage: %prog mass-migrate [ARGS] APPLICATION
63 Mass migrates an application to the new repository format.
64 Essentially equivalent to running '%prog migrate' on all
65 autoinstalls for a particular application found by parallel-find,
66 but with advanced reporting.
68 When doing an actual run, it is recommended to use --seen to
69 be able to resume gracefully (without it, mass-migrate must
70 stat every install to find out if it migrated it yet).
72 This command is intended to be run as root on a server with
73 the scripts AFS patch. You may run it as an unpriviledged
74 user for testing purposes, but then you MUST NOT run this on
75 untrusted repositories."""
76 parser = command.WizardOptionParser(usage)
77 parser.add_option("--no-parallelize", dest="no_parallelize", action="store_true",
78 default=False, help="Turn off parallelization")
79 parser.add_option("--dry-run", dest="dry_run", action="store_true",
80 default=False, help="Print commands that would be run. Implies --no-parallelize")
81 parser.add_option("--max", dest="max",
82 default=10, help="Maximum subprocesses to run concurrently")
83 parser.add_option("--seen", dest="seen",
84 default=None, help="File to read/write paths of already processed installs. These will be skipped.")
85 baton.push(parser, "versions_path")
86 baton.push(parser, "srv_path")
87 options, args, = parser.parse_all(argv)
89 parser.error("too many arguments")
91 parser.error("must specify application to migrate")
93 options.no_parallelize = True
96 def calculate_base_args(options):
97 base_args = command.makeBaseArgs(options, dry_run="--dry-run", srv_path="--srv-path")
99 base_args.append("--quiet")
102 def make_shell(options):
103 if options.no_parallelize:
104 sh = shell.DummyParallelShell()
106 sh = shell.ParallelShell(max=int(options.max))
109 def make_serialized_set(options):
111 seen = sset.SerializedSet(options.seen)
113 seen = sset.DummySerializedSet()