DevOps Zone is brought to you in partnership with:

I’ve spent most of my career working as LAMP stack developer, but most recently I’ve been dabbing my grubby fingers into Python, Ruby, and NoSQL databases. I enjoy nothing more than scalable code, cache that accelerates your websites, and well indexed database. Kasia is a DZone MVB and is not an employee of DZone and has posted 5 posts at DZone. You can read more from them at their website. View Full User Profile

Deploying PHP Projects with Webistrano

10.31.2012
| 15987 views |
  • submit to reddit

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:

Project configuration

Here’s where the magic happens.

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 your environments. For my purpose I’m going to create Test stage only though.

Setting up test stage

Adding your first stage is really simple!

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

Adding new host for a stage

You might want to add hosts as their domains, to avoid confusion in the future

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

Stage specific configuration

You can configure pretty much everything!

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 about Receipies

Recipes

The most powerful tool of Capistrano / Webistrano deployments are recipes. We use Plesk 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 group psaserv 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





Published at DZone with permission of Kasia Gogolek, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)