]> scripts.mit.edu Git - wizard.git/blob - wizard/command/migrate.py
Rewrite parametrize to use new parametrizeWithVars
[wizard.git] / wizard / command / migrate.py
1 import os
2 import os.path
3 import shutil
4 import logging
5
6 from wizard import command, deploy, shell, util
7
8 def main(argv, baton):
9     options, args = parse_args(argv, baton)
10     dir = os.path.abspath(args[0]) if args else os.getcwd()
11     shell.drop_priviledges(dir, options.log_file)
12     util.chdir(dir)
13
14     sh = shell.Shell(options.dry_run)
15
16     logging.info("Migrating %s" % dir)
17     logging.debug("uid is %d" % os.getuid())
18
19     deployment = deploy.ProductionCopy(".")
20
21     # deal with old-style migration, remove this later
22     if os.path.isfile(".scripts/old-version") and not os.path.isfile(".scripts-version"):
23         os.rename(".scripts/old-version", ".scripts-version")
24
25     os.unsetenv("GIT_DIR") # prevent some perverse errors
26
27     try:
28         deployment.verify()
29         raise AlreadyMigratedError(deployment.location)
30     except deploy.NotMigratedError:
31         pass
32     except (deploy.CorruptedAutoinstallError, AlreadyMigratedError):
33         if not options.force:
34             raise
35
36     deployment.verifyTag(options.srv_path)
37
38     if options.force_version:
39         version = deployment.application.makeVersion(options.force_version)
40     else:
41         try:
42             deployment.verifyVersion()
43             version = deployment.app_version
44         except deploy.VersionMismatchError as e:
45             # well, we'll use that then
46             version = deployment.application.makeVersion(str(e.real_version))
47     repo = version.application.repository(options.srv_path)
48     tag = version.scripts_tag
49     try:
50         sh.call("git", "--git-dir=%s" % repo, "rev-parse", tag)
51     except shell.CallError:
52         raise UnsupportedVersion(version.version)
53
54     with util.LockDirectory(".scripts-migrate-lock"):
55         try:
56             if options.force:
57                 perform_force(options)
58             make_repository(sh, options, repo, tag)
59             check_variables(deployment, options)
60         except KeyboardInterrupt:
61             # revert it; barring zany race conditions this is safe
62             if os.path.exists(".scripts"):
63                 shutil.rmtree(".scripts")
64             if os.path.exists(".git"):
65                 shutil.rmtree(".git")
66
67 def parse_args(argv, baton):
68     usage = """usage: %prog migrate [ARGS] DIR
69
70 Migrates a directory to our Git-based autoinstall format.
71 Performs basic sanity checking and intelligently determines
72 what repository and tag to use.
73
74 This command is meant to be run as the owner of the install
75 it is upgrading (see the scripts AFS kernel patch).  Do
76 NOT run this command as root."""
77     parser = command.WizardOptionParser(usage)
78     baton.push(parser, "srv_path")
79     parser.add_option("--dry-run", dest="dry_run", action="store_true",
80             default=False, help="Prints would would be run without changing anything")
81     parser.add_option("--force", "-f", dest="force", action="store_true",
82             default=False, help="If .git or .scripts directory already exists,"
83             "delete them and migrate")
84     parser.add_option("--force-version", dest="force_version",
85             default=None, help="If .scripts-version tells lies, explicitly specify"
86             "a version to migrate to.")
87     options, args = parser.parse_all(argv)
88     if len(args) > 1:
89         parser.error("too many arguments")
90     return (options, args)
91
92 def perform_force(options):
93     has_git = os.path.isdir(".git")
94     has_scripts = os.path.isdir(".scripts")
95
96     if has_git:
97         logging.warning("Force removing .git directory")
98         if not options.dry_run: backup = util.safe_unlink(".git")
99         logging.info(".git backed up to %s" % backup)
100     if has_scripts:
101         logging.warning("Force removing .scripts directory")
102         if not options.dry_run: backup = util.safe_unlink(".scripts")
103         logging.info(".scripts backed up to %s" % backup)
104
105 def make_repository(sh, options, repo, tag):
106     sh.call("git", "init") # create repository
107     # configure our alternates (to save space and make this quick)
108     data = os.path.join(repo, "objects")
109     file = ".git/objects/info/alternates"
110     if not options.dry_run:
111         alternates = open(file, "w")
112         alternates.write(data)
113         alternates.close()
114         htaccess = open(".git/.htaccess", "w")
115         htaccess.write("Deny from all\n")
116         htaccess.close()
117     else:
118         logging.info("# create %s containing \"%s\"" % (file, data))
119         logging.info('# create .htaccess containing "Deny from all"')
120     # configure our remote (this is merely for convenience; wizard scripts
121     # will not rely on this)
122     sh.call("git", "remote", "add", "origin", repo)
123     # configure what would normally be set up on a 'git clone' for consistency
124     sh.call("git", "config", "branch.master.remote", "origin")
125     sh.call("git", "config", "branch.master.merge", "refs/heads/master")
126     # perform the initial fetch
127     sh.call("git", "fetch", "origin")
128     # soft reset to our tag
129     sh.call("git", "reset", tag, "--")
130     # checkout the .scripts directory
131     sh.call("git", "checkout", ".scripts")
132     logging.info("Diffstat:\n" + sh.eval("git", "diff", "--stat"))
133     # commit user local changes
134     message = "Autoinstall migration of %s locker.\n\n%s" % (util.get_dir_owner(), util.get_git_footer())
135     util.set_git_env()
136     try:
137         message += "\nMigrated-by: " + util.get_operator_git()
138     except util.NoOperatorInfo:
139         pass
140     sh.call("git", "commit", "--allow-empty", "-a", "-m", message)
141
142 def check_variables(d, options):
143     """Attempt to extract variables and complain if some are missing."""
144     variables = d.extract()
145     for k,v in variables.items():
146         if v is None and k not in d.application.deprecated_keys:
147             logging.warning("Variable %s not found" % k)
148         else:
149             logging.debug("Variable %s is %s" % (k,v))
150
151 class Error(command.Error):
152     """Base exception for all exceptions raised by migrate"""
153     pass
154
155 class AlreadyMigratedError(Error):
156     quiet = True
157     def __init__(self, dir):
158         self.dir = dir
159     def __str__(self):
160         return """
161
162 This autoinstall is already migrated; move along, nothing to
163 see here.  (If you really want to, you can force a re-migration
164 with --force, but this will blow away the existing .git and
165 .scripts directories (i.e. your history and Wizard configuration).)
166 """
167
168 class UnsupportedVersion(Error):
169     def __init__(self, version):
170         self.version = version
171     def __str__(self):
172         return """
173
174 ERROR: This autoinstall is presently on %s, which is unsupported by
175 Wizard.  Please manually upgrade it to one that is supported,
176 and then retry the migration; usually the latest version is supported.
177 """ % self.version