summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--chef/lib/chef/client.rb66
-rw-r--r--chef/lib/chef/file_cache.rb39
-rw-r--r--chef/lib/chef/node.rb45
-rw-r--r--chef/lib/chef/run_context.rb3
-rw-r--r--chef/spec/unit/client_spec.rb6
-rw-r--r--chef/spec/unit/file_cache_spec.rb147
-rw-r--r--chef/spec/unit/node_spec.rb88
-rw-r--r--features/chef-client/attribute_settings.feature13
-rw-r--r--features/data/json_attribs/attribute_settings.json3
-rw-r--r--features/steps/run_client_steps.rb9
-rw-r--r--features/support/env.rb3
11 files changed, 261 insertions, 161 deletions
diff --git a/chef/lib/chef/client.rb b/chef/lib/chef/client.rb
index 0169e855b5..7d35262bb6 100644
--- a/chef/lib/chef/client.rb
+++ b/chef/lib/chef/client.rb
@@ -146,9 +146,9 @@ class Chef
def determine_node_name
unless node_name
if Chef::Config[:node_name]
- self.node_name = Chef::Config[:node_name]
+ @node_name = Chef::Config[:node_name]
else
- self.node_name = ohai[:fqdn] ? ohai[:fqdn] : ohai[:hostname]
+ @node_name = ohai[:fqdn] ? ohai[:fqdn] : ohai[:hostname]
Chef::Config[:node_name] = node_name
end
@@ -163,37 +163,29 @@ class Chef
# === Returns
# node<Chef::Node>:: Returns the created node object, also stored in @node
def build_node
- Chef::Log.debug("Building node object for #{node_name}")
+ Chef::Log.debug("Building node object for #{@node_name}")
unless Chef::Config[:solo]
- self.node = begin
- rest.get_rest("nodes/#{node_name}")
- rescue Net::HTTPServerException => e
- raise unless e.message =~ /^404/
- end
+ begin
+ @node = rest.get_rest("nodes/#{@node_name}")
+ rescue Net::HTTPServerException => e
+ raise unless e.message =~ /^404/
+ end
end
unless node
@node_exists = false
- self.node = Chef::Node.new
- node.name(node_name)
+ @node = Chef::Node.new
+ @node.name(node_name)
end
- node.consume_attributes(json_attribs)
-
- node.automatic_attrs = ohai.data
+ @node.prepare_for_run(ohai.data, @json_attribs)
+ # Need to nil-ify the json attribs so they are not applied on subsequent runs
+ @json_attribs = nil
- platform, version = Chef::Platform.find_platform_and_version(node)
- Chef::Log.debug("Platform is #{platform} version #{version}")
- @node.automatic_attrs[:platform] = platform
- @node.automatic_attrs[:platform_version] = version
- # We clear defaults and overrides, so that any deleted attributes between runs are
- # still gone.
- @node.default_attrs = Mash.new
- @node.override_attrs = Mash.new
@node
end
-
+
#
# === Returns
# rest<Chef::REST>:: returns Chef::REST connection object
@@ -255,7 +247,7 @@ class Chef
def cleanup_file_cache(valid_cache_entries)
# Delete each file in the cache that we didn't encounter in the
# manifest.
- Chef::FileCache.list.each do |cache_filename|
+ Chef::FileCache.find(File.join(%w{cookbooks ** *})).each do |cache_filename|
unless valid_cache_entries[cache_filename]
Chef::Log.info("Removing #{cache_filename} from the cache; it is no longer on the server.")
Chef::FileCache.delete(cache_filename)
@@ -273,13 +265,13 @@ class Chef
Chef::Log.debug("Cookbooks to load: #{cookbook_hash.inspect}")
# Remove all cookbooks no longer relevant to this node
- Chef::FileCache.list.each do |cache_file|
- if cache_file =~ /^cookbooks\/(.+?)\//
- unless cookbook_hash.has_key?($1)
- Chef::Log.info("Removing #{cache_file} from the cache; its cookbook is no longer needed on this client.")
- Chef::FileCache.delete(cache_file)
- end
+ Chef::FileCache.find(File.join(%w{cookbooks ** *})).each do |cache_file|
+ #if cache_file =~ /^cookbooks\/(.+?)\//
+ unless cookbook_hash.has_key?($1)
+ Chef::Log.info("Removing #{cache_file} from the cache; its cookbook is no longer needed on this client.")
+ Chef::FileCache.delete(cache_file)
end
+ #end
end
# Synchronize each of the node's cookbooks
@@ -297,16 +289,16 @@ class Chef
# Updates the current node configuration on the server.
#
# === Returns
- # true:: Always returns true
+ # Chef::Node - the current node
def save_node
Chef::Log.debug("Saving the current state of node #{node_name}")
- self.node = if node_exists
- rest.put_rest("nodes/#{node_name}", node)
- else
- result = rest.post_rest("nodes", node)
- @node_exists = true
- rest.get_rest(result['uri'])
- end
+ if node_exists
+ @node = @node.save
+ else
+ result = rest.post_rest("nodes", node)
+ @node_exists = true
+ @node = rest.get_rest(result['uri'])
+ end
end
# Converges the node.
diff --git a/chef/lib/chef/file_cache.rb b/chef/lib/chef/file_cache.rb
index 18ecd66512..9f326ce0d5 100644
--- a/chef/lib/chef/file_cache.rb
+++ b/chef/lib/chef/file_cache.rb
@@ -31,7 +31,7 @@ class Chef
#
# === Parameters
# path<String>:: The path to the file you want to put in the cache - should
- # be relative to Chef::Config[:file_cache_path]
+ # be relative to file_cache_path
# contents<String>:: A string with the contents you want written to the file
#
# === Returns
@@ -51,9 +51,9 @@ class Chef
file_path_array = File.split(path)
file_name = file_path_array.pop
cache_path = create_cache_path(File.join(file_path_array))
- io = File.open(File.join(cache_path, file_name), "w")
- io.print(contents)
- io.close
+ File.open(File.join(cache_path, file_name), "w") do |io|
+ io.print(contents)
+ end
true
end
@@ -90,7 +90,7 @@ class Chef
#
# === Parameters
# path<String>:: The path to the file you want to load - should
- # be relative to Chef::Config[:file_cache_path]
+ # be relative to file_cache_path
# read<True/False>:: Whether to return the file contents, or the path.
# Defaults to true.
#
@@ -121,7 +121,7 @@ class Chef
#
# === Parameters
# path<String>:: The path to the file you want to delete - should
- # be relative to Chef::Config[:file_cache_path]
+ # be relative to file_cache_path
#
# === Returns
# true
@@ -145,12 +145,19 @@ class Chef
#
# === Returns
# Array:: An array of files in the cache, suitable for use with load, delete and store
- def list()
+ def list
+ find("**#{File::Separator}*")
+ end
+
+ ##
+ # Find files in the cache by +glob_pattern+
+ # === Returns
+ # [String] - An array of file cache keys matching the glob
+ def find(glob_pattern)
keys = Array.new
- Dir[File.join(Chef::Config[:file_cache_path], '**', '*')].each do |f|
+ Dir[File.join(file_cache_path, glob_pattern)].each do |f|
if File.file?(f)
- path = f.match("^#{Dir[Chef::Config[:file_cache_path]].first}\/(.+)")[1]
- keys << path
+ keys << f[/^#{Regexp.escape(Dir[file_cache_path].first) + File::Separator}(.+)/, 1]
end
end
keys
@@ -160,7 +167,7 @@ class Chef
#
# === Parameters
# path:: The path to the file you want to check - is relative
- # to Chef::Config[:file_cache_path]
+ # to file_cache_path
#
# === Returns
# True:: If the file exists
@@ -186,13 +193,13 @@ class Chef
# also creates the path if it does not exist.
#
# === Parameters
- # path:: The path to create, relative to Chef::Config[:file_cache_path]
+ # path:: The path to create, relative to file_cache_path
# create_if_missing:: True by default - whether to create the path if it does not exist
#
# === Returns
# String:: The fully expanded path
def create_cache_path(path, create_if_missing=true)
- cache_dir = File.expand_path(File.join(Chef::Config[:file_cache_path], path))
+ cache_dir = File.expand_path(File.join(file_cache_path, path))
if create_if_missing
create_path(cache_dir)
else
@@ -200,6 +207,12 @@ class Chef
end
end
+ private
+
+ def file_cache_path
+ Chef::Config[:file_cache_path]
+ end
+
end
end
end
diff --git a/chef/lib/chef/node.rb b/chef/lib/chef/node.rb
index 443915633c..0ce2c816ef 100644
--- a/chef/lib/chef/node.rb
+++ b/chef/lib/chef/node.rb
@@ -352,18 +352,24 @@ class Chef
run_list.detect { |r| r == item } ? true : false
end
- def consume_attributes(attrs)
- attrs ||= {}
- Chef::Log.debug("Adding JSON Attributes")
+ def consume_run_list(attrs)
+ attrs = attrs ? attrs.dup : {}
if new_run_list = attrs.delete("recipes") || attrs.delete("run_list")
if attrs.key?("recipes") || attrs.key?("run_list")
raise Chef::Exceptions::AmbiguousRunlistSpecification, "please set the node's run list using the 'run_list' attribute only."
end
- Chef::Log.info("Replacing the run_list with #{new_run_list.inspect} from JSON")
+ Chef::Log.info("Setting the run_list to #{new_run_list.inspect} from JSON")
run_list(new_run_list)
end
- Chef::Mixin::DeepMerge.merge(@normal_attrs, attrs)
+ attrs
+ end
+ # TODO: this code should be killed and removed, but it is used by shef and
+ # I don't have time to fix it right now. [Dan - 09/Jun/2010]
+ def consume_attributes(attrs)
+ attrs = consume_run_list(attrs)
+ Chef::Log.debug("Applying attributes from json file")
+ @normal_attrs = Chef::Mixin::DeepMerge.merge(@normal_attrs,attrs)
self[:tags] = Array.new unless attribute?(:tags)
end
@@ -530,8 +536,25 @@ class Chef
self
end
+ def prepare_for_run(ohai_data, json_cli_attrs)
+ Chef::Log.debug("Extracting run list from JSON attributes provided on command line")
+ @json_attrib_for_expansion = consume_run_list(json_cli_attrs)
+
+ node.automatic_attrs = ohai_data
+
+ platform, version = Chef::Platform.find_platform_and_version(self)
+ Chef::Log.debug("Platform is #{platform} version #{version}")
+ @automatic_attrs[:platform] = platform
+ @automatic_attrs[:platform_version] = version
+ # We clear defaults and overrides, so that any deleted attributes between runs are
+ # still gone.
+ @default_attrs = Mash.new
+ @override_attrs = Mash.new
+ end
+
# Expands the node's run list and deep merges the default and
- # override attributes.
+ # override attributes. Also applies stored attributes (from json provided
+ # on the command line)
#
# Returns the fully-expanded list of recipes.
#
@@ -540,11 +563,15 @@ class Chef
# run_list is mutated? Or perhaps do something smarter like
# on-demand generation of default_attrs and override_attrs,
# invalidated only when run_list is mutated?
- def expand_node!
+ def expand!
# This call should only be called on a chef-client run.
recipes, expanded_default_attrs, expanded_override_attrs = run_list.expand('server')
- self.default_attrs = Chef::Mixin::DeepMerge.merge(default_attrs, expanded_default_attrs)
- self.override_attrs = Chef::Mixin::DeepMerge.merge(override_attrs, expanded_override_attrs)
+ Chef::Log.debug("Applying attributes from json file")
+ @normal_attrs = Chef::Mixin::DeepMerge.merge(@normal_attrs, @json_attrib_for_expansion)
+ self[:tags] = Array.new unless attribute?(:tags)
+ @default_attrs = Chef::Mixin::DeepMerge.merge(default_attrs, expanded_default_attrs)
+ @override_attrs = Chef::Mixin::DeepMerge.merge(override_attrs, expanded_override_attrs)
+
recipes
end
diff --git a/chef/lib/chef/run_context.rb b/chef/lib/chef/run_context.rb
index e88e66591c..d9ee566fbe 100644
--- a/chef/lib/chef/run_context.rb
+++ b/chef/lib/chef/run_context.rb
@@ -85,7 +85,8 @@ class Chef
# Retrieve the fully expanded list of recipes for the node by
# resolving roles; this step also merges attributes into the
# node from the roles/recipes included.
- recipe_names = node.expand_node!
+ recipe_names = node.expand!
+
recipe_names.each do |recipe_name|
# TODO: timh/cw, 5-14-2010: It's distasteful to be including
# the DSL in a class outside the context of the DSL
diff --git a/chef/spec/unit/client_spec.rb b/chef/spec/unit/client_spec.rb
index 7bfc158bc3..4232f75492 100644
--- a/chef/spec/unit/client_spec.rb
+++ b/chef/spec/unit/client_spec.rb
@@ -41,9 +41,12 @@ describe Chef::Client, "run" do
# Fake node
node = Chef::Node.new(HOSTNAME)
+ node.name(FQDN)
node[:platform] = "example-platform"
node[:platform_version] = "example-platform-1.0"
-
+
+ #node.stub!(:expand!)
+
mock_chef_rest_for_node = OpenStruct.new({ })
mock_chef_rest_for_client = OpenStruct.new({ })
mock_couchdb = OpenStruct.new({ })
@@ -59,6 +62,7 @@ describe Chef::Client, "run" do
Chef::Config[:client_key] = temp_client_key_file.path
# Client.register will register with the validation client name.
+ Chef::REST.should_receive(:new).with(Chef::Config[:chef_server_url]).at_least(1).times.and_return(mock_chef_rest_for_node)
Chef::REST.should_receive(:new).with(Chef::Config[:client_url], Chef::Config[:validation_client_name], Chef::Config[:validation_key]).and_return(mock_chef_rest_for_client)
mock_chef_rest_for_client.should_receive(:register).with(FQDN, Chef::Config[:client_key]).and_return(true)
# Client.register will then turn around create another
diff --git a/chef/spec/unit/file_cache_spec.rb b/chef/spec/unit/file_cache_spec.rb
index 7026a4368b..311ab1d712 100644
--- a/chef/spec/unit/file_cache_spec.rb
+++ b/chef/spec/unit/file_cache_spec.rb
@@ -18,108 +18,97 @@
require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
-describe Chef::FileCache, "store method" do
- before(:each) do
- Chef::Config[:file_cache_path] = "/tmp/foo"
- Dir.stub!(:mkdir).and_return(true)
- File.stub!(:directory?).and_return(true)
- @io = mock("IO", { :print => true, :close => true })
- File.stub!(:open).and_return(@io)
+describe Chef::FileCache do
+ before do
+ @file_cache_path = Dir.mktmpdir
+ Chef::Config[:file_cache_path] = @file_cache_path
+ @io = StringIO.new
end
- it "should create the directories leading up to bang" do
- File.stub!(:directory?).and_return(false)
- Dir.should_receive(:mkdir).with("/tmp").and_return(true)
- Dir.should_receive(:mkdir).with("/tmp/foo").and_return(true)
- Dir.should_receive(:mkdir).with("/tmp/foo/whiz").and_return(true)
- Dir.should_not_receive(:mkdir).with("/tmp/foo/whiz/bang").and_return(true)
- Chef::FileCache.store("whiz/bang", "I found a poop")
+ after do
+ FileUtils.rm_rf(Chef::Config[:file_cache_path])
end
- it "should create a file at /tmp/foo/whiz/bang" do
- File.should_receive(:open).with("/tmp/foo/whiz/bang", "w").and_return(@io)
- Chef::FileCache.store("whiz/bang", "I found a poop")
- end
+ describe "when the relative path to the cache file doesn't exist" do
+ it "creates intermediate directories as needed" do
+ Chef::FileCache.store("whiz/bang", "I found a poop")
+ File.should exist(File.join(@file_cache_path, 'whiz'))
+ end
- it "should print the contents to the file" do
- @io.should_receive(:print).with("I found a poop")
- Chef::FileCache.store("whiz/bang", "I found a poop")
- end
+ it "creates the cached file at the correct relative path" do
+ File.should_receive(:open).with(File.join(@file_cache_path, 'whiz', 'bang'), "w").and_yield(@io)
+ Chef::FileCache.store("whiz/bang", "borkborkbork")
+ end
- it "should close the file" do
- @io.should_receive(:close)
- Chef::FileCache.store("whiz/bang", "I found a poop")
end
+
+ describe "when storing a file" do
+ before do
+ File.stub!(:open).and_yield(@io)
+ end
-end
+ it "should print the contents to the file" do
+ Chef::FileCache.store("whiz/bang", "borkborkbork")
+ @io.string.should == "borkborkbork"
+ end
-describe Chef::FileCache, "load method" do
- before(:each) do
- Chef::Config[:file_cache_path] = "/tmp/foo"
- Dir.stub!(:mkdir).and_return(true)
- File.stub!(:directory?).and_return(true)
- File.stub!(:exists?).and_return(true)
- File.stub!(:read).and_return("I found a poop")
end
- it "should find the full path to whiz/bang" do
- File.should_receive(:read).with("/tmp/foo/whiz/bang").and_return(true)
- Chef::FileCache.load('whiz/bang')
- end
+ describe "when loading cached files" do
+ it "finds and reads the cached file" do
+ FileUtils.mkdir_p(File.join(@file_cache_path, 'whiz'))
+ File.open(File.join(@file_cache_path, 'whiz', 'bang'), 'w') { |f| f.print("borkborkbork") }
+ Chef::FileCache.load('whiz/bang').should == 'borkborkbork'
+ end
- it "should raise a Chef::Exceptions::FileNotFound if the file doesn't exist" do
- File.stub!(:exists?).and_return(false)
- lambda { Chef::FileCache.load('whiz/bang') }.should raise_error(Chef::Exceptions::FileNotFound)
+ it "should raise a Chef::Exceptions::FileNotFound if the file doesn't exist" do
+ lambda { Chef::FileCache.load('whiz/bang') }.should raise_error(Chef::Exceptions::FileNotFound)
+ end
end
-end
-describe Chef::FileCache, "delete method" do
- before(:each) do
- Chef::Config[:file_cache_path] = "/tmp/foo"
- Dir.stub!(:mkdir).and_return(true)
- File.stub!(:directory?).and_return(true)
- File.stub!(:exists?).and_return(true)
- File.stub!(:unlink).and_return(true)
- end
+ describe "when deleting cached files" do
+ before(:each) do
+ FileUtils.mkdir_p(File.join(@file_cache_path, 'whiz'))
+ File.open(File.join(@file_cache_path, 'whiz', 'bang'), 'w') { |f| f.print("borkborkbork") }
+ end
+
+ it "unlinks the file" do
+ Chef::FileCache.delete("whiz/bang")
+ File.should_not exist(File.join(@file_cache_path, 'whiz', 'bang'))
+ end
- it "should unlink the full path to whiz/bang" do
- File.should_receive(:unlink).with("/tmp/foo/whiz/bang").and_return(true)
- Chef::FileCache.delete("whiz/bang")
end
-end
+ describe "when listing files in the cache" do
+ before(:each) do
+ FileUtils.mkdir_p(File.join(@file_cache_path, 'whiz'))
+ FileUtils.touch(File.join(@file_cache_path, 'whiz', 'bang'))
+ FileUtils.mkdir_p(File.join(@file_cache_path, 'snappy'))
+ FileUtils.touch(File.join(@file_cache_path, 'snappy', 'patter'))
+ end
-describe Chef::FileCache, "list method" do
- before(:each) do
- Chef::Config[:file_cache_path] = "/tmp/foo"
- Dir.stub!(:[]).with(File.join(Chef::Config[:file_cache_path], '**', '*')).and_return(["/tmp/foo/whiz/bang", "/tmp/foo/snappy/patter"])
- Dir.stub!(:[]).with(Chef::Config[:file_cache_path]).and_return(["/tmp/foo"])
- File.stub!(:file?).and_return(true)
- end
+ it "should return the relative paths" do
+ Chef::FileCache.list.sort.should == %w{snappy/patter whiz/bang}
+ end
- it "should return the relative paths" do
- Chef::FileCache.list.should eql([ "whiz/bang", "snappy/patter" ])
- end
-end
+ it "searches for cached files by globbing" do
+ Chef::FileCache.find('snappy/**/*').should == %w{snappy/patter}
+ end
-describe Chef::FileCache, "has_key? method" do
- before(:each) do
- Chef::Config[:file_cache_path] = "/tmp/foo"
end
- it "should check the full path to the file" do
- File.should_receive(:exists?).with("/tmp/foo/whiz/bang")
- Chef::FileCache.has_key?("whiz/bang")
- end
+ describe "when checking for the existence of a file" do
+ before do
+ FileUtils.mkdir_p(File.join(@file_cache_path, 'whiz'))
+ end
- it "should return true if the file exists" do
- File.stub!(:exists?).and_return(true)
- Chef::FileCache.has_key?("whiz/bang").should eql(true)
- end
+ it "has a key if the corresponding cache file exists" do
+ FileUtils.touch(File.join(@file_cache_path, 'whiz', 'bang'))
+ Chef::FileCache.should have_key("whiz/bang")
+ end
- it "should return false if the file does not exist" do
- File.stub!(:exists?).and_return(false)
- Chef::FileCache.has_key?("whiz/bang").should eql(false)
+ it "doesn't have a key if the corresponding cache file doesn't exist" do
+ Chef::FileCache.should_not have_key("whiz/bang")
+ end
end
end
-
diff --git a/chef/spec/unit/node_spec.rb b/chef/spec/unit/node_spec.rb
index f5746738f0..c626420f67 100644
--- a/chef/spec/unit/node_spec.rb
+++ b/chef/spec/unit/node_spec.rb
@@ -184,34 +184,41 @@ describe Chef::Node do
end
describe "consuming json" do
- it "should add any json attributes to the node" do
- @node.consume_attributes "one" => "two", "three" => "four"
- @node.one.should eql("two")
- @node.three.should eql("four")
- end
- it "should allow you to set recipes from the json attributes" do
- @node.consume_attributes "recipes" => [ "one", "two", "three" ]
- @node.recipes.should == [ "one", "two", "three" ]
+ it "consumes the run list portion of a collection of attributes and returns the remainder" do
+ attrs = {"run_list" => [ "role[base]", "recipe[chef::server]" ], "foo" => "bar"}
+ @node.consume_run_list(attrs).should == {"foo" => "bar"}
+ @node.run_list.should == [ "role[base]", "recipe[chef::server]" ]
end
- it "should overwrite the run list if you set recipes twice" do
- @node.consume_attributes "recipes" => [ "one", "two" ]
- @node.consume_attributes "recipes" => [ "three" ]
+ it "should overwrites the run list with the run list it consumes" do
+ @node.consume_run_list "recipes" => [ "one", "two" ]
+ @node.consume_run_list "recipes" => [ "three" ]
@node.recipes.should == [ "three" ]
end
- it "should allow you to set a run_list from the json attributes" do
- @node.consume_attributes "run_list" => [ "role[base]", "recipe[chef::server]" ]
- @node.run_list.should == [ "role[base]", "recipe[chef::server]" ]
- end
-
it "should not add duplicate recipes from the json attributes" do
@node.recipes << "one"
- @node.consume_attributes "recipes" => [ "one", "two", "three" ]
+ @node.consume_run_list "recipes" => [ "one", "two", "three" ]
@node.recipes.should == [ "one", "two", "three" ]
end
+ it "doesn't change the run list if no run_list is specified in the json" do
+ @node.run_list << "role[database]"
+ @node.consume_run_list "foo" => "bar"
+ @node.run_list.should == ["role[database]"]
+ end
+
+ it "raises an exception if you provide both recipe and run_list attributes, since this is ambiguous" do
+ lambda { @node.consume_run_list "recipes" => "stuff", "run_list" => "other_stuff" }.should raise_error(Chef::Exceptions::AmbiguousRunlistSpecification)
+ end
+
+ it "should add json attributes to the node" do
+ @node.consume_attributes "one" => "two", "three" => "four"
+ @node.one.should eql("two")
+ @node.three.should eql("four")
+ end
+
it "should set the tags attribute to an empty array if it is not already defined" do
@node.consume_attributes({})
@node.tags.should eql([])
@@ -238,9 +245,52 @@ describe Chef::Node do
@node.one.to_hash.should == {"two" => {"three" => "forty-two"}}
end
- it "raises an exception if you provide both recipe and run_list attributes, since this is ambiguous" do
- lambda { @node.consume_attributes "recipes" => "stuff", "run_list" => "other_stuff" }.should raise_error(Chef::Exceptions::AmbiguousRunlistSpecification)
+ end
+
+ describe "preparing for a chef client run" do
+ before do
+ @ohai_data = {:platform => 'foobuntu', :platform_version => '23.42'}
+ end
+
+# @json_attribs = node.consume_run_list(json_attribs)
+#
+# node.automatic_attrs = ohai.data
+#
+# platform, version = Chef::Platform.find_platform_and_version(node)
+# Chef::Log.debug("Platform is #{platform} version #{version}")
+# @node.automatic_attrs[:platform] = platform
+# @node.automatic_attrs[:platform_version] = version
+# # We clear defaults and overrides, so that any deleted attributes between runs are
+# # still gone.
+# @node.default_attrs = Mash.new
+# @node.override_attrs = Mash.new
+
+ it "clears the default and override attributes" do
+ @node.default_attrs["foo"] = "bar"
+ @node.override_attrs["baz"] = "qux"
+ @node.prepare_for_run(@ohai_data, {})
+ @node.default_attrs.should be_empty
+ @node.override_attrs.should be_empty
+ end
+
+ it "sets its platform according to platform detection" do
+ @node.prepare_for_run(@ohai_data, {})
+ @node.automatic_attrs[:platform].should == 'foobuntu'
+ @node.automatic_attrs[:platform_version].should == '23.42'
+ end
+
+ it "consumes the run list from provided json attributes" do
+ @node.prepare_for_run(@ohai_data, {"run_list" => ['recipe[unicorn]']})
+ @node.run_list.should == ['recipe[unicorn]']
+ end
+
+ it "saves non-runlist json attrs for later" do
+ @node.run_list.stub!(:expand).and_return([[], {}, {}])
+ @node.prepare_for_run(@ohai_data, {"foo" => "bar"})
+ @node.expand!
+ @node.normal_attrs.should == {"foo" => "bar", "tags" => []}
end
+
end
# TODO: timh, cw: 2010-5-19: Node.recipe? deprecated. See node.rb
diff --git a/features/chef-client/attribute_settings.feature b/features/chef-client/attribute_settings.feature
index 566c57ea24..4427f719b2 100644
--- a/features/chef-client/attribute_settings.feature
+++ b/features/chef-client/attribute_settings.feature
@@ -116,4 +116,15 @@ Feature: Set default, normal, and override attributes
Then the run should exit '0'
And a file named 'attribute_setting.txt' should contain 'snakes'
-
+ @chef1286
+ Scenario: Attributes are provided as JSON via the command line
+ Given a validated node
+ And it includes the recipe 'attribute_settings_normal'
+ When I run the chef-client with json attributes
+ Then the run should exit '0'
+ Then a file named 'attribute_setting.txt' should contain 'from_json_file'
+ When the node is retrieved from the API
+ Then the inflated responses key 'attribute_priority_was' should match 'from_json_file'
+
+
+
diff --git a/features/data/json_attribs/attribute_settings.json b/features/data/json_attribs/attribute_settings.json
new file mode 100644
index 0000000000..effc4941e8
--- /dev/null
+++ b/features/data/json_attribs/attribute_settings.json
@@ -0,0 +1,3 @@
+{
+ "attribute_priority_was": "from_json_file"
+} \ No newline at end of file
diff --git a/features/steps/run_client_steps.rb b/features/steps/run_client_steps.rb
index c1de01e913..41b23e10e6 100644
--- a/features/steps/run_client_steps.rb
+++ b/features/steps/run_client_steps.rb
@@ -16,6 +16,8 @@
# limitations under the License.
#
+CHEF_CLIENT = File.join(CHEF_PROJECT_ROOT, "chef", "bin", "chef-client")
+
###
# When
###
@@ -60,6 +62,13 @@ When /^I run the chef\-client at log level '(.+)'$/ do |log_level|
When "I run the chef-client"
end
+When 'I run the chef-client with json attributes' do
+ @log_level = :debug
+ @chef_args = "-j #{File.join(FEATURES_DATA, 'json_attribs', 'attribute_settings.json')}"
+ When "I run the chef-client"
+end
+
+
When /^I run the chef\-client with config file '(.+)'$/ do |config_file|
@config_file = config_file
When "I run the chef-client"
diff --git a/features/support/env.rb b/features/support/env.rb
index a50847afe9..8a985066aa 100644
--- a/features/support/env.rb
+++ b/features/support/env.rb
@@ -22,7 +22,8 @@ require 'spec/expectations'
CHEF_PROJECT_ROOT = File.expand_path(File.dirname(__FILE__) + '/../../')
KNIFE_CONFIG = CHEF_PROJECT_ROOT + '/features/data/config/knife.rb'
KNIFE_CMD = File.expand_path(File.join(CHEF_PROJECT_ROOT, "chef", "bin", "knife"))
-INTEGRATION_COOKBOOKS = File.join(CHEF_PROJECT_ROOT, "features", "data", "cookbooks")
+FEATURES_DATA = File.join(CHEF_PROJECT_ROOT, "features", "data")
+INTEGRATION_COOKBOOKS = File.join(FEATURES_DATA, "cookbooks")
$:.unshift(CHEF_PROJECT_ROOT + '/chef/lib')
$:.unshift(CHEF_PROJECT_ROOT + '/chef-server-api/lib')