summaryrefslogtreecommitdiff
path: root/lib/chef/run_context
diff options
context:
space:
mode:
authordanielsdeleo <dan@opscode.com>2012-11-29 15:45:55 -0800
committerdanielsdeleo <dan@opscode.com>2012-11-30 14:51:47 -0800
commit20fd1383020261c3756b54c26f4c2ea9e652d43c (patch)
treebd00bd9fa4a0e0e18b81a27ca337afc64f1fc287 /lib/chef/run_context
parentb213ae8e8c7aac472332298f0f455d36d122a7b5 (diff)
downloadchef-20fd1383020261c3756b54c26f4c2ea9e652d43c.tar.gz
[CHEF-3376] give CookbookCompiler its own file
Diffstat (limited to 'lib/chef/run_context')
-rw-r--r--lib/chef/run_context/cookbook_compiler.rb253
1 files changed, 253 insertions, 0 deletions
diff --git a/lib/chef/run_context/cookbook_compiler.rb b/lib/chef/run_context/cookbook_compiler.rb
new file mode 100644
index 0000000000..8edc1ade3c
--- /dev/null
+++ b/lib/chef/run_context/cookbook_compiler.rb
@@ -0,0 +1,253 @@
+#
+# Author:: Daniel DeLeo (<dan@opscode.com>)
+# Copyright:: Copyright (c) 2012 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/log'
+require 'chef/recipe'
+require 'chef/resource'
+require 'chef/provider'
+require 'chef/resource_definition_list'
+
+class Chef
+ class RunContext
+
+ # Implements the compile phase of the chef run by loading/eval-ing files
+ # from cookbooks in the correct order and in the correct context.
+ class CookbookCompiler
+ attr_reader :node
+ attr_reader :events
+ attr_reader :run_list_expansion
+ attr_reader :cookbook_collection
+
+ # Resource Definitions from the compiled cookbooks. This is populated by
+ # calling #compile_resource_definitions (which is called by #compile)
+ attr_reader :definitions
+
+ def initialize(node, cookbook_collection, run_list_expansion, events)
+ @node = node
+ @events = events
+ @run_list_expansion = run_list_expansion
+ @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] = []}
+ # @loaded_recipes = {}
+ # @loaded_attributes = {}
+ #
+
+ @definitions = Hash.new
+ @cookbook_order = nil
+ end
+
+ # Run the compile phase of the chef run. Loads files in the following order:
+ # * Libraries
+ # * Attributes
+ # * LWRPs
+ # * Resource Definitions
+ # * Recipes
+ #
+ # Recipes are loaded in precisely the order specified by the expanded run_list.
+ #
+ # Other files are loaded in an order derived from the expanded run_list
+ # and the dependencies declared by cookbooks' metadata. See
+ # #cookbook_order for more information.
+ def compile
+ compile_libraries
+ compile_attributes
+ compile_lwrps
+ compile_resource_definitions
+ #compile_recipes
+ end
+
+ # Extracts the cookbook names from the expanded run list, then iterates
+ # over the list, recursing through dependencies to give a run_list
+ # ordered array of cookbook names with no duplicates. Dependencies appear
+ # before the cookbook they depend on.
+ def cookbook_order
+ @cookbook_order ||= begin
+ ordered_cookbooks = []
+ seen_cookbooks = {}
+ run_list_expansion.recipes.each do |recipe|
+ cookbook = Chef::Recipe.parse_recipe_name(recipe).first
+ add_cookbook_with_deps(ordered_cookbooks, seen_cookbooks, cookbook)
+ end
+ ordered_cookbooks
+ end
+ end
+
+ # Loads library files from cookbooks according to #cookbook_order.
+ def compile_libraries
+ @events.library_load_start(count_files_by_segment(:libraries))
+ cookbook_order.each do |cookbook|
+ load_libraries_from_cookbook(cookbook)
+ end
+ @events.library_load_complete
+ end
+
+ # Loads attributes files from cookbooks. Attributes files are loaded
+ # according to #cookbook_order; within a cookbook, +default.rb+ is loaded
+ # first, then the remaining attributes files in lexical sort order.
+ def compile_attributes
+ @events.attribute_load_start(count_files_by_segment(:attributes))
+ cookbook_order.each do |cookbook|
+ load_attributes_from_cookbook(cookbook)
+ end
+ @events.attribute_load_complete
+ end
+
+ # Loads LWRPs according to #cookbook_order. Providers are loaded before
+ # resources on a cookbook-wise basis.
+ def compile_lwrps
+ lwrp_file_count = count_files_by_segment(:providers) + count_files_by_segment(:resources)
+ @events.lwrp_load_start(lwrp_file_count)
+ cookbook_order.each do |cookbook|
+ load_lwrps_from_cookbook(cookbook)
+ end
+ @events.lwrp_load_complete
+ end
+
+ def compile_resource_definitions
+ @events.definition_load_start(count_files_by_segment(:definitions))
+ cookbook_order.each do |cookbook|
+ load_resource_definitions_from_cookbook(cookbook)
+ end
+ @events.definition_load_complete
+ end
+
+
+ private
+
+ def load_attributes_from_cookbook(cookbook_name)
+ list_of_attr_files = files_in_cookbook_by_segment(cookbook_name, :attributes).dup
+ if default_file = list_of_attr_files.find {|path| File.basename(path) == "default.rb" }
+ list_of_attr_files.delete(default_file)
+ load_attribute_file(cookbook_name.to_s, default_file)
+ end
+
+ list_of_attr_files.each do |filename|
+ load_attribute_file(cookbook_name.to_s, filename)
+ end
+ end
+
+ def load_attribute_file(cookbook_name, filename)
+ 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
+
+ def load_libraries_from_cookbook(cookbook_name)
+ files_in_cookbook_by_segment(cookbook_name, :libraries).each do |filename|
+ begin
+ Chef::Log.debug("Loading cookbook #{cookbook_name}'s library file: #{filename}")
+ Kernel.load(filename)
+ @events.library_file_loaded(filename)
+ rescue Exception => e
+ @events.library_file_load_failed(filename, e)
+ raise
+ end
+ end
+ end
+
+ def load_lwrps_from_cookbook(cookbook_name)
+ files_in_cookbook_by_segment(cookbook_name, :providers).each do |filename|
+ load_lwrp_provider(cookbook_name, filename)
+ end
+ files_in_cookbook_by_segment(cookbook_name, :resources).each do |filename|
+ load_lwrp_resource(cookbook_name, filename)
+ end
+ end
+
+ def load_lwrp_provider(cookbook_name, filename)
+ 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
+ @events.lwrp_file_load_failed(filename, e)
+ raise
+ end
+
+ def load_lwrp_resource(cookbook_name, filename)
+ 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
+
+
+ def load_resource_definitions_from_cookbook(cookbook_name)
+ files_in_cookbook_by_segment(cookbook_name, :definitions).each do |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)
+ raise
+ end
+ end
+ end
+
+ # Builds up the list of +ordered_cookbooks+ by first recursing through the
+ # dependencies of +cookbook+, and then adding +cookbook+ to the list of
+ # +ordered_cookbooks+. A cookbook is skipped if it appears in
+ # +seen_cookbooks+, otherwise it is added to the set of +seen_cookbooks+
+ # before its dependencies are processed.
+ def add_cookbook_with_deps(ordered_cookbooks, seen_cookbooks, cookbook)
+ return false if seen_cookbooks.key?(cookbook)
+
+ seen_cookbooks[cookbook] = true
+ each_cookbook_dep(cookbook) do |dependency|
+ add_cookbook_with_deps(ordered_cookbooks, seen_cookbooks, dependency)
+ end
+ ordered_cookbooks << cookbook
+ end
+
+
+ def count_files_by_segment(segment)
+ cookbook_collection.inject(0) do |count, cookbook_by_name|
+ count + cookbook_by_name[1].segment_filenames(segment).size
+ end
+ end
+
+ # Lists the local paths to files in +cookbook+ of type +segment+
+ # (attribute, recipe, etc.), sorted lexically.
+ def files_in_cookbook_by_segment(cookbook, segment)
+ cookbook_collection[cookbook].segment_filenames(segment).sort
+ end
+
+ # Yields the name of each cookbook depended on by +cookbook_name+ in
+ # lexical sort order.
+ def each_cookbook_dep(cookbook_name, &block)
+ cookbook = cookbook_collection[cookbook_name]
+ cookbook.metadata.dependencies.keys.sort.each(&block)
+ end
+
+ end
+
+ end
+end