module Jumpup module Heroku class Integrate attr_reader :app def self.deploy message = "Starting to deploy on Heroku app #{integrate.app}" integrate.when_branch_send_to_heroku(message) do integrate.deploy end end def self.deploy_to_production app = Env.all[:production_app] integrate = new(app) message = "Starting to deploy to production on Heroku app #{integrate.app}" integrate.when_branch_send_to_heroku(message) do integrate.deploy_to_production end end def self.add_remote message = "Adding Heroku git remotes for app #{integrate.app}" integrate.when_branch_send_to_heroku(message) do integrate.add_remote end end def self.check message = "Checking if there's already someone integrating to #{integrate.app}" integrate.when_branch_send_to_heroku(message) do integrate.check end end def self.lock message = "Locking Heroku integration on app #{integrate.app}" integrate.when_branch_send_to_heroku(message) do integrate.lock end end def self.unlock message = "Unlocking Heroku integration on app #{integrate.app}" integrate.when_branch_send_to_heroku(message) do integrate.unlock end end def self.integrate envs = Env.all app = envs[:app] || envs[:staging_app] new(app) end def initialize(app) @app = app @envs = Env.all end def when_branch_send_to_heroku(message, &block) puts "--> #{message}" if branches_that_send_to_heroku.include?(actual_branch) block.call else puts "----> Skipping since you are in a feature branch [#{actual_branch}]" end end def branches_that_send_to_heroku [Env.all[:deploy_branch], Env.all[:deploy_to_production_branch]] end def deploy if run_database_tasks? check_if_migration_is_needed check_if_seed_is_needed end backup push(Env.all[:deploy_branch]) migrate seed restart end def deploy_to_production if run_database_tasks? check_if_migration_is_needed check_if_seed_is_needed end confirm_deploy spec confirm_maintenance maintenance backup tag push(Env.all[:deploy_to_production_branch]) migrate seed close_maintenance restart end def add_remote remote = run_with_clean_env("git remote | grep heroku").strip exec_with_clean_env("git remote add heroku git@heroku.com:#{app}.git") if remote.blank? end def check return if can_start_integration?(user) puts "----> Project is already being integrated by '#{integrating_by}', halting" exit 1 end def lock return if integrating_by?(user) puts "----> Locking Heroku integration for you (#{user})" exec_with_clean_env("heroku config:set INTEGRATING_BY='#{user}' --app #{app}") end def unlock exec_with_clean_env("heroku config:unset INTEGRATING_BY --app #{app}") end private attr_reader :envs, :maintenance_mode def check_if_migration_is_needed files_changed = run_with_clean_env("git diff HEAD #{latest_remote_sha} --name-only -- db/migrate | wc -l").strip.to_i @migrations_changed = files_changed > 0 end def check_if_seed_is_needed files_changed = run_with_clean_env("git diff HEAD #{latest_remote_sha} --name-only -- db/seeds* | wc -l").strip.to_i @seeds_changed = files_changed > 0 end def latest_remote_sha @latest_remote_sha ||= run_with_clean_env("git ls-remote git@heroku.com:#{app}.git HEAD 2>/dev/null | awk '{ print $1 }'").strip end def can_start_integration?(user) integrating_by.blank? || integrating_by?(user) end def integrating_by?(user) integrating_by == user end def integrating_by @integrating_by ||= run_with_clean_env("heroku config:get INTEGRATING_BY --app #{app}").strip end def user @user ||= validate_username end def validate_username user = run_with_clean_env("git config --get user.name").strip if user.blank? puts "----> You must setup your git user name. Use: git config --global user.name 'your name'" exit 1 end user end def run_with_clean_env(command) Bundler.with_clean_env { `#{command}` } end def exec_with_clean_env(command) Bundler.with_clean_env do unless system(command) raise "Error while running #{command}" end end end def confirm(message) print "\n#{message}\nAre you sure? [yN] " raise 'Ok. Bye...' unless STDIN.gets.chomp.downcase == 'y' end def run_database_tasks? @envs[:run_database_tasks] end def backup return unless run_database_tasks? puts "----> [#{app}] Backing up database" exec_with_clean_env("heroku pgbackups:capture --expire --app #{app}") end def migrate return unless run_database_tasks? if @migrations_changed puts "----> [#{app}] Migrating" exec_with_clean_env("heroku run rake db:migrate --app #{app}") else puts "----> [#{app}] Skipping migrations" end end def seed return unless run_database_tasks? if @seeds_changed puts "----> [#{app}] Seeding" exec_with_clean_env("heroku run rake db:seed --app #{app}") else puts "----> [#{app}] Skipping seeds" end end def restart puts "----> [#{app}] Restarting" exec_with_clean_env("heroku restart --app #{app}") end def push(branch) puts "----> [#{app}] Pushing to #{host} from branch [#{branch}]" exec_with_clean_env("git push git@#{host}:#{app}.git #{branch}:master") end def confirm_deploy confirm("[#{app}] Deploying to production using branch [#{Env.all[:deploy_to_production_branch]}]") end def spec puts "----> Running all specs" Rake::Task['spec'].invoke end def confirm_maintenance print "\nPut #{app} in maintenance mode? [Yn] " @maintenance_mode = true if STDIN.gets.chomp.downcase == 'y' end def maintenance? @maintenance_mode end def maintenance if maintenance? puts "----> [#{app}] Setting Maintenance on" exec_with_clean_env("heroku maintenance:on --app #{app}") restart puts "----> [#{app}] Waiting 20 seconds to app come back (in maintenance mode)" sleep(20) end end def tag iso_date = Time.now.strftime('%Y-%m-%dT%H%M%S') tag_name = "production-#{iso_date}" puts "----> Tagging as #{tag_name}" exec_with_clean_env("git tag #{tag_name} master") puts "----> Pushing to origin" exec_with_clean_env("git push origin #{tag_name}") end def close_maintenance if maintenance? puts "Setting Maintenance off" exec_with_clean_env("heroku maintenance:off --app #{app}") end end def host Env.all[:host] end def actual_branch run_with_clean_env("git rev-parse --abbrev-ref HEAD").strip end end end end