TODO NOW:
+- --retry option for install, so it won't complain about a directory already
+ being there.
+- The calling web code invocations are a mess, with stubs living
+ in the install, deploy modules and the real deal living in util. Furthermore,
+ we use the scripts-specific heuristic to determine where the app
+ lives, and the only reason my test scripts work is because they
+ get manually fed the domain and path by my environment variables.
+
+ We will record the URL used for the initial installation, and save it in
+ .scripts/url. If autodetection in either direction is
+ available, we verify this value against the actual file path the installation
+ lives in (for the scripts case, we can do a file-level comparison because we
+ know the web root of any given file). If they mismatch, we error out
+ and have someone manually resolve the problem. If autodetection is not
+ available, we use the saved .scripts/url for operations.
+
+- Test code duplication (app specific parameters), also naming sucks for autocomplete
+- wizard install wordpress should ask for password
- Test code should auto-nuke the database using `wizard remove` before doing a new install
- git diff :1:$file :2:$file to find out what the user did, or is it :3:?
- Document how to fix a broken upgrade
import os
import re
import logging
+ import distutils
from wizard import app, install, resolve, sql, util
from wizard.app import php
Exceptions
----------
.. autoexception:: Error
-.. autoexception:: RecoverableFailure
- :members:
.. autoexception:: NoRepositoryError
:members:
.. autoexception:: DeploymentParseError
:members:
.. autoexception:: NoSuchApplication
:members:
+
+Failures
+''''''''
+.. autoexception:: Failure
+.. autoexception:: InstallFailure
+ :members:
+.. autoexception:: RecoverableInstallFailure
+ :members:
+ :show-inheritance:
.. autoexception:: UpgradeFailure
:members:
.. autoexception:: UpgradeVerificationFailure
:members:
.. autoexception:: CorruptedAutoinstallError
:members:
-.. autoexception:: NotAutoinstallError
- :members:
.. autoexception:: NoTagError
:members:
.. autoexception:: NoLocalTagError
Exceptions
----------
.. autoexception:: Error
-.. autoexception:: Failure
- :show-inheritance:
.. autoexception:: StrategyFailed
:show-inheritance:
.. autoexception:: UnrecognizedPreloads
--- /dev/null
+#!/bin/bash -e
+
+TESTNAME="upgrade_wordpress"
+source ./setup
+
+wizard install wordpress-$VERSION-scripts "$TESTDIR" --non-interactive -- --title="My Blog"
+wizard upgrade "$TESTDIR"
should provide an implementation.
"""
raise NotImplementedError
+ def detectVersionFromFile(self, filename, regex):
+ """
+ Helper method that detects a version by using a regular expression
+ from a file. The regexed value is passed through :mod:`shlex`.
+ This assumes that the current working directory is the deployment.
+ """
+ contents = open(filename).read()
+ match = regex.search(contents)
+ if not match: return None
+ return distutils.version.LooseVersion(shlex.split(match.group(2))[0])
def download(self, version):
"""
Returns a URL that can be used to download a tarball of ``version`` of
# XXX: add support for getting these out of options
vars = d.extract()
if 'WIZARD_DBNAME' not in vars:
- raise app.BackupFailure("Could not determine database name")
+ raise BackupFailure("Could not determine database name")
triplet = scripts.get_sql_credentials(vars)
args = []
if triplet is not None:
"""Generic error class for this module."""
pass
-class RecoverableFailure(Error):
- """
- The installer failed, but we were able to determine what the
- error was, and should give the user a second chance if we were
- running interactively.
- """
- #: List of the errors that were found.
- errors = None
- def __init__(self, errors):
- self.errors = errors
- def __str__(self):
- return """Installation failed due to the following errors: %s""" % ", ".join(self.errors)
-
class NoRepositoryError(Error):
"""
:class:`Application` does not appear to have a Git repository
def __init__(self, app):
self.app = app
-class UpgradeFailure(Error):
+class Failure(Error):
+ """
+ Represents a failure when performing some double-dispatched operation
+ such as an installation or an upgrade. Failure classes are postfixed
+ with Failure, not Error.
+ """
+ pass
+
+class InstallFailure(Error):
+ """Installation failed for unknown reason."""
+ pass
+
+class RecoverableInstallFailure(InstallFailure):
+ """
+ Installation failed, but we were able to determine what the
+ error was, and should give the user a second chance if we were
+ running interactively.
+ """
+ #: List of the errors that were found.
+ errors = None
+ def __init__(self, errors):
+ self.errors = errors
+ def __str__(self):
+ return """Installation failed due to the following errors: %s""" % ", ".join(self.errors)
+
+class UpgradeFailure(Failure):
"""Upgrade script failed."""
#: String details of failure (possibly stdout or stderr output)
details = None
%s""" % self.details
-class UpgradeVerificationFailure(Error):
+class UpgradeVerificationFailure(Failure):
"""Upgrade script passed, but website wasn't accessible afterwards"""
#: String details of failure (possibly stdout or stderr output)
details = None
%s""" % self.details
-class BackupFailure(Error):
+class BackupFailure(Failure):
"""Backup script failed."""
#: String details of failure
details = None
%s""" % self.details
-class RestoreFailure(Error):
+class RestoreFailure(Failure):
"""Restore script failed."""
#: String details of failure
details = None
})
class Application(app.Application):
- parametrized_files = ['LocalSettings.php', 'php.ini']
+ parametrized_files = ['LocalSettings.php'] + php.parametrized_files
deprecated_keys = set(['WIZARD_IP']) | php.deprecated_keys
extractors = app.make_extractors(seed)
extractors.update(php.extractors)
def checkConfig(self, deployment):
return os.path.isfile("LocalSettings.php")
def detectVersion(self, deployment):
- contents = deployment.read("includes/DefaultSettings.php")
- regex = php.re_var("wgVersion")
- match = regex.search(contents)
- if not match: return None
- return distutils.version.LooseVersion(match.group(2)[1:-1])
+ return self.detectVersionFromFile("includes/DefaultSettings.php", php.re_var("wgVersion"))
def checkWeb(self, deployment, out=None):
page = deployment.fetch("/index.php?title=Main_Page")
if type(out) is list:
if options.verbose or options.debug: print result
if result.find("Installation successful") == -1:
if not error_messages:
- raise install.Failure()
- raise app.RecoverableFailure(error_messages)
+ raise app.InstallFailure()
+ else:
+ raise app.RecoverableInstallFailure(error_messages)
os.rename('config/LocalSettings.php', 'LocalSettings.php')
def upgrade(self, d, version, options):
sh = shell.Shell()
import os
import re
import logging
+import distutils
+import urlparse
from wizard import app, install, resolve, sql, util
from wizard.app import php
'WIZARD_LOGGED_IN_KEY': 'LOGGED_IN_KEY',
'WIZARD_NONCE_KEY': 'NONCE_KEY',
})
-# XXX: I have omitted an implementation for table prefix, on grounds that we
-# do not permit it to be configured. If we do end up growing support for
-# arbitrary table prefixes this should be added.
class Application(app.Application):
parametrized_files = ['wp-config.php'] + php.parametrized_files
return "http://wordpress.org/wordpress-%s.tar.gz" % version
def checkConfig(self, deployment):
return os.path.isfile("wp-config.php")
+ def checkWeb(self, deployment, out=None):
+ page = deployment.fetch("")
+ if type(out) is list:
+ out.append(page)
+ return page.find("<html") != -1
def detectVersion(self, deployment):
# XXX: Very duplicate code with MediaWiki; refactor
contents = deployment.read("wp-includes/version.php")
if not match: return None
return distutils.version.LooseVersion(match.group(2)[1:-1])
def prepareMerge(self, deployment):
- resolve.fix_newlines("wp-config.php")
+ # This file shouldn't really be edited by users, but be careful: it's
+ # stored as DOS and not as UNIX, so you'll get conflicts if you add this line:
+ # resolve.fix_newlines("wp-config.php")
+ pass
def install(self, version, options):
# XXX: Hmm... we should figure out something about this
try:
logging.debug("install.php output\n\n" + result)
os.chmod(".", old_mode)
if "Finished" not in result and "Success" not in result:
- raise install.Failure()
+ raise app.InstallFailure()
# not sure what to do about this
meta = sql.mysql_connect(options)
wp_options = meta.tables["wp_options"]
wp_options.update().where(wp_options.c.option_name == 'siteurl').values(option_value=options.web_path).execute()
wp_options.update().where(wp_options.c.option_name == 'home').values(option_value="http://%s%s" % (options.web_host, options.web_path)).execute() # XXX: what if missing leading slash; this should be put in a function
+ # should also set the username and admin password
+ def upgrade(self, d, version, options):
+ result = d.fetch("wp-admin/upgrade.php?step=1")
+ if "Upgrade Complete" not in result and "No Upgrade Required" not in result:
+ raise app.UpgradeFailure(result)
+ def backup(self, deployment, backup_dir, options):
+ app.backup_database(backup_dir, deployment)
+ def restore(self, deployment, backup_dir, options):
+ app.restore_database(backup_dir, deployment)
"-p", base_virtual_commit, input="", log=True)
sh.call("git", "checkout", user_virtual_commit, "--")
wc.prepareMerge()
- sh.call("git", "commit", "--amend", "-a", "-m", "amendment")
+ try:
+ sh.call("git", "commit", "--amend", "-a", "-m", "amendment")
+ except shell.CallError as e:
+ pass
try:
sh.call("git", "merge", next_virtual_commit)
except shell.CallError as e:
#: If true, is a password
password = False
@property
- def option(self):
- """Full string of the option."""
- return attr_to_option(self.name)
- @property
def envname(self):
"""Name of the environment variable containing this arg."""
return 'WIZARD_' + self.name.upper()
"""Base error class for this module."""
pass
-# XXX: This is in the wrong place
-class Failure(Error):
- """Installation failed."""
- pass
-
class StrategyFailed(Error):
"""Strategy couldn't figure out values."""
pass
-from docutils import nodes
-from sphinx.util.compat import make_admonition
-from sphinx.util.compat import Directive
+import docutils
+import sphinx.util.compat
def setup(app):
app.add_node(supplement,
text=(visit_supplement_node, depart_supplement_node))
app.add_directive('supplement', SupplementDirective)
-class supplement(nodes.Admonition, nodes.Element):
+class supplement(docutils.nodes.Admonition, docutils.nodes.Element):
pass
def visit_supplement_node(self, node):
def depart_supplement_node(self, node):
self.depart_admonition(node)
-class SupplementDirective(Directive):
+class SupplementDirective(sphinx.util.compat.Directive):
has_content = True
optional_arguments = 1
final_argument_whitespace = True
def run(self):
- text = _('Supplement')
+ text = 'Supplement'
if len(self.arguments) > 0:
- text = "For %s" % _(self.arguments[0])
- return make_admonition(supplement, self.name, [text], self.options,
+ text = "For %s" % self.arguments[0]
+ return sphinx.util.compat.make_admonition(supplement, self.name, [text], self.options,
self.content, self.lineno, self.content_offset,
self.block_text, self.state, self.state_machine)