-
-
Save finack/2055725 to your computer and use it in GitHub Desktop.
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 |
#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"]) |
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 |
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 |
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 :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 |
{ | |
# Handwritten from memory, probably broken | |
"id": "yourname", | |
"database_passwords": { | |
"staging": "foo" | |
} | |
} | |
Or where can I find the cookbook for this?
I have not release this in a gem or cookbook. This should be pretty complete example. Are you looking for more?
Yes, I'ld like to see all the templates and the structure of the data bag as well.
And what is the app server it will use? I mean Unicorn, Nginx, et...
How the attributes will look like along with data_bag item as well?
Hey there!
Updated the code a bit. Hopefully that helped. I have been using one of the unicorn cookbooks (i think from opscode) and apache to serve all of this. I have been separating out the app deployment from the server as we also deploy delay jobs and other systems that need RoR but not unicorn. For example our run_lists kinda look like
- www_front_end = app, app_apache # Want apache and assets
- app_server = app, app_unicorn
- dj_server = app, app_dj
Hopefully this answered some of your questions. I will try to get to others but will be delayed :) Have fun!!
@finack, Thanks for the update and its really helpful.
But a few pieces left out to make it work for me.
First, the following definition to create the database.
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
What would be the structure of the attributes/default.rb
file?
Does the following work or I think I need to change the attributes?
default['rails']['app']['app_root'] = "/home/deployer"
default['rails']['railsbp']['db']['railsbp_prod']['encoding'] = "utf8"
default['rails']['railsbp']['db']['railsbp_prod']['reconnect'] = "true"
default['rails']['railsbp']['db']['railsbp_prod']['adapter'] = "mysql2"
And lastly, what that source "#{release_path}/config/environments/chef_development.rb.erb"
template looks like?
Yeah, I know I'm aksing a lot. But your help is directing me to the final deploy.
Can you address those queries?
Great!
Have you packaged it any gem?