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