Skip to content

Commit

Permalink
Checkpoint lots of work. Includes application cookbook split to appli…
Browse files Browse the repository at this point in the history
…cation_(rails, django). Also hey look, Django and Gunicorn plugins!
  • Loading branch information
coderanger authored and Andrea Campi committed Apr 2, 2012
0 parents commit a100358
Show file tree
Hide file tree
Showing 14 changed files with 803 additions and 0 deletions.
363 changes: 363 additions & 0 deletions README.md

Large diffs are not rendered by default.

75 changes: 75 additions & 0 deletions examples/data_bags/apps/rails_app.json
@@ -0,0 +1,75 @@
{
"id": "my_app",
"server_roles": [
"application specific role(s), typically the name of the app, e.g., my_app",
"my_app"
],
"type": {
"my_app": [
"recipes in this application cookbook to run for this role",
"rails",
"unicorn"
]
},
"memcached_role": [
"name of the role used for the app-specific memcached server",
"my_app_memcached"
],
"database_slave_role": [
"name of the role used by database slaves, typically named after the app, 'my_app_database_slave'",
"my_app_database_slave"
],
"database_master_role": [
"name of the role used by database master, typically named after the app 'my_app_database_master'",
"my_app_database_master"
],
"repository": "git@github.com:company/my_app.git",
"revision": {
"production": "commit hash, branch or tag to deploy"
},
"force": {
"production": "true or false w/o quotes to force deployment, see the rails.rb recipe"
},
"migrate": {
"production": "true or false boolean to force migration, see rails.rb recipe"
},
"databases": {
"production": {
"reconnect": "true",
"encoding": "utf8",
"username": "db_user",
"adapter": "mysql",
"password": "awesome_password",
"database": "db_name_production"
}
},
"mysql_root_password": {
"production": "password for the root user in mysql"
},
"mysql_debian_password": {
"production": "password for the debian-sys-maint user on ubuntu/debian"
},
"mysql_repl_password": {
"production": "password for the 'repl' user for replication."
},
"snapshots_to_keep": {
"production": "if using EBS, integer of the number of snapshots we're going to keep for this environment."
},
"deploy_key": "SSH private key used to deploy from a private git repository",
"deploy_to": "path to deploy, e.g. /srv/my_app",
"owner": "owner for the application files when deployed",
"group": "group for the application files when deployed",
"packages": {
"package_name": "specific packages required for installation at the OS level to run the app like libraries and specific version, e.g.",
"curl": "7.19.5-1ubuntu2"
},
"gems": {
"gem_name": "specific gems required for installation to run the application, and if a specific version is required, e.g.",
"rails": "2.3.5"
},
"memcached": {
"production": {
"namespace": "specify the memcache namespace, ie my_app_environment"
}
}
}
2 changes: 2 additions & 0 deletions examples/environments
@@ -0,0 +1,2 @@
name "production"
description "Production environment"
10 changes: 10 additions & 0 deletions metadata.rb
@@ -0,0 +1,10 @@
maintainer "Opscode, Inc."
maintainer_email "cookbooks@opscode.com"
license "Apache 2.0"
description "Deploys and configures Rails applications"
long_description IO.read(File.join(File.dirname(__FILE__), 'README.md'))
version "0.99.12"

%w{ application runit unicorn apache2 passenger_apache2 }.each do |cb|
depends cb
end
170 changes: 170 additions & 0 deletions providers/rails.rb
@@ -0,0 +1,170 @@
#
# Author:: Noah Kantrowitz <noah@opscode.com>
# Cookbook Name:: application
# Provider:: rails
#
# Copyright:: 2011, Opscode, Inc <legal@opscode.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

action :before_compile do

new_resource.migration_command "rake db:migrate" if !new_resource.migration_command

new_resource.environment.update({
"RAILS_ENV" => new_resource.environment_name,
})

new_resource.symlink_before_migrate.update({
"database.yml" => "config/database.yml",
"memcached.yml" => "config/memcached.yml",
})

end

action :before_deploy do

new_resource.environment['RAILS_ENV'] = new_resource.environment_name

new_resource.gems.each do |gem, ver|
gem_package gem do
action :install
version ver if ver && ver.length > 0
end
end

if new_resource.database_master_role
dbm = nil
# If we are the database master
if node['roles'].include?(new_resource.database_master_role)
dbm = node
else
# Find the database master
results = search(:node, "role:#{new_resource.database_master_role} AND chef_environment:#{node.chef_environment}", nil, 0, 1)
rows = results[0]
if rows.length == 1
dbm = rows[0]
end
end

# Assuming we have one...
if dbm
template "#{new_resource.path}/shared/database.yml" do
source "database.yml.erb"
cookbook "application_rails"
owner new_resource.owner
group new_resource.group
mode "644"
variables(
:host => (dbm.attribute?('cloud') ? dbm['cloud']['local_ipv4'] : dbm['ipaddress']),
:database => new_resource.database,
:rails_env => new_resource.environment_name
)
end
else
Chef::Log.warn("No node with role #{new_resource.database_master_role}, database.yml not rendered!")
end
end

if new_resource.memcached_role
results = search(:node, "role:#{new_resource.memcached_role} AND chef_environment:#{node.chef_environment} NOT hostname:#{node[:hostname]}")
if results.length == 0
if node['roles'].include?(new_resource.memcached_role)
results << node
end
end
template "#{new_resource.path}/shared/memcached.yml" do
source "memcached.yml.erb"
cookbook "application_rails"
owner new_resource.owner
group new_resource.group
mode "644"
variables(
:memcached_envs => new_resource.memcached,
:hosts => results.sort_by { |r| r.name }
)
end
end

