diff options
author | danielsdeleo <dan@opscode.com> | 2012-10-19 15:58:41 -0700 |
---|---|---|
committer | danielsdeleo <dan@opscode.com> | 2012-10-19 15:58:41 -0700 |
commit | d90433ae23ec93dc9e9804c721911462c7a1b331 (patch) | |
tree | bc594753736164e616ec0c1c587bde42b6893627 | |
parent | d489549de16f15660c600dc100f22084194feeca (diff) | |
parent | 3fe49d7751a0194790b887708b28ca31d7046053 (diff) | |
download | chef-d90433ae23ec93dc9e9804c721911462c7a1b331.tar.gz |
Merge branch 'CHEF-2992'
28 files changed, 769 insertions, 641 deletions
diff --git a/chef/lib/chef/dsl.rb b/chef/lib/chef/dsl.rb new file mode 100644 index 0000000000..74244fafbb --- /dev/null +++ b/chef/lib/chef/dsl.rb @@ -0,0 +1,5 @@ +require 'chef/dsl/recipe' +require 'chef/dsl/platform_introspection' +require 'chef/dsl/data_query' +require 'chef/dsl/include_recipe' +require 'chef/dsl/include_attribute' diff --git a/chef/lib/chef/dsl/data_query.rb b/chef/lib/chef/dsl/data_query.rb new file mode 100644 index 0000000000..ef5b490020 --- /dev/null +++ b/chef/lib/chef/dsl/data_query.rb @@ -0,0 +1,66 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Copyright:: Copyright (c) 2008 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/search/query' +require 'chef/data_bag' +require 'chef/data_bag_item' +require 'chef/encrypted_data_bag_item' + +class Chef + module DSL + + # ==Chef::DSL::DataQuery + # Provides DSL for querying data from the chef-server via search or data + # bag. + module DataQuery + + def search(*args, &block) + # If you pass a block, or have at least the start argument, do raw result parsing + # + # Otherwise, do the iteration for the end user + if Kernel.block_given? || args.length >= 4 + Chef::Search::Query.new.search(*args, &block) + else + results = Array.new + Chef::Search::Query.new.search(*args) do |o| + results << o + end + results + end + end + + def data_bag(bag) + DataBag.validate_name!(bag.to_s) + rbag = DataBag.load(bag) + rbag.keys + rescue Exception + Log.error("Failed to list data bag items in data bag: #{bag.inspect}") + raise + end + + def data_bag_item(bag, item) + DataBag.validate_name!(bag.to_s) + DataBagItem.validate_id!(item) + DataBagItem.load(bag, item) + rescue Exception + Log.error("Failed to load data bag item: #{bag.inspect} #{item.inspect}") + raise + end + end + end +end diff --git a/chef/lib/chef/dsl/include_attribute.rb b/chef/lib/chef/dsl/include_attribute.rb new file mode 100644 index 0000000000..d8342af6a7 --- /dev/null +++ b/chef/lib/chef/dsl/include_attribute.rb @@ -0,0 +1,60 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Copyright:: Copyright (c) 2008, 2009 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' + +class Chef + module DSL + module IncludeAttribute + + # Loads the attribute file specified by the short name of the + # file, e.g., loads specified cookbook's + # "attributes/mailservers.rb" + # if passed + # "mailservers" + def include_attribute(*attr_file_specs) + attr_file_specs.flatten.each do |attr_file_spec| + cookbook_name, attr_file = parse_attribute_file_spec(attr_file_spec) + if run_context.loaded_fully_qualified_attribute?(cookbook_name, attr_file) + Chef::Log.debug("I am not loading attribute file #{cookbook_name}::#{attr_file}, because I have already seen it.") + else + Chef::Log.debug("Loading Attribute #{cookbook_name}::#{attr_file}") + run_context.loaded_attribute(cookbook_name, attr_file) + attr_file_path = run_context.resolve_attribute(cookbook_name, attr_file) + node.from_file(attr_file_path) + end + end + true + end + + # Takes a attribute file specification, like "apache2" or "mysql::server" + # and converts it to a 2 element array of [cookbook_name, attribute_file_name] + def parse_attribute_file_spec(file_spec) + if match = file_spec.match(/(.+?)::(.+)/) + [match[1], match[2]] + else + [file_spec, "default"] + end + end + + end + end +end + + + diff --git a/chef/lib/chef/dsl/include_recipe.rb b/chef/lib/chef/dsl/include_recipe.rb new file mode 100644 index 0000000000..8cbee7a733 --- /dev/null +++ b/chef/lib/chef/dsl/include_recipe.rb @@ -0,0 +1,42 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Copyright:: Copyright (c) 2008, 2009 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' + +class Chef + module DSL + module IncludeRecipe + + def include_recipe(*recipe_names) + run_context.include_recipe(*recipe_names) + end + + def load_recipe(recipe_name) + run_context.load_recipe(recipe_name) + end + + def require_recipe(*args) + Chef::Log.warn("require_recipe is deprecated and will be removed in a future release, please use include_recipe") + include_recipe(*args) + end + + end + end +end + + diff --git a/chef/lib/chef/dsl/platform_introspection.rb b/chef/lib/chef/dsl/platform_introspection.rb new file mode 100644 index 0000000000..211def2797 --- /dev/null +++ b/chef/lib/chef/dsl/platform_introspection.rb @@ -0,0 +1,215 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Copyright:: Copyright (c) 2008 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. +# + +class Chef + module DSL + + # == Chef::DSL::PlatformIntrospection + # Provides the DSL for platform-dependent switch logic, such as + # #value_for_platform. + module PlatformIntrospection + + # Implementation class for determining platform dependent values + class PlatformDependentValue + + # Create a platform dependent value object. + # === Arguments + # platform_hash (Hash) a hash of the same structure as Chef::Platform, + # like this: + # { + # :debian => {:default => 'the value for all debian'} + # [:centos, :redhat, :fedora] => {:default => "value for all EL variants"} + # :ubuntu => { :default => "default for ubuntu", '10.04' => "value for 10.04 only"}, + # :default => "the default when nothing else matches" + # } + # * platforms can be specified as Symbols or Strings + # * multiple platforms can be grouped by using an Array as the key + # * values for platforms need to be Hashes of the form: + # {platform_version => value_for_that_version} + # * the exception to the above is the default value, which is given as + # :default => default_value + def initialize(platform_hash) + @values = {} + platform_hash.each { |platforms, value| set(platforms, value)} + end + + def value_for_node(node) + platform, version = node[:platform].to_s, node[:platform_version].to_s + if @values.key?(platform) && @values[platform].key?(version) + @values[platform][version] + elsif @values.key?(platform) && @values[platform].key?("default") + @values[platform]["default"] + elsif @values.key?("default") + @values["default"] + else + nil + end + end + + private + + def set(platforms, value) + if platforms.to_s == 'default' + @values["default"] = value + else + assert_valid_platform_values!(platforms, value) + Array(platforms).each { |platform| @values[platform.to_s] = normalize_keys(value)} + value + end + end + + def normalize_keys(hash) + hash.inject({}) do |h, key_value| + keys, value = *key_value + Array(keys).each do |key| + h[key.to_s] = value + end + h + end + end + + def assert_valid_platform_values!(platforms, value) + unless value.kind_of?(Hash) + msg = "platform dependent values must be specified in the format :platform => {:version => value} " + msg << "you gave a value #{value.inspect} for platform(s) #{platforms}" + raise ArgumentError, msg + end + end + end + + + + # Given a hash similar to the one we use for Platforms, select a value from the hash. Supports + # per platform defaults, along with a single base default. Arrays may be passed as hash keys and + # will be expanded. + # + # === Parameters + # platform_hash:: A platform-style hash. + # + # === Returns + # value:: Whatever the most specific value of the hash is. + def value_for_platform(platform_hash) + PlatformDependentValue.new(platform_hash).value_for_node(node) + end + + # Given a list of platforms, returns true if the current recipe is being run on a node with + # that platform, false otherwise. + # + # === Parameters + # args:: A list of platforms. Each platform can be in string or symbol format. + # + # === Returns + # true:: If the current platform is in the list + # false:: If the current platform is not in the list + def platform?(*args) + has_platform = false + + args.flatten.each do |platform| + has_platform = true if platform.to_s == node[:platform] + end + + has_platform + end + + + + # Implementation class for determining platform family dependent values + class PlatformFamilyDependentValue + + # Create a platform family dependent value object. + # === Arguments + # platform_family_hash (Hash) a map of platform families to values. + # like this: + # { + # :rhel => "value for all EL variants" + # :fedora => "value for fedora variants fedora and amazon" , + # [:fedora, :rhel] => "value for all known redhat variants" + # :debian => "value for debian variants including debian, ubuntu, mint" , + # :default => "the default when nothing else matches" + # } + # * platform families can be specified as Symbols or Strings + # * multiple platform families can be grouped by using an Array as the key + # * values for platform families can be any object, with no restrictions. Some examples: + # - [:stop, :start] + # - "mysql-devel" + # - { :key => "value" } + def initialize(platform_family_hash) + @values = {} + @values["default"] = nil + platform_family_hash.each { |platform_families, value| set(platform_families, value)} + end + + def value_for_node(node) + if node.key?(:platform_family) + platform_family = node[:platform_family].to_s + if @values.key?(platform_family) + @values[platform_family] + else + @values["default"] + end + else + @values["default"] + end + end + + private + + def set(platform_family, value) + if platform_family.to_s == 'default' + @values["default"] = value + else + Array(platform_family).each { |family| @values[family.to_s] = value } + value + end + end + end + + + # Given a hash mapping platform families to values, select a value from the hash. Supports a single + # base default if platform family is not in the map. Arrays may be passed as hash keys and will be + # expanded + # + # === Parameters + # platform_family_hash:: A hash in the form { platform_family_name => value } + # + # === Returns + # value:: Whatever the most specific value of the hash is. + def value_for_platform_family(platform_family_hash) + PlatformFamilyDependentValue.new(platform_family_hash).value_for_node(node) + end + + # Given a list of platform families, returns true if the current recipe is being run on a + # node within that platform family, false otherwise. + # + # === Parameters + # args:: A list of platform families. Each platform family can be in string or symbol format. + # + # === Returns + # true:: if the current node platform family is in the list. + # false:: if the current node platform family is not in the list. + def platform_family?(*args) + has_pf = false + args.flatten.each do |platform_family| + has_pf = true if platform_family.to_s == node[:platform_family] + end + has_pf + end + + end + end +end diff --git a/chef/lib/chef/dsl/recipe.rb b/chef/lib/chef/dsl/recipe.rb new file mode 100644 index 0000000000..1bfe8da0e0 --- /dev/null +++ b/chef/lib/chef/dsl/recipe.rb @@ -0,0 +1,84 @@ +#-- +# Author:: Adam Jacob (<adam@opscode.com>) +# Author:: Christopher Walters (<cw@opscode.com>) +# Copyright:: Copyright (c) 2008, 2009 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' +require 'chef/resource_platform_map' +require 'chef/mixin/convert_to_class_name' +require 'chef/mixin/language' + +class Chef + module DSL + + # == Chef::DSL::Recipe + # Provides the primary recipe DSL functionality for defining Chef resource + # objects via method calls. + module Recipe + + include Chef::Mixin::ConvertToClassName + + def method_missing(method_symbol, *args, &block) + # If we have a definition that matches, we want to use that instead. This should + # let you do some really crazy over-riding of "native" types, if you really want + # to. + if run_context.definitions.has_key?(method_symbol) + # This dupes the high level object, but we still need to dup the params + new_def = run_context.definitions[method_symbol].dup + new_def.params = new_def.params.dup + new_def.node = run_context.node + # This sets up the parameter overrides + new_def.instance_eval(&block) if block + new_recipe = Chef::Recipe.new(cookbook_name, @recipe_name, run_context) + new_recipe.params = new_def.params + new_recipe.params[:name] = args[0] + new_recipe.instance_eval(&new_def.recipe) + else + # Otherwise, we're rocking the regular resource call route. + + # Checks the new platform => short_name => resource mapping initially + # then fall back to the older approach (Chef::Resource.const_get) for + # backward compatibility + resource_class = Chef::Resource.resource_for_node(method_symbol, run_context.node) + + super unless resource_class + raise ArgumentError, "You must supply a name when declaring a #{method_symbol} resource" unless args.size > 0 + + # If we have a resource like this one, we want to steal its state + args << run_context + resource = resource_class.new(*args) + resource.load_prior_resource + resource.cookbook_name = cookbook_name + resource.recipe_name = @recipe_name + resource.params = @params + resource.source_line = caller[0] + # Determine whether this resource is being created in the context of an enclosing Provider + resource.enclosing_provider = self.is_a?(Chef::Provider) ? self : nil + # Evaluate resource attribute DSL + resource.instance_eval(&block) if block + + # Run optional resource hook + resource.after_created + + run_context.resource_collection.insert(resource) + resource + end + end + + end + end +end diff --git a/chef/lib/chef/mixin/language.rb b/chef/lib/chef/mixin/language.rb index 52b2065755..3aa6a6d800 100644 --- a/chef/lib/chef/mixin/language.rb +++ b/chef/lib/chef/mixin/language.rb @@ -16,233 +16,20 @@ # limitations under the License. # -require 'chef/search/query' -require 'chef/data_bag' -require 'chef/data_bag_item' -require 'chef/encrypted_data_bag_item' +require 'chef/dsl/platform_introspection' +require 'chef/dsl/data_query' class Chef module Mixin - module Language - - # Implementation class for determining platform dependent values - class PlatformDependentValue - - # Create a platform dependent value object. - # === Arguments - # platform_hash (Hash) a hash of the same structure as Chef::Platform, - # like this: - # { - # :debian => {:default => 'the value for all debian'} - # [:centos, :redhat, :fedora] => {:default => "value for all EL variants"} - # :ubuntu => { :default => "default for ubuntu", '10.04' => "value for 10.04 only"}, - # :default => "the default when nothing else matches" - # } - # * platforms can be specified as Symbols or Strings - # * multiple platforms can be grouped by using an Array as the key - # * values for platforms need to be Hashes of the form: - # {platform_version => value_for_that_version} - # * the exception to the above is the default value, which is given as - # :default => default_value - def initialize(platform_hash) - @values = {} - platform_hash.each { |platforms, value| set(platforms, value)} - end - - def value_for_node(node) - platform, version = node[:platform].to_s, node[:platform_version].to_s - if @values.key?(platform) && @values[platform].key?(version) - @values[platform][version] - elsif @values.key?(platform) && @values[platform].key?("default") - @values[platform]["default"] - elsif @values.key?("default") - @values["default"] - else - nil - end - end - - private - - def set(platforms, value) - if platforms.to_s == 'default' - @values["default"] = value - else - assert_valid_platform_values!(platforms, value) - Array(platforms).each { |platform| @values[platform.to_s] = normalize_keys(value)} - value - end - end - - def normalize_keys(hash) - hash.inject({}) do |h, key_value| - keys, value = *key_value - Array(keys).each do |key| - h[key.to_s] = value - end - h - end - end - - def assert_valid_platform_values!(platforms, value) - unless value.kind_of?(Hash) - msg = "platform dependent values must be specified in the format :platform => {:version => value} " - msg << "you gave a value #{value.inspect} for platform(s) #{platforms}" - raise ArgumentError, msg - end - end - end - - - - # Given a hash similar to the one we use for Platforms, select a value from the hash. Supports - # per platform defaults, along with a single base default. Arrays may be passed as hash keys and - # will be expanded. - # - # === Parameters - # platform_hash:: A platform-style hash. - # - # === Returns - # value:: Whatever the most specific value of the hash is. - def value_for_platform(platform_hash) - PlatformDependentValue.new(platform_hash).value_for_node(node) - end - - # Given a list of platforms, returns true if the current recipe is being run on a node with - # that platform, false otherwise. - # - # === Parameters - # args:: A list of platforms. Each platform can be in string or symbol format. - # - # === Returns - # true:: If the current platform is in the list - # false:: If the current platform is not in the list - def platform?(*args) - has_platform = false - args.flatten.each do |platform| - has_platform = true if platform.to_s == node[:platform] - end - - has_platform - end - - - - # Implementation class for determining platform family dependent values - class PlatformFamilyDependentValue - - # Create a platform family dependent value object. - # === Arguments - # platform_family_hash (Hash) a map of platform families to values. - # like this: - # { - # :rhel => "value for all EL variants" - # :fedora => "value for fedora variants fedora and amazon" , - # [:fedora, :rhel] => "value for all known redhat variants" - # :debian => "value for debian variants including debian, ubuntu, mint" , - # :default => "the default when nothing else matches" - # } - # * platform families can be specified as Symbols or Strings - # * multiple platform families can be grouped by using an Array as the key - # * values for platform families can be any object, with no restrictions. Some examples: - # - [:stop, :start] - # - "mysql-devel" - # - { :key => "value" } - def initialize(platform_family_hash) - @values = {} - @values["default"] = nil - platform_family_hash.each { |platform_families, value| set(platform_families, value)} - end - - def value_for_node(node) - if node.key?(:platform_family) - platform_family = node[:platform_family].to_s - if @values.key?(platform_family) - @values[platform_family] - else - @values["default"] - end - else - @values["default"] - end - end - - private - - def set(platform_family, value) - if platform_family.to_s == 'default' - @values["default"] = value - else - Array(platform_family).each { |family| @values[family.to_s] = value } - value - end - end - end - - - # Given a hash mapping platform families to values, select a value from the hash. Supports a single - # base default if platform family is not in the map. Arrays may be passed as hash keys and will be - # expanded. - # - # === Parameters - # platform_family_hash:: A hash in the form { platform_family_name => value } - # - # === Returns - # value:: Whatever the most specific value of the hash is. - def value_for_platform_family(platform_family_hash) - PlatformFamilyDependentValue.new(platform_family_hash).value_for_node(node) - end - - # Given a list of platform families, returns true if the current recipe is being run on a - # node within that platform family, false otherwise. - # - # === Parameters - # args:: A list of platform families. Each platform family can be in string or symbol format. - # - # === Returns - # true:: if the current node platform family is in the list. - # false:: if the current node platform family is not in the list. - def platform_family?(*args) - has_pf = false - args.flatten.each do |platform_family| - has_pf = true if platform_family.to_s == node[:platform_family] - end - has_pf - end - - def search(*args, &block) - # If you pass a block, or have at least the start argument, do raw result parsing - # - # Otherwise, do the iteration for the end user - if Kernel.block_given? || args.length >= 4 - Chef::Search::Query.new.search(*args, &block) - else - results = Array.new - Chef::Search::Query.new.search(*args) do |o| - results << o - end - results - end - end - - def data_bag(bag) - DataBag.validate_name!(bag.to_s) - rbag = DataBag.load(bag) - rbag.keys - rescue Exception - Log.error("Failed to list data bag items in data bag: #{bag.inspect}") - raise - end + # == [DEPRECATED] Chef::Mixin::Language + # This module is deprecated and remains only for backwards compatibility. + # + # See Chef::DSL::PlatformIntrospection and Chef::DSL::DataQuery + module Language - def data_bag_item(bag, item) - DataBag.validate_name!(bag.to_s) - DataBagItem.validate_id!(item) - DataBagItem.load(bag, item) - rescue Exception - Log.error("Failed to load data bag item: #{bag.inspect} #{item.inspect}") - raise - end + include Chef::DSL::PlatformIntrospection + include Chef::DSL::DataQuery end end diff --git a/chef/lib/chef/mixin/language_include_attribute.rb b/chef/lib/chef/mixin/language_include_attribute.rb index 5d926a6761..283773b25d 100644 --- a/chef/lib/chef/mixin/language_include_attribute.rb +++ b/chef/lib/chef/mixin/language_include_attribute.rb @@ -6,9 +6,9 @@ # 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. @@ -16,46 +16,14 @@ # limitations under the License. # -require 'chef/log' +require 'chef/dsl/include_attribute' class Chef module Mixin - module LanguageIncludeAttribute - - # Loads the attribute file specified by the short name of the - # file, e.g., loads specified cookbook's - # "attributes/mailservers.rb" - # if passed - # "mailservers" - def include_attribute(*fully_qualified_attribute_short_filenames) - if self.kind_of?(Chef::Node) - node = self - else - node = @node - end - - fully_qualified_attribute_short_filenames.flatten.each do |fully_qualified_attribute_short_filename| - if node.run_state[:seen_attributes].has_key?(fully_qualified_attribute_short_filename) - Chef::Log.debug("I am not loading attribute file #{fully_qualified_attribute_short_filename}, because I have already seen it.") - next - end - - Chef::Log.debug("Loading Attribute #{fully_qualified_attribute_short_filename}") - node.run_state[:seen_attributes][fully_qualified_attribute_short_filename] = true - - if amatch = fully_qualified_attribute_short_filename.match(/(.+?)::(.+)/) - cookbook_name = amatch[1].to_sym - node.load_attribute_by_short_filename(amatch[2], cookbook_name) - else - cookbook_name = fully_qualified_attribute_short_filename.to_sym - node.load_attribute_by_short_filename("default", cookbook_name) - end - end - true - end - end + # DEPRECATED: This is just here for compatibility, use + # Chef::DSL::IncludeAttribute instead. + LanguageIncludeAttribute = Chef::DSL::IncludeAttribute end end - diff --git a/chef/lib/chef/mixin/language_include_recipe.rb b/chef/lib/chef/mixin/language_include_recipe.rb index c819211feb..0566046560 100644 --- a/chef/lib/chef/mixin/language_include_recipe.rb +++ b/chef/lib/chef/mixin/language_include_recipe.rb @@ -16,43 +16,11 @@ # limitations under the License. # -require 'chef/log' +require 'chef/dsl/include_recipe' class Chef module Mixin - module LanguageIncludeRecipe - - def include_recipe(*recipe_names) - result_recipes = Array.new - recipe_names.flatten.each do |recipe_name| - if node.run_state[:seen_recipes].has_key?(recipe_name) or node.run_state[:seen_recipes].has_key?(recipe_name + "::default") - Chef::Log.debug("I am not loading #{recipe_name}, because I have already seen it.") - next - end - - result_recipes << load_recipe(recipe_name) - end - result_recipes - end - - def load_recipe(recipe_name) - Chef::Log.debug("Loading Recipe #{recipe_name} via include_recipe") - node.run_state[:seen_recipes][recipe_name] = true - - cookbook_name, recipe_short_name = Chef::Recipe.parse_recipe_name(recipe_name) - - run_context = self.is_a?(Chef::RunContext) ? self : self.run_context - cookbook = run_context.cookbook_collection[cookbook_name] - cookbook.load_recipe(recipe_short_name, run_context) - end - - - def require_recipe(*args) - Chef::Log.warn("require_recipe is deprecated and will be removed in a future release, please use include_recipe") - include_recipe(*args) - end - - end + LanguageIncludeRecipe = Chef::DSL::IncludeRecipe end end - + diff --git a/chef/lib/chef/mixin/recipe_definition_dsl_core.rb b/chef/lib/chef/mixin/recipe_definition_dsl_core.rb index cf89600654..ff422d892f 100644 --- a/chef/lib/chef/mixin/recipe_definition_dsl_core.rb +++ b/chef/lib/chef/mixin/recipe_definition_dsl_core.rb @@ -17,70 +17,17 @@ # limitations under the License. # -require 'chef/resource' -require 'chef/resource_platform_map' -require 'chef/mixin/convert_to_class_name' -require 'chef/mixin/language' +### +# NOTE: This file and constant are here only for backwards compatibility. +# New code should use Chef::DSL::Recipe instead. +# +# This constant (module name) will eventually be deprecated and then removed. +### -#-- -# UGH. this is a circular require that will cause an uninitialized constant -# error, but this file really does depend on Chef::Recipe. oh well. -# require 'chef/recipe' +require 'chef/dsl/recipe' class Chef module Mixin - module RecipeDefinitionDSLCore - - include Chef::Mixin::ConvertToClassName - include Chef::Mixin::Language - - def method_missing(method_symbol, *args, &block) - # If we have a definition that matches, we want to use that instead. This should - # let you do some really crazy over-riding of "native" types, if you really want - # to. - if run_context.definitions.has_key?(method_symbol) - # This dupes the high level object, but we still need to dup the params - new_def = run_context.definitions[method_symbol].dup - new_def.params = new_def.params.dup - new_def.node = run_context.node - # This sets up the parameter overrides - new_def.instance_eval(&block) if block - new_recipe = Chef::Recipe.new(cookbook_name, @recipe_name, run_context) - new_recipe.params = new_def.params - new_recipe.params[:name] = args[0] - new_recipe.instance_eval(&new_def.recipe) - else - # Otherwise, we're rocking the regular resource call route. - - # Checks the new platform => short_name => resource mapping initially - # then fall back to the older approach (Chef::Resource.const_get) for - # backward compatibility - resource_class = Chef::Resource.resource_for_node(method_symbol, run_context.node) - - super unless resource_class - raise ArgumentError, "You must supply a name when declaring a #{method_symbol} resource" unless args.size > 0 - - # If we have a resource like this one, we want to steal its state - args << run_context - resource = resource_class.new(*args) - resource.load_prior_resource - resource.cookbook_name = cookbook_name - resource.recipe_name = @recipe_name - resource.params = @params - resource.source_line = caller[0] - # Determine whether this resource is being created in the context of an enclosing Provider - resource.enclosing_provider = self.is_a?(Chef::Provider) ? self : nil - # Evaluate resource attribute DSL - resource.instance_eval(&block) if block - - # Run optional resource hook - resource.after_created - - run_context.resource_collection.insert(resource) - resource - end - end - - end + RecipeDefinitionDSLCore = Chef::DSL::Recipe end end diff --git a/chef/lib/chef/mixins.rb b/chef/lib/chef/mixins.rb index e95acbdaae..557932c0e6 100644 --- a/chef/lib/chef/mixins.rb +++ b/chef/lib/chef/mixins.rb @@ -7,12 +7,8 @@ require 'chef/mixin/create_path' require 'chef/mixin/deep_merge' require 'chef/mixin/enforce_ownership_and_permissions' require 'chef/mixin/from_file' -require 'chef/mixin/language' -require 'chef/mixin/language_include_attribute' -require 'chef/mixin/language_include_recipe' require 'chef/mixin/params_validate' require 'chef/mixin/path_sanity' -require 'chef/mixin/recipe_definition_dsl_core' require 'chef/mixin/template' require 'chef/mixin/securable' require 'chef/mixin/xml_escape' diff --git a/chef/lib/chef/node.rb b/chef/lib/chef/node.rb index d0b073d6a3..1229e0db28 100644 --- a/chef/lib/chef/node.rb +++ b/chef/lib/chef/node.rb @@ -21,13 +21,12 @@ require 'forwardable' require 'chef/config' -require 'chef/cookbook/cookbook_collection' require 'chef/nil_argument' require 'chef/mixin/check_helper' require 'chef/mixin/params_validate' require 'chef/mixin/from_file' -require 'chef/mixin/language_include_attribute' require 'chef/mixin/deep_merge' +require 'chef/dsl/include_attribute' require 'chef/environment' require 'chef/couchdb' require 'chef/rest' @@ -48,15 +47,13 @@ class Chef attr_accessor :recipe_list, :couchdb, :couchdb_rev, :run_state, :run_list attr_reader :couchdb_id - # TODO: 5/18/2010 cw/timh. cookbook_collection should be removed - # from here and for any place it's needed, it should be accessed - # through a Chef::RunContext - attr_accessor :cookbook_collection + attr_accessor :run_context - include Chef::Mixin::CheckHelper include Chef::Mixin::FromFile + include Chef::DSL::IncludeAttribute + + include Chef::Mixin::CheckHelper include Chef::Mixin::ParamsValidate - include Chef::Mixin::LanguageIncludeAttribute include Chef::IndexQueue::Indexable DESIGN_DOCUMENT = { @@ -165,14 +162,7 @@ class Chef @couchdb_id = nil @couchdb = couchdb || Chef::CouchDB.new - @run_state = { - :template_cache => Hash.new, - :seen_recipes => Hash.new, - :seen_attributes => Hash.new - } - # TODO: 5/20/2010 need this here as long as other objects try to access - # the cookbook collection via Node, otherwise get NoMethodError on nil. - @cookbook_collection = CookbookCollection.new + @run_state = {} end def couchdb_id=(value) @@ -189,24 +179,6 @@ class Chef Chef::REST.new(Chef::Config[:chef_server_url]) end - # Find a recipe for this Chef::Node by fqdn. Will search first for - # Chef::Config["node_path"]/fqdn.rb, then hostname.rb, then default.rb. - # - # Returns a new Chef::Node object. - # - # Raises an ArgumentError if it cannot find the node. - def find_file(fqdn) - host_parts = fqdn.split(".") - hostname = host_parts[0] - - [fqdn, hostname, "default"].each { |fname| - node_file = File.join(Chef::Config[:node_path], "#{fname.to_s}.rb") - return self.from_file(node_file) if File.exists?(node_file) - } - - raise ArgumentError, "Cannot find a node matching #{fqdn}, not even with default.rb!" - end - # Set the name of this Node, or return the current name. def name(arg=nil) if arg != nil @@ -351,12 +323,11 @@ class Chef # # First, the run list is consulted to see whether the recipe is # explicitly included. If it's not there, it looks in - # run_state[:seen_recipes], which is populated by include_recipe - # statements in the DSL (and thus would not be in the run list). + # `node[:recipes]`, which is populated when the run_list is expanded # # NOTE: It's used by cookbook authors def recipe?(recipe_name) - run_list.include?(recipe_name) || run_state[:seen_recipes].include?(recipe_name) + run_list.include?(recipe_name) || self[recipes].include?(recipe_name) end # Returns true if this Node expects a given role, false if not. @@ -652,32 +623,5 @@ class Chef "node[#{name}]" end - # Load all attribute files for all cookbooks associated with this - # node. - def load_attributes - cookbook_collection.values.each do |cookbook| - cookbook.segment_filenames(:attributes).each do |segment_filename| - Chef::Log.debug("Node #{name} loading cookbook #{cookbook.name}'s attribute file #{segment_filename}") - self.from_file(segment_filename) - end - end - end - - # Used by DSL. - # Loads the attribute file specified by the short name of the - # file, e.g., loads specified cookbook's - # "attributes/mailservers.rb" - # if passed - # "mailservers" - def load_attribute_by_short_filename(name, src_cookbook_name) - src_cookbook = cookbook_collection[src_cookbook_name] - raise Chef::Exceptions::CookbookNotFound, "could not find cookbook #{src_cookbook_name} while loading attribute #{name}" unless src_cookbook - - attribute_filename = src_cookbook.attribute_filenames_by_short_filename[name] - raise Chef::Exceptions::AttributeNotFound, "could not find filename for attribute #{name} in cookbook #{src_cookbook_name}" unless attribute_filename - - self.from_file(attribute_filename) - self - end end end diff --git a/chef/lib/chef/provider.rb b/chef/lib/chef/provider.rb index d392c299c1..7bbcc3b915 100644 --- a/chef/lib/chef/provider.rb +++ b/chef/lib/chef/provider.rb @@ -19,12 +19,12 @@ require 'chef/mixin/from_file' require 'chef/mixin/convert_to_class_name' -require 'chef/mixin/recipe_definition_dsl_core' +require 'chef/dsl/recipe' require 'chef/mixin/enforce_ownership_and_permissions' require 'chef/mixin/why_run' class Chef class Provider - include Chef::Mixin::RecipeDefinitionDSLCore + include Chef::DSL::Recipe include Chef::Mixin::WhyRun include Chef::Mixin::EnforceOwnershipAndPermissions @@ -192,7 +192,13 @@ class Chef new_provider_class = Class.new self do |cls| - include Chef::Mixin::RecipeDefinitionDSLCore + include Chef::DSL::Recipe + + # These were previously provided by Chef::Mixin::RecipeDefinitionDSLCore. + # They are not included by its replacment, Chef::DSL::Recipe, but + # they may be used in existing LWRPs. + include Chef::DSL::PlatformIntrospection + include Chef::DSL::DataQuery def load_current_resource # silence Chef::Exceptions::Override exception diff --git a/chef/lib/chef/provider/deploy.rb b/chef/lib/chef/provider/deploy.rb index 2920917d11..60c626ab08 100644 --- a/chef/lib/chef/provider/deploy.rb +++ b/chef/lib/chef/provider/deploy.rb @@ -20,12 +20,13 @@ require "chef/mixin/command" require "chef/mixin/from_file" require "chef/provider/git" require "chef/provider/subversion" +require 'chef/dsl/recipe' class Chef class Provider class Deploy < Chef::Provider - include Chef::Mixin::RecipeDefinitionDSLCore + include Chef::DSL::Recipe include Chef::Mixin::FromFile include Chef::Mixin::Command diff --git a/chef/lib/chef/recipe.rb b/chef/lib/chef/recipe.rb index 258a56e715..aca35db049 100644 --- a/chef/lib/chef/recipe.rb +++ b/chef/lib/chef/recipe.rb @@ -18,10 +18,12 @@ # -require 'chef/mixin/recipe_definition_dsl_core' +require 'chef/dsl/recipe' +require 'chef/dsl/data_query' +require 'chef/dsl/platform_introspection' +require 'chef/dsl/include_recipe' + require 'chef/mixin/from_file' -require 'chef/mixin/language' -require 'chef/mixin/language_include_recipe' require 'chef/mixin/deprecation' @@ -30,10 +32,12 @@ class Chef # A Recipe object is the context in which Chef recipes are evaluated. class Recipe + include Chef::DSL::DataQuery + include Chef::DSL::PlatformIntrospection + include Chef::DSL::IncludeRecipe + include Chef::DSL::Recipe + include Chef::Mixin::FromFile - include Chef::Mixin::Language - include Chef::Mixin::LanguageIncludeRecipe - include Chef::Mixin::RecipeDefinitionDSLCore include Chef::Mixin::Deprecation attr_accessor :cookbook_name, :recipe_name, :recipe, :params, :run_context diff --git a/chef/lib/chef/resource.rb b/chef/lib/chef/resource.rb index 536f75ca88..9a1b983360 100644 --- a/chef/lib/chef/resource.rb +++ b/chef/lib/chef/resource.rb @@ -19,7 +19,7 @@ require 'chef/mixin/params_validate' require 'chef/mixin/check_helper' -require 'chef/mixin/language' +require 'chef/dsl/platform_introspection' require 'chef/mixin/convert_to_class_name' require 'chef/resource/conditional' require 'chef/resource_collection' @@ -121,7 +121,7 @@ F include Chef::Mixin::CheckHelper include Chef::Mixin::ParamsValidate - include Chef::Mixin::Language + include Chef::DSL::PlatformIntrospection include Chef::Mixin::ConvertToClassName include Chef::Mixin::Deprecation diff --git a/chef/lib/chef/run_context.rb b/chef/lib/chef/run_context.rb index 8805800b5a..625a993941 100644 --- a/chef/lib/chef/run_context.rb +++ b/chef/lib/chef/run_context.rb @@ -21,16 +21,12 @@ require 'chef/resource_collection' require 'chef/node' require 'chef/role' require 'chef/log' -require 'chef/mixin/language_include_recipe' class Chef # == Chef::RunContext # Value object that loads and tracks the context of a Chef run class RunContext - # Used to load the node's recipes after expanding its run list - include Chef::Mixin::LanguageIncludeRecipe - attr_reader :node, :cookbook_collection, :definitions # Needs to be settable so deploy can run a resource_collection independent @@ -39,6 +35,9 @@ class Chef 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. # @@ -51,11 +50,11 @@ class Chef @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 - # TODO: 5/18/2010 cw/timh - See note on Chef::Node's - # cookbook_collection attr_accessor - node.cookbook_collection = cookbook_collection + @node.run_context = self end def load(run_list_expansion) @@ -74,8 +73,6 @@ class Chef @events.recipe_load_start(run_list_expansion.recipes.size) run_list_expansion.recipes.each do |recipe| begin - # TODO: timh/cw, 5-14-2010: It's distasteful to be including - # the DSL in a class outside the context of the DSL include_recipe(recipe) rescue Chef::Exceptions::RecipeNotFound => e @events.recipe_not_found(e) @@ -95,6 +92,16 @@ class Chef 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) @@ -129,8 +136,54 @@ class Chef 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)) @@ -189,7 +242,8 @@ class Chef foreach_cookbook_load_segment(:attributes) do |cookbook_name, filename| begin Chef::Log.debug("Node #{@node.name} loading cookbook #{cookbook_name}'s attribute file #{filename}") - @node.from_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 diff --git a/chef/lib/chef/shell/ext.rb b/chef/lib/chef/shell/ext.rb index 12d424e6d7..30d841e4cd 100644 --- a/chef/lib/chef/shell/ext.rb +++ b/chef/lib/chef/shell/ext.rb @@ -19,6 +19,7 @@ require 'tempfile' require 'chef/recipe' require 'fileutils' +require 'chef/dsl/platform_introspection' require 'chef/version' require 'chef/shell/shell_session' require 'chef/shell/model_wrapper' @@ -559,7 +560,8 @@ E obj.instance_eval(&MainContextExtensions) obj.instance_eval(&RESTApiExtensions) obj.extend(FileUtils) - obj.extend(Chef::Mixin::Language) + obj.extend(Chef::DSL::PlatformIntrospection) + obj.extend(Chef::DSL::DataQuery) end def self.extend_context_node(node_obj) diff --git a/chef/spec/spec_helper.rb b/chef/spec/spec_helper.rb index 2e0bca0b0c..1d101022ac 100644 --- a/chef/spec/spec_helper.rb +++ b/chef/spec/spec_helper.rb @@ -38,6 +38,7 @@ require 'chef' require 'chef/knife' Chef::Knife.load_commands require 'chef/mixins' +require 'chef/dsl' require 'chef/application' require 'chef/applications' diff --git a/chef/spec/unit/cookbook_spec.rb b/chef/spec/unit/cookbook_spec.rb index e494e81124..c28a5c7a2a 100644 --- a/chef/spec/unit/cookbook_spec.rb +++ b/chef/spec/unit/cookbook_spec.rb @@ -68,20 +68,6 @@ describe Chef::CookbookVersion do @cookbook.preferred_filename(@node, :files, 'a-filename', 'the-checksum').should be_nil end -# TODO: timh, cw: 5/20/2010: removed CookbookVersion.recipe? as it's not used; see cookbook.rb -# it "should allow you to test for a recipe with recipe?" do -# @cookbook.recipe_filenames = [ "one", "two" ] -# @cookbook.recipe?("one").should eql(true) -# @cookbook.recipe?("shanghai").should eql(false) -# end - -# TODO: timh, cw: 5/20/2010: removed CookbookVersion.recipe? as it's not used; see cookbook.rb -# it "should allow you to test for a recipe? with a fq recipe name" do -# @cookbook.recipe_filenames = [ "one", "two" ] -# @cookbook.recipe?("openldap::one").should eql(true) -# @cookbook.recipe?("shanghai::city").should eql(false) -# end - it "should allow you to include a fully-qualified recipe using the DSL" do # DSL method include_recipe allows multiple arguments, so extract the first recipe = @run_context.include_recipe("openldap::gigantor").first @@ -95,9 +81,4 @@ describe Chef::CookbookVersion do lambda { @cookbook.load_recipe("doesnt_exist", @node) }.should raise_error(ArgumentError) end - it "should allow you to load an attribute file by name via load_attribute" do - @node.include_attribute("openldap::smokey") - @node.smokey.should == "robinson" - end - end diff --git a/chef/spec/unit/dsl/data_query_spec.rb b/chef/spec/unit/dsl/data_query_spec.rb new file mode 100644 index 0000000000..8960ad9957 --- /dev/null +++ b/chef/spec/unit/dsl/data_query_spec.rb @@ -0,0 +1,66 @@ +# +# Author:: Seth Falcon (<seth@opscode.com>) +# Copyright:: Copyright (c) 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 'spec_helper' +require 'chef/dsl/data_query' + +class DataQueryDSLTester + include Chef::DSL::DataQuery +end + +describe Chef::DSL::DataQuery do + before(:each) do + @language = DataQueryDSLTester.new + @node = Hash.new + @language.stub!(:node).and_return(@node) + end + + describe "when loading data bags and items" do + it "lists the items in a data bag" do + Chef::DataBag.should_receive(:load).with("bag_name").and_return("item_1" => "http://url_for/item_1", "item_2" => "http://url_for/item_2") + @language.data_bag("bag_name").sort.should == %w[item_1 item_2] + end + + it "validates the name of the data bag you're trying to load" do + lambda {@language.data_bag("!# %^&& ")}.should raise_error(Chef::Exceptions::InvalidDataBagName) + end + + it "fetches a data bag item" do + @item = Chef::DataBagItem.new + @item.data_bag("bag_name") + @item.raw_data = {"id" => "item_name", "FUU" => "FUU"} + Chef::DataBagItem.should_receive(:load).with("bag_name", "item_name").and_return(@item) + @language.data_bag_item("bag_name", "item_name").should == @item + end + + it "validates the name of the data bag you're trying to load an item from" do + lambda {@language.data_bag_item(" %%^& ", "item_name")}.should raise_error(Chef::Exceptions::InvalidDataBagName) + end + + it "validates the id of the data bag item you're trying to load" do + lambda {@language.data_bag_item("bag_name", " 987 (*&()")}.should raise_error(Chef::Exceptions::InvalidDataBagItemID) + end + + it "validates that the id of the data bag item is not nil" do + lambda {@language.data_bag_item("bag_name", nil)}.should raise_error(Chef::Exceptions::InvalidDataBagItemID) + end + + end + +end + diff --git a/chef/spec/unit/mixin/language_spec.rb b/chef/spec/unit/dsl/platfrom_introspection_spec.rb index 42f6b6bbe3..e6cc7ad9ff 100644 --- a/chef/spec/unit/mixin/language_spec.rb +++ b/chef/spec/unit/dsl/platfrom_introspection_spec.rb @@ -17,13 +17,13 @@ # require 'spec_helper' -require 'chef/mixin/language' +require 'chef/dsl/platform_introspection' class LanguageTester - include Chef::Mixin::Language + include Chef::DSL::PlatformIntrospection end -describe Chef::Mixin::Language do +describe Chef::DSL::PlatformIntrospection do before(:each) do @language = LanguageTester.new @node = Hash.new @@ -35,13 +35,13 @@ describe Chef::Mixin::Language do "1.2.3" => "#{x}-1.2.3" } end - @platform_hash["debian"] = {["5", "6"] => "debian-5/6", "default" => "debian"} + @platform_hash["debian"] = {["5", "6"] => "debian-5/6", "default" => "debian"} @platform_hash["default"] = "default" - @platform_family_hash = { - "debian" => "debian value", - [:rhel, :fedora] => "redhatty value", - "suse" => "suse value", + @platform_family_hash = { + "debian" => "debian value", + [:rhel, :fedora] => "redhatty value", + "suse" => "suse value", :default => "default value" } end @@ -51,28 +51,28 @@ describe Chef::Mixin::Language do @language.value_for_platform(@platform_hash).should == "default" end - it "returns a default value when there is no known platform family" do - @language.value_for_platform_family(@platform_family_hash).should == "default value" + it "returns a default value when there is no known platform family" do + @language.value_for_platform_family(@platform_family_hash).should == "default value" end - + it "returns a default value when the current platform doesn't match" do @node[:platform] = "not-a-known-platform" @language.value_for_platform(@platform_hash).should == "default" end - it "returns a default value when current platform_family doesn't match" do + it "returns a default value when current platform_family doesn't match" do @node[:platform_family] = "ultra-derived-linux" - @language.value_for_platform_family(@platform_family_hash).should == "default value" + @language.value_for_platform_family(@platform_family_hash).should == "default value" end - + it "returns a value based on the current platform" do @node[:platform] = "openbsd" @language.value_for_platform(@platform_hash).should == "openbsd" end - it "returns a value based on the current platform family" do + it "returns a value based on the current platform family" do @node[:platform_family] = "debian" - @language.value_for_platform_family(@platform_family_hash).should == "debian value" + @language.value_for_platform_family(@platform_family_hash).should == "debian value" end it "returns a version-specific value based on the current platform" do @@ -101,52 +101,52 @@ describe Chef::Mixin::Language do end end - describe "when checking platform?" do + describe "when checking platform?" do before(:each) do @language = LanguageTester.new @node = Hash.new @language.stub!(:node).and_return(@node) end - - it "returns true if the node is a provided platform and platforms are provided as symbols" do + + it "returns true if the node is a provided platform and platforms are provided as symbols" do @node[:platform] = 'ubuntu' @language.platform?([:redhat, :ubuntu]).should == true end - - it "returns true if the node is a provided platform and platforms are provided as strings" do + + it "returns true if the node is a provided platform and platforms are provided as strings" do @node[:platform] = 'ubuntu' @language.platform?(["redhat", "ubuntu"]).should == true end - - it "returns false if the node is not of the provided platforms" do + + it "returns false if the node is not of the provided platforms" do @node[:platform] = 'ubuntu' @language.platform?(:splatlinux).should == false end end - - describe "when checking platform_family?" do + + describe "when checking platform_family?" do before(:each) do @language = LanguageTester.new @node = Hash.new @language.stub!(:node).and_return(@node) end - - it "returns true if the node is in a provided platform family and families are provided as symbols" do + + it "returns true if the node is in a provided platform family and families are provided as symbols" do @node[:platform_family] = 'debian' @language.platform_family?([:rhel, :debian]).should == true end - - it "returns true if the node is a provided platform and platforms are provided as strings" do + + it "returns true if the node is a provided platform and platforms are provided as strings" do @node[:platform_family] = 'rhel' @language.platform_family?(["rhel", "debian"]).should == true end - - it "returns false if the node is not of the provided platforms" do + + it "returns false if the node is not of the provided platforms" do @node[:platform_family] = 'suse' @language.platform_family?(:splatlinux).should == false end - - it "returns false if the node is not of the provided platforms and platform_family is not set" do + + it "returns false if the node is not of the provided platforms and platform_family is not set" do @language.platform_family?(:splatlinux).should == false end @@ -173,44 +173,12 @@ describe Chef::Mixin::Language do @node[:platform] = "debian" @node[:platform_version] = '4.0' @language.value_for_platform(@platform_hash).should == [:restart, :reload] - end - end - - describe "when loading data bags and items" do - it "lists the items in a data bag" do - Chef::DataBag.should_receive(:load).with("bag_name").and_return("item_1" => "http://url_for/item_1", "item_2" => "http://url_for/item_2") - @language.data_bag("bag_name").sort.should == %w[item_1 item_2] - end - - it "validates the name of the data bag you're trying to load" do - lambda {@language.data_bag("!# %^&& ")}.should raise_error(Chef::Exceptions::InvalidDataBagName) end - - it "fetches a data bag item" do - @item = Chef::DataBagItem.new - @item.data_bag("bag_name") - @item.raw_data = {"id" => "item_name", "FUU" => "FUU"} - Chef::DataBagItem.should_receive(:load).with("bag_name", "item_name").and_return(@item) - @language.data_bag_item("bag_name", "item_name").should == @item - end - - it "validates the name of the data bag you're trying to load an item from" do - lambda {@language.data_bag_item(" %%^& ", "item_name")}.should raise_error(Chef::Exceptions::InvalidDataBagName) - end - - it "validates the id of the data bag item you're trying to load" do - lambda {@language.data_bag_item("bag_name", " 987 (*&()")}.should raise_error(Chef::Exceptions::InvalidDataBagItemID) - end - - it "validates that the id of the data bag item is not nil" do - lambda {@language.data_bag_item("bag_name", nil)}.should raise_error(Chef::Exceptions::InvalidDataBagItemID) - end - end end -describe Chef::Mixin::Language::PlatformDependentValue do +describe Chef::DSL::PlatformIntrospection::PlatformDependentValue do before do platform_hash = { :openbsd => {:default => 'free, functional, secure'}, @@ -218,7 +186,7 @@ describe Chef::Mixin::Language::PlatformDependentValue do :ubuntu => {'10.04' => 'using upstart more', :default => 'using init more'}, :default => 'bork da bork' } - @platform_specific_value = Chef::Mixin::Language::PlatformDependentValue.new(platform_hash) + @platform_specific_value = Chef::DSL::PlatformIntrospection::PlatformDependentValue.new(platform_hash) end it "returns the default value when the platform doesn't match" do @@ -246,29 +214,29 @@ describe Chef::Mixin::Language::PlatformDependentValue do it "returns nil if there is no default and no platforms match" do # this matches the behavior in the original implementation. # whether or not it's correct is another matter. - platform_specific_value = Chef::Mixin::Language::PlatformDependentValue.new({}) + platform_specific_value = Chef::DSL::PlatformIntrospection::PlatformDependentValue.new({}) platform_specific_value.value_for_node(:platform => 'foo').should be_nil end it "raises an argument error if the platform hash is not correctly structured" do bad_hash = {:ubuntu => :foo} # should be :ubuntu => {:default => 'foo'} - lambda {Chef::Mixin::Language::PlatformDependentValue.new(bad_hash)}.should raise_error(ArgumentError) + lambda {Chef::DSL::PlatformIntrospection::PlatformDependentValue.new(bad_hash)}.should raise_error(ArgumentError) end end -describe Chef::Mixin::Language::PlatformFamilyDependentValue do +describe Chef::DSL::PlatformIntrospection::PlatformFamilyDependentValue do before do - @array_values = [:stop, :start, :reload] + @array_values = [:stop, :start, :reload] - @platform_family_hash = { - "debian" => "debian value", - [:rhel, "fedora"] => "redhatty value", - "suse" => @array_values, + @platform_family_hash = { + "debian" => "debian value", + [:rhel, "fedora"] => "redhatty value", + "suse" => @array_values, :gentoo => "gentoo value", :default => "default value" } - - @platform_family_value = Chef::Mixin::Language::PlatformFamilyDependentValue.new(@platform_family_hash) + + @platform_family_value = Chef::DSL::PlatformIntrospection::PlatformFamilyDependentValue.new(@platform_family_hash) end it "returns the default value when the platform family doesn't match" do @@ -277,28 +245,27 @@ describe Chef::Mixin::Language::PlatformFamilyDependentValue do it "returns a value for the platform family when it was set as a string but fetched as a symbol" do - @platform_family_value.value_for_node(:platform_family => :debian).should == "debian value" + @platform_family_value.value_for_node(:platform_family => :debian).should == "debian value" end - - + it "returns a value for the platform family when it was set as a symbol but fetched as a string" do - @platform_family_value.value_for_node(:platform_family => "gentoo").should == "gentoo value" + @platform_family_value.value_for_node(:platform_family => "gentoo").should == "gentoo value" end - - it "returns an array value stored for a platform family" do + + it "returns an array value stored for a platform family" do @platform_family_value.value_for_node(:platform_family => "suse").should == @array_values end it "returns a value for the platform family when it was set within an array hash key as a symbol" do - @platform_family_value.value_for_node(:platform_family => :rhel).should == "redhatty value" + @platform_family_value.value_for_node(:platform_family => :rhel).should == "redhatty value" end - + it "returns a value for the platform family when it was set within an array hash key as a string" do - @platform_family_value.value_for_node(:platform_family => "fedora").should == "redhatty value" + @platform_family_value.value_for_node(:platform_family => "fedora").should == "redhatty value" end - + it "returns nil if there is no default and no platforms match" do - platform_specific_value = Chef::Mixin::Language::PlatformFamilyDependentValue.new({}) + platform_specific_value = Chef::DSL::PlatformIntrospection::PlatformFamilyDependentValue.new({}) platform_specific_value.value_for_node(:platform_family => 'foo').should be_nil end diff --git a/chef/spec/unit/formatters/error_inspectors/resource_failure_inspector_spec.rb b/chef/spec/unit/formatters/error_inspectors/resource_failure_inspector_spec.rb index 277e710ddf..54a57d8077 100644 --- a/chef/spec/unit/formatters/error_inspectors/resource_failure_inspector_spec.rb +++ b/chef/spec/unit/formatters/error_inspectors/resource_failure_inspector_spec.rb @@ -19,7 +19,7 @@ require 'spec_helper' describe Chef::Formatters::ErrorInspectors::ResourceFailureInspector do - include Chef::Mixin::RecipeDefinitionDSLCore + include Chef::DSL::Recipe def run_context node = Chef::Node.new diff --git a/chef/spec/unit/node_spec.rb b/chef/spec/unit/node_spec.rb index 2be154c5b3..4577d5098b 100644 --- a/chef/spec/unit/node_spec.rb +++ b/chef/spec/unit/node_spec.rb @@ -21,7 +21,6 @@ require 'ostruct' describe Chef::Node do before(:each) do - Chef::Config.node_path(File.expand_path(File.join(CHEF_SPEC_DATA, "nodes"))) @node = Chef::Node.new() end @@ -63,12 +62,9 @@ describe Chef::Node do end describe "run_state" do - it "should have a template_cache hash" do - @node.run_state[:template_cache].should be_a_kind_of(Hash) - end - - it "should have a seen_recipes hash" do - @node.run_state[:seen_recipes].should be_a_kind_of(Hash) + it "is an empty hash" do + @node.run_state.should respond_to(:keys) + @node.run_state.should be_empty end end @@ -123,18 +119,6 @@ describe Chef::Node do end describe "attributes" do - it "should be loaded from the node's cookbooks" do - @cookbook_repo = File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "cookbooks")) - cl = Chef::CookbookLoader.new(@cookbook_repo) - cl.load_cookbooks - @node.cookbook_collection = Chef::CookbookCollection.new(cl) - @node.load_attributes - @node.ldap_server.should eql("ops1prod") - @node.ldap_basedn.should eql("dc=hjksolutions,dc=com") - @node.ldap_replication_password.should eql("forsure") - @node.smokey.should eql("robinson") - end - it "should have attributes" do @node.attribute.should be_a_kind_of(Hash) end @@ -440,33 +424,34 @@ describe Chef::Node do end end - # TODO: timh, cw: 2010-5-19: Node.recipe? deprecated. See node.rb - # describe "recipes" do - # it "should have a RunList of recipes that should be applied" do - # @node.recipes.should be_a_kind_of(Chef::RunList) - # end - # - # it "should allow you to query whether or not it has a recipe applied with recipe?" do - # @node.recipes << "sunrise" - # @node.recipe?("sunrise").should eql(true) - # @node.recipe?("not at home").should eql(false) - # end - # - # it "should allow you to query whether or not a recipe has been applied, even if it was included" do - # @node.run_state[:seen_recipes]["snakes"] = true - # @node.recipe?("snakes").should eql(true) - # end - # - # it "should return false if a recipe has not been seen" do - # @node.recipe?("snakes").should eql(false) - # end - # - # it "should allow you to set recipes with arguments" do - # @node.recipes "one", "two" - # @node.recipe?("one").should eql(true) - # @node.recipe?("two").should eql(true) - # end - # end + describe "when evaluating attributes files" do + before do + @node = Chef::Node.new + + @cookbook_repo = File.expand_path(File.join(CHEF_SPEC_DATA, "cookbooks")) + @cookbook_loader = Chef::CookbookLoader.new(@cookbook_repo) + @cookbook_loader.load_cookbooks + + @cookbook_collection = Chef::CookbookCollection.new(@cookbook_loader.cookbooks_by_name) + + @events = Chef::EventDispatch::Dispatcher.new + @run_context = Chef::RunContext.new(@node, @cookbook_collection, @events) + + @node.include_attribute("openldap::default") + @node.include_attribute("openldap::smokey") + end + + it "sets attributes from the files" do + @node.ldap_server.should eql("ops1prod") + @node.ldap_basedn.should eql("dc=hjksolutions,dc=com") + @node.ldap_replication_password.should eql("forsure") + @node.smokey.should eql("robinson") + end + + it "gives a sensible error when attempting to load a missing attributes file" do + lambda { @node.include_attribute("nope-this::doesnt-exist") }.should raise_error(Chef::Exceptions::CookbookNotFound) + end + end describe "roles" do it "should allow you to query whether or not it has a recipe applied with role?" do @@ -514,37 +499,6 @@ describe Chef::Node do end end - describe "find_file" do - it "should load a node from a file by fqdn" do - @node.find_file("test.example.com") - @node.name.should == "test.example.com" - @node.chef_environment.should == "dev" - end - - it "should load a node from a file by hostname" do - File.stub!(:exists?).and_return(true) - File.should_receive(:exists?).with(File.join(Chef::Config[:node_path], "test.example.com.rb")).and_return(false) - @node.find_file("test.example.com") - @node.name.should == "test.example.com-short" - end - - it "should load a node from the default file" do - File.stub!(:exists?).and_return(true) - File.should_receive(:exists?).with(File.join(Chef::Config[:node_path], "test.example.com.rb")).and_return(false) - File.should_receive(:exists?).with(File.join(Chef::Config[:node_path], "test.rb")).and_return(false) - @node.find_file("test.example.com") - @node.name.should == "test.example.com-default" - end - - it "should raise an ArgumentError if it cannot find any node file at all" do - File.stub!(:exists?).and_return(true) - File.should_receive(:exists?).with(File.join(Chef::Config[:node_path], "test.example.com.rb")).and_return(false) - File.should_receive(:exists?).with(File.join(Chef::Config[:node_path], "test.rb")).and_return(false) - File.should_receive(:exists?).with(File.join(Chef::Config[:node_path], "default.rb")).and_return(false) - lambda { @node.find_file("test.example.com") }.should raise_error(ArgumentError) - end - end - describe "update_from!" do before(:each) do @node.name("orig") @@ -607,7 +561,7 @@ describe Chef::Node do describe "json" do it "should serialize itself as json", :json => true do - @node.find_file("test.example.com") + @node.from_file(File.expand_path("nodes/test.example.com.rb", CHEF_SPEC_DATA)) json = Chef::JSONCompat.to_json(@node) json.should =~ /json_class/ json.should =~ /name/ @@ -628,7 +582,7 @@ describe Chef::Node do end it "should deserialize itself from json", :json => true do - @node.find_file("test.example.com") + @node.from_file(File.expand_path("nodes/test.example.com.rb", CHEF_SPEC_DATA)) json = Chef::JSONCompat.to_json(@node) serialized_node = Chef::JSONCompat.from_json(json) serialized_node.should be_a_kind_of(Chef::Node) diff --git a/chef/spec/unit/provider/package/apt_spec.rb b/chef/spec/unit/provider/package/apt_spec.rb index e40a640837..06ada5189e 100644 --- a/chef/spec/unit/provider/package/apt_spec.rb +++ b/chef/spec/unit/provider/package/apt_spec.rb @@ -22,7 +22,6 @@ require 'ostruct' describe Chef::Provider::Package::Apt do before(:each) do @node = Chef::Node.new - @node.cookbook_collection = {} @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::Package.new("irssi", @run_context) diff --git a/chef/spec/unit/provider/package/ips_spec.rb b/chef/spec/unit/provider/package/ips_spec.rb index dad880a93b..641a527012 100644 --- a/chef/spec/unit/provider/package/ips_spec.rb +++ b/chef/spec/unit/provider/package/ips_spec.rb @@ -24,7 +24,6 @@ require 'ostruct' describe Chef::Provider::Package::Ips do before(:each) do @node = Chef::Node.new - @node.cookbook_collection = {} @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::Package.new("crypto/gnupg", @run_context) diff --git a/chef/spec/unit/recipe_spec.rb b/chef/spec/unit/recipe_spec.rb index 797e566190..4615bcb4d4 100644 --- a/chef/spec/unit/recipe_spec.rb +++ b/chef/spec/unit/recipe_spec.rb @@ -206,9 +206,9 @@ describe Chef::Recipe do res.pretty_kitty.should eql(true) end - it "should store that it has seen a recipe in node.run_state[:seen_recipes]" do + it "should store that it has seen a recipe in the run_context" do @run_context.include_recipe "openldap" - @node.run_state[:seen_recipes].should have_key("openldap") + @run_context.loaded_recipe?("openldap").should be_true end it "should not include the same recipe twice" do diff --git a/chef/spec/unit/run_context_spec.rb b/chef/spec/unit/run_context_spec.rb index 7bdcc60de0..51fa0e81f9 100644 --- a/chef/spec/unit/run_context_spec.rb +++ b/chef/spec/unit/run_context_spec.rb @@ -24,13 +24,12 @@ Chef::Log.level = :debug describe Chef::RunContext do before(:each) do - Chef::Config.node_path(File.expand_path(File.join(CHEF_SPEC_DATA, "run_context", "nodes"))) @chef_repo_path = File.expand_path(File.join(CHEF_SPEC_DATA, "run_context", "cookbooks")) cl = Chef::CookbookLoader.new(@chef_repo_path) cl.load_cookbooks @cookbook_collection = Chef::CookbookCollection.new(cl) @node = Chef::Node.new - @node.find_file("run_context") + @node.run_list << "test" << "test::one" << "test::two" @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, @cookbook_collection, @events) end @@ -55,12 +54,25 @@ describe Chef::RunContext do end it "should load all the recipes specified for this node" do - @run_context.resource_collection[0].to_s.should == "cat[einstein]" + @run_context.resource_collection[0].to_s.should == "cat[einstein]" @run_context.resource_collection[1].to_s.should == "cat[loulou]" @run_context.resource_collection[2].to_s.should == "cat[birthday]" @run_context.resource_collection[3].to_s.should == "cat[peanut]" @run_context.resource_collection[4].to_s.should == "cat[fat peanut]" end + + it "loads all the attribute files in the cookbook collection" do + @run_context.loaded_fully_qualified_attribute?("test", "george").should be_true + @node[:george].should == "washington" + end + + it "registers attributes files as loaded so they won't be reloaded" do + # This test unfortunately is pretty tightly intertwined with the + # implementation of how nodes load attribute files, but is the only + # convenient way to test this behavior. + @node.should_not_receive(:from_file) + @node.include_attribute("test::george") + end end end |