lib/paratrooper/deploy.rb
require 'forwardable'
require 'paratrooper/configuration'
require 'paratrooper/error'
module Paratrooper
class Deploy
extend Forwardable
delegate [:system_caller, :migration_check, :notifiers,
:deloyment_host, :heroku, :source_control, :screen_notifier
] => :config
attr_writer :config
def self.call(app_name, options = {}, &block)
new(app_name, options, &block).deploy
end
# Public: Initializes a Deploy
#
# app_name - A String naming the Heroku application to be interacted with.
# options - The Hash options is used to provide additional functionality.
# :screen_notifier - Object used for outputting to screen
# (optional).
# :notifiers - Array of objects interested in being
# notified of steps in deployment process
# (optional).
# :heroku - Object wrapper around heroku-api (optional).
# :branch - String name to be used as a git reference
# point for deploying from specific branch.
# Use :head to deploy from current branch
# (optional).
# :force - Force deploy using (-f flag) on deploy
# (optional, default: false)
# :system_caller - Object responsible for calling system
# commands (optional).
# :protocol - String web protocol to be used when pinging
# application (optional, default: 'http').
# :deployment_host - String host name to be used in git URL
# (optional, default: 'heroku.com').
# :migration_check - Object responsible for checking pending
# migrations (optional).
# :maintenance - If true, show maintenance page when pending
# migrations exists. False by default (optional).
# migrations (optional).
# :api_key - String version of heroku api key.
# (default: looks in local Netrc file).
# :http_client - Object responsible for making http calls
# (optional).
#
def initialize(app_name, options = {}, &block)
config.attributes = options.merge(app_name: app_name)
block.call(config) if block_given?
end
def config
@config ||= Configuration.new
end
# Public: Hook method called first in the deploy process.
#
def setup
callback(:setup) do
notify(:setup)
migration_check.last_deployed_commit
end
end
# Public: Hook method called last in the deploy process.
#
def teardown
callback(:teardown) do
notify(:teardown)
end
notify(:deploy_finished)
end
def update_repo_tag
if source_control.taggable?
callback(:update_repo_tag) do
notify(:update_repo_tag)
source_control.update_repo_tag
end
end
end
# Public: Activates Heroku maintenance mode.
#
def activate_maintenance_mode
return unless maintenance_necessary?
callback(:activate_maintenance_mode) do
notify(:activate_maintenance_mode)
heroku.app_maintenance_on
end
end
# Public: Deactivates Heroku maintenance mode.
#
def deactivate_maintenance_mode
return unless maintenance_necessary?
callback(:deactivate_maintenance_mode) do
notify(:deactivate_maintenance_mode)
heroku.app_maintenance_off
end
end
# Public: Pushes repository to Heroku.
#
# Based on the following precedence:
# branch_name / 'master'
#
def push_repo
callback(:push_repo) do
notify(:push_repo)
source_control.push_to_deploy
end
end
# Public: Runs rails database migrations on your application.
#
def run_migrations
return unless pending_migrations?
callback(:run_migrations) do
notify(:run_migrations)
heroku.run_migrations
end
end
# Public: Restarts application on Heroku.
#
def app_restart
return unless restart_required?
callback(:app_restart) do
notify(:app_restart)
heroku.app_restart
end
end
# Public: Execute common deploy steps.
#
# Default deploy consists of:
# * Activating maintenance page
# * Pushing repository to Heroku
# * Running database migrations
# * Restarting application on Heroku
# * Deactivating maintenance page
#
# Alias: #deploy
def default_deploy
setup
update_repo_tag
push_repo
maintenance_mode do
run_migrations
app_restart
end
teardown
rescue Paratrooper::Error => e
abort(e.message)
end
alias_method :deploy, :default_deploy
# Public: Runs task on your heroku instance.
#
# task_name - String name of task to run on heroku instance
#
def add_remote_task(task_name)
heroku.run_task(task_name)
end
private
def maintenance_mode(&block)
activate_maintenance_mode
block.call if block_given?
deactivate_maintenance_mode
end
def maintenance_necessary?
config.maintenance? && pending_migrations?
end
def callback(name, &block)
config.build_callback(name, screen_notifier, &block)
end
# Internal: Payload data to be sent with notifications
#
def default_payload
{
app_name: config.app_name,
deployment_remote: deployment_remote,
force_push: config.force_push,
reference_point: source_control.reference_point,
}
end
def deployment_remote
source_control.remote
end
# Internal: Notifies other objects that an event has occurred
#
# step - String event name
# options - Hash of options to be sent as data payload
#
def notify(step, options = {})
notifiers.each do |notifier|
notifier.notify(step, default_payload.merge(options))
end
end
def pending_migrations?
@pending_migrations ||= migration_check.migrations_waiting?
end
def restart_required?
pending_migrations?
end
end
end