]> scripts.mit.edu Git - wizard.git/blob - wizard/command/massmigrate.py
Refactor to get rid of _package.py using __import__ magic.
[wizard.git] / wizard / command / massmigrate.py
1 import optparse
2 import logging
3 import os
4 import os.path
5 import pwd
6
7 import wizard
8 from wizard import deploy, util, shell, sset, command
9 from wizard.command import migrate
10
11 def main(argv, baton):
12     options, args = parse_args(argv, baton)
13     app = args[0]
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
18     # loop stuff
19     errors = {}
20     uid = None
21     for line in deploy.getInstallLines(options.versions_path):
22         # validate and filter the deployments
23         try:
24             d = deploy.Deployment.parse(line)
25         except deploy.DeploymentParseError, deploy.NoSuchApplication:
26             continue
27         name = d.application.name
28         if name != app: continue
29         if d.location in seen:
30             continue
31         # security check: see if the user's directory is the prefix of what
32         if not my_uid:
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)
37                 continue
38         # actual meat
39         def make_on_pair(d):
40             def on_success(stdout, stderr):
41                 seen.add(d.location)
42             def on_error(e):
43                 if e.name == "wizard.command.migrate.AlreadyMigratedError":
44                     seen.add(d.location)
45                     logging.info("Skipped already migrated %s" % d.location)
46                 else:
47                     name = e.name
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(shell.wizard, "migrate", d.location, *base_args,
55                       uid=uid, on_success=on_success, on_error=on_error)
56     sh.join()
57     for name, deploys in errors.items():
58         logging.warning("%s from %d installs" % (name, len(deploys)))
59
60 def parse_args(argv, baton):
61     usage = """usage: %prog massmigrate [ARGS] APPLICATION
62
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.
67
68 When doing an actual run, it is recommended to use --seen to
69 be able to resume gracefully (without it, massmigrate must
70 stat every install to find out if it migrated it yet).
71
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     options, args, = parser.parse_all(argv)
87     if len(args) > 1:
88         parser.error("too many arguments")
89     elif not args:
90         parser.error("must specify application to migrate")
91     if options.dry_run:
92         options.no_parallelize = True
93     return options, args
94
95 def calculate_base_args(options):
96     base_args = command.makeBaseArgs(options, dry_run="--dry-run")
97     if not options.debug:
98         base_args.append("--quiet")
99     return base_args
100
101 def make_shell(options):
102     if options.no_parallelize:
103         sh = shell.DummyParallelShell()
104     else:
105         sh = shell.ParallelShell(max=int(options.max))
106     return sh
107
108 def make_serialized_set(options):
109     if options.seen:
110         seen = sset.SerializedSet(options.seen)
111     else:
112         seen = sset.DummySerializedSet()
113     return seen