end

action :before_migrate do

gem_names = new_resource.gems.map{|gem, ver| gem}
if gem_names.include?('bundler')
Chef::Log.info "Running bundle install"
directory "#{new_resource.path}/shared/vendor_bundle" do
owner new_resource.owner
group new_resource.group
mode '0755'
end
link "#{new_resource.release_path}/vendor/bundle" do
to "#{new_resource.path}/shared/vendor_bundle"
end
common_groups = %w{development test cucumber staging production}
bundler_deployment = new_resource.bundler_deployment
if bundler_deployment.nil?
# Check for a Gemfile.lock
bundler_deployment = ::File.exists?(::File.join(new_resource.release_path, "Gemfile.lock"))
end
execute "bundle install #{bundler_deployment ? "--deployment " : ""}--without #{(common_groups -([node.chef_environment])).join(' ')}" do
ignore_failure true
cwd new_resource.release_path
end
elsif gem_names.include?('bundler08')
Chef::Log.info "Running gem bundle"
execute "gem bundle" do
ignore_failure true
cwd new_resource.release_path
end
else
# chef runs before_migrate, then symlink_before_migrate symlinks, then migrations,
# yet our before_migrate needs database.yml to exist (and must complete before
# migrations).
#
# maybe worth doing run_symlinks_before_migrate before before_migrate callbacks,
# or an add'l callback.
execute "(ln -s ../../../shared/database.yml config/database.yml && rake gems:install); rm config/database.yml" do
ignore_failure true
cwd new_resource.release_path
end
end

if new_resource.migration_command.include?('rake') && !gem_names.include?('rake')
gem_package "rake" do
action :install
end
end

end

action :before_symlink do

ruby_block "remove_run_migrations" do
block do
if node.role?("#{new_resource.id}_run_migrations")
Chef::Log.info("Migrations were run, removing role[#{new_resource.id}_run_migrations]")
node.run_list.remove("role[#{new_resource.id}_run_migrations]")
end
end
end

end

action :before_restart do
end

action :after_restart do
end

61 changes: 61 additions & 0 deletions providers/unicorn.rb
@@ -0,0 +1,61 @@
#
# Author:: Noah Kantrowitz <noah@opscode.com>
# Cookbook Name:: application
# Provider:: unicorn
#
# Copyright:: 2011, Opscode, Inc <legal@opscode.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

action :before_compile do
new_resource.restart_command "/etc/init.d/#{new_resource.id} hup" if !new_resource.restart_command
end

action :before_deploy do
end

action :before_migrate do
end

action :before_symlink do
end

action :before_restart do

new_resource = @new_resource

unicorn_config "/etc/unicorn/#{new_resource.id}.rb" do
listen({ new_resource.port => new_resource.options })
working_directory ::File.join(new_resource.path, 'current')
worker_timeout new_resource.worker_timeout
preload_app new_resource.preload_app
worker_processes new_resource.worker_processes
before_fork new_resource.before_fork
end

runit_service new_resource.id do
template_name 'unicorn'
cookbook 'application_rails'
options(
:app => new_resource,
:rails_env => new_resource.environment_name,
:smells_like_rack => ::File.exists?(::File.join(new_resource.path, "current", "config.ru"))
)
run_restart false
end

end

action :after_restart do
end
36 changes: 36 additions & 0 deletions resources/rails.rb
@@ -0,0 +1,36 @@
#
# Author:: Noah Kantrowitz <noah@opscode.com>
# Cookbook Name:: application
# Resource:: rails
#
# Copyright:: 2011, Opscode, Inc <legal@opscode.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

include Chef::Resource::ApplicationBase

attribute :database_master_role, :kind_of => [String, NilClass], :default => nil
attribute :memcached_role, :kind_of => [String, NilClass], :default => nil
attribute :gems, :kind_of => [Array, Hash], :default => []
attribute :bundler_deployment, :kind_of => [NilClass, TrueClass, FalseClass], :default => nil

def database(*args, &block)
@database ||= Mash.new
@database.update(options_block(*args, &block))
end

def memcached(*args, &block)
@database ||= Mash.new
@database.update(options_block(*args, &block))
end
33 changes: 33 additions & 0 deletions resources/unicorn.rb
@@ -0,0 +1,33 @@
#
# Author:: Noah Kantrowitz <noah@opscode.com>
# Cookbook Name:: application
# Resource:: unicorn
#
# Copyright:: 2011, Opscode, Inc <legal@opscode.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

include Chef::Resource::ApplicationBase

attribute :preload_app, :kind_of => [TrueClass, FalseClass], :default => false
attribute :worker_processes, :kind_of => Integer, :default => [node[:cpu][:total].to_i * 4, 8].min
attribute :before_fork, :kind_of => String, :default => 'sleep 1'
attribute :port, :kind_of => Integer, :default => 8080
attribute :worker_timeout, :kind_of => Integer, :default => 60
attribute :bundler, :kind_of => [TrueClass, FalseClass], :default => false

def options(*args, &block)
@options ||= Mash[:tcp_nodelay => true, :backlog => 100]
@options.update(options_block(*args, &block))
end

0 comments on commit a100358

Please sign in to comment.