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