]> scripts.mit.edu Git - wizard.git/blob - wizard/command/migrate.py
Fix numerous bugs from our test runs.
[wizard.git] / wizard / command / migrate.py
1 import os
2 import shutil
3 import logging
4 import errno
5 import sys
6
7 from wizard import command, deploy, shell, util
8
9 def main(argv, baton):
10     options, args = parse_args(argv, baton)
11     dir = args[0]
12     command.chdir(dir)
13
14     shell.drop_priviledges(options)
15
16     logging.info("Migrating %s" % dir)
17     logging.debug("uid is %d" % os.getuid())
18
19     check_if_already_migrated(options)
20
21     deployment = make_deployment() # uses chdir
22     version = deployment.app_version
23     repo    = version.application.repository(options.srv_path)
24     tag     = version.scripts_tag
25
26     os.unsetenv("GIT_DIR") # prevent some perverse errors
27
28     sh = shell.Shell(options.dry_run)
29     check_if_tag_exists(sh, repo, tag)
30     make_repository(sh, options, repo, tag)
31     check_variables(deployment, options)
32
33     if not options.dry_run:
34         deployment.scriptsifyVersion()
35     else:
36         logging.info("# create .scripts/version containing \"%s-%s-scripts\"" % (deployment.application.name, deployment.version))
37
38 def parse_args(argv, baton):
39     usage = """usage: %prog migrate [ARGS] DIR
40
41 Migrates a directory to our Git-based autoinstall format.
42 Performs basic sanity checking and intelligently determines
43 what repository and tag to use.
44
45 This command is meant to be run as the owner of the install
46 it is upgrading (see the scripts AFS kernel patch).  Do
47 NOT run this command as root."""
48     parser = command.WizardOptionParser(usage)
49     baton.push(parser, "srv_path")
50     parser.add_option("--dry-run", dest="dry_run", action="store_true",
51             default=False, help="Prints would would be run without changing anything")
52     parser.add_option("--force", "-f", dest="force", action="store_true",
53             default=False, help="If .git or .scripts directory already exists, delete them and migrate")
54     options, args = parser.parse_all(argv)
55     if len(args) > 1:
56         parser.error("too many arguments")
57     elif not args:
58         parser.error("must specify directory")
59     return (options, args)
60
61 def check_if_already_migrated(options):
62     # XXX: duplicates some logic with Deployment.migrated
63     if os.path.isdir(".git") or os.path.isdir(".scripts"):
64         if not options.force:
65             raise AlreadyMigratedError(dir)
66         else:
67             if os.path.isdir(".git"):
68                 logging.warning("Force removing .git directory")
69                 if not options.dry_run:
70                     shutil.rmtree(".git.bak", ignore_errors=True)
71                     os.rename(".git", ".git.bak")
72             if os.path.isdir(".scripts"):
73                 logging.warning("Force removing .scripts directory")
74                 if not options.dry_run: shutil.rmtree(".scripts")
75
76 def make_deployment():
77     try:
78         return deploy.Deployment(".")
79     except IOError as e:
80         if e.errno == errno.ENOENT:
81             raise NotAutoinstallError(dir)
82         else: raise e
83
84 def check_if_tag_exists(sh, repo, tag):
85     # check if the version we're trying to convert exists. We assume
86     # a convention here, namely, v1.2.3-scripts is what we want. If
87     # you broke the convention... shame on you.
88     try:
89         sh.call("git", "--git-dir", repo, "rev-parse", tag)
90     except shell.CallError:
91         raise NoTagError(version)
92
93 def make_repository(sh, options, repo, tag):
94     sh.call("git", "init") # create repository
95     # configure our alternates (to save space and make this quick)
96     data = os.path.join(repo, "objects")
97     file = ".git/objects/info/alternates"
98     if not options.dry_run:
99         alternates = open(file, "w")
100         alternates.write(data)
101         alternates.close()
102         htaccess = open(".git/.htaccess", "w")
103         htaccess.write("Deny from all\n")
104         htaccess.close()
105     else:
106         logging.info("# create %s containing \"%s\"" % (file, data))
107         logging.info('# create .htaccess containing "Deny from all"')
108     # configure our remote (this is merely for convenience; wizard scripts
109     # will not rely on this)
110     sh.call("git", "remote", "add", "origin", repo)
111     # configure what would normally be set up on a 'git clone' for consistency
112     sh.call("git", "config", "branch.master.remote", "origin")
113     sh.call("git", "config", "branch.master.merge", "refs/heads/master")
114     # perform the initial fetch
115     sh.call("git", "fetch", "origin")
116     # soft reset to our tag
117     sh.call("git", "reset", tag, "--")
118     # checkout the .scripts directory
119     sh.call("git", "checkout", ".scripts")
120     logging.info("Diffstat:\n" + sh.eval("git", "diff", "--stat"))
121     # commit user local changes
122     message = "Autoinstall migration of %s locker.\n\n%s" % (util.get_dir_owner(), util.get_git_footer())
123     util.set_git_env()
124     try:
125         message += "\nMigrated-by: " + util.get_operator_git()
126     except util.NoOperatorInfo:
127         pass
128     sh.call("git", "commit", "--allow-empty", "-a", "-m", message)
129
130 def check_variables(d, options):
131     """Attempt to extract variables and complain if some are missing."""
132     variables = d.extract()
133     for k,v in variables.items():
134         if v is None and k not in d.application.deprecated_keys:
135             logging.warning("Variable %s not found" % k)
136         else:
137             logging.debug("Variable %s is %s" % (k,v))
138
139 class Error(command.Error):
140     """Base exception for all exceptions raised by migrate"""
141     pass
142
143 class AlreadyMigratedError(Error):
144     def __init__(self, dir):
145         self.dir = dir
146     def __str__(self):
147         return """
148
149 ERROR: Directory already contains a .git and/or
150 .scripts directory.  Did you already migrate it?
151 """
152
153 class NotAutoinstallError(Error):
154     def __init__(self, dir):
155         self.dir = dir
156     def __str__(self):
157         return """
158
159 ERROR: Could not find .scripts-version file. Are you sure
160 this is an autoinstalled application?
161 """
162
163 class NoTagError(Error):
164     def __init__(self, version):
165         self.version = version
166     def __str__(self):
167         return """
168
169 ERROR: Could not find tag v%s-scripts in repository
170 for %s.  Double check and make sure
171 the repository was prepared with all necessary tags!
172 """ % (self.version.version, self.version.application.name)