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