Skip to content

Instantly share code, notes, and snippets.

@ahawkins
Created March 29, 2012 13:59
Show Gist options
  • Save ahawkins/2237714 to your computer and use it in GitHub Desktop.
Save ahawkins/2237714 to your computer and use it in GitHub Desktop.
Deploy script for Heroku apps
#!/usr/bin/env ruby
# This is a basic deploy script for Heroku apps.
# It provides a structure you can use to expand on
# and add your own prereqs and deploy tasks.
#
# It basically ensures that:
# 1. There are no uncommited files
# 2. You can ssh to github
# 3. You can connect to heroku
# 4. This commit is a fast forward commit to master
# 5. This HEAD hasn't been deployed to Heroku
#
# Then given all that it:
# 1. Records this commit & time in a deploys file
# 2. Commits the deploy file
# 3. Pushes to Github
# 4. Pushes to Heroku
#
# The whole process is logged to deploy.log
#
# Use:
# $ curl -s "https://raw.github.com/gist/2237714/c5b4c117014c0dfa026c7d8e8c193429b0c30a45/deploy.rb" -o script/deploy
# $ chmod +x script/deploy
# $ git add script/deploy
# $ git commit -m 'Add simple deploy script'
# $ ./script/deploy
#
# Enjoy!
require "rubygems" # ruby1.9 doesn't "require" it though
require "thor"
RAILS_ROOT = File.expand_path "../../", __FILE__
LOG_FILE = "#{RAILS_ROOT}/deploy.log"
DEPLOY_FILE = "#{RAILS_ROOT}/deploys.md"
class Deploy < Thor
include Thor::Actions
class CommandFailed < StandardError ; end
no_tasks do
def run(command, options = {})
`echo "#{command}" > #{LOG_FILE}`
command = "#{command} > #{LOG_FILE} 2>&1" unless options[:capture]
options[:verbose] ||= false
super command, options
end
def run_with_status(command, options = {})
run command, options
$?
end
def success?(command, options = {})
run_with_status(command, options).success?
end
def run!(command, options = {})
raise CommandFailed, "Expected #{command} to return successfully, but didn't" unless success?(command, options)
end
def pass(message)
say_status "OK", message, :green
true
end
def abort_deploy(message)
say_status "ABORT", message, :red
say "Deploy Failed! Check log file #{LOG_FILE}"
end
def failure(message)
say_status "FAIL", message, :red
false
end
end
desc "ensure_github_connection", "Tests this user can ssh to github"
def ensure_github_connection
if run_with_status("ssh -T git@github.com ").exitstatus == 1
pass "Github conencted"
else
failure "SSH keys missing for Github"
end
end
desc "ensure_heroku_connection", "Tests this user can access heroku"
def ensure_heroku_connection
if success? "heroku config"
pass "Heroku connected"
else
failure "SSH key missing or user is not a collabator"
end
end
desc "ensure_clean", "Test to see if the repo is clean"
def ensure_clean
if success? "git diff --exit-code"
pass "No uncommited files"
else
failure "There are uncommited files"
end
end
desc "ensure_heroku_outdated", "Test to see if this code has been deployed or not"
def ensure_heroku_outdated
if !success? "git diff head heroku/master --exit-code"
pass "Code not deployed"
else
failure "Code already deployed"
end
end
desc "ensure_fast_forward", "Tests if this is a fast forward commit"
def ensure_fast_forward
inside RAILS_ROOT do
if success? "git pull origin master"
return pass "Fast forwarded"
else
failure "Could not fast forward. Human required"
run "git reset --hard HEAD"
return false
end
end
end
desc "record", "Records this deploy in deploys.md"
def record
inside RAILS_ROOT do
commit_info = run('git show --format="format:%h - %an: %s"', :capture => true, :verbose => false).split("\n")[0]
format = "* [%s] %s\n"
run "touch #{DEPLOY_FILE}"
existing_contents = File.read DEPLOY_FILE
File.open DEPLOY_FILE, 'w' do |f|
f.puts format % [Time.now.strftime("%Y-%m-%d %H:%M %z"), commit_info]
f.puts existing_contents.chomp
end
say_status "Deploy Log", commit_info
end
true
end
desc "commit", "Commits files neede to deploy"
def commit
inside RAILS_ROOT do
run! "git add #{DEPLOY_FILE}"
run! "git commit -m '[Deploy]'"
@new_commit = true # so we can catch the failure and blow away the last commit
end
say_status "Deploy Files", "Commited"
end
desc "run_deploy", "Tests prereqs and runs a deploy"
method_option :environment, :default => "production"
def run_deploy
say "Checking prereqs..."
prereqs = invoke(:ensure_clean) &&
invoke(:ensure_github_connection) &&
invoke(:ensure_heroku_connection) &&
invoke(:ensure_heroku_outdated) &&
invoke(:ensure_fast_forward)
if !prereqs
abort_deploy "Failed prereqs"
return false
end
say "Running predeploy tasks..."
begin
invoke :record
invoke :commit
rescue CommandFailed => ex
abort_deploy "A deploy step failed to run: #{ex}"
if @new_commit
run "git reset HEAD~1"
else
run "git reset --hard HEAD"
end
return false
end
say "Deploying..."
begin
inside RAILS_ROOT do
run! "git push origin master"
say_status "Github", "Pushed"
run! "git push heroku master"
say_status "Heorku", "Deployed"
end
rescue CommandFailed => ex
abort_deploy "Push failed. Please check logs."
end
end
default_task :run_deploy
end
Deploy.start
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment