Browse Source

Initial model for application LWRPs.

Noah Kantrowitz 12 years ago
parent
commit
2ebd9c61d5

+ 103 - 0
libraries/default.rb

@@ -0,0 +1,103 @@
+#
+# Author:: Noah Kantrowitz <noah@opscode.com>
+# Cookbook Name:: application
+# Library:: default
+#
+# 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.
+#
+
+class Chef
+  class Resource
+    # Globally update the blocklists to prevent infinite recursion in #to_json and similar
+    FORBIDDEN_IVARS += [:@application, :@application_provider]
+    HIDDEN_IVARS += [:@application, :@application_provider]
+
+    module ApplicationBase
+      def self.included(klass)
+        klass.actions :before_compile, :before_deploy, :before_migrate, :before_symlink, :before_restart, :after_restart
+        klass.attribute :id, :kind_of => String, :name_attribute => true
+        klass.attribute :environment, :kind_of => Hash, :default => {}
+        klass.attribute :purge_before_symlink, :kind_of => Array, :default => []
+        klass.attribute :create_dirs_before_symlink, :kind_of => Array, :default => []
+        klass.attribute :symlinks, :kind_of => Hash, :default => {}
+        klass.attribute :symlink_before_migrate, :kind_of => Hash, :default => {}
+        klass.attribute :migration_command, :kind_of => [String, NilClass], :default => 'rake db:migrate'
+        klass.attribute :restart_command, :kind_of => [String, NilClass], :default => nil
+        klass.attribute :application
+        klass.attribute :application_provider
+      end
+
+      def method_missing(name, *args)
+        if application.respond_to? name
+          application.send(name, *args)
+        else
+          super
+        end
+      end
+
+      def release_path
+        application_provider.release_path
+      end
+    end
+  end
+
+  class Provider
+    module ApplicationBase
+
+      def self.included(klass)
+        klass.extend Chef::Mixin::FromFile
+      end
+
+      def release_path
+        if !@deploy_provider
+          #@deploy_provider = Chef::Platform.provider_for_resource(@run_context.resource_collection.find(:deploy_revision => @new_resource.id))
+          @deploy_provider = Chef::Platform.provider_for_resource(@deploy_resource)
+          @deploy_provider.load_current_resource
+        end
+        @deploy_provider.release_path
+      end
+
+      def callback(what, callback_code=nil)
+        Chef::Log.debug("Got callback #{what}: #{callback_code.inspect}")
+        @collection = Chef::ResourceCollection.new
+        case callback_code
+        when Proc
+          Chef::Log.info "#{@new_resource} running callback #{what}"
+          recipe_eval(&callback_code)
+        when String
+          callback_file = "#{release_path}/#{callback_code}"
+          unless ::File.exist?(callback_file)
+            raise RuntimeError, "Can't find your callback file #{callback_file}"
+          end
+          run_callback_from_file(callback_file)
+        when nil
+          nil
+        else
+          raise RuntimeError, "You gave me a callback I don't know what to do with: #{callback_code.inspect}"
+        end
+      end
+
+      def run_callback_from_file(callback_file)
+        if ::File.exist?(callback_file)
+          Dir.chdir(release_path) do
+            Chef::Log.info "#{@new_resource} running deploy hook #{callback_file}"
+            recipe_eval { from_file(callback_file) }
+          end
+        end
+      end
+
+    end
+  end
+end

+ 149 - 0
providers/default.rb

