11 from wizard import deploy, util, shell, sset, command
12 from wizard.command import migrate
14 def main(argv, baton):
15 options, args = parse_args(argv, baton)
17 base_args = calculate_base_args(options)
18 sh = make_shell(options)
19 seen = make_serialized_set(options)
20 my_uid = os.getuid() # to see if we have root
24 os.mkdir(options.log_dir)
26 if e.errno != errno.EEXIST:
29 options.log_dir = os.path.join(options.log_dir, str(int(time.time())))
30 os.mkdir(options.log_dir) # if fails, be fatal
31 os.chmod(options.log_dir, 0o777)
35 for line in deploy.get_install_lines(options.versions_path):
36 child_args = list(base_args)
37 # validate and filter the deployments
39 d = deploy.Deployment.parse(line)
40 except deploy.DeploymentParseError, deploy.NoSuchApplication:
42 name = d.application.name
43 if name != app: continue
44 if d.location in seen:
46 # security check: see if the user's directory is the prefix of
47 # the deployment we're upgrading
49 uid = util.get_dir_uid(d.location)
50 real = os.path.realpath(d.location)
51 if not real.startswith(pwd.getpwuid(uid).pw_dir + "/"):
52 logging.error("Security check failed, owner of deployment and owner of home directory mismatch for %s" % d.location)
54 # check if we want to punt due to --limit
56 if options.limit and i > options.limit:
58 # calculate the log file, if a log dir was specified
60 log_file = os.path.join(options.log_dir, shorten(i, d.location))
61 child_args.append("--log-file=" + log_file)
64 def on_success(stdout, stderr):
67 if e.name == "wizard.command.migrate.AlreadyMigratedError" or \
68 e.name == "AlreadyMigratedError":
70 logging.info("Skipped already migrated %s" % d.location)
73 if name not in errors: errors[name] = []
74 errors[name].append(d)
75 logging.error("%s in %s" % (name, d.location))
76 return (on_success, on_error)
77 on_success, on_error = make_on_pair(d)
78 sh.wait() # wait for a parallel processing slot to be available
79 sh.call("wizard", "migrate", d.location, *child_args,
80 on_success=on_success, on_error=on_error)
82 for name, deploys in errors.items():
83 logging.warning("%s from %d installs" % (name, len(deploys)))
85 def parse_args(argv, baton):
86 usage = """usage: %prog mass-migrate [ARGS] APPLICATION
88 Mass migrates an application to the new repository format.
89 Essentially equivalent to running '%prog migrate' on all
90 autoinstalls for a particular application found by parallel-find,
91 but with advanced reporting.
93 When doing an actual run, it is recommended to use --seen to
94 be able to resume gracefully (without it, mass-migrate must
95 stat every install to find out if it migrated it yet).
97 This command is intended to be run as root on a server with
98 the scripts AFS patch. You may run it as an unpriviledged
99 user for testing purposes, but then you MUST NOT run this on
100 untrusted repositories."""
101 parser = command.WizardOptionParser(usage)
102 parser.add_option("--no-parallelize", dest="no_parallelize", action="store_true",
103 default=False, help="Turn off parallelization")
104 parser.add_option("--dry-run", dest="dry_run", action="store_true",
105 default=False, help="Print commands that would be run. Implies --no-parallelize")
106 parser.add_option("--max", dest="max",
107 default=10, help="Maximum subprocesses to run concurrently")
108 parser.add_option("--seen", dest="seen",
109 default=None, help="File to read/write paths of already processed installs. These will be skipped.")
110 parser.add_option("--force", dest="force", action="store_true",
111 default=False, help="Force migrations to occur even if .scripts or .git exists.")
112 parser.add_option("--limit", dest="limit", type="int",
113 default=0, help="Limit the number of autoinstalls to look at.")
114 baton.push(parser, "versions_path")
115 baton.push(parser, "srv_path")
116 baton.push(parser, "log_dir")
117 options, args, = parser.parse_all(argv)
119 parser.error("too many arguments")
121 parser.error("must specify application to migrate")
123 options.no_parallelize = True
126 def calculate_base_args(options):
127 base_args = command.makeBaseArgs(options, dry_run="--dry-run", srv_path="--srv-path", force="--force")
128 base_args += '--quiet'
132 return "%04d" % i + dir.replace('/', '-') + ".log"
134 def make_shell(options):
135 if options.no_parallelize:
136 sh = shell.DummyParallelShell()
138 sh = shell.ParallelShell(max=int(options.max))
141 def make_serialized_set(options):
143 seen = sset.SerializedSet(options.seen)
145 seen = sset.DummySerializedSet()