Example Chef Deploy Revision for Rails 3+
define :rails_base, :ruby_ver=>nil, :gemset=>nil do
include_recipe "nodejs"
package "mysql-client"
package "libmysqlclient-dev"
gem_package "newrelic_rpm"
gem_package "airbrake"
directory '/export' do
owner 'root'
group 'root'
recursive true
action :create
only_if "test -e /export"
end
create_deploy_user
rvm_gemset params[:gemset] do
ruby_string params[:ruby_ver]
action :create
end
rvm_gem 'bundler' do
ruby_string "#{params[:ruby_ver]}@#{params[:gemset]}"
end
# Creates /usr/local/rvm/bin/appname_bundle
rvm_wrapper params[:name] do
ruby_string "#{params[:ruby_ver]}@#{params[:gemset]}"
binary 'bundle'
end
end
define :notify_newrelic, :deploy_path=>nil, :environment=>nil do
script "notifying new relic of deploy" do
interpreter "ruby"
user "deployer"
cwd params[:name]
code <<-EOS
require 'rubygems'
unless File.exist?("#{params[:name]}/config/newrelic.yml")
puts "!!! newrelic.yml not found. Not notifying New Relic of the deploy !!!"
exit 0
end
## When new relic is loaded it automagically loads up its configuration file
## And sets the config[:environment]. We are injecting the path to the configuration
## file and the environment via ENV variables.
ENV['APP_ROOT'] = "#{params[:name]}"
ENV['RAILS_ENV'] = "#{params[:environment]}"
require 'newrelic_rpm'
require 'new_relic/command'
config = NewRelic::Control.instance
revision = `git rev-parse HEAD`.chomp
deployment = NewRelic::Command::Deployments.new({:revision => revision, :user => 'chef'})
print "New Relic : "
deployment.run
EOS
end
end
define :notify_airbrake, :name=>nil, :environment=>nil, :repository=>nil do
script "notifying airbrake of deploy" do
interpreter "ruby"
user "deployer"
cwd params[:name]
code <<-EOS
require 'rubygems'
unless File.exist?("#{params[:name]}/config/initializers/airbrake.rb")
puts "!!! airbrake.rb not found. Not notifying Airbrake of the deploy !!!"
exit 0
end
# Setup the airbrake api_key
require 'airbrake'
require 'airbrake_tasks'
require "#{params[:name]}/config/initializers/airbrake.rb"
revision = `cd #{params[:name]} && git rev-parse HEAD`.chomp
print "Airbrake : "
AirbrakeTasks.deploy(:rails_env => '#{params[:environment]}',
:scm_revision => revision,
:scm_repository => '#{params[:repository]}',
:local_username => 'chef')
EOS
end
end
{
# Handwritten from memory, probably broken
"id": "yourname",
"database_passwords": {
"staging": "foo"
}
}
define :create_database_config, :passwords=>{} do
databases = Hash.new
params[:databases].each do |db_name|
if node[:rails][params[:name]][:db].has_key?(db_name)
values = node[:rails][params[:name]][:db][db_name].to_hash
if params[:passwords].has_key?(db_name.to_s)
values["password"] = params[:passwords].fetch(db_name.to_s)
end
name = values.has_key?(name) ? values[:name] : db_name
databases.merge!({ name.to_s => values })
end
end
if params.has_key? :aliases
params[:aliases].each do |target,source|
databases[target.to_s] = databases[source.to_s]
end
end
file "#{params[:app_root]}/shared/config/database.yml" do
owner params[:deploy_user]
group params[:deploy_user]
mode '400'
content databases.to_yaml
end
end
#Used to configure variables for the app per environment (instead of attributes).
#I have since refactored how this works so treat this as example code, aka I
#have not tested this and it is not complete.
#FWIW using libraries in the cookbook to set these variables
#for smaller apps I would keep this in the attributes
app_name = 'yourname'
default = {
name: app_name
gemset: app_name
ruby_ver: 'ruby-1.8.7-p352',
deploy_user: 'deployer',
environment: node[:environment],
#...
}
staging = {
app_root: '/export/staging/yourname',
#...
}
case node[:environment]
when "staging"
rails_attributes = Chef::Mixin::DeepMerge.merge(default, staging)
end
# Don't know why I saved this in a seperate hash before the deep merge...
original_node_attributes = node.default.to_hash
node.default[:rails] = Chef::Mixin::DeepMerge.merge(rails_attributes, original_node_attributes["rails"])
app = node[:rails][:app]
rails_base app[:name] do
ruby_ver app[:ruby_ver]
gemset app[:gemset]
end
%w{config log pids cached-copy bundle system}.each do |dir|
directory "#{app[:app_root]}/shared/#{dir}" do
owner app[:deploy_user]
group app[:deploy_user]
mode '0755'
recursive true
action :create
end
end
keys = Chef::EncryptedDataBagItem.load('rails', app[:name])
create_database_config app[:name] do
app_root node[:rails][:app][:app_root]
deploy_user node[:rails][:app][:deploy_user]
databases node[:rails][:app][:db][:databases]
aliases node[:rails][:app][:db][:aliases]
passwords keys["database_passwords"]
end
deploy_revision "#{app[:app_root]}" do
repo app[:repo]
revision cluster_settings["revision"]
user app[:deploy_user]
group app[:deploy_user]
enable_submodules true
environment({
"RAILS_ENV" => app[:environment],
})
shallow_clone false
if node["rails"][app[:name]].attribute?('one_time_action')
# rollback, deploy, force_deploy
action node["rails"][app[:name]]["one_time_action"]
node["rails"][app[:name]].delete('one_time_action')
else
action :deploy
end
migrate true
migration_command "/usr/local/rvm/bin/appname_bundle exec rake db:migrate --trace"
before_migrate do
template "#{release_path}/config/environments/#{app[:environment]}.rb" do
source "#{release_path}/config/environments/chef_development.rb.erb"
local true
owner app[:deploy_user]
group app[:deploy_user]
mode '0400'
variables(:environment_settings => app[:environment_settings].to_hash)
end
rvm_shell "Bundle install and assets precompile" do
ruby_string "#{app[:ruby_ver]}@#{app[:gemset]}"
cwd release_path
user app[:deploy_user]
group app[:deploy_user]
# common_groups = %w{development test cucumber}
# bundle install --deployment --path #{app[:app_root]}/shared/bundle --without #{common_groups.join(' ')}
code %{
bundle install --path #{app[:app_root]}/shared/bundle
}
end
end
before_restart do
rvm_shell "assets precompile" do
ruby_string "#{app[:ruby_ver]}@#{app[:gemset]}"
cwd release_path
user app[:deploy_user]
group app[:deploy_user]
# TODO I could not set the environment via the builtin command. Does not look like it is getting passed to popen4
# So instead of `environment "RAILS_ENV" => app[:environment]` I have it in the code block
code %{
export RAILS_ENV=#{app[:environment]}
bundle exec rake assets:precompile
}
end
end
symlink_before_migrate({
"config/database.yml" => "config/database.yml",
"config/environments/#{app[:environment]}.rb" => "config/environments/#{app[:environment]}.rb"
})
after_restart do
notify_airbrake release_path do
environment app[:environment]
repository app[:repo]
end
notify_newrelic release_path do
environment app[:environment]
end
end
end