@@ -0,0 +1,149 @@
+#
+# Author:: Noah Kantrowitz <noah@opscode.com>
+# Cookbook Name:: application
+# Provider:: default
+#
+# 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::Provider::ApplicationBase
+
+action :deploy do
+  # Alias to a variable so I can use in sub-resources
+  new_resource = @new_resource
+
+  new_resource.sub_resources.each do |resource|
+    resource.application_provider self
+  end
+
+  new_resource.packages.each do |pkg,ver|
+    package pkg do
+      action :install
+      version ver if ver && ver.length > 0
+    end
+  end
+
+  directory new_resource.path do
+    owner new_resource.owner
+    group new_resource.group
+    mode '0755'
+    recursive true
+  end
+
+  directory "#{new_resource.path}/shared" do
+    owner new_resource.owner
+    group new_resource.group
+    mode '0755'
+    recursive true
+  end
+
+  if new_resource.deploy_key
+    ruby_block "write_key" do
+      block do
+        f = ::File.open("#{new_resource.path}/id_deploy", "w")
+        f.print(new_resource.deploy_key)
+        f.close
+      end
+      not_if do ::File.exists?("#{new_resource.path}/id_deploy"); end
+    end
+
+    file "#{new_resource.path}/id_deploy" do
+      owner new_resource.owner
+      group new_resource.group
+      mode '0600'
+    end
+
+    template "#{new_resource.path}/deploy-ssh-wrapper" do
+      source "deploy-ssh-wrapper.erb"
+      owner new_resource.owner
+      group new_resource.group
+      mode "0755"
+      variables :id => new_resource.id, :deploy_to => new_resource.path
+    end
+  end
+
+  new_resource.sub_resources.each do |resource|
+    resource.run_action :before_compile
+  end
+
+  ruby_block "#{new_resource.id} before_deploy" do
+    block do
+      new_resource.sub_resources.each do |resource|
+        resource.run_action :before_deploy
+      end
+      callback(:before_deploy, new_resource.before_deploy)
+    end
+  end
+
+  @deploy_resource = deploy_revision new_resource.id do
+    revision new_resource.revision
+    repository new_resource.repository
+    user new_resource.owner
+    group new_resource.group
+    deploy_to new_resource.path
+    ssh_wrapper "#{new_resource.path}/deploy-ssh-wrapper" if new_resource.deploy_key
+    shallow_clone true
+    all_environments = [new_resource.environment]+new_resource.sub_resources.map{|res| res.environment}
+    environment all_environments.inject({}){|acc, val| acc.merge(val)}
+    migrate new_resource.migrate
+    all_migration_commands = ([new_resource.migration_command]+new_resource.sub_resources.map{|res| res.migration_command}).select{|res| res}
+    migration_command all_migration_commands.join(';')
+    all_restart_commands = ([new_resource.restart_command]+new_resource.sub_resources.map{|res| res.restart_command}).select{|res| res}
+    restart_command all_restart_commands.join(';')
+    purge_before_symlink new_resource.purge_before_symlink
+    create_dirs_before_symlink new_resource.create_dirs_before_symlink
+    symlinks new_resource.symlinks
+    all_symlinks_before_migrate = [new_resource.symlink_before_migrate]+new_resource.sub_resources.map{|res| res.symlink_before_migrate}
+    symlink_before_migrate all_symlinks_before_migrate.inject({}){|acc, val| acc.merge(val)}
+    # Yes, this needs to be refactored together
+    before_migrate do
+      new_resource.sub_resources.each do |resource|
+        saved_run_context = resource.instance_variable_get :@run_context
+        resource.instance_variable_set :@run_context, @run_context
+        resource.run_action :before_migrate
+        resource.instance_variable_set :@run_context, saved_run_context
+      end
+      callback(:before_migrate, new_resource.before_migrate)
+    end
+    before_symlink do
+      new_resource.sub_resources.each do |resource|
+        saved_run_context = resource.instance_variable_get :@run_context
+        resource.instance_variable_set :@run_context, @run_context
+        resource.run_action :before_symlink
+        resource.instance_variable_set :@run_context, saved_run_context
+      end
+      callback(:before_symlink, new_resource.before_symlink)
+    end
+    before_restart do
+      new_resource.sub_resources.each do |resource|
+        saved_run_context = resource.instance_variable_get :@run_context
+        resource.instance_variable_set :@run_context, @run_context
+        resource.run_action :before_restart
+        resource.instance_variable_set :@run_context, saved_run_context
+      end
+      callback(:before_restart, new_resource.before_restart)
+    end
+    after_restart do
+      new_resource.sub_resources.each do |resource|
+        saved_run_context = resource.instance_variable_get :@run_context
+        resource.instance_variable_set :@run_context, @run_context
+        resource.run_action :after_restart
+        resource.instance_variable_set :@run_context, saved_run_context
+      end
+      callback(:after_restart, new_resource.after_restart)
+    end
+  end
+
+end

+ 305 - 0
providers/rails.rb

