Repository conversion
=====================
+.. highlight:: sh
+
One of Wizard's goals is to replace the previous autoinstaller infrastructure.
Pre-wizard autoinstalls live in :file:`/mit/scripts/deploy` and consist of a
tarball from upstream, possibly a scripts patch, and possibly some post-install
Setup
-----
-.. highlight:: sh
-
Probably the easiest way to do development is entirely on :term:`AFS`: all
of your source code should live in publically readable (i.e.
``system:anyuser`` as read permissions) directories, so that if you
Pristine
--------
-.. highlight:: sh
-
This is a tutorial centered around migrating `Wordpress <http://wordpress.org/>`_
into our Git repository. For the sake of demonstration,
we shall assume that this repository hasn't been created yet.
cd wordpress
git init
-.. highlight:: python
-
We also have to create a module for the application, so we
-create ``$WIZARD/wizard/app/wordpress.py`` and fill it in with a bare bones template::
+create ``$WIZARD/wizard/app/wordpress.py`` and fill it in with a bare bones template:
+
+.. code-block:: python
+
+ import os
+ import re
+ import logging
+
+ from wizard import app, install, resolve, sql, util
+ from wizard.app import php
- from wizard import app
class Application(app.Application):
pass
-.. highlight:: sh
-
Now we are ready to put some code in our repository: the first thing we will
add is the :term:`pristine` branch, which contains verbatim the code from upstream.
If we were starting a new autoinstaller, we'd pop off and use the latest version,
wizard prepare-pristine wordpress-2.0.2
-.. highlight:: python
-
You should get an error complaining about :meth:`wizard.app.Application.download`
-not being implemented yet. Let's fix that::
+not being implemented yet. Let's fix that:
+
+.. code-block:: python
class Application(app.Application):
def download(self, version):
return "http://wordpress.org/wordpress-%s.tar.gz" % version
-.. highlight:: sh
-
We determined this by finding `Wordpress's Release Archive <http://wordpress.org/download/release-archive/>`_
and inferring the naming scheme by inspecting various links. You should now
be able to run the prepare-pristine command successfully: when it is
c -> cs
a []
-.. highlight:: sh
-
First things first: verify that we are on the master branch::
git checkout master
There's an in-depth explanation of named input values in
:mod:`wizard.install`. The short version is that your application
contains a class-wide :data:`~wizard.app.Application.install_schema`
-attribute that encodes this information. The constructor takes
-an arbitrary number of arguments (to be continued)
+attribute that encodes this information. Instantiate it with
+:class:`wizard.install.ArgSchema` (passing in arguments to get
+some pre-canned input values), and then add application specific
+arguments by passing instances of :class:`wizard.install.Arg`
+to the method :meth:`~wizard.install.ArgSchema.add`. Usually you should
+be able to get away with pre-canned attributes. You can access
+these arguments inside :meth:`~wizard.app.Application.install` via
+the ``options`` value.
Some tips and tricks for writing :meth:`wizard.app.Application.install`:
consequently be queried. For convenience, we've bound metadata
to the connection, you can perform implicit execution.
+.. todo::
+
+ Our installer needs to also parametrize :file:`php.ini`, which we haven't
+ done yet.
+
To test if your installation function works, it's probably convenient to
create a test script in :file:`tests`; :file:`tests/test-install-wordpress.sh`
in the case of Wordpress. It will look something like::
operation without any version specified (since we haven't tagged any of our
commits yet, we can't use the specific version functionality; not that we'd want
to, though). ``TESTNAME`` is simply the name of the file with the leading
-``test-`` stripped and dashes converted to underscores.
+``test-`` stripped and dashes converted to underscores. Run the script with
+verbose debugging information by using::
+
+ env WIZARD_DEBUG=1 ./test-install-wordpress.sh
+
+Versioning config
+-----------------
+
+A design decision that was made early on during Wizard's development was that
+the scriptsified versions would contain generic copies of the configuration
+files. You're going to generate this generic copy now and in doing so,
+overload your previous scripts commit. Because some installers
+exhibit different behavior depending on server configuration, you should run
+the installation on a Scripts server::
+
+ wizard install wordpress --no-commit
+
+The installer will interactively prompt you for some values, and then conclude
+the install. ``--no-commit`` prevents the installer from generating a Git
+commit after the install, and will make it easier for us to propagate the
+change back to the parent repository.
+
+Look at the changes the installer made::
+
+ git status
+
+There are probably now a few unversioned files lounging around; these are probably
+the configuration files that the installer generated.
+
+You will now need to implement the following data attributes and methods in your
+:class:`~wizard.app.Application` class: :attr:`~wizard.app.Application.extractors`,
+:attr:`~wizard.app.Application.substitutions`, :attr:`~wizard.app.Application.parametrized_files`,
+:meth:`~wizard.app.Application.checkConfig` and :meth:`~wizard.app.Application.detectVersion`.
+These are all closely related to the configuration files that the installer generated.
+
+:meth:`~wizard.app.Application.checkConfig` is the most straightforward method to
+write: you will usually only need to test for the existence of the configuration file.
+Note that this function will always be called with the current working directory being
+the deployment, so you can simplify your code accordingly:
+
+.. code-block:: python
+
+ def checkConfig(self, deployment):
+ return os.path.isfile("wp-config.php")
+
+:meth:`~wizard.app.Application.detectVersion` should detect the version of the application
+by regexing it out of a source file. We first have to figure out where the version number
+is stored: a quick grep tells us that it's in :file:`wp-includes/version.php`:
+
+.. code-block:: php
+
+ <?php
+
+ // This just holds the version number, in a separate file so we can bump it without cluttering the SVN
+
+ $wp_version = '2.0.4';
+ $wp_db_version = 3440;
+
+ ?>
+
+We could now grab the :mod:`re` module and start constructing a regex to grab ``2.0.4``, but it
+turns out this isn't necessary: :meth:`wizard.app.php.re_var` does this for us already!
+If such a module doesn't exist, you should create it and place an equivalent ``re_var()``
+function for that language there.
+
+With this function in hand, writing a version detection function is pretty straightforward.
+There is one gotcha: the value that ``re_var`` returns as the second subpattern is quoted (the reasons for this
+will become clear shortly), so you will need to trim off the last and first characters or
+use :mod:`shlex`. In the case of version numbers, there are probably no escape characters
+in the string, so the former is relatively safe.
+
+.. code-block:: python
+
+ def detectVersion(self, deployment):
+ contents = open("wp-includes/version.php").read()
+ match = php.re_var("wp_version").search(contents)
+ if not match: return None
+ return distutils.version.LooseVersion(match.group(2)[1:-1])
+
+:attr:`~wizard.app.Application.parametrized_files` is a simple list of files that the
+program's installer wrote or touched during the installation process.
+
+.. code-block:: python
+
+ parametrized_files = ['wp-config.php']
+
+This is actually is a lie: we also need to include changes to :file:`php.ini` that
+we made:
+
+.. code-block:: python
+
+ parametrized_files = ['wp-config.php'] + php.parametrized_files
+
-(to be continued)
+"""
+Common data and functions for use in PHP applications.
+
+.. testsetup:: *
+
+ from wizard.app.php import *
+"""
+
import re
from wizard import app, util
def re_var(var):
+ """
+ Generates a regexp for assignment to ``var`` in PHP; the quoted
+ value is the second subpattern.
+
+ >>> re_var('key').search("$key = 'val';").group(2)
+ "'val'"
+ """
return re.compile('^(\$' + app.expand_re(var) + r'''\s*=\s*)(.*)(;)''', re.M)
-def make_filename_regex(var):
+def _make_filename_regex(var):
return 'php.ini', re.compile('^(' + app.expand_re(var) + r'\s*=\s*)(.*)()$', re.M)
-seed = util.dictmap(make_filename_regex, {
+seed = util.dictmap(_make_filename_regex, {
'WIZARD_SESSIONNAME': 'session.name',
'WIZARD_TMPDIR': ('upload_tmp_dir', 'session.save_path'),
})
+#: Common extractors for parameters in :file:`php.ini`.
extractors = app.make_extractors(seed)
+#: Common substitutions for parameters in :file:`php.ini`.
substitutions = app.make_substitutions(seed)
+#: A list containing :file:`php.ini`.
+parametrized_files = ["php.ini"]
+#: Nop for consistency.
deprecated_keys = set([])