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