Created
March 29, 2012 13:59
-
-
Save ahawkins/2237714 to your computer and use it in GitHub Desktop.
Deploy script for Heroku apps
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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