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 # check if we want to punt due to --limit
52 if options.limit and i > options.limit:
54 if d.location in seen:
56 # security check: see if the user's directory is the prefix of
57 # the deployment we're upgrading
59 uid = util.get_dir_uid(d.location)
60 real = os.path.realpath(d.location)
62 if not real.startswith(pwd.getpwuid(uid).pw_dir + "/"):
63 logging.error("Security check failed, owner of deployment and owner of home directory mismatch for %s" % d.location)
66 logging.error("Security check failed, could not look up owner of %s (uid %d)" % (d.location, uid))
68 # calculate the log file, if a log dir was specified
70 log_file = os.path.join(options.log_dir, shorten(i, d.location))
71 child_args.append("--log-file=" + log_file)
73 def make_on_pair(d, i):
74 def on_success(stdout, stderr):
76 warnings_log.write("%s\n" % d.location)
77 logging.warning("Warnings [%04d] %s:\n%s" % (i, d.location, stderr))
80 if e.name == "wizard.command.migrate.AlreadyMigratedError" or \
81 e.name == "AlreadyMigratedError":
83 logging.info("Skipped already migrated %s" % d.location)
86 if name not in errors: errors[name] = []
87 errors[name].append(d)
88 logging.error("%s in [%04d] %s" % (name, i, d.location))
89 errors_log.write("%s\n" % d.location)
90 return (on_success, on_error)
91 on_success, on_error = make_on_pair(d, i)
92 sh.wait() # wait for a parallel processing slot to be available
93 sh.call("wizard", "migrate", d.location, *child_args,
94 on_success=on_success, on_error=on_error)
96 for name, deploys in errors.items():
97 logging.warning("%s from %d installs" % (name, len(deploys)))
99 def parse_args(argv, baton):
100 usage = """usage: %prog mass-migrate [ARGS] APPLICATION
102 Mass migrates an application to the new repository format.
103 Essentially equivalent to running '%prog migrate' on all
104 autoinstalls for a particular application found by parallel-find,
105 but with advanced reporting.
107 When doing an actual run, it is recommended to use --seen to
108 be able to resume gracefully (without it, mass-migrate must
109 stat every install to find out if it migrated it yet).
111 This command is intended to be run as root on a server with
112 the scripts AFS patch. You may run it as an unpriviledged
113 user for testing purposes, but then you MUST NOT run this on
114 untrusted repositories."""
115 parser = command.WizardOptionParser(usage)
116 parser.add_option("--no-parallelize", dest="no_parallelize", action="store_true",
117 default=False, help="Turn off parallelization")
118 parser.add_option("--dry-run", dest="dry_run", action="store_true",
119 default=False, help="Print commands that would be run. Implies --no-parallelize")
120 parser.add_option("--max", dest="max",
121 default=10, help="Maximum subprocesses to run concurrently")
122 parser.add_option("--seen", dest="seen",
123 default=None, help="File to read/write paths of already processed installs. These will be skipped.")
124 parser.add_option("--force", dest="force", action="store_true",
125 default=False, help="Force migrations to occur even if .scripts or .git exists.")
126 parser.add_option("--limit", dest="limit", type="int",
127 default=0, help="Limit the number of autoinstalls to look at.")
128 baton.push(parser, "versions_path")
129 baton.push(parser, "srv_path")
130 baton.push(parser, "log_dir")
131 options, args, = parser.parse_all(argv)
133 parser.error("too many arguments")
135 parser.error("must specify application to migrate")
137 options.no_parallelize = True
140 def calculate_base_args(options):
141 base_args = command.makeBaseArgs(options, dry_run="--dry-run", srv_path="--srv-path", force="--force")
145 return "%04d" % i + dir.replace('/', '-') + ".log"
147 def make_shell(options):
148 if options.no_parallelize:
149 sh = shell.DummyParallelShell()
151 sh = shell.ParallelShell(max=int(options.max))
154 def make_serialized_set(options):
156 seen = sset.SerializedSet(options.seen)
158 seen = sset.DummySerializedSet()