- Make parallel-find.pl use `sudo -u username git describe --tags`
to determine version. Make parallel-find.pl have this have greater
- precedence. (Have patch, pending testing and commit)
+ precedence. This also means, however, that we get
+ full mediawiki-1.2.3-2-abcdef names (Have patch, pending testing and commit)
- Make the installer use 'wizard install' /or/ do a migration
after doing a normal install (the latter makes it easier
for mass-rollbacks).
#!/bin/bash -e
TESTNAME="continue_upgrade_mediawiki"
-source setup
+source ./setup
wizard install mediawiki-$VERSION-scripts "$TESTDIR" -- --title="TestApp"
#!/bin/bash -e
TESTNAME="upgrade_mediawiki"
-source setup
+source ./setup
wizard install mediawiki-$VERSION-scripts "$TESTDIR" -- --title="TestApp"
wizard upgrade "$TESTDIR"
logging_setup = False
-class Error(wizard.Error):
- """Base error class for all command errors"""
- pass
-
-class PermissionsError(Error):
- def __init__(self, dir):
- self.dir = dir
- def __str__(self):
- return """
-
-ERROR: You don't have permissions to access this directory.
-Do you have tokens for AFS with your root instance, and
-is your root instance on scripts-security-upd?
-
-You can check by running the commands 'klist' and
-'blanche scripts-security-upd'.
-"""
-
-class NoSuchDirectoryError(Error):
- def __init__(self, dir):
- self.dir = dir
- def __str__(self):
- return """
-
-ERROR: No such directory... check your typing
-"""
-
def boolish(val):
"""
Parse the contents of an environment variable as a boolean.
return False
return bool(val)
-def chdir(dir):
- try:
- os.chdir(dir)
- except OSError as e:
- if e.errno == errno.EACCES:
- raise PermissionsError(dir)
- elif e.errno == errno.ENOENT:
- raise NoSuchDirectoryError(dir)
- else: raise e
-
def makeLogger(options, numeric_args):
global logging_setup
if logging_setup: return logging.getLogger()
"""Hands off parameters to option parser"""
for key in args:
option_parser.add_option(self.store[key])
+
+class Error(wizard.Error):
+ """Base error class for all command errors"""
+ pass
def main(argv, baton):
options, args = parse_args(argv, baton)
dir = args[0]
- command.chdir(dir)
- shell.drop_priviledges(options)
+ shell.drop_priviledges(dir, options)
+
+ util.chdir(dir)
sh = shell.Shell(options.dry_run)
logging.info("Migrating %s" % dir)
logging.debug("uid is %d" % os.getuid())
- deployment = make_deployment() # uses chdir
+ deployment = deploy.Deployment(".")
+
+ # deal with old-style migration, remove this later
+ if os.path.isfile(".scripts/old-version") and not os.path.isfile(".scripts-version"):
+ os.rename(".scripts/old-version", ".scripts-version")
+
+ os.unsetenv("GIT_DIR") # prevent some perverse errors
+
+ try:
+ deployment.verify()
+ raise AlreadyMigratedError(deployment.location)
+ except deploy.NotMigratedError:
+ pass
+ except (deploy.CorruptedAutoinstallError, AlreadyMigratedError):
+ if options.force:
+ perform_force(options)
+ else:
+ raise
- # turn this into some kind of assert
- if not deployment.configured:
- raise NotConfiguredError(deployment.location)
+ deployment.verifyTag(options.srv_path)
version = deployment.app_version
repo = version.application.repository(options.srv_path)
tag = version.scripts_tag
- check_if_tag_exists(sh, repo, tag, version)
-
- check_if_already_migrated(options)
-
- os.unsetenv("GIT_DIR") # prevent some perverse errors
# XXX: turn this into a context
try:
parser.error("must specify directory")
return (options, args)
-def check_if_already_migrated(options):
+def perform_force(options):
has_git = os.path.isdir(".git")
has_scripts = os.path.isdir(".scripts")
+<<<<<<< HEAD
# deal with old-style migration
if os.path.isfile(".scripts/old-version") and not os.path.isfile(".scripts-version"):
os.rename(".scripts/old-version", ".scripts-version")
sh.call("git", "--git-dir", repo, "rev-parse", tag)
except shell.CallError:
raise NoTagError(version)
+=======
+ if has_git:
+ logging.warning("Force removing .git directory")
+ if not options.dry_run: backup = safe_unlink(".git")
+ logging.info(".git backed up to %s" % backup)
+ if has_scripts:
+ logging.warning("Force removing .scripts directory")
+ if not options.dry_run: backup = safe_unlink(".scripts")
+ logging.info(".scripts backed up to %s" % backup)
+
+def safe_unlink(file):
+ """Moves a file to a backup location."""
+ prefix = "%s.bak" % file
+ name = None
+ for i in itertools.count():
+ name = "%s.%d" % (prefix, i)
+ if not os.path.exists(name):
+ break
+ os.rename(file, name)
+ return name
+>>>>>>> 255245146b500845fa13a75bfbeb6cece074417c
def make_repository(sh, options, repo, tag):
sh.call("git", "init") # create repository
both of these directories will be removed.
"""
-class AlreadyVersionedError(Error):
- def __init__(self, dir):
- self.dir = dir
- def __str__(self):
- return """
-
-ERROR: Directory contains a .git directory, but not
-a .scripts directory. If this is not a corrupt
-migration, this means that the user was versioning their
-install using Git. You cannot force this case.
-"""
-
-class NotConfiguredError(Error):
- def __init__(self, dir):
- self.dir = dir
- def __str__(self):
- return """
-
-ERROR: The install was well-formed, but not configured
-(essential configuration files were not found.)
-"""
-
-class CorruptedAutoinstallError(Error):
- def __init__(self, dir):
- self.dir = dir
- def __str__(self):
- return """
-
-ERROR: Directory contains a .scripts directory,
-but not a .git directory. If you force this migration,
-the .scripts directory will be removed.
-"""
-
-class NotAutoinstallError(Error):
- def __init__(self, dir):
- self.dir = dir
- def __str__(self):
- return """
-
-ERROR: Could not find .scripts-version file. Are you sure
-this is an autoinstalled application?
-"""
-
class DirectoryLockedError(Error):
def __init__(self, dir):
self.dir = dir
another migration process running?
"""
-class NoTagError(Error):
- def __init__(self, version):
- self.version = version
- def __str__(self):
- return """
-
-ERROR: Could not find tag v%s-scripts in repository
-for %s. Double check and make sure
-the repository was prepared with all necessary tags!
-""" % (self.version.version, self.version.application.name)
from wizard import command, deploy, shell, util
-# XXX: need errors for history sanity checking (if the user is on a completely
-# different history tree, we should abort, and manually figure out the
-# appropriate rebase)
-# $(git merge-base P C) == $(git rev-parse P)
-
def main(argv, baton):
options, args = parse_args(argv, baton)
if args:
- command.chdir(args[0])
+ util.chdir(args[0])
sh = shell.Shell()
util.set_git_env()
if options.continue_:
user_commit, next_commit = open(".git/WIZARD_PARENTS", "r").read().split()
repo = open(".git/WIZARD_REPO", "r").read()
version = open(".git/WIZARD_UPGRADE_VERSION", "r").read()
- command.chdir(sh.eval("git", "config", "remote.origin.url"))
- d = make_deployment_from_cwd()
+ util.chdir(sh.eval("git", "config", "remote.origin.url"))
+ d = deploy.Deployment(".")
else:
- d = make_deployment_from_cwd()
+ d = deploy.Deployment(".")
+ d.verify()
+ d.verifyTag(options.srv_path)
+ d.verifyGit(options.srv_path)
+ d.verifyConfigured()
repo = d.application.repository(options.srv_path)
version = calculate_newest_version(sh, repo)
+ if version == d.app_version.scripts_tag:
+ raise AlreadyUpgraded
if not options.dry_run:
perform_pre_commit(sh)
temp_wc_dir = perform_tmp_clone(sh)
# process might have frobbed it. Don't be
# particularly worried if the segment dissappeared
-def make_deployment_from_cwd():
- if not os.path.isdir(".git"):
- raise NotAutoinstallError()
- try:
- d = deploy.Deployment(".")
- except IOError as e:
- if e.errno == errno.ENOENT:
- raise NotAutoinstallError()
- else: raise e
- return d
-
def make_commit_message(version):
message = "Upgraded autoinstall in %s to %s.\n\n%s" % (util.get_dir_owner(), version, util.get_git_footer())
try:
"""Base exception for all exceptions raised by upgrade"""
pass
-class NotAutoinstallError(Error):
+class AlreadyUpgraded(Error):
def __str__(self):
return """
-ERROR: Could not find .git file. Are you sure
-this is an autoinstalled application? Did you remember
-to migrate it?"""
+ERROR: This autoinstall is already at the latest version."""
class MergeFailed(Error):
def __str__(self):
a :class:`wizard.deploy.Error` if ``yield_errors`` is ``True``. You can
filter out applications and versions by specifying ``app``
or ``app-1.2.3`` in ``show``. This function may generate
- log output.
+ log output. Make sure that ``show`` is a list and not a string.
"""
if not show: show = applications()
show = frozenset(show)
Checks if the application is configured.
"""
raise NotImplemented
+
+ def verify(self):
+ """
+ Checks if this is an autoinstall, throws an exception if there
+ are problems.
+ """
+ with util.ChangeDirectory(self.location):
+ has_git = os.path.isdir(".git")
+ has_scripts = os.path.isdir(".scripts")
+ if not has_git and has_scripts:
+ raise CorruptedAutoinstallError(self.location)
+ elif has_git and not has_scripts:
+ raise AlreadyVersionedError(self.location)
+ elif not has_git and not has_scripts:
+ if os.path.isfile(".scripts-version"):
+ raise NotMigratedError(self.location)
+
+ def verifyTag(self, srv_path):
+ """
+ Checks if the purported version has a corresponding tag
+ in the upstream repository.
+ """
+ repo = self.application.repository(srv_path)
+ try:
+ shell.Shell().eval("git", "--git-dir", repo, "rev-parse", self.app_version.scripts_tag)
+ except shell.CallError:
+ raise NoTagError(self.app_version.scripts_tag)
+
+ def verifyGit(self, srv_path):
+ """
+ Checks if the autoinstall's Git repository makes sense,
+ checking if the tag is parseable and corresponds to
+ a real application, and if the tag in this repository
+ corresponds to the one in the remote repository.
+ """
+ with util.ChangeDirectory(self.location):
+ sh = shell.Shell()
+ repo = self.application.repository(srv_path)
+ def repo_rev_parse(tag):
+ return sh.eval("git", "--git-dir", repo, "rev-parse", tag)
+ def self_rev_parse(tag):
+ return sh.safeCall("git", "rev-parse", tag, strip=True)
+ def compare_tags(tag):
+ return repo_rev_parse(tag) == self_rev_parse(tag)
+ if not compare_tags(self.app_version.pristine_tag):
+ raise InconsistentPristineTagError()
+ if not compare_tags(self.app_version.scripts_tag):
+ raise InconsistentScriptsTagError()
+ parent = repo_rev_parse(self.app_version.scripts_tag)
+ merge_base = sh.safeCall("git", "merge-base", parent, "HEAD", strip=True)
+ if merge_base != parent:
+ raise HeadNotDescendantError()
+
+ def verifyConfigured(self):
+ """
+ Checks if the autoinstall is configured running.
+ """
+ if not self.configured:
+ raise NotConfiguredError(self.location)
+
@property
def configured(self):
"""Whether or not an autoinstall has been configured/installed for use."""
"""
Returns the Git repository that would contain this application.
``srv_path`` corresponds to ``options.srv_path`` from the global baton.
- Throws :exc:`NoRepositoryError` if the calculated path does not
- exist.
"""
repo = os.path.join(srv_path, self.name + ".git")
if not os.path.isdir(repo):
for file in self.parametrized_files:
fullpath = os.path.join(dir, file)
try:
- f = open(fullpath, "r")
+ contents = open(fullpath, "r").read()
except IOError:
continue
- contents = f.read()
- f.close()
for key, value in variables.items():
if value is None: continue
contents = contents.replace(key, value)
tmp = tempfile.NamedTemporaryFile(delete=False)
tmp.write(contents)
- tmp.close()
os.rename(tmp.name, fullpath)
def prepareConfig(self, deployment):
"""
self.version = version
self.application = application
@property
+ def tag(self):
+ """
+ Returns the name of the git describe tag for the commit the user is
+ presently on, something like mediawiki-1.2.3-scripts-4-g123abcd
+ """
+ return "%s-%s" % (self.application, self.version)
+ @property
def scripts_tag(self):
"""
Returns the name of the Git tag for this version.
def __str__(self):
return """Could not find Git repository for '%s'. If you would like to use a local version, try specifying --srv-path or WIZARD_SRV_PATH.""" % self.app
-# If you want, you can wrap this up into a registry and access things
-# through that, but it's not really necessary
+class NotMigratedError(Error):
+ """
+ The deployment contains a .scripts-version file, but no .git
+ or .scripts directory.
+ """
+ def __init__(self, dir):
+ self.dir = dir
+ def __str__(self):
+ return """This installation was not migrated"""
+
+class AlreadyVersionedError(Error):
+ def __init__(self, dir):
+ self.dir = dir
+ def __str__(self):
+ return """
+
+ERROR: Directory contains a .git directory, but not
+a .scripts directory. If this is not a corrupt
+migration, this means that the user was versioning their
+install using Git."""
+
+class NotConfiguredError(Error):
+ def __init__(self, dir):
+ self.dir = dir
+ def __str__(self):
+ return """
+
+ERROR: The install was well-formed, but not configured
+(essential configuration files were not found.)"""
+
+class CorruptedAutoinstallError(Error):
+ def __init__(self, dir):
+ self.dir = dir
+ def __str__(self):
+ return """
+
+ERROR: Directory contains a .scripts directory,
+but not a .git directory."""
+
+class NotAutoinstallError(Error):
+ def __init__(self, dir):
+ self.dir = dir
+ def __str__(self):
+ return """
+
+ERROR: Could not find .scripts-version file. Are you sure
+this is an autoinstalled application?
+"""
+
+class NoTagError(Error):
+ def __init__(self, tag):
+ self.tag = tag
+ def __str__(self):
+ return """
+
+ERROR: Could not find tag %s in repository.""" % self.tag
_application_list = [
"mediawiki", "wordpress", "joomla", "e107", "gallery2",
"""Detects whether or not an argument list invokes a Python program."""
return args[0] == "python" or args[0] == "wizard"
-def drop_priviledges(options):
+def drop_priviledges(dir, options):
"""
Checks if we are running as root. If we are, attempt to drop
- priviledges to the user who owns the current directory, by re-calling
+ priviledges to the user who owns ``dir``, by re-calling
itself using sudo with exec, such that the new process subsumes our
current one.
"""
if os.getuid():
return
- uid = util.get_dir_uid('.')
+ uid = util.get_dir_uid(dir)
if not uid:
return
args = []
import pwd
import sys
import socket
+import errno
import wizard
self.olddir = None
def __enter__(self):
self.olddir = os.getcwd()
- os.chdir(self.dir)
+ chdir(self.dir)
def __exit__(self, *args):
- os.chdir(self.olddir)
+ chdir(self.olddir)
class Counter(object):
"""
self.proc.wait()
sys.stdout = self.old_stdout
+def chdir(dir):
+ """
+ Changes a directory, but has special exceptions for certain
+ classes of errors.
+ """
+ try:
+ os.chdir(dir)
+ except OSError as e:
+ if e.errno == errno.EACCES:
+ raise PermissionsError()
+ elif e.errno == errno.ENOENT:
+ raise NoSuchDirectoryError()
+ else: raise e
+
def dictmap(f, d):
"""
A map function for dictionaries. Only changes values.
"""No information could be found about the operator from Kerberos."""
pass
+class PermissionsError(IOError):
+ errno = errno.EACCES
+
+class NoSuchDirectoryError(IOError):
+ errno = errno.ENOENT