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