@@ -0,0 +1,305 @@
+#
+# 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.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"
+        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"
+      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"
+    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
+
+# action :deploy do
+#   # Alias to a variable so I can use in sub-resources
+#   new_resource = @new_resource
+
+#   # make the _default chef_environment look like the Rails production environment
+#   rails_env = (node.chef_environment =~ /_default/ ? "production" : node.chef_environment)
+#   node.run_state[:rails_env] = rails_env
+
+#   application new_resource.id do
+#     action :deploy
+#     path new_resource.path
+#     owner new_resource.owner
+#     group new_resource.group
+#     revision new_resource.revision
+#     repository new_resource.repository
+#     deploy_key new_resource.deploy_key
+#     force new_resource.force
+#     packages new_resource.packages
+#     environment new_resource.environment.merge('RAILS_ENV' => rails_env)
+#     before_deploy do
+#       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"
+#             owner new_resource.owner
+#             group new_resource.group
+#             mode "644"
+#             variables(
+#               :host => (dbm.attribute?('cloud') ? dbm['cloud']['local_ipv4'] : dbm['ipaddress']),
+#               :databases => new_resource.databases,
+#               :rails_env => rails_env
+#             )
+#           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"
+#           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
+
+#       callback(:before_deploy, new_resource.before_deploy)
+#     end
+
+#     before_migrate do
+#       gem_names = new_resource.gems.map{|gem, ver| gem}
+#       if gem_names.include?('bundler')
+#         link "#{release_path}/vendor/bundle" do
+#           to "#{new_resource.path}/shared/vendor_bundle"
+#         end
+#         common_groups = %w{development test cucumber staging production}
+#         execute "bundle install --deployment --without #{(common_groups -([node.chef_environment])).join(' ')}" do
+#           ignore_failure true
+#           cwd release_path
+#         end
+#       elsif gem_names.include?('bundler08')
+#         execute "gem bundle" do
+#           ignore_failure true
+#           cwd release_path
+#         end
+
+#       elsif new_resource.databases.has_key?(rails_env)
+#         # 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 release_path
+#         end
+#       end
+
+#       if new_resource.migration_command.include?('rake') && !gem_names.include?('rake')
+#         gem_package "rake" do
+#           action :install
+#         end
+#       end
+#     end
+
+#     symlink_before_migrate({
+#       "database.yml" => "config/database.yml",
+#       "memcached.yml" => "config/memcached.yml"
+#     })
+
+#     migrate new_resource.migrate
+#     migration_command new_resource.migration_command
+
+#     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
+
+#   end
+
+# end

+ 73 - 0
providers/unicorn.rb

@@ -0,0 +1,73 @@
+#
+# 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
+end
+
+action :before_deploy do
+end
+
+action :before_migrate do
+end
+
+action :before_symlink do
+end
+
+action :before_restart do
+
+  node.default[:unicorn][:worker_timeout] = 60
+  node.default[:unicorn][:preload_app] = false
+  node.default[:unicorn][:worker_processes] = [node[:cpu][:total].to_i * 4, 8].min
+  node.default[:unicorn][:preload_app] = false
+  node.default[:unicorn][:before_fork] = 'sleep 1' 
+  node.default[:unicorn][:port] = '8080'
+  node.set[:unicorn][:options] = { :tcp_nodelay => true, :backlog => 100 }
+
+  unicorn_config "/etc/unicorn/#{app['id']}.rb" do
+    listen({ node[:unicorn][:port] => node[:unicorn][:options] })
+    working_directory ::File.join(app['deploy_to'], 'current')
+    worker_timeout node[:unicorn][:worker_timeout] 
+    preload_app node[:unicorn][:preload_app] 
+    worker_processes node[:unicorn][:worker_processes]
+    before_fork node[:unicorn][:before_fork] 
+  end
+
+  runit_service app['id'] do
+    template_name 'unicorn'
+    cookbook 'application'
+    options(
+      :app => app,
+      :rails_env => node.run_state[:rails_env] || node.chef_environment,
+      :smells_like_rack => ::File.exists?(::File.join(app['deploy_to'], "current", "config.ru"))
+    )
+    run_restart false
+  end
+
+  if ::File.exists?(::File.join(app['deploy_to'], "current"))
+    d = resources(:deploy_revision => app['id'])
+    d.restart_command do
+      execute "/etc/init.d/#{app['id']} hup"
+    end
+  end
+
+end
+
+action :after_restart do
+end

+ 95 - 0
resources/default.rb

