diff options
author | danielsdeleo <dan@opscode.com> | 2012-11-29 15:45:55 -0800 |
---|---|---|
committer | danielsdeleo <dan@opscode.com> | 2012-11-30 14:51:47 -0800 |
commit | 20fd1383020261c3756b54c26f4c2ea9e652d43c (patch) | |
tree | bd00bd9fa4a0e0e18b81a27ca337afc64f1fc287 /lib/chef/run_context | |
parent | b213ae8e8c7aac472332298f0f455d36d122a7b5 (diff) | |
download | chef-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.rb | 253 |
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 |