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