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
21 warnings_logname = "/tmp/wizard-migrate-warnings.log"
22 errors_logname = "/tmp/wizard-migrate-errors.log"
26 os.mkdir(options.log_dir)
28 if e.errno != errno.EEXIST:
31 options.log_dir = os.path.join(options.log_dir, str(int(time.time())))
32 os.mkdir(options.log_dir) # if fails, be fatal
33 os.chmod(options.log_dir, 0o777)
34 warnings_logname = os.path.join(options.log_dir, "warnings.log")
35 errors_logname = os.path.join(options.log_dir, "errors.log")
36 warnings_log = open(warnings_logname, "a")
37 errors_log = open(errors_logname, "a")
41 for line in deploy.get_install_lines(options.versions_path):
42 child_args = list(base_args)
43 # validate and filter the deployments
45 d = deploy.Deployment.parse(line)
46 except deploy.DeploymentParseError, deploy.NoSuchApplication:
48 name = d.application.name
49 if name != app: continue
50 if d.location in seen:
52 # security check: see if the user's directory is the prefix of
53 # the deployment we're upgrading
55 uid = util.get_dir_uid(d.location)
56 real = os.path.realpath(d.location)
57 if not real.startswith(pwd.getpwuid(uid).pw_dir + "/"):
58 logging.error("Security check failed, owner of deployment and owner of home directory mismatch for %s" % d.location)
60 # check if we want to punt due to --limit
62 if options.limit and i > options.limit:
64 # calculate the log file, if a log dir was specified
66 log_file = os.path.join(options.log_dir, shorten(i, d.location))
67 child_args.append("--log-file=" + log_file)
69 def make_on_pair(d, i):
70 def on_success(stdout, stderr):
72 warnings_log.write("%s\n" % d.location)
73 logging.warning("Warnings [%04d] %s:\n%s" % (d.location, i, stderr))
76 if e.name == "wizard.command.migrate.AlreadyMigratedError" or \
77 e.name == "AlreadyMigratedError":
79 logging.info("Skipped already migrated %s" % d.location)
82 if name not in errors: errors[name] = []
83 errors[name].append(d)
84 logging.error("%s in [%04d] %s" % (name, i, d.location))
85 errors_log.write("%s\n" % d.location)
86 return (on_success, on_error)
87 on_success, on_error = make_on_pair(d, i)
88 sh.wait() # wait for a parallel processing slot to be available
89 sh.call("wizard", "migrate", d.location, *child_args,
90 on_success=on_success, on_error=on_error)
92 for name, deploys in errors.items():
93 logging.warning("%s from %d installs" % (name, len(deploys)))
95 def parse_args(argv, baton):
96 usage = """usage: %prog mass-migrate [ARGS] APPLICATION
98 Mass migrates an application to the new repository format.
99 Essentially equivalent to running '%prog migrate' on all
100 autoinstalls for a particular application found by parallel-find,
101 but with advanced reporting.
103 When doing an actual run, it is recommended to use --seen to
104 be able to resume gracefully (without it, mass-migrate must
105 stat every install to find out if it migrated it yet).
107 This command is intended to be run as root on a server with
108 the scripts AFS patch. You may run it as an unpriviledged
109 user for testing purposes, but then you MUST NOT run this on
110 untrusted repositories."""
111 parser = command.WizardOptionParser(usage)
112 parser.add_option("--no-parallelize", dest="no_parallelize", action="store_true",
113 default=False, help="Turn off parallelization")
114 parser.add_option("--dry-run", dest="dry_run", action="store_true",
115 default=False, help="Print commands that would be run. Implies --no-parallelize")
116 parser.add_option("--max", dest="max",
117 default=10, help="Maximum subprocesses to run concurrently")
118 parser.add_option("--seen", dest="seen",
119 default=None, help="File to read/write paths of already processed installs. These will be skipped.")
120 parser.add_option("--force", dest="force", action="store_true",
121 default=False, help="Force migrations to occur even if .scripts or .git exists.")
122 parser.add_option("--limit", dest="limit", type="int",
123 default=0, help="Limit the number of autoinstalls to look at.")
124 baton.push(parser, "versions_path")
125 baton.push(parser, "srv_path")
126 baton.push(parser, "log_dir")
127 options, args, = parser.parse_all(argv)
129 parser.error("too many arguments")
131 parser.error("must specify application to migrate")
133 options.no_parallelize = True
136 def calculate_base_args(options):
137 base_args = command.makeBaseArgs(options, dry_run="--dry-run", srv_path="--srv-path", force="--force")
141 return "%04d" % i + dir.replace('/', '-') + ".log"
143 def make_shell(options):
144 if options.no_parallelize:
145 sh = shell.DummyParallelShell()
147 sh = shell.ParallelShell(max=int(options.max))
150 def make_serialized_set(options):
152 seen = sset.SerializedSet(options.seen)
154 seen = sset.DummySerializedSet()