PHP Deployment: Source Control

This is an article that is based off of a talk I did covering various deployment mechanisms.  The slides can be found at Slideshare

The next deployment option that we're going to look at is source control.  You're using source control, yes?  There are arguments as to which is the best.  Git seems to be winning that war in the open source world, but what it comes down to is that the source control you use is less important than whether or not you're using source control.

The example that we're going to use is Subversion.  Git may work really well and it might be the future, but Subversion is the now.  So we'll use that.

Like most deployment mechanisms there are a lot of ways that you could approach this.  I am going to go with the tagging approach.  Some people might be tempted to use trunk as their deployment source since trunk is supposed to contain pristine code.  However, as with all things, rollbacks are a difficult issue to resolve.  If trunk is your source you will need to know the revision of the code that you want to roll back to.  And it might not be immediately clear which revision you need to roll back to.  You will either need to have the previous version revision number written down somewhere or recorded as part of your deployment process.

Why is that?  It's because your revision numbers on trunk are based off of the entire repository, not just trunk.  If you need to roll back your application from revision 1853 to its previous release it that revision number will probably not be 1852.  That's not to say that you can't build ways around it.  What I'm saying is why bother with building ways around it when you can just tag it?  Then when you need to roll back your app you just take the previous tag number and switch your source tree to it.

So how do you do it?  First let's check out our source code locally

[root@dev ~]# cd /var/www/
[root@dev www]# svn co svn://testing/repos/HelloWorld/branches/1.0 application
A    application/tests
A    application/tests/application
A    application/tests/application/bootstrap.php
/snip/
A    application/public/index.php
 U   application
Checked out revision 13.

Then we'll change a line of code in the application, using vi.

[root@dev www]# vi application/application/views/scripts/index/index.phtml

And we commit our code

[root@dev www]# cd application/
[root@dev application]# svn ci -m 'Changed version number'
Sending        application/views/scripts/index/index.phtml
Transmitting file data .
Committed revision 14.

Now that we've committed our code we go to our testing server and test it.  Once we have tested it we need to merge that into trunk, which can sometimes be a bit of a bear.  I actually do it differently in Zend Studio.  In Zend Studio all I do is switch to trunk and replace trunk with the branch.  It's much easier.  Maybe lazy.  But easier.  However, if you're going to merge the branch into trunk you will need to do something like this.

[root@dev tmp]# cd /var/www/
[root@dev www]# mkdir /tmp/HelloWorld && cd /tmp/HelloWorld
[root@dev HelloWorld]# svn co svn://testing/repos/HelloWorld/trunk
A    trunk/tests
A    trunk/tests/application
A    trunk/tests/application/bootstrap.php
/snip/
A    trunk/public/index.php
 U   trunk
Checked out revision 21.
[root@dev HelloWorld]# cd trunk/
[root@dev trunk]# svn log svn://testing/repos/HelloWorld/branches/1.0/

Moving branch 1.0 to trunk
------------------------------------------------------------------------
r12 | (no author) | 2010-06-17 06:20:42 -0500 (Thu, 17 Jun 2010) | 1 line
/snip/
For 0.0.2
------------------------------------------------------------------------
r2 | (no author) | 2010-06-16 15:28:24 -0500 (Wed, 16 Jun 2010) | 1 line

Initial Commit
------------------------------------------------------------------------
r1 | (no author) | 2010-06-16 15:28:05 -0500 (Wed, 16 Jun 2010) | 1 line

Share project "HelloWorld" into "svn://testing/repos"
------------------------------------------------------------------------
[root@dev trunk]# svn merge -r 2:HEAD svn://testing/repos/HelloWorld/branches/1.0
D    application/controllers/ServiceController.php
 G   .
[root@dev trunk]# svn ci -m 'Commiting new 1.0 changes to trunk'
Deleting       application/controllers/ServiceController.php

Committed revision 22.

A few things to note.  What we're doing here checking out the trunk first.  Then we go into the trunk and do a log of the 1.0 branch.  The reason we do this is to find out the revision number of when we branched the source code.  In this case it was r2.  Then I do the svn merge against the branch, stating the revision of when the branch was made and doing it against HEAD.  To me this doesn't make sense, but it works, so I won't complain.

The next thing we have to do is tag the current version of trunk (which we just merged) as the new release.  To do that we simply copy trunk into the tags directory.

[root@dev trunk]# svn copy -m 'Tagging for 0.0.3' svn://testing/repos/HelloWorld/trunk svn://testing/repos/HelloWorld/tags/0.0.3

Committed revision 23.

At this point we are ready to deploy to production.  To do that we need to log into our production server and simply check out the tagged version into our application directory.

[root@prod www]# svn co svn://testing/repos/HelloWorld/tags/0.0.3 application
A    application/tests
A    application/tests/application
A    application/tests/application/bootstrap.php
/snip/
A    application/public/.htaccess
A    application/public/index.php
 U   application
Checked out revision 23.

Then if we need to roll back the installation, rather than re-checking it out we simply switch it.

[root@prod www]# cd application/
[root@prod application]# svn switch svn://testing/repos/HelloWorld/tags/0.0.2
A    application/controllers/ServiceController.php
Updated to revision 23.

And there you have it.  On top of this you can also set up pre and post install scripts.  But there are a couple of problems.  The first one is the .svn directories.  Well, actually the only one is the .svn directories, but there are two problems with it.  The first problem is that you are putting information on how to access your repository on a production box.  A web-facing box.  I personally don't like the sound of that.  There is a fix, I suppose.  Simply disallow access to URLs with .svn in there.  However, that's the second problem.  That's assuming that the ONLY way an attacker would gain access to it is by directly accessing it via the URL.  There are other ways that could be vulnerable and the last thing you want is to have two vulnerabilities to work through at the same time.  But overall it's not really a bad solution.

 


 

2 comments
Kevin
Kevin

Thanks for the comment. I wouldn't check it out either (I also personally wouldn't use this method either). But one of the goals of all of the examples is to have a rollback mechanism which is built into the example as much as possible. The closest thing to that would be a switch with SVN.

The mechanism of which you noted in your comment is a similar mechanism that I had noted in the rsync example for handling rollbacks, which I probably should have noted here as well.

And sorry about the bit bug. I've known about some minor error handling issues in the comments section but I haven't had time to really look at them.

Matthew Weier O'Phinney
Matthew Weier O'Phinney

Personaly, I wouldn't do an svn checkout directly on my production server. Instead, I'd do an svn export. This negates the ability to do an svn switch for rollback, but that can be solved by having a directory per tag, and having your vhost point at a symlink; you then update the symlink to point at the new "tag".

Using svn export ensures you don't have the .svn directories, solving your largest issue with the approach.

Post Navigation

Web Analytics