Preparing an upgrade

Author:Edward Z. Yang <ezyang@mit.edu>

Wizard is designed to make pushing upgrades as painless as possible. In the best case scenario, adding a new version to a Wizard repository is as simple as running a few commands. Even when upstream makes backwards incompatible changes, or some of your patches conflict with other changes, Wizard aims to make resolving these changes tractable.

Summary

$VERSION is a version number i.e. 1.2.4, $APPLICATION is the official application name i.e. MediaWiki, and $APP is our internal name i.e. mediawiki. The key thing to note is that we use wizard prepare-pristine in order to simulate the upstream upgrade, and then we piggy back off of Git’s merge machinery to do everything else.

First you prepare the pristine copy:

git checkout pristine
wizard prepare-pristine $APP-$VERSION
git commit -sm "$APPLICATION $VERSION"
git tag $APP-$VERSION

Next, you merge those changes to the scriptsified master copy:

git checkout master
git merge pristine --no-commit
# resolve conflicts
git commit -sm "$APPLICATION $VERSION-scripts"

Note

If you are creating a fix for a previous scripts version, you should bump the version to $VERSION-scripts2.

If the files associated with installation (for example, the install script) have changed, we need to double check that the configuration files are in order. On a scripts server with Wizard pointed at the latest version, run:

cd tests
env WIZARD_NO_COMMIT=1 ./$APP-install-test.sh
cd testdir_$APP_install_head
wizard prepare-config

With any luck, there will be no differences. However, if there are changes, manually restore any custom changes we may have made to the configuration file (a git checkout -p should allow you to selectively back out the relevant bits). Furthermore, make sure that no upstream changes broke our regular expressions for matching. The merge your changes back (preferably via your local machine, since you probably don’t have appropriate author credentials setup to be accessible on Scripts):

git commit --amend -a
git tag $APP-$VERSION-scripts
git push --force
git push --force --tags

Note

If you have a split AFS and local Wizard setup, you may prefer to instead create a dummy commit and do the merging in your local copy. The flow looks like:

# from the scripts copy
git commit -asm "Dummy"
git push
# from your copy
git pull
git reset HEAD~
git commit --amend -a
git tag $APP-$VERSION-scripts
git push -f

Be sure to verify that your commit is the correct one; you can check with git show, which should show the changes you made when amending the commit. Be especially careful to make sure you don’t nuke any in configuration scripts changes.

Implementation

If it is the first time you have pushed an upgrade for an application, you will have to write a few more methods in your wizard.app.Application class: upgrade(), checkWeb(), backup() and restore(). The latter three may not seem so useful for just pushing an upgrade, but are helpful for integrity checking installations post-upgrade, and rolling back if something went wrong.

checkWeb() is a method that should check whether or not an application is running properly. We use this to prevent us from trying to upgrade an install that is not publically accessible, or was broken from the very start, and we use it to automatically determine if our upgrade was successful or not. A common and easy way to perform this check is to use the checkWebPage() method, which, along with the parameters checkWeb() accepts, accepts two more: page and output, which correspond to the page to grab from the web and the output string to match for in this page,

Warning

We still haven’t quite figured out a good combination of in-depth error checking and robustness against skin changes. This section should be further developed with tips about this.

backup() and restore() perform backup and restoration of non-filesystem contents; the most common application is for the database. wizard.app.backup_database() and wizard.app.restore_database() implement this common functionality, and for most application implementing these methods is as simple as:

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)

Finally, upgrade() actually performs an upgrade, and will most frequently call a shell script or fetches a web page that will perform a schema upgrade.

Note

When migrating an old-style autoinstall, it is neither expected nor required for upgrade scripts for the intervening versions to be created.

Troubleshooting

Merge failed

When a merge fails, it’s often good to refresh your memory about what particular patch was made to that file. You can find out with:

git diff :1:$FILE :2:$FILE

If you are performing a repository conversion, a failed merge likely means that there is an updated patch lying around in /mit/scripts/deploy/$APP-$VERSION. You can then revert the files to the pristine version:

git checkout --theirs $FILE

And then apply the patch. If the patch is complicated, you may get warnings about hunks already being applied; you can ignore those warnings (don’t assume -R!)

Going retro

Under certain circumstances, you may need to splice in older versions of the application into your history. Do not rebase: you should never rebase published history. Instead, use the following procedure:

Identify the version that, with regards to version numbering, directly precedes the version you’d like to add. For example, if you have the following commit tree:

digraph original_dag {
node [shape=square]
subgraph cluster_master {
    bs -> as
    as [label="1.0-scripts"]
    bs [label="2.0-scripts"]
    label = "master"
    color = white
}
subgraph cluster_pristine {
    b -> a
    a [label="1.0"]
    b [label="2.0"]
    label = "pristine"
    color = white
}
bs -> b
as -> a
}

And you are adding the 1.1 version, 1.0 and 1.0-scripts are the tags immediately preceding this version. Create temporary branches (we’ll name them tmaster and tpristine) pointing to these tags:

git checkout -b tmaster
git reset --hard 1.0-scripts
git checkout -b tpristine
git reset --hard 1.0

Find the committer date associated with the tmaster commit using git show tmaster and note it somewhere:

DATE=`git show tmaster --pretty="format:%cd" | head -n1`

Next, begin performing ordinary procedure for preparing the pristine copy. There are two caveats: you will need to use --force to make wizard prepare-pristine not complain about not being on the pristine branch, and you will need to falsify the author and committer time on the commits to be the times we noted down previously:

# on the tpristine branch
wizard prepare-pristine $APP-$VERSION --force
env GIT_AUTHOR_DATE="$DATE" GIT_COMMITTER_DATE="$DATE" git commit -asm "$APPLICATION $VERSION"
git tag $APP-$VERSION

Note

The date falsification is necessary to make Git prefer the later version tag when a commit has this (newer) commit and the most up-to-date version as merge parents. By default Git prefers the temporally closest commit.

Next, merge the changes to the scriptsified tmaster (not master!) copy, and falsify the dates as well:

git checkout tmaster
git merge tpristine --no-commit
# resolve conflicts
env GIT_AUTHOR_DATE="$DATE" GIT_COMMITTER_DATE="$DATE" git commit -asm "$APPLICATION $VERSION-scripts"
git tag $APP-$VERSION-scripts

Note that we are creating a tag, because otherwise there is not an easy way to refer to this non-HEAD tag. On a scripts server with Wizard pointed at the latest version, run:

cd tests
env WIZARD_NO_COMMIT=1 ./$APP-install-test.sh $VERSION
cd testdir_install_$APP_$VERSION
wizard prepare-config

Note that $VERSION is specified explicitly. If there are changes, manually restore any custom changes we may have made, then amend your commit and push back:

# you probably lost your environment variable
DATE=`git show HEAD --pretty="format:%ad" | head -n1`
env GIT_AUTHOR_DATE="$DATE" GIT_COMMITTER_DATE="$DATE" git commit --amend -a
git tag -d $APP-$VERSION-scripts
git tag $APP-$VERSION-scripts
git push --force --tags

And on your now invalid version, grab the new version:

git fetch --tags --force $REMOTE

Note that there is no need to reset the master branch, which hasn’t changed.

Table Of Contents

Previous topic

Creating a repository

Next topic

Testing

This Page