double-z
10/19/2012 - 7:29 PM

Example Chef Deploy Revision for Rails 3+

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