diff options
Diffstat (limited to 'lib/chef/run_context.rb')
-rw-r--r-- | lib/chef/run_context.rb | 290 |
1 files changed, 290 insertions, 0 deletions
diff --git a/lib/chef/run_context.rb b/lib/chef/run_context.rb new file mode 100644 index 0000000000..625a993941 --- /dev/null +++ b/lib/chef/run_context.rb @@ -0,0 +1,290 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Author:: Christopher Walters (<cw@opscode.com>) +# Author:: Tim Hinderliter (<tim@opscode.com>) +# Copyright:: Copyright (c) 2008-2010 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# 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 'chef/resource_collection' +require 'chef/node' +require 'chef/role' +require 'chef/log' + +class Chef + # == Chef::RunContext + # Value object that loads and tracks the context of a Chef run + class RunContext + + attr_reader :node, :cookbook_collection, :definitions + + # Needs to be settable so deploy can run a resource_collection independent + # of any cookbooks. + attr_accessor :resource_collection, :immediate_notification_collection, :delayed_notification_collection + + attr_reader :events + + attr_reader :loaded_recipes + attr_reader :loaded_attributes + + # Creates a new Chef::RunContext object and populates its fields. This object gets + # used by the Chef Server to generate a fully compiled recipe list for a node. + # + # === Returns + # object<Chef::RunContext>:: Duh. :) + def initialize(node, cookbook_collection, events) + @node = node + @cookbook_collection = cookbook_collection + @resource_collection = Chef::ResourceCollection.new + @immediate_notification_collection = Hash.new {|h,k| h[k] = []} + @delayed_notification_collection = Hash.new {|h,k| h[k] = []} + @definitions = Hash.new + @loaded_recipes = {} + @loaded_attributes = {} + @events = events + + @node.run_context = self + end + + def load(run_list_expansion) + load_libraries + + load_lwrps + load_attributes + load_resource_definitions + + # Precendence rules state that roles' attributes come after + # cookbooks. Now we've loaded attributes from cookbooks with + # load_attributes, apply the expansion attributes (loaded from + # roles) to the node. + @node.apply_expansion_attributes(run_list_expansion) + + @events.recipe_load_start(run_list_expansion.recipes.size) + run_list_expansion.recipes.each do |recipe| + begin + include_recipe(recipe) + rescue Chef::Exceptions::RecipeNotFound => e + @events.recipe_not_found(e) + raise + rescue Exception => e + path = resolve_recipe(recipe) + @events.recipe_file_load_failed(path, e) + raise + end + end + @events.recipe_load_complete + end + + def resolve_recipe(recipe_name) + cookbook_name, recipe_short_name = Chef::Recipe.parse_recipe_name(recipe_name) + cookbook = cookbook_collection[cookbook_name] + cookbook.recipe_filenames_by_name[recipe_short_name] + end + + def resolve_attribute(cookbook_name, attr_file_name) + cookbook = cookbook_collection[cookbook_name] + raise Chef::Exceptions::CookbookNotFound, "could not find cookbook #{cookbook_name} while loading attribute #{name}" unless cookbook + + attribute_filename = cookbook.attribute_filenames_by_short_filename[attr_file_name] + raise Chef::Exceptions::AttributeNotFound, "could not find filename for attribute #{attr_file_name} in cookbook #{cookbook_name}" unless attribute_filename + + attribute_filename + end + + def notifies_immediately(notification) + nr = notification.notifying_resource + if nr.instance_of?(Chef::Resource) + @immediate_notification_collection[nr.name] << notification + else + @immediate_notification_collection[nr.to_s] << notification + end + end + + def notifies_delayed(notification) + nr = notification.notifying_resource + if nr.instance_of?(Chef::Resource) + @delayed_notification_collection[nr.name] << notification + else + @delayed_notification_collection[nr.to_s] << notification + end + end + + def immediate_notifications(resource) + if resource.instance_of?(Chef::Resource) + return @immediate_notification_collection[resource.name] + else + return @immediate_notification_collection[resource.to_s] + end + end + + def delayed_notifications(resource) + if resource.instance_of?(Chef::Resource) + return @delayed_notification_collection[resource.name] + else + return @delayed_notification_collection[resource.to_s] + end + end + + def include_recipe(*recipe_names) + result_recipes = Array.new + recipe_names.flatten.each do |recipe_name| + if result = load_recipe(recipe_name) + result_recipes << result + end + end + result_recipes + end + + def load_recipe(recipe_name) + Chef::Log.debug("Loading Recipe #{recipe_name} via include_recipe") + + cookbook_name, recipe_short_name = Chef::Recipe.parse_recipe_name(recipe_name) + if loaded_fully_qualified_recipe?(cookbook_name, recipe_short_name) + Chef::Log.debug("I am not loading #{recipe_name}, because I have already seen it.") + false + else + loaded_recipe(cookbook_name, recipe_short_name) + + cookbook = cookbook_collection[cookbook_name] + cookbook.load_recipe(recipe_short_name, self) + end + end + + def loaded_fully_qualified_recipe?(cookbook, recipe) + @loaded_recipes.has_key?("#{cookbook}::#{recipe}") + end + + def loaded_recipe?(recipe) + cookbook, recipe_name = Chef::Recipe.parse_recipe_name(recipe) + loaded_fully_qualified_recipe?(cookbook, recipe_name) + end + + def loaded_fully_qualified_attribute?(cookbook, attribute_file) + @loaded_attributes.has_key?("#{cookbook}::#{attribute_file}") + end + + def loaded_attribute(cookbook, attribute_file) + @loaded_attributes["#{cookbook}::#{attribute_file}"] = true + end + + private + + def loaded_recipe(cookbook, recipe) + @loaded_recipes["#{cookbook}::#{recipe}"] = true + end + + def load_libraries + @events.library_load_start(count_files_by_segment(:libraries)) + + foreach_cookbook_load_segment(:libraries) do |cookbook_name, filename| + begin + Chef::Log.debug("Loading cookbook #{cookbook_name}'s library file: #{filename}") + Kernel.load(filename) + @events.library_file_loaded(filename) + rescue Exception => e + # TODO wrap/munge exception to highlight syntax/name/no method errors. + @events.library_file_load_failed(filename, e) + raise + end + end + + @events.library_load_complete + end + + def load_lwrps + lwrp_file_count = count_files_by_segment(:providers) + count_files_by_segment(:resources) + @events.lwrp_load_start(lwrp_file_count) + load_lwrp_providers + load_lwrp_resources + @events.lwrp_load_complete + end + + def load_lwrp_providers + foreach_cookbook_load_segment(:providers) do |cookbook_name, filename| + begin + Chef::Log.debug("Loading cookbook #{cookbook_name}'s providers from #{filename}") + Chef::Provider.build_from_file(cookbook_name, filename, self) + @events.lwrp_file_loaded(filename) + rescue Exception => e + # TODO: wrap exception with helpful info + @events.lwrp_file_load_failed(filename, e) + raise + end + end + end + + def load_lwrp_resources + foreach_cookbook_load_segment(:resources) do |cookbook_name, filename| + begin + Chef::Log.debug("Loading cookbook #{cookbook_name}'s resources from #{filename}") + Chef::Resource.build_from_file(cookbook_name, filename, self) + @events.lwrp_file_loaded(filename) + rescue Exception => e + @events.lwrp_file_load_failed(filename, e) + raise + end + end + end + + def load_attributes + @events.attribute_load_start(count_files_by_segment(:attributes)) + foreach_cookbook_load_segment(:attributes) do |cookbook_name, filename| + begin + Chef::Log.debug("Node #{@node.name} loading cookbook #{cookbook_name}'s attribute file #{filename}") + attr_file_basename = ::File.basename(filename, ".rb") + @node.include_attribute("#{cookbook_name}::#{attr_file_basename}") + rescue Exception => e + @events.attribute_file_load_failed(filename, e) + raise + end + end + @events.attribute_load_complete + end + + def load_resource_definitions + @events.definition_load_start(count_files_by_segment(:definitions)) + foreach_cookbook_load_segment(:definitions) do |cookbook_name, filename| + begin + Chef::Log.debug("Loading cookbook #{cookbook_name}'s definitions from #{filename}") + resourcelist = Chef::ResourceDefinitionList.new + resourcelist.from_file(filename) + definitions.merge!(resourcelist.defines) do |key, oldval, newval| + Chef::Log.info("Overriding duplicate definition #{key}, new definition found in #{filename}") + newval + end + @events.definition_file_loaded(filename) + rescue Exception => e + @events.definition_file_load_failed(filename, e) + end + end + @events.definition_load_complete + end + + def count_files_by_segment(segment) + cookbook_collection.inject(0) do |count, ( cookbook_name, cookbook )| + count + cookbook.segment_filenames(segment).size + end + end + + def foreach_cookbook_load_segment(segment, &block) + cookbook_collection.each do |cookbook_name, cookbook| + segment_filenames = cookbook.segment_filenames(segment) + segment_filenames.each do |segment_filename| + block.call(cookbook_name, segment_filename) + end + end + end + + end +end |