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