Hashrocket.com / blog

When Pushing Just Isn't Getting the Job Done

posted on and written by in

Image 100x100 matt polito

Like any good development shop we are constantly looking to make commonly recurring tasks more easily repeatable. Code deployment is one of those things that is happening throughout the day. With Heroku being our hosting provider of choice it makes sense to make deploying our projects as easy as possible.

Paratrooper Image

Some of you may be thinking, 'wait a minute... isn't a deployment to Heroku just a git push away?' You would be correct. It is just that easy to deploy and I want to personally congratulate whoever came up with that idea!

Sometimes projects just require a couple extra steps when deploying. For example a common checklist for deploying a rails app may look like this:

  • Turn maintenance page on
  • Git push your changes to Heroku
  • Run database migrations
  • Turn maintenance page off

These are all things we are doing right? Instead of performing multiple tasks in the command line, you've probably made a rake task or some flavor of task to handle this. This is mostly a great solution to the problem, but let me try to make this a bit easier. Enter Paratrooper, a library to make your Heroku deployment rake tasks simple and powerful.

Let's take a look at what our previous deployment task would look like:

namespace :deploy do
  desc 'Deploy the app'
  task :production do
    app = "amazing-production-app"
    remote = "git@heroku.com:#{app}.git"

    system "heroku maintenance:on --app #{app}"
    system "git push #{remote} master"
    system "heroku run rake db:migrate --app #{app}"
    system "heroku maintenance:off --app #{app}"
  end
end

This makes sense and handles our basic deployment case. Now check out how this changes with Paratrooper:

require 'paratrooper'

namespace :deploy do
  desc 'Deploy app in production environment'
  task :production do
    Paratrooper::Deploy.new("amazing-production-app").deploy
  end
end

Ok... that's definitely less code but what is it doing? This will perform the following tasks:

  • Activating maintenance mode
  • Push changes to Heroku
  • Run database migrations
  • Restart the application
  • Deactivate maintenance mode
  • Warm application instance

Whoa, one statement and we get all of this? That's pretty awesome and easily repeatable. We've had a very easy time porting applications over to Paratrooper just because these are common tasks that we are doing on most projects. Now what happens when my project needs a little bit more deployment love? Well, Paratrooper essentially gives you a bunch of convienence methods that cover common steps of the deployment process. Which means you can just call these methods in any particular order or add in your own code in between. Maybe you have to send a call to New Relic to turn off application monitoring during your deploy. That would probably look like this:

require 'paratrooper'

namespace :deploy do
  desc 'Deploy app in production environment'
  task :production do
    deployment = Paratrooper::Deploy.new("amazing-production-app")

    %x[curl https://heroku.newrelic.com/accounts/ACCOUNT_ID/applications/APPLICATION_ID/ping_targets/disable -X POST -H "X-Api-Key: API_KEY"]

    deployment.activate_maintenance_mode
    deployment.push_repo
    deployment.run_migrations
    deployment.app_restart
    deployment.deactivate_maintenance_mode
    deployment.warm_instance

    %x[curl https://heroku.newrelic.com/accounts/ACCOUNT_ID/applications/APPLICATION_ID/ping_targets/enable -X POST -H "X-Api-Key: API_KEY"]
  end
end

Now you're getting simplicity as well as flexibility!

One more tidbit for you. I am a fan of using tags for managing where my code is at in different environments. So I've built in the ability to utilize tags for deployment. Take the common scenario where you have both a staging and production app on Heroku. This is what my deployment tasks would look like:

require 'paratrooper'

namespace :deploy do
  desc 'Deploy app in staging environment'
  task :staging do
    deployment = Paratrooper::Deploy.new("amazing-staging-app",
      tag: 'staging'
    )

    deployment.deploy
  end

  desc 'Deploy app in production environment'
  task :production do
    deployment = Paratrooper::Deploy.new("amazing-production-app",
      tag: 'production',
      match_tag_to: 'staging'
    )

    deployment.deploy
  end
end

Hmm, so what did this change? Well we passed in a :tag and :match_tag_to option. For the staging task it will create a 'staging' tag at HEAD and push that tag to your repo and Heroku. Then for production the same happens except the :match_tag_to option is telling Paratrooper to match the 'production' tag where 'staging' is at and then push the 'production' tag to your repo and Heroku.

This is Paratrooper, I hope you like and find it as useful as I have. Feel free to provide feedback and comment on how it has helped you.

Posted in Development and tagged with Deployment