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