8 from wizard import command, deploy, shell, util
10 def main(argv, baton):
11 options, args = parse_args(argv, baton)
14 shell.drop_priviledges(dir, options)
17 sh = shell.Shell(options.dry_run)
19 logging.info("Migrating %s" % dir)
20 logging.debug("uid is %d" % os.getuid())
22 deployment = deploy.Deployment(".")
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")
28 os.unsetenv("GIT_DIR") # prevent some perverse errors
32 raise AlreadyMigratedError(deployment.location)
33 except deploy.NotMigratedError:
35 except (deploy.CorruptedAutoinstallError, AlreadyMigratedError):
37 perform_force(options)
41 deployment.verifyTag(options.srv_path)
43 if options.force_version:
44 version = deployment.application.makeVersion(options.force_version)
46 deployment.verifyVersion()
47 version = deployment.app_version
48 repo = version.application.repository(options.srv_path)
49 tag = version.scripts_tag
51 # XXX: turn this into a context
54 os.open(".scripts-migrate-lock", os.O_CREAT | os.O_EXCL)
56 if e.errno == errno.EEXIST:
57 raise DirectoryLockedError
58 elif e.errno == errno.EACCES:
59 raise command.PermissionsError(dir)
61 make_repository(sh, options, repo, tag)
62 check_variables(deployment, options)
65 os.unlink(".scripts-migrate-lock")
69 def parse_args(argv, baton):
70 usage = """usage: %prog migrate [ARGS] DIR
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.
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)
91 parser.error("too many arguments")
93 parser.error("must specify directory")
94 return (options, args)
96 def perform_force(options):
97 has_git = os.path.isdir(".git")
98 has_scripts = os.path.isdir(".scripts")
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)
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)
109 def safe_unlink(file):
110 """Moves a file to a backup location."""
111 prefix = "%s.bak" % file
113 for i in itertools.count():
114 name = "%s.%d" % (prefix, i)
115 if not os.path.exists(name):
117 os.rename(file, name)
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)
129 htaccess = open(".git/.htaccess", "w")
130 htaccess.write("Deny from all\n")
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())
152 message += "\nMigrated-by: " + util.get_operator_git()
153 except util.NoOperatorInfo:
155 sh.call("git", "commit", "--allow-empty", "-a", "-m", message)
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)
164 logging.debug("Variable %s is %s" % (k,v))
166 class Error(command.Error):
167 """Base exception for all exceptions raised by migrate"""
170 class AlreadyMigratedError(Error):
171 def __init__(self, dir):
176 ERROR: Directory already contains a .git and
177 .scripts directory. If you force this migration,
178 both of these directories will be removed.
181 class DirectoryLockedError(Error):
182 def __init__(self, dir):
187 ERROR: Could not acquire lock on directory. Maybe there is
188 another migration process running?