@@ -0,0 +1,95 @@
+#
+# Author:: Noah Kantrowitz <noah@opscode.com>
+# Cookbook Name:: application
+# Resource:: default
+#
+# 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.
+#
+
+require 'weakref'
+
+include Chef::Mixin::RecipeDefinitionDSLCore
+
+def initialize(*args)
+  super
+  @action = :deploy
+  @sub_resources = []
+end
+
+actions :deploy, :remove
+
+attribute :id, :kind_of => String, :name_attribute => true
+attribute :environment_name, :kind_of => String, :default => (node.chef_environment =~ /_default/ ? "production" : node.chef_environment)
+attribute :path, :kind_of => String
+attribute :owner, :kind_of => String
+attribute :group, :kind_of => String
+attribute :revision, :kind_of => String
+attribute :repository, :kind_of => String
+attribute :environment, :kind_of => Hash, :default => {}
+attribute :deploy_key, :kind_of => [String, NilClass], :default => nil
+attribute :force, :kind_of => [TrueClass, FalseClass], :default => false
+attribute :purge_before_symlink, :kind_of => Array, :default => []
+attribute :create_dirs_before_symlink, :kind_of => Array, :default => []
+attribute :symlinks, :kind_of => Hash, :default => {}
+attribute :symlink_before_migrate, :kind_of => Hash, :default => {}
+attribute :migrate, :kind_of => [TrueClass, FalseClass], :default => false
+attribute :migration_command, :kind_of => [String, NilClass], :default => nil
+attribute :restart_command, :kind_of => [String, NilClass], :default => nil
+attribute :packages, :kind_of => [Array, Hash], :default => []
+attr_reader :sub_resources
+
+# Callback fires before deploy is started.
+def before_deploy(arg=nil, &block)
+  arg ||= block
+  set_or_return(:before_deploy, arg, :kind_of => [Proc, String])
+end
+
+# Callback fires before migration is run.
+def before_migrate(arg=nil, &block)
+  arg ||= block
+  set_or_return(:before_migrate, arg, :kind_of => [Proc, String])
+end
+
+# Callback fires before symlinking
+def before_symlink(arg=nil, &block)
+  arg ||= block
+  set_or_return(:before_symlink, arg, :kind_of => [Proc, String])
+end
+
+# Callback fires before restart
+def before_restart(arg=nil, &block)
+  arg ||= block
+  set_or_return(:before_restart, arg, :kind_of => [Proc, String])
+end
+
+# Callback fires after restart
+def after_restart(arg=nil, &block)
+  arg ||= block
+  set_or_return(:after_restart, arg, :kind_of => [Proc, String])
+end
+
+def method_missing(name, &block)
+  begin
+    resource = super("application_#{name.to_s}", id, &block)
+  rescue NoMethodError
+    resource = super(name, id, &block)
+  end
+  # Enforce action :nothing in case people forget
+  resource.action :nothing
+  # Make this a weakref to prevent a cycle between the application resource and the sub resources
+  resource.application WeakRef.new(self)
+  @sub_resources << resource
+  resource
+end

+ 28 - 0
resources/rails.rb

@@ -0,0 +1,28 @@
+#
+# 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 :database, :kind_of => Hash, :default => {}
+attribute :memcached, :kind_of => Hash, :default => {}
+attribute :gems, :kind_of => [Array, Hash], :default => []
+attribute :bundler_deployment, :kind_of => [NilClass, TrueClass, FalseClass], :default => nil

+ 21 - 0
resources/unicorn.rb

@@ -0,0 +1,21 @@
+#
+# 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

+ 6 - 8
templates/default/database.yml.erb

@@ -1,10 +1,8 @@
-<%- @databases.each do |env, db| %>
-<%= (env =~ /_default/ ? "production" : env) %>:
-  adapter: <%= db['adapter'] %>
+<%= @rails_env %>:
+  adapter: <%= @database['adapter'] %>
   host: <%= @host %>
-  database: <%= db['database'] %>
-  username: <%= db['username'] %>
-  password: <%= db['password'] %>
-  encoding: <%= db.has_key?('encoding') ? db['encoding'] : 'utf8' %>
+  database: <%= @database['database'] %>
+  username: <%= @database['username'] %>
+  password: <%= @database['password'] %>
+  encoding: <%= @database.has_key?('encoding') ? @database['encoding'] : 'utf8' %>
   reconnect: true
-<%- end %>