From 13ac6455d30e2964842fd56f370e4c555320cb81 Mon Sep 17 00:00:00 2001 From: Adam Jacob Date: Fri, 27 Nov 2009 16:39:16 -0800 Subject: Attribute inclusion mixin, and features --- Rakefile | 3 + chef/lib/chef/compile.rb | 4 +- chef/lib/chef/cookbook.rb | 70 +++++++++++++++------- chef/lib/chef/mixin/language_include.rb | 54 ----------------- chef/lib/chef/mixin/language_include_attribute.rb | 57 ++++++++++++++++++ chef/lib/chef/mixin/language_include_recipe.rb | 53 ++++++++++++++++ chef/lib/chef/node.rb | 5 +- chef/lib/chef/recipe.rb | 4 +- chef/spec/unit/cookbook_spec.rb | 14 +++-- cucumber.yml | 1 + .../data/cookbooks/attribute_include/README.rdoc | 8 +++ .../cookbooks/attribute_include/attributes/a.rb | 4 ++ .../cookbooks/attribute_include/attributes/b.rb | 2 + .../data/cookbooks/attribute_include/metadata.rb | 6 ++ .../cookbooks/attribute_include/recipes/default.rb | 23 +++++++ .../cookbooks/recipe_include/recipes/default.rb | 2 +- features/language/attribute_inclusion.feature | 13 ++++ 17 files changed, 236 insertions(+), 87 deletions(-) delete mode 100644 chef/lib/chef/mixin/language_include.rb create mode 100644 chef/lib/chef/mixin/language_include_attribute.rb create mode 100644 chef/lib/chef/mixin/language_include_recipe.rb create mode 100644 features/data/cookbooks/attribute_include/README.rdoc create mode 100644 features/data/cookbooks/attribute_include/attributes/a.rb create mode 100644 features/data/cookbooks/attribute_include/attributes/b.rb create mode 100644 features/data/cookbooks/attribute_include/metadata.rb create mode 100644 features/data/cookbooks/attribute_include/recipes/default.rb create mode 100644 features/language/attribute_inclusion.feature diff --git a/Rakefile b/Rakefile index a51b62586c..72b45f408a 100644 --- a/Rakefile +++ b/Rakefile @@ -367,6 +367,9 @@ namespace :features do Cucumber::Rake::Task.new(:recipe_include) do |t| t.profile = "recipe_inclusion" end + Cucumber::Rake::Task.new(:attribute_include) do |t| + t.profile = "attribute_inclusion" + end end Cucumber::Rake::Task.new(:lwrp) do |t| diff --git a/chef/lib/chef/compile.rb b/chef/lib/chef/compile.rb index dcdc4b7160..a44fa8792f 100644 --- a/chef/lib/chef/compile.rb +++ b/chef/lib/chef/compile.rb @@ -22,14 +22,14 @@ require 'chef/node' require 'chef/role' require 'chef/log' require 'chef/mixin/deep_merge' -require 'chef/mixin/language_include' +require 'chef/mixin/language_include_recipe' class Chef class Compile attr_accessor :node, :cookbook_loader, :collection, :definitions - include Chef::Mixin::LanguageInclude + include Chef::Mixin::LanguageIncludeRecipe # Creates a new Chef::Compile object and populates its fields. This object gets # used by the Chef Server to generate a fully compiled recipe list for a node. diff --git a/chef/lib/chef/cookbook.rb b/chef/lib/chef/cookbook.rb index 0d92d02c4f..6bee31021b 100644 --- a/chef/lib/chef/cookbook.rb +++ b/chef/lib/chef/cookbook.rb @@ -27,9 +27,9 @@ class Chef class Cookbook include Chef::Mixin::ConvertToClassName - attr_accessor :attribute_files, :definition_files, :template_files, :remote_files, + attr_accessor :definition_files, :template_files, :remote_files, :lib_files, :resource_files, :provider_files, :name - attr_reader :recipe_files + attr_reader :recipe_files, :attribute_files # Creates a new Chef::Cookbook object. # @@ -38,6 +38,7 @@ class Chef def initialize(name) @name = name @attribute_files = Array.new + @attribute_names = Hash.new @definition_files = Array.new @template_files = Array.new @remote_files = Array.new @@ -71,15 +72,23 @@ class Chef # === Raises # :: If the argument is not a kind_of? def load_attributes(node) - unless node.kind_of?(Chef::Node) - raise ArgumentError, "You must pass a Chef::Node to load_attributes!" - end @attribute_files.each do |file| - Chef::Log.debug("Loading attributes from #{file}") - node.from_file(file) + load_attribute_file(file, node) end node end + + def load_attribute_file(file, node) + Chef::Log.debug("Loading attributes from #{file}") + node.from_file(file) + end + + def load_attribute(name, node) + attr_name = shorten_name(name) + file = @attribute_files[@attribute_names[attr_name]] + load_attribute_file(file, node) + node + end # Loads all the resource definitions in this cookbook. # @@ -119,20 +128,14 @@ class Chef end def recipe_files=(*args) - @recipe_files = args.flatten - @recipe_files.each_index do |i| - file = @recipe_files[i] - case file - when /(.+\/)(.+).rb$/ - @recipe_names[$2] = i - when /(.+).rb$/ - @recipe_names[$1] = i - else - @recipe_names[file] = i - end - end + @recipe_files, @recipe_names = set_with_names(args.flatten) @recipe_files end + + def attribute_files=(*args) + @attribute_files, @attribute_names = set_with_names(args.flatten) + @attribute_files + end def recipe?(name) lookup_name = name @@ -154,9 +157,7 @@ class Chef def load_recipe(name, node, collection=nil, definitions=nil, cookbook_loader=nil) cookbook_name = @name - recipe_name = nil - nmatch = name.match(/^(.+?)::(.+)$/) - recipe_name = nmatch ? nmatch[2] : name + recipe_name = shorten_name(name) unless @recipe_names.has_key?(recipe_name) raise ArgumentError, "Cannot find a recipe matching #{recipe_name} in cookbook #{@name}" @@ -167,6 +168,31 @@ class Chef recipe.from_file(@recipe_files[@recipe_names[recipe_name]]) recipe end + + private + + def shorten_name(name) + short_name = nil + nmatch = name.match(/^(.+?)::(.+)$/) + short_name = nmatch ? nmatch[2] : name + end + + def set_with_names(file_list) + files = file_list + names = Hash.new + files.each_index do |i| + file = files[i] + case file + when /(.+\/)(.+).rb$/ + names[$2] = i + when /(.+).rb$/ + names[$1] = i + else + names[file] = i + end + end + [ files, names ] + end end end diff --git a/chef/lib/chef/mixin/language_include.rb b/chef/lib/chef/mixin/language_include.rb deleted file mode 100644 index ff07f75c59..0000000000 --- a/chef/lib/chef/mixin/language_include.rb +++ /dev/null @@ -1,54 +0,0 @@ -# -# Author:: Adam Jacob () -# Author:: Christopher Walters () -# 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 Mixin - module LanguageInclude - - def include_recipe(*args) - args.flatten.each do |recipe| - if @node.run_state[:seen_recipes].has_key?(recipe) - Chef::Log.debug("I am not loading #{recipe}, because I have already seen it.") - next - end - - Chef::Log.debug("Loading Recipe #{recipe} via include_recipe") - @node.run_state[:seen_recipes][recipe] = true - - rmatch = recipe.match(/(.+?)::(.+)/) - if rmatch - cookbook = @cookbook_loader[rmatch[1]] - cookbook.load_recipe(rmatch[2], @node, @collection, @definitions, @cookbook_loader) - else - cookbook = @cookbook_loader[recipe] - cookbook.load_recipe("default", @node, @collection, @definitions, @cookbook_loader) - end - end - end - - def require_recipe(*args) - include_recipe(*args) - end - - end - end -end - diff --git a/chef/lib/chef/mixin/language_include_attribute.rb b/chef/lib/chef/mixin/language_include_attribute.rb new file mode 100644 index 0000000000..79c5421a40 --- /dev/null +++ b/chef/lib/chef/mixin/language_include_attribute.rb @@ -0,0 +1,57 @@ +# +# Author:: Adam Jacob () +# 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 Mixin + module LanguageIncludeAttribute + + def include_attribute(*args) + if self.kind_of?(Chef::Node) + node = self + else + node = @node + end + + args.flatten.each do |attrib| + if node.run_state[:seen_attributes].has_key?(attrib) + Chef::Log.debug("I am not loading attribute file #{attrib}, because I have already seen it.") + next + end + + Chef::Log.debug("Loading Attribute #{attrib}") + node.run_state[:seen_attributes][recipe] = true + + amatch = attrib.match(/(.+?)::(.+)/) + if amatch + cookbook = @cookbook_loader[amatch[1]] + cookbook.load_attribute(amatch[2], node) + else + cookbook = @cookbook_loader[amatch[1]] + cookbook.load_attribute("default", node) + end + end + true + end + + end + end +end + + diff --git a/chef/lib/chef/mixin/language_include_recipe.rb b/chef/lib/chef/mixin/language_include_recipe.rb new file mode 100644 index 0000000000..6e81862515 --- /dev/null +++ b/chef/lib/chef/mixin/language_include_recipe.rb @@ -0,0 +1,53 @@ +# +# Author:: Adam Jacob () +# 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 Mixin + module LanguageIncludeRecipe + + def include_recipe(*args) + args.flatten.each do |recipe| + if @node.run_state[:seen_recipes].has_key?(recipe) + Chef::Log.debug("I am not loading #{recipe}, because I have already seen it.") + next + end + + Chef::Log.debug("Loading Recipe #{recipe} via include_recipe") + @node.run_state[:seen_recipes][recipe] = true + + rmatch = recipe.match(/(.+?)::(.+)/) + if rmatch + cookbook = @cookbook_loader[rmatch[1]] + cookbook.load_recipe(rmatch[2], @node, @collection, @definitions, @cookbook_loader) + else + cookbook = @cookbook_loader[recipe] + cookbook.load_recipe("default", @node, @collection, @definitions, @cookbook_loader) + end + end + end + + def require_recipe(*args) + include_recipe(*args) + end + + end + end +end + diff --git a/chef/lib/chef/node.rb b/chef/lib/chef/node.rb index 6166555c67..b8bbfe2612 100644 --- a/chef/lib/chef/node.rb +++ b/chef/lib/chef/node.rb @@ -20,6 +20,7 @@ require 'chef/config' require 'chef/mixin/check_helper' require 'chef/mixin/params_validate' require 'chef/mixin/from_file' +require 'chef/mixin/language_include_attribute' require 'chef/couchdb' require 'chef/rest' require 'chef/run_list' @@ -35,6 +36,7 @@ class Chef include Chef::Mixin::CheckHelper include Chef::Mixin::FromFile include Chef::Mixin::ParamsValidate + include Chef::Mixin::LanguageIncludeAttribute DESIGN_DOCUMENT = { "version" => 9, @@ -134,7 +136,8 @@ class Chef @run_state = { :template_cache => Hash.new, - :seen_recipes => Hash.new + :seen_recipes => Hash.new, + :seen_attributes => Hash.new } end diff --git a/chef/lib/chef/recipe.rb b/chef/lib/chef/recipe.rb index 24e1cc4f9c..d9a2362889 100644 --- a/chef/lib/chef/recipe.rb +++ b/chef/lib/chef/recipe.rb @@ -21,7 +21,7 @@ require 'chef/resource' Dir[File.join(File.dirname(__FILE__), 'resource/**/*.rb')].sort.each { |lib| require lib } require 'chef/mixin/from_file' require 'chef/mixin/language' -require 'chef/mixin/language_include' +require 'chef/mixin/language_include_recipe' require 'chef/mixin/recipe_definition_dsl_core' require 'chef/resource_collection' require 'chef/cookbook_loader' @@ -32,7 +32,7 @@ class Chef include Chef::Mixin::FromFile include Chef::Mixin::Language - include Chef::Mixin::LanguageInclude + include Chef::Mixin::LanguageIncludeRecipe include Chef::Mixin::RecipeDefinitionDSLCore attr_accessor :cookbook_name, :recipe_name, :recipe, :node, :collection, diff --git a/chef/spec/unit/cookbook_spec.rb b/chef/spec/unit/cookbook_spec.rb index 650d0dbf0e..9f9d4a24c1 100644 --- a/chef/spec/unit/cookbook_spec.rb +++ b/chef/spec/unit/cookbook_spec.rb @@ -54,10 +54,6 @@ describe Chef::Cookbook do node.smokey.should eql("robinson") end - it "should raise an ArgumentError if you don't pass a node object to load_attributes" do - lambda { @cookbook.load_attributes("snake oil") }.should raise_error(ArgumentError) - end - it "should have a list of definition files" do @cookbook.definition_files.should be_a_kind_of(Array) end @@ -130,5 +126,13 @@ describe Chef::Cookbook do node.name "Julia Child" lambda { @cookbook.load_recipe("smackdown", node) }.should raise_error(ArgumentError) end + + it "should allow you to load an attribute file by name via load_attribute" do + @cookbook.attribute_files = Dir[File.join(COOKBOOK_PATH, "attributes", "**", "*.rb")] + node = Chef::Node.new + node.name "Julia Child" + @cookbook.load_attribute("openldap::smokey", node) + node.smokey.should == "robinson" + end -end \ No newline at end of file +end diff --git a/cucumber.yml b/cucumber.yml index a63ea346ea..43d77cbd5e 100644 --- a/cucumber.yml +++ b/cucumber.yml @@ -41,6 +41,7 @@ provider_package_macports: --tags @macports --format pretty -r features/steps -r language: --tags @language --format pretty -r features/steps -r features/support features client_run_interval: --tags client_run_interval --format pretty -r features/steps -r features/support features recipe_inclusion: --tags recipe_inclusion --format pretty -r features/steps -r features/support features +attribute_inclusion: --tags @attribute_inclusion --format pretty -r features/steps -r features/support features cookbooks: --tags @cookbooks --format pretty -r features/steps -r features/support features provider_remote_file: --tags provider,remote_file --format pretty -r features/steps -r features/support features provider_git: --tags provider,git --format pretty -r features/steps -r features/support features diff --git a/features/data/cookbooks/attribute_include/README.rdoc b/features/data/cookbooks/attribute_include/README.rdoc new file mode 100644 index 0000000000..8d774805b9 --- /dev/null +++ b/features/data/cookbooks/attribute_include/README.rdoc @@ -0,0 +1,8 @@ += DESCRIPTION: + += REQUIREMENTS: + += ATTRIBUTES: + += USAGE: + diff --git a/features/data/cookbooks/attribute_include/attributes/a.rb b/features/data/cookbooks/attribute_include/attributes/a.rb new file mode 100644 index 0000000000..d6edaf4b91 --- /dev/null +++ b/features/data/cookbooks/attribute_include/attributes/a.rb @@ -0,0 +1,4 @@ +include_attribute 'attribute_include::b' +node.set[:mars_volta] = node[:mars_volta_name] +node.set[:mars_volta_is] = node[:mars_volta_will_be] + diff --git a/features/data/cookbooks/attribute_include/attributes/b.rb b/features/data/cookbooks/attribute_include/attributes/b.rb new file mode 100644 index 0000000000..e30b2dc772 --- /dev/null +++ b/features/data/cookbooks/attribute_include/attributes/b.rb @@ -0,0 +1,2 @@ +node.set[:mars_volta_name] = 'mars_volta' +node.set[:mars_volta_will_be] = 'dope' diff --git a/features/data/cookbooks/attribute_include/metadata.rb b/features/data/cookbooks/attribute_include/metadata.rb new file mode 100644 index 0000000000..07c1801d28 --- /dev/null +++ b/features/data/cookbooks/attribute_include/metadata.rb @@ -0,0 +1,6 @@ +maintainer "Opscode" +maintainer_email "do_not_reply@opscode.com" +license "Apache 2.0" +description "Installs/Configures recipe_include" +long_description IO.read(File.join(File.dirname(__FILE__), 'README.rdoc')) +version "0.1" diff --git a/features/data/cookbooks/attribute_include/recipes/default.rb b/features/data/cookbooks/attribute_include/recipes/default.rb new file mode 100644 index 0000000000..37d1612938 --- /dev/null +++ b/features/data/cookbooks/attribute_include/recipes/default.rb @@ -0,0 +1,23 @@ +# +# Cookbook Name:: recipe_include +# Recipe:: second +# +# Copyright 2009, Opscode +# +# 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. +# + +execute "append to #{node[:tmpdir]}/mars_volta" do + command "echo '#{node[:mars_volta]} is #{node[:mavolta_is]}' >> #{node[:tmpdir]}/mars_volta" +end + diff --git a/features/data/cookbooks/recipe_include/recipes/default.rb b/features/data/cookbooks/recipe_include/recipes/default.rb index 8132a1c571..f05c067f4b 100644 --- a/features/data/cookbooks/recipe_include/recipes/default.rb +++ b/features/data/cookbooks/recipe_include/recipes/default.rb @@ -1,5 +1,5 @@ # -# Cookbook Name:: recipe_include +# Cookbook Name:: attribute_include # Recipe:: default # # Copyright 2009, Opscode diff --git a/features/language/attribute_inclusion.feature b/features/language/attribute_inclusion.feature new file mode 100644 index 0000000000..d5e8c992c7 --- /dev/null +++ b/features/language/attribute_inclusion.feature @@ -0,0 +1,13 @@ +@language @attribute_inclusion +Feature: Attribute Inclusion + In order to encapsulate functionality and re-use it + As a developer + I want to include an attribute file from another one + + Scenario: Include an attribute directly + Given a validated node + And it includes the recipe 'attribute_include' + When I run the chef-client + Then the run should exit '0' + And a file named 'mars_volta' should contain 'mars_volta is dope' only '1' time + -- cgit v1.2.1