Skip to content

Instantly share code, notes, and snippets.

@jfeaver
Created March 27, 2014 22:28
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save jfeaver/9820478 to your computer and use it in GitHub Desktop.
Save jfeaver/9820478 to your computer and use it in GitHub Desktop.
#Deploy and rollback on Heroku in staging and production
class RakeHerokuDeployer
def initialize app_env
@app = ENV["#{app_env.to_s.upcase}_APP"]
end
def run_migrations
push; turn_app_off; migrate; restart; turn_app_on; tag;
end
def deploy
push; restart; tag;
end
def rollback
turn_app_off; push_previous; restart; turn_app_on;
end
private
def push
current_branch = `git rev-parse --abbrev-ref HEAD`.chomp
branch_to_branch = (current_branch.length > 0) ? "#{current_branch}:master" : ""
puts 'Deploying site to Heroku ...'
puts "git push -f git@heroku.com:#{@app}.git #{branch_to_branch}"
puts `git push -f git@heroku.com:#{@app}.git #{branch_to_branch}`
end
def restart
puts 'Restarting app servers ...'
Bundler.with_clean_env { puts `heroku restart --app #{@app}` }
end
def tag
release_name = "#{@app}_release-#{Time.now.utc.strftime("%Y%m%d%H%M%S")}"
puts "Tagging release as '#{release_name}'"
puts `git tag -a #{release_name} -m 'Tagged release'`
puts `git push --tags git@heroku.com:#{@app}.git`
end
def migrate
puts 'Running database migrations ...'
Bundler.with_clean_env { puts `heroku run 'bundle exec rake db:migrate' --app #{@app}` }
end
def turn_app_off
puts 'Putting the app into maintenance mode ...'
Bundler.with_clean_env { puts `heroku maintenance:on --app #{@app}` }
end
def turn_app_on
puts 'Taking the app out of maintenance mode ...'
Bundler.with_clean_env { puts `heroku maintenance:off --app #{@app}` }
end
def push_previous
prefix = "#{@app}_release-"
releases = `git tag`.split("\n").select { |t| t[0..prefix.length-1] == prefix }.sort
current_release = releases.last
previous_release = releases[-2] if releases.length >= 2
if previous_release
puts "Rolling back to '#{previous_release}' ..."
puts "Checking out '#{previous_release}' in a new branch on local git repo ..."
puts `git checkout #{previous_release}`
puts `git checkout -b #{previous_release}`
puts "Removing tagged version '#{previous_release}' (now transformed in branch) ..."
puts `git tag -d #{previous_release}`
puts `git push git@heroku.com:#{@app}.git :refs/tags/#{previous_release}`
puts "Pushing '#{previous_release}' to Heroku master ..."
puts `git push git@heroku.com:#{@app}.git +#{previous_release}:master --force`
puts "Deleting rollbacked release '#{current_release}' ..."
puts `git tag -d #{current_release}`
puts `git push git@heroku.com:#{@app}.git :refs/tags/#{current_release}`
puts "Retagging release '#{previous_release}' in case to repeat this process (other rollbacks)..."
puts `git tag -a #{previous_release} -m 'Tagged release'`
puts `git push --tags git@heroku.com:#{@app}.git`
puts "Turning local repo checked out on master ..."
puts `git checkout master`
puts 'All done!'
else
puts "No release tags found - can't roll back!"
puts releases
end
end
end
namespace :deploy do
namespace :staging do
task :migrations do
deployer = RakeHerokuDeployer.new(:staging)
deployer.run_migrations
end
task :rollback do
deployer = RakeHerokuDeployer.new(:staging)
deployer.rollback
end
end
task :staging do
deployer = RakeHerokuDeployer.new(:staging)
deployer.deploy
end
namespace :production do
task :migrations do
deployer = RakeHerokuDeployer.new(:production)
deployer.run_migrations
end
task :rollback do
deployer = RakeHerokuDeployer.new(:production)
deployer.rollback
end
end
task :production do
deployer = RakeHerokuDeployer.new(:production)
deployer.deploy
end
end
@supremebeing7
Copy link

This is really cool. Thanks!
Sorry if this is a noob question, but where is app_env coming from on your initialize method? Everything runs fine when I set my env variable explicitly ENV['MY_APP'] but not how you have it with app_env.

EDIT: I think I see where it's coming from - deployer = RakeHerokuDeployer.new(:production) - but how do I specify what gets passed in as :production? For example, on the below code, what would the command be to pass in the app name as :production? I tried rake deploy:production MY_APP_NAME but that didn't do it.

namespace :deploy do
  task :production do
    deployer = RakeHerokuDeployer.new(:production)
    deployer.deploy
  end
end

EDIT 2:
Nevermind, I got it. Rookie mistake. Realized it was just taking the :production param and converting it to PRODUCTION, then using that in the ENV as ENV['PRODUCTION_APP']

@jfeaver
Copy link
Author

jfeaver commented Dec 18, 2014

Sorry that I missed your questions @supremebeing7! May and June were crazy months this past year and the email notification must have been lost in the storm.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment