In my previous article I talked about installing Webistrano. Now, let’s start using it.
Most of the applications I write, are PHP based, so all of my examples will be based on that assumption, but you can re-use the ideas mentioned for software written in any other programming language.
The setup mentioned below discusses just the deployment to the test project. Deployment to the production will be similar, and by the end of the article, you should understand what differences it will require. If you have any questions, or suggestions, drop me an email, or leave a comment below.
Creating new project
Let’s start with creating a new project. As it will be PHP based, the project type we want to select is pure type. Type in your name and description, and it’s all done.
Webistrano allows you to set up configuration parameters for each of your projects e.g. svn path, deployment path etc. You can view the full list of configuration parameters on Webistrano github wiki pages.
Have a look at the project you created, you will see the basic configuration for all the variables:
With all of our projects being set up in mostly the same manner, I would like to make sure that by default, each new projects uses the same svn user, ** deployment method** etc. This will minimize any potential errors, and speed up the process. In order to set this up, we will have to dive into the Webistrano code-base.
The file that holds our default setup is called lib/webistrano/template/base.rb
$ vim lib/webistrano/template/base.rb
Now edit your settings to fit your needs:
:deploy_via => ':export',
:scm_username => 'webistrano',
:scm_password => 'xyz',
:user => 'webistrano',
:password => 'xyzx',
:deploy_to => '/var/www/vhosts/project.com',
In order for this config file to start working, you’ll have to restart your mongrel
$ su - webistrano $ mongrel_rails stop $ mongrel_rails start -e production -d -p 3000
All of those settings can be overwritten at each of the stages if we want to later on – through the Webistrano UI.
Creating stages
The next step is to create stages for the project. Stages should reflect yourenvironments. For my purpose I’m going to create Test stage only though.
Every stage needs a host or a group of hosts it will be deployed to. For the purpose of this article, I’m just going to use the local server.
Click on hosts and add the new host IP or DNS – 127.0.0.1
Now go back to the Test stage of your project, and add a new host
One last thing before we can start deploying is to change the svn path. For this we will overwrite default Webistrano configuration with Stage specific configuration. You can overwrite all of the settings here, as well, as creating new ones. For our purpose we’re just going to create repository
For all of the Test stages, I allow users to type in their own path (most likely to be trunk) but for Production, I make sure it’s being filled in on deployment. Production will always deploy stable tags, so I want users to make sure they are deploying the correct tag.
Deployment
You’re now ready to do a basic deployment. Capistrano checks out/exports the code from your code repository and puts it in the folder called revisions, in the path you specified. Each deployment will have it’s own folder, with it’s name being the date and time of the deployment. Capistrano will remove the older revisions automatically.
At the end of each deployment Capistrano creates a symbolic link to the latest revision, in form of a folder called current. That means that reverting your changes (as long as no database changes are involved) is as simple as destroying the link, and linking to the previous revision. This way you don’t have to worry about long checkouts, all the versions of the application are available on hand.
With this simple set up you should be able to deploy applications with one click. But if this is not enough and you want to automate certain processess, read on aboutReceipies
Recipes
The most powerful tool of Capistrano / Webistrano deployments are recipes. We usePlesk on all our servers, so the example recipe I’m going to talk about takes it into consideration.
Here’s a blank template for a new receipe
namespace :deploy do do smth... end
Capistrano uses namespaces as a tool that allows authors to differentiate their tasks from other tasks with the same name. As mentioned in capistrano documentation
Namespaces may be nested to arbitrary depths. In this case, a namespace’s fully qualified name includes the name of all parent namespaces. For example:
namespace :deploy do namespace :web do task :enable, :roles => :web do # ... end end end
This will make the basis for our deployment recipe.
With regards to the automation,there are a few things I would like to make sure happen, each time I make a deployment:
Setting correct permissions and cleaning up
If you decided to get your code via svn checkout your code on the server will be full of.svn
folders. This can cause security risk, and you probably will want to get rid of them. In order to do this, start task finalize_update in your recipe, and add the line below:
namespace :deploy do task :finalize_update do run "rm -rf 'find #{latest_release} -type d -name .svn'" end end
now you see you can use run
to run any bash commands you require. If you need to run them as root, use sudo
instead.
Couple of more things I would like to add is making sure that the cache folder has rw rights, as our smarty templates are being compiled there:
sudo "chmod -R 777 #{latest_release}/cache"
Updating the DB
For both test and live environments, we might want the db scripts to be run automatically. It’ll be the developers responsibility to maintain the db update scripts. For example, the first time the site is created the script will contain CREATE DATABASE
statement, but later on will only contain UPDATES, this will mean all of the db changes are versioned.
For the purpose of the deployment recipe, I’m adding a function that will check if remote file exists, and return true or false
def remote\_file\_exists?(full_path) 'true' == capture("if [ -e #{full_path} ]; then echo 'true'; fi").strip end
now for the DB scripts, I’m going to create a folder called install in the root of application. If the developer created db.sql file in it, I want the mysql to run it
install_db_path = "#{latest_release}/install/db.sql" if remote_file_exists?(install_db_path) run "mysql -u #{mysql_user} -p#{mysql_password} -h localhost #{mysql_database} < #{latest_release}/install/db.sql" end
To use this script, I will create mysql_user
, mysql_database
and mysql_password
in my stage config, and populate it with correct values.
Managing assets
As the code is being checked out / exported each time you deploy, you want your user uploaded assets to remain the same, and symlinked between revisions. Capistrano uses variable shared_path
for such cases. In our case we’re keeping all of the hidden assets in root of application in resources folder, and the public ones in wwwroot/resources, hence the deployment:
run "ln -nfs #{shared_path}/assets #{latest_release}/resources/" run "ln -nfs #{shared_path}/assets/wwwroot #{latest_release}/wwwroot/resources/"
For test environment I might want to also run an rsync script. I will remove this for live environment recipe:
install_rsync_path = "#{latest_release}/install/rsync.sh" if remote_file_exists?(install_rsync_path) run install_rsync_path end
libraries.sh reads the conf files for our framework and searches for the library definitions i.e.
# !/bin/bash # Migrating all the media files over to the current directories DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" if [ -n "$1" ]; then username=`echo $1 | tr '[:upper:]' '[:lower:]'` else username=`whoami` fi echo "Linking Framework from your ini file" framework_path=$(sed -n 's/.*framework_dir *= *([^ ]*.*)/\1/p' < $DIR/../application/conf/$username.ini | tr -d '"') echo "You chose Framework: ln -s ${framework_path} $DIR/../lib/Framework" ln -s ${framework_path} ${DIR}/../lib/Framework echo "Linking Smarty from your ini file" smarty_path=$(sed -n 's/.*smarty_dir *= *([^ ]*.*)/\1/p' < $DIR/../application/conf/$username.ini | tr -d '"') echo "You chose Smarty: ln -s ${smarty_path} $DIR/../lib/Smarty" ln -s ${smarty_path} ${DIR}/../lib/Smarty
with staging.conf containing:
[lib] vanilla_dir = "/var/www/libs/Vanilla/trunk" smarty_dir = "/var/www/libs/Smarty/3.0.8/"
You can change the sed pattern to fit your needs.
Fixing Plesk permissions problem
Webistrano is set up to log into each of the servers as user called webistrano. This will cause issues with any Plesk installations, as Plesk doesn’t allow anyone apart from owners to write into httpdocs. This can be solved if we add Webistrano to Plesk grouppsaserv
and change the permissions of the httpdocs before the deployment starts. Here’s how to achieve it:
before "deploy", :change_permissions before "deploy:migrations", :change_permissions desc "Change permissions" task :change_permissions, :roles => [ :web ] do sudo "chmod g+w #{deploy_to}" end
Conclusion
This is just a little teaser of what Webistrano can do for your deployment You can use it to minify your JS and CSS files, overwrite your environment configs with the variables specified in your webistrano configs, multi-host deployments. The world is your oyster. Please find the full recipe below:
def remote_file_exists?(full_path) 'true' == capture("if [ -e #{full_path} ]; then echo 'true'; fi").strip end namespace :deploy do task :finalize_update do run "rm -rf `find #{latest_release} -type d -name .svn`" sudo "chmod -R 777 #{latest_release}/cache" run "ln -nfs #{shared_path}/assets #{latest_release}/resources/" run "ln -nfs #{shared_path}/assets/wwwroot #{latest\_release}/wwwroot/resources/" install_db_path = "#{latest_release}/install/db.sql" if remote_file_exists?(install_db_path) run "mysql -u #{mysql_user} -p#{mysql_password} -h localhost #{mysql_database} < #{latest_release}/install/db.sql" end install_rsync_path = "#{latest_release}/install/rsync.sh" if remote_file_exists?(install_rsync_path) run install_rsync_path end # linking libraries install_libraries_path = "#{latest_release}/install/libraries.sh" if remote_file_exists?(install_libraries_path) run "#{latest_release}/install/libraries.sh staging" end end task :apache_graceful do if( restart_apache ) logger.info "Graceful restart apache" sudo "/etc/init.d/httpd graceful" end end end after "deploy:update", "deploy:cleanup" before "deploy", :change_permissions before "deploy:migrations", :change_permissions desc "Change permissions" task :change_permissions, :roles => [ :web ] do sudo "chmod g+w #{deploy_